Rebuilding When CPPFLAGS Changes

be run to build foo.o have changed.

$ make DEBUG=1
g++    -c -DDEBUG=1 -o foo.o foo.c

Of course, bar.o was not rebuilt because it was truly up to date (it's object was new and there were no command changes). Running make DEBUG=1 again says that there's nothing to be done, but just typing make rebuilds foo.o again because DEBUG is now undefined.

$ make DEBUG=1
make: Nothing to be done for `all'.
$ make
g++    -c -DDEBUG= -o foo.o foo.c

The signature system also works for variables that are hidden within a recursive variable. In GNU Make COMPILE.C actual expands CPPFLAGS to create the complete compiler command-line. Here's what happens if we modify CPPFLAGS on the command-line by adding a definition:

$ make CPPFLAGS+=-DFOO=foo
g++ -DFOO=foo -c -DDEBUG= -o foo.o foo.c
g++ -DFOO=foo -c -o bar.o bar.c

Both foo.o and bar.o were rebuilt because CPPFLAGS had changed (and because CPPFLAGS was part of the commands used to build those two object files).

Of course, changing a variable that isn't referenced doesn't cause anything to be updated. Here's an example starting from a clean build and then redefining SOMEVAR.

$ make
g++    -c -DDEBUG= -o foo.o foo.c
g++    -c -o bar.o bar.c
$ make SOMEVAR=42
make: Nothing to be done for `all'.

How Signature Works

To understand how this works the first place to look is inside a .sig file. The .sig files are automatically generated by signature for each rule that uses the $(call do,...) (the details of how are later on).

Here, for example, is the contents of the foo.sig file after the first clean build was run:

$(eval @ := foo.o)
$(eval % := )
$(eval < := foo.c)
$(eval ? := foo.force)
$(eval ^ := foo.c foo.force)
$(eval + := foo.c foo.force)
$(eval * := foo)

foo.o: foo.force

$(if $(call sne,$(COMPILE.C) -DDEBUG=$(DEBUG) -o $@ $<,g++    -c -DDEBUG= -o foo.o foo.c),$(shell touch foo.force))

The first seven lines capture the state of the automatic variables as defined when the foo.o rule is being processed. These are needed so that the current commands for a rule can be compared with the commands the last time the rule was run.

Next comes the line foo.o: foo.force. This says that foo.o must be rebuilt is foo.force is newer. It's this line that causes foo.o to get rebuilt when the commands change, and it's the next line that touches foo.force if the commands have changed.

The long $(if ...) statement uses the GMSL (see sne (string not equal) to compare the current commands for foo.o (by expanding them) against their value the last time they were expanded. If the commands have changed then $(shell touch foo.force) is called.

Since the .sig files are processed when the Makefile is being parsed (they are just Makefile's themselves read using include), all the .force files will have been updated before any rules run. And so this small .sig file does all the work of forcing an object file to rebuild when the commands change.

The .sig files themselves are created by signature:

include gmsl

last_target :=

dump_var = \$$(eval $1 := $($1))

define new_rule
@echo "$(call map,dump_var,@ % < ? ^ + *)" > $S
@$(if $(wildcard $F),,touch $F)
@echo $@: $F >> $S

define do
$(eval S := $*.sig)$(eval F := $*.force)$(eval C := $1)
$(if $(call sne,$@,$(last_target)),$(call new_rule),$(eval last_target := $@))
@echo "$(subst $$,\$$,$$(if $$(call sne,$1,$C),$$(shell touch $F)))" >> $S

signature include the GNU Make Standard Library and then defines the important do macro used to wrap the commands in a rule. When do is called it creates the appropriate .sig file containing

About the author

AgileConnection is a TechWell community.

Through conferences, training, consulting, and online resources, TechWell helps you develop and deliver great software every day.