make

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.