Pages

Thursday, June 9, 2011

"Make'' Your Programs

A General Overview


make is a command generator. Using a description file and some general templates, it creates a sequence of commands for execution by the UNIX shell. These commands commonly relate to the maintenance of the files comprising a software development project. Here, ``maintenance'' refers to a whole array of tasks, ranging from status reporting and the purging of temporary files, to building the final, executable version of a complex group of programs.

The description file is usually named as Makefile or makefile. After this file is created, one can run the program make to make executables as described in the makefile:

make




Preparing a Description File


Program make expects the required tasks be described in a file with name makefile or Makefile. This file is referred to as the description file.

In discussing make, we shall call program the target of the operation. A program is built from one or more files, called the prerequisites or dependents. Each of these files may in turn have other files as prerequisites. For example, your C program executable myprog might be generated by compiling two C source files, a.c and b.c. Hence, myprog has prerequisites a.c and b.c.

These prerequisites should be carefully stated in the description file. There are two important types of lines in a description file. A dependency line or rule line contains a colon, and a command line always starts with a tab.

To the left of the colon on the dependency line is a target; to the right of the colon are the target's prerequisites. Let us continue with the previous example. Since myprog has prerequisites a.c and b.c, we have

myprog : a.c b.c
The command lines just tells make how to make the target. For example, to generate myprog from a.c and b.c, what we might want to do is compiling both files and naming the output executable myprog with the following command:

$ gcc -o myprog a.c b.c
In this example, the GNU C compiler gcc is used to compile files a.c and b.c and place the executable to file myprog. The switch -o indicates the output file name. Note that $ is the UNIX prompt. Therefore, in the description file, we might have the following dependency line and command line:
myprog : a.c b.c
    gcc -o myprog a.c b.c
Please do not forget that the first character of a command line must be a tab.
Once the above two lines are placed into makefile or Makefile. From the UNIX prompt, type the following command, make will generate the executable \verb|myprog|:

make
However, before make really makes the executable for the user, it will check to see if the specified task is necessary. In fact, make will look at the creation/modification date of each prerequisite and the target. If one of the prerequisites has a creation/modification date that is newer than the creation/modification date of the target, the target is not up-to-date and regeneration is necessary. Then, make will generate a sequence of commands from the command line for execution by the UNIX shell. On the other hand, if the creation/modification date of the target is newer than the creation/modification dates of the prerequisites, the target is up-to-date and make will indicate this fact to the user and skip the specified task.


Comments


