The Makefile in example 5.11 has a dependency rule for the compilation of each of the source files in the source directory. Remembering to add a new rule to the Makefile each time a new source file is added to the project would be an unwanted distraction from the real work of writing code. Accordingly, Make offers a way to specify how groups of similar files are kept up to date, which is especially useful in cases like this where the commands for each rule are almost identical.
Make will determine when to apply these rules based on the file
suffix that follows the last dot in the pertinent file name. The
explicit rules all follow this template:
main.o: main.c $(CC) $(CFLAGS) $(CPPFLAGS) -c main.c
Example 5.19: A Make rule for compiling main.c
The relationship between the target, main.o
, and its dependency,
main.c
is characterised by the suffixes .o
and .c
.
An equivalent suffix rule, which can also be used to update any target
with a .o
suffix from an existing .c
suffixed filename is
as follows:
.c.o: $(CC) $(CFLAGS) $(CPPFLAGS) -c $<
Example 5.20: A suffix rule for compiling C source files
Confusingly, if you compare example 5.19 and example 5.20, the
order of the suffixes to the left of the :
above, is reversed with
respect to how they would appear in the earlier explicit rule. This
takes some getting used to, but follows the precedent set by prehistoric
implementations of the Make utility. The odd looking variable
reference, $<
refers to the .c
file that matched the
rule, and is explained fully in Automatic Variables.
Suffix rules take a very similar form to normal dependency rules.
However, the target-suffix follows the dependent-suffix
immediately with no separation:
dependent-suffixtarget-suffix: command ...
Example 5.21: Format of a double suffix rule
Notice that unlike example 5.3 there is
nowhere to list dependencies in a suffix rule. As a matter of
fact, Make will not interpret a rule that does list
dependencies as a suffix rule; it will be treated as a dependency
rule with a target named, say, .c.o
.
GNU Make already knows about many of the common suffixes like
.c
and .o
, and in fact has a fairly exhaustive list of
suffixes and default suffix rules declared for you, as explained in
Invoking Make. It uses this knowledge to distinguish between a
suffix rule for generating .o
files from similarly named
.c
files, and a standard dependency rule to create a wierd target
file named .c.o
. You must declare any additional
suffixes used by the suffix rules in your Makefile
. Make uses a
special target named .SUFFIXES for this, like so:
.SUFFIXES: .c .o
The default suffix list is a double edged sword though, and will
occasionally elicit surprising behaviour from Make when some of your
files happen to match one of the predeclared suffix rules. We advocate
the principle of least surprise1, and as such recommend enabling only the suffix rules that are
specifically required by each individual Makefile
. First, empty
the suffix list with an empty .SUFFIXES
target, and then declare
the suffixes you actually need in a second .SUFFIXES
target.
This works because, unlike normal targets, when .SUFFIXES
appears
multiple times within a Makefile
, the dependencies for each new
appearance are added to a growing list of known file suffixes. Except
that at any point .SUFFIXES
is written with no dependencies, this
growing list is reset by removing every suffix it contains - subsequent
appearances will then be able to add more suffixes back in to the list.
Applying these idioms to our Makefile, we now have:
CC = gcc CFLAGS = -ggdb3 CPPFLAGS = -DDEBUG=1 m4_OBJECTS = main.o freeze.o stackovf.o m4_LIBS = ../lib/libm4.a .SUFFIXES: .SUFFIXES: .c .o m4: $(m4_OBJECTS) $(m4_LIBS) $(CC) $(CFLAGS) -o m4 $(m4_OBJECTS) $(m4_LDADD) $(LIBS) .c.o: $(CC) $(CFLAGS) $(CPPFLAGS) -c $<
Example 5.22: The complete Makefile with suffix rule
In general, you will need a mixture of suffix rules and target dependency rules to describe the entire build process required to transform your source code into an executable. Sometimes you might want to use a single suffix rule for the vast majority of your files, but make an exception for an odd one or two.
For example, one set CPPFLAGS
might be fine for most of your C
compilation, but you need to add an additional -DDEBUG=1
option
to one or two special files:
CC = gcc CFLAGS = -ggdb3 CPPFLAGS = ... .c.o: $(CC) $(CFLAGS) $(CPPFLAGS) -c $< stackovf.o: stackovf.c $(CC) $(CFLAGS) $(CPPFLAGS) -DDEBUG=1 -c stackovf.c
Example 5.23: Compiling some files with different options
Here, the files compiled by the suffix rule use only the options
specified in the variables, but stackovf.o
is compiled by a
specific rule which explicitly adds -DDEBUG=1
. By listing the
file that needs different options as a specific target, that rule
overrides the suffix rule in that case.
Let's see this in action:
$ rm *.o $ make m4 gcc -ggdb3 -c main.c gcc -ggdb3 -c freeze.c gcc -ggdb3 -DDEBUG=1 -c stackovf.c.c gcc -o m4 main.o freeze.o stackovf.o ../lib/libm4.a