A simple makefile

Suppose you are writing a C++ program which has two source modules, processing.cxx and gui.cxx, along with numerous include files. If you were to build your program from scratch, you would need to execute something like these commands:

c++ -c processing.cxx -o processing.o
c++ -c gui.cxx -o gui.o
c++ processing.o gui.o -o my_program

The first two commands are compilation commands, and the third invokes the linker to combine the two object files into a single executable. If you make changes to gui.cxx but not to processing.cxx, then you don't need to reexecute the first command, but you do need to execute the last two commands. makepp can figure this out for you automatically.

(If you've never worked with make before, you may be thinking that you could combine the above three commands into a single command, like this:

c++ processing.cxx gui.cxx -o my_program

When you omit the -c option to the compiler, it combines the compilation and linking step. This is often quite convenient when you are not writing a makefile. However, it's not a good idea to do this in a makefile, because it always recompiles both modules even if one of them hasn't changed, and this can take a significant amount of extra time.)

In order to use makepp to control the build process, you'll need to write a makefile. The makefile is a text file that contains the recipe for building your program. It usually resides in the same directory as the sources, and it is usually called Makefile.

Each one of these commands should be a separate rule in a makefile. A rule is an instruction for building one or more output files from one or more input files. Makepp determines which rules need to be reexecuted by determining whether any of the input files for a rule have changed since the last time the rule was executed.

A rule has a syntax like this:

output_filenames : input_filenames
	actions

The first line of the rule contains a space-separated list of output files, followed by a colon, followed by a space-separated list of input files. The output files are also called targets, and the input files are also called dependencies; one says that the target file(s) depends on the dependencies, because if any of the dependencies change, the target must be rebuilt.

The remaining lines of the rule (the "actions") are shell commands to be executed. Each action must be indented with at least one space (traditional make requires a tab character). Usually, there's just one action line, but there can be as many as you want; each line is executed sequentially, and if any one of them fails, the remainder are not executed. The rule ends at the first line which is not indented.

You can place the rules in any order in the makefile, but it is traditional to write the rule that links the program first, followed by the compilation rules. One reason for this is that if you simply type "makepp", then makepp attempts to build the first target in the file, which means that it will build your whole program and not just a piece of it. (If you want to build something other than the first target, you have to specify the name of the target on the command line, e.g., "makepp processing.o".)

The above compilation commands should be written as three separate rules. A makefile for building this program could look like this:

# Link command:
my_program: processing.o gui.o
	c++ processing.o gui.o -o my_program

# Compilation commands:
processing.o: processing.cxx
	c++ -c processing.cxx -o processing.o

gui.o: gui.cxx
	c++ -c gui.cxx -o gui.o

(Characters on a line following a # are ignored; they are just comments. You do not need the "# Link command:" comment in the makefile at all.)

To use this makefile, simply cd to the directory and type "makepp". Makepp will attempt to build the first target in the makefile, which is my_program. (If you don't want it to build the first target, then you have to supply a the name of the target you actually want to build on the command line.)

When makepp attempts to build my_program, it realizes that it first must build processing.o and gui.o before it can execute the link command. So it looks at the other rules in the makefile to determine how to build these.

In order to build processing.o, makepp uses the second rule. Since processing.o depends on processing.cxx, makepp will also try to make processing.cxx. There is no rule to make processing.cxx; it must already exist.

Makepp checks whether processing.cxx has changed since the last time processing.o was built. By default, it determines this by looking at the dates on the file. Makepp remembers what the date of processing.cxx was the last time processing.o was made by storing it in a separate file (in a subdirectory called .makepp). Makepp will execute the actions to build the target if any of the following is true:

It might seem a little funny that makepp executes the action if either the output file or the input files have changed since the last build. Makepp is designed to guarantee that your build is correct, according to the commands in the makefile. If you go and modify the file yourself, then makepp can't guarantee that the modified file is actually correct, so it insists on rebuilding. (For more information on how makepp decides whether to rebuild, and how you can control this, see the section in the reference manual on signature checking.)

Now processing.o might not depend only on processing.cxx; if processing.cxx includes any .h files, then it needs to be recompiled if any of those .h files has changed, even if processing.cxx itself has not changed. You could modify the rule like this:

# Unnecessary listing of .h files
processing.o: processing.cxx processing.h simple_vector.h list.h
	c++ -c processing.cxx -o processing.o

However, it is a real nuisance to modify the makefile every time you change the list of files that are included, and it is also extremely error prone. You would not only have to list the files that processing.cxx includes, but also all the files that those files include, etc. You don't have to do this. Makepp is smart enough to check for include files automatically. Any time it sees a command that looks like a C or C++ compilation (by looking at the first word of the action), it reads in the source files looking for #include directives. It knows where to look for include files by scanning for -I options on your compiler command line. Any files which are included are automatically added to the dependency list, and any files which those include. If any of them has changed, the file will be recompiled.

Once makepp knows that processing.o is up to date, it then determines whether gui.o needs to be rebuilt by applying the same procedure to the third rule. When both processing.o and gui.o are known to be built correctly, then makepp applies the same procedure to see if the link command needs to be reexecuted.

The above makefile will work, but even for this simple problem, an experienced user is not likely to write his makefile this way. Several improvements are discussed in the next pages.


Tutorial index | Next (using variables) | Previous (unix compilation commands)
Last modified: Wed Dec 27 13:11:16 PST 2000