make ignores blank lines. It also ignores all characters from a pound sign (#) to the end of a line. Therefore, # marks the beginning of a comment. It is similar to the use of // in C++. The following is an example:

CC = gcc -ansi # use GNU C

myprog : main.c # myprog only depends on main.c
${CC} -o myprog main.c # compile my program


Generating Multiple Targets


Discussions on previous pages work only for generating one program. In many cases, one might want to generate more than one programs. For example, let program prog01 depend on a.c and b.c, prog02 depend on m.c and n.c, and prog03 depend on u.c, v.c and w.c. We want to generate all three of them with only one makefile. To do so, we can play a simple trick as follows:

all : prog01 prog02 prog03

prog01 : a.c b.c
gcc -o prog01 a.c b.c

prog02 : m.c n.c
gcc -o prog02 m.c n.c

prog03 : u.c v.c w.c
gcc -o prog03 u.c v.c w.c
In addition to the dependency lines and command lines for generating prog01, prog02, and prog03, one more dependency line without a command line is added. It states that target all depends on prog01, prog02, and prog03. Thus, if one of these three programs is not up-to-date, all will be generated which in turn causes all programs compiled.
With this makefile, you can issue the following command

$ make all
telling make to process the target all, which in turn forces to process prog01, prog02, and prog03. In other words, the above command has a meaning like ``all is not up-to-date, so generate it ''.
If the dependency line defining all is the first line of your Makefile, you can use the following:

$ make
In this case, if the command line does not have a target name, the first target in the description file is made (together with all its prerequisites).
The command line make all asks for processing all, which is a target. Similarly, one can ask make to process prog01, prog02, or prog03. For example, if we want to process the target prog02, then issue the following command:

$ make prog02


Macros


Description file entries of the form

NAME = text string
are macro definitions. This line defines NAME to be the text string. Subsequent references to
${NAME}
or
$(NAME)
are interpreted as
text string
The following are some valid macro definitions:

LIBS = -lX11 -lm
OBJS = a.o b.o u.o some_thing.o
ARGUMENTS = "0 12 ABC"
CC = gcc
DEBUG_FLAGS =
result = /usr/local/bin
Six macros are defined: LIBS, OBJS, ARGUMENTS, CC, DEBUG_FLAGS and result.
A macro definition is a line containing an equal sign (=). When make encounters a name that appears to the left of the equal sign, that name is replaced by the contents to the right of the equal sign. It is not necessary to delimit the text string with double quote (") or single quote ('). If they are used, they become part of the text string. For example, if ARGUMENTS is used in your makefile, it will be replace by "0 12 ABC".

If the text string to the right of the equal sign is too long (longer than a line), a back slash (\) can be used as the last character of that line, indicating that the next line is a continuation line. Here is an example:

LONG_LINE = -o myprog a.o openfile.o display_result.o \
main_program.o plot_diagram.o \
signal_handling.o \
-lX11 -lm
make replaces any back slash by a blank and concatenates all continuation lines, yielding a long text string.
To ensure that make can distinguish a macro definition from a dependency line or a command line, no colons or tabs are permitted before the equal sign.

By convention, macro names are in uppercase. But, you can use any combination you want: uppercase and lowercase letters, digits, and underlines. You might also use some other punctuation characters; however, you should use them with care, since there is no guarantee that make will handle it correctly.

When you refer to a macro, as indicated earlier, you could enclose the name with ${} or $(). However, if the name has only one character, {} and () can be omitted. For example, if we have the following definition:

C = gcc
we can refer to gcc with $C, ${C}, or $(C).
Macros can use previously defined macros too. For example, if we have the following definitions:

OBJ_FILE_EXTENSION = .OBJ
EXE_FILE_EXTENSION = .EXE
EXECUTABLE = myprog${EXE_FILE_EXTENSION}
OBJECT_FILE = myprog${OBJ_FILE_EXTENSION}
references to EXECUTABLE and OBJECT_EXTENSION yield myprog.EXE and myprog.OBJ, respectively.
To the right of the equal sign, there could be no text string. In this case, the text string is considered to be a null string. DEBUG_FLAGS defined earlier is an example. If macros OBJ_FILE_EXTENSION and EXE_FILE_EXTENSION are replaced by the following definitions:

OBJ_FILE_EXTENSION = .o
EXE_FILE_EXTENSION =
EXECUTABLE = myprog${EXE_FILE_EXTENSION}
OBJECT_FILE = myprog${OBJ_FILE_EXTENSION}
references to EXECUTABLE and OBJECT_FILE yield myprog and myprog.o, respectively.


Internally Defined Macros

make predefines a number of common commands as macros. For example, the macro ${CC} is usually defined to be the system C compiler, while ${LD} is defined to be the system linker. Therefore, if you have the following dependency line and command line:

myprog : main.c
${CC} -o myprog main.c
It is usually equivalent to the following:
myprog : main.c
cc -o myprog main.c
If your C compiler is not an ANSI C compiler, using macro ${CC} to compile an ANSI C program could generate a lot of error messages. To use gcc, you can redefine the CC macro as follows:

CC = gcc -ansi
myprog : main.c
${CC} -o myprog main.c
This will use gcc to compile the source program main.c producing an executable myprog.


An Example


Let us consider a real example. Suppose you are provided with two files, MyThreads.h and MyThreads.o. The former is a header file that has to be included into your source files and the latter is a library of functions for you to use. In other words, your source files in which multithreaded capability is used depend on MyThreads.h and your executable depends on MyThreads.o.

Suppose your assignment has the following files, prog01.c (the main function), and abc.c, def.c and xyz.c. All of these files contain access to multithread functions defined in MyThreads.h. You also designed some utility functions collected in files ut1.c and ut2.c. None of these two have access to the multithread capability. However, in your judgment, you believe any change made to ut1.c and ut2.c requires recompilation of abc.c and xyz.c. Therefore, on the source level, prog01.c, abc.c, def.c and xyz.c depend on MyThreads.h, and abc.c and xyz.c also depend on ut1.c and ut2.c. Suppose the final executable is to be called prog01.

To generate

prog01, we need the object files prog01.o, abc.o, def.o, xyz.o, ut1.o and ut2.o. The executable requires MyThreads.o as well. Thus, if all of these object files are available, to obtain the executable, you need the following command line:

$ gcc -o prog01 prog01.o abc.o def.o xyz.o ut1.o ut2.o MyThreads.o -lthread
Therefore, the dependency line and the command line are
prog01 : prog01.o abc.o def.o xyz.o ut1.o ut2.o MyThreads.o
gcc -o prog01 prog01.o abc.o def.o xyz.o ut1.o ut2.o MyThreads.o -lthread
To generate prog01.o, we need prog01.c and MyThreads.h and use the following dependency and command lines:
prog01.o : prog01.c MyThreads.h
gcc -c prog01.c
Similarly, we have the dependency and command lines for abc.c, def.c and xyz.c:
abc.o : abc.c ut1.c ut2.c MyThreads.h
gcc -c abc.c
def.o : def.c MyThreads.h
gcc -c def.c
xyz.o : xyz.c ut1.c ut2.c MyThreads.h
gcc -c xyz.c
Since ut1.c and ut2.c do not use multithread capability, their dependency lines do not have to include MyThreads.h:
ut1.o : ut1.c
gcc -c ut1.c
ut2.o : ut2.c
gcc -c ut2.c
Combining all dependency and command lines together, we have the following Makefile:
CC = gcc -ansi

prog01 : prog01.o abc.o def.o xyz.o ut1.o ut2.o MyThreads.o
${CC} -o prog01 prog01.o abc.o def.o xyz.o ut1.o ut2.o MyThreads.o -lthread

prog01.o : prog01.c MyThreads.h
${CC} -c prog01.c

abc.o : abc.c c ut2.c MyThreads.h
${CC} -c abc.c
def.o : def.c MyThreads.h
${CC} -c def.c
xyz.o : xyz.c ut1.c ut2.c MyThreads.h
${CC} -c xyz.c

ut1.o : ut1.c
${CC} -c ut1.c
ut2.o : ut2.c
${CC} -c ut2.c

No comments:

Post a Comment