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.