In order for Make to help with your project, you must tell it about the
relationships between all of the files it will be maintaining for you.
Each relationship is expressed as a dependency rule and takes the
following form:
target: dependency ... command ...
Example 5.3: Format of a dependency rule
Here, target relates to a file that needs to be built during
the compilation phase of your project, for example hello.o
. A
space delimited list of dependency files name every other file
that needs to be up to date before target can be built. And an
indented list of command each comprise a line of shell script code
for Make to run when target needs refreshing. You might say that
Make uses command to build target from dependency.
- NOTE
- Make will understand that each command is part of the preceding rule only if the first character on each line is a literal tab. Although a string of spaces, or even a space followed by a tab look the same in most editors, they will cause an error like this when you try to run
make
:$ make Makefile:159: *** missing separator. Stop.Notice that GNU make tells you the name of and the line number within it that caused the error (line 159 of
Makefile
in this case), making it easy to locate and correct once you know what the cryptic error message means.
Recall that in the last chapter you saw that the following command will
build the m4
binary:
$ gcc -o m4 main.c freeze.c stackovf.c ../libs/libm4.a
The simplest rule to achieve the same effect using a Makefile
is
as follows:
m4: main.c freeze.c stackovf.c ../libs/libm4.a gcc -o m4 main.c freeze.c stackovf.c ../libs/libm4.a
Example 5.4: A sample dependency rule
With just this one rule in the Makefile
, recompiling m4
is a simple matter of entering the command make m4
at your shell
prompt:
$ make m4 gcc -o m4 main.c freeze.c stackovf.c ../libs/libm4.a $ make m4 make: `m4' is up to date.
On the second invocation, make
doesn't build m4
again,
because it is already newer than all of the dependencies listed in the
rule.
Anyway, as we explained in A Refreshing Change, this isn't a good
way to structure a project Makefile
, since absolutely everything
is recompiled each time any file is changed. You can avoid such
needless recompilations, and drastically cut down on the amount of work
GCC has to do when recompiling after changing some source files, by
breaking the compilation down into phases. Recall that source code can
be compiled into object code files, which are in turn linked to form the
target executable (voir The GNU Compiler Collection). Each stage is
represented by its own rule in your Makefile
, like this:
m4: main.o freeze.o stackovf.o ../lib/libm4.a gcc -o m4 main.o freeze.o stackovf.o ../lib/libm4.a main.o: main.c gcc -c main.c freeze.o: freeze.c gcc -c freeze.c stackovf.o: stackovf.c gcc -c stackovf.c
Example 5.5: Compilation in phases with make
Now, with this Makefile
in place, imagine that freeze.c
has been edited since the compilation we just did with example 5.4. Invoking make m4
now executes only
the commands from the set of rules needed to refresh the m4
target file:
$ make m4 gcc -c freeze.c gcc -o m4 main.o freeze.o stackovf.o ../lib/libm4.a
Previously, gcc
had needed to recompile all three source
files, even if only freeze.c
had actally changed. This can save
literally hours when recompiling projects spread across a great many
source files.
A typical Makefile
will specify many rules. Together they can be
represented as a tree of dependencies, where many of the dependencies
for a particular target are themselves targets in another rule, often
with more dependencies of their own. The root of this tree is the
target that Make ultimately aims to bring up to date, and is
normally specified on the command line. We used make m4
above,
which caused Make to start building the tree with the m4
rule at
its root, aiming to bring m4
up to date.
,----. | m4 | `----' | ,-------------+-----+------+--------------. / / \ \ v v v v ,--------. ,----------. ,------------. ,---------. | main.o | | freeze.o | | stackovf.o | | libm4.a | `--------' `----------' `------------' `---------' | | | v v v ,--------. ,----------. ,------------. | main.c | | freeze.c | | stackovf.c | `--------' `----------' `------------'
Example 5.6: Part of a Makefile dependency tree
If make
is invoked without any arguments, then the
target of the first dependency rule in the Makefile
is
refreshed by default. To take advantage of this behaviour, it is common
practice to name the first rule all
, and set it up to be
dependent on all of the programs and libraries compiled by this
Makefile
. By doing this, invoking make all or even just
make will refresh all of the targets managed by this
Makefile
. For example, in the directory where all of a project's
plugin modules are compiled, the Makefile would contain an all
target that depends on all of the plugin targets that are built by that
Makefile:
all: gnu.so load.so m4.so traditional.so perl.so stdlib.so gnu.so: $(gnu_so_OBJECTS) $(LD) $(LDFLAGS) -o gnu.so $(gnu_so_OBJECTS) $(gnu_so_LDADD) load.so: $(load_so_OBJECTS) ...
Example 5.7: A typical all target Makefile fragment
Make can determine whether any target in its rules has gone out of date from the modification times of the files referred to in the target and dependency parts of all of its rules. Make first checks that each of the dependency files listed against the target do not have later modification times than the target itself. If any of them do, then Make executes the list of command lines associated with the out of date target. However, the timestamp checks are performed recursively, so before it can determine whether the current target is older than any of its listed dependency files, it must first recursively ensure that each of those files is up to date with respect to their own dependency lists.
If you look at the commands in each rule from example 5.5, you will see that each command is written so that it builds a new target file from those files listed as its dependencies. Whenever target names a file, this statement is always true: command transforms dependency into target. If you bear this in mind as you create new rules, then choosing appropriate file names and commands becomes quite straight forward.