This article shows how to implement an important "missing feature" of GNU Make: the ability to rebuild targets when the commands for those targets change. GNU Make rebuilds a target when it is "out of date;" that is, it rebuilds when some of the prerequisites are newer than the target itself. But what if the target appears up to date, under the definition, but the actual commands to build the target have changed.
For example, what happens if you do a non-debug build (by typing make) and then run a debug build by typing make DEBUG=1. Unless the build has been structured so that the names of targets are dependent on whether the build is debug or non-debug, nothing happens at all.
GNU Make has no way of detecting that some targets ought to be rebuilt, because it doesn't take into account changing the commands. If, for example DEBUG=1 causes the flags passed to the compiler to change then the target ought to be rebuilt.
This article shows how, in a few lines of GNU Make code, to make that happen.
An Example
Here's an example Makefile that's used throughout this article to demonstrate the rebuilding when commands change system. To make the operation of the system very clear I've avoided using built-in GNU Make rules so this Makefile isn't as simple as it could be.
The Makefile creates two .o files: foo.o and bar.o by compiling corresponding .c files. The compilation is done using the built-in variable COMPILE.C (which will be something like normally be the name of a suitable compiler for your system, references to variables like CPPFLAGS and use of $@ and $< to compile the right thing).
There's a specific reference to $(DEBUG), it's turned into a preprocessor variable called DEBUG using the compiler's -D option. I haven't bothered showing the contents of foo.c and bar.c as they are irrelevant.
all: foo.o bar.o
foo.o: foo.c
$(COMPILE.C) -DDEBUG=$(DEBUG) -o $@ $<
bar.o: bar.c
$(COMPILE.C) -o $@ $<
$ make
g++ -c -DDEBUG= -o foo.o foo.c
g++ -c -o bar.o bar.c
$ make
make: Nothing to be done for `all'.
$ make DEBUG=1
make: Nothing to be done for `all'.
The Example Revisited
To fix the problem described above this article introduces a helper Makefile called signature. The contents and working of signature are described below, but first let's look at how the example Makefile is modified to used the signature helper.
include signature
all: foo.o bar.o
foo.o: foo.c
$(call do,$$(COMPILE.C) -DDEBUG=$$(DEBUG) -o $$@ $$<)
bar.o: bar.c
$(call do,$$(COMPILE.C) -o $$@ $$<)
-include foo.sig bar.sig
Lastly for each .o file being managed by signature there's an include of a corresponding .sig file. The final line of the Makefile includes foo.sig (for foo.o) and bar.sig (for bar.o). Notice that -include is used in case the .sig file is missing.
Before seeing how this works here are some example of it in operation. First run a clean build (i.e. with no .o files present) and then rerun make to see that there's nothing to do:
$ make
g++ -c -DDEBUG= -o foo.o foo.c
g++ -c -o bar.o bar.c
$ make
make: Nothing to be done for `all'.






