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:
$(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 http://gmsl.sf.net/) 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
endef
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
$C
endef
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 the state of all the automatic variables.
The capture of automatic variables is done by the new_rule macro called by do which uses the GMSL map function to call another macro (dump_var) for each of @ % < ? ^ + *. new_rule also ensures that the corresponding .force file has been created.
Lastly, do writes out the complex $(if ...) statement that contains both the unexpanded and expanded versions of the commands for the current rule. And then it actually runs the commands (that's the $C) at the end.
Limitations
There are a small number of limitations to this technique. Firstly if the commands in a rule contain any side effects (e.g. they call $(shell ...) then the system may misbehave if there was an assumption that the $(shell ...) was only called once.
Secondly, it's vital that signature is included before any of the .sig files.
And lastly, if the Makefile is edited and the commands in a rule change the signature system will not notice. If you do that then it's vital to regenerate the corresponding target so that the .sig is updated.
Conclusion
Not a lot of code, for a lot of benefit. Let me know if you use it in a large project!
Lastly, reader Semih Cemiloglu wrote to say that a while back I'd promised to write an article on tracing which rules in a Makefile fire and in what order. Although some of that is covered in recent articles on the GNU Make Debugger, I'll return in April with the missing piece! Thanks for the reminder Semih.
John Graham-Cumming is Founder and VP of Engineering at Electric Cloud, Inc. Prior to joining Electric Cloud, John was a Venture Consultant with Accel Partners, VP of Internet Technology at Interwoven, Inc. (IWOV), VP of Engineering at Scriptics Corporation (acquired by Interwoven), and Chief Architect at Optimal Networks, Inc.
John holds BA and MA degrees in Mathematics and Computation and a Doctorate in Computer Security from Oxford University. John is the creator of the highly acclaimed open source POPFile project. He also holds two patents in network analysis and has others pending.
@echo $@: $F >> $S
to
@echo '$@: | $F' >> $S
then the .force file does not appear in $? or $^.
John.