The make utility is used to update files that depend on
other files. In this course we will use make to compile programs.
You can use make without any further thought, if you simply follow the
pattern given here. However, if you plan to use Unix quite a bit in
your careers, it is recommended that you learn more about how it
works. See, for example the online man pages and B.W. Kernighan and
R. Pike, The Unix Programming Environment (Prentice Hall,
London, 1984), p. 241 ff.
Suppose you have a source file called runge.cc and routinely
compile it to make an executable runge using the command:
g++ -O runge.cc -o runge
The output executable file runge depends on the source file
runge.cc. If you make a change to runge.cc you should
recompile the executable to bring it up to date. Unix tracks the date
and time a file is modified. So by comparing the dates of runge.cc and runge one can tell whether it is necessary to
update runge. It takes two pieces of information to accomplish
this - first, the dependency file runge depends on file runge.cc, and second, the rule for updating the dependent file, i.e. the compilation statement above. These two pieces of information are
encoded in a special file called called the ``makefile''. By default
that file is named Makefile or makefile. For this example
the makefile consists of two lines:
runge: runge.cc
<Tab> g++ -O runge.cc -o runge
Please note that the <Tab> means ``hit the tab key''.
After you create the makefile, it is a simple matter to compile your
program. From the Unix prompt, type
make runge
The make utility looks at your file Makefile and sees
that the executable runge depends on the source runge.cc.
It then compares the date and time stamps on these files. If the
source program is more recent, which would be the case if you made a
change in the source program after the last compilation, then
make uses the next command to make the new version. Please
note that the <Tab> is necessary in the makefile to signify
that the line is a shell command. The file should end with an
end-of-line character. That is, be sure after typing the last line in
the file, that you hit Enter. (If you don't do this, the make
utility is apt to ignore the last line, and the cause of the problem
will be invisible and baffling.)
The above example actually happens to be the default compilation
procedure for the make utility. That is, if your makefile is
missing, but you type make runge anyway, then make will do
the compilation exactly as specified in this example, compiling with
the optimization option -O, linking with the standard libraries
(but not the commonly used C math library), and producing an
executable with the name runge. If your source program name
ends with .cc, make uses g++, and if it ends with
.c, make uses gcc to do the compilation. However, if
your executable programs involve more than one object file, or require
any of the nonstandard libraries or a different set of compiler
options, then you have to create a makefile and you will find that
make is indeed your friend.
If you are writing C, you would replace the g++ command with
the corresponding gcc command and, of course, change the name
of the source file to a .c file.
Here is an example of a makefile that involves two source files:
runge: runge.cc functn.cc
<Tab> g++ -O runge.cc functn.cc -o $@
In this example, the executable runge depends on two source
files, and you want the whole thing recompiled when you change either
source file. The $@ is a special make variable that gets
replaced by the ``target'', in this case runge.
You can be more sophisticated and efficient if you understand that
there are actually two steps to creating an executable from a source
file. First it is necessary to translate the source files such as
runge.cc to object files, conventionally runge.o, and then
take the object files and ``link'' them with the libraries to produce
the executable. The make rule above is inefficient, since if you
change just one source file, all source files are recompiled in order
to create the executable. Why not just recompile the one you need,
making a new object file, and the link it with the other object files
that didn't have to be recompiled. Here is the way we do it:
OBJECTS= runge.o functn.o
.cc.o:
<Tab> g++ -O -c $*.cc
runge: ${OBJECTS}
<Tab> g++ ${OBJECTS} -o $@
Here a make variable (macro) called OBJECTS is defined to
be a list of object files. In the next two lines a generic rule is
given for converting any .cc file into the corresponding .o file. The -c option tells g++ to do the compilation and
produce the .o file, but not to link the file into an executable
file yet. The $*.cc says that the source file name is the same
as the object file, except that it ends in .cc. Finally, make is told that the executable file runge depends on the
object files in the list OBJECTS, and a rule is given for making
the executable. Since in this case g++ is being given a list of
object files, make will simply link them all to make the
executable file, recompiling only where necessary.