Atomic Rules in GNU Make

[article]

    touch .sentinel

The rule to build a and b can only be run once because there's only one target specified (.sentinel).  If c or d are newer then .sentinel gets rebuilt and hence a and b.  If the Makefile asks for either a or b then they are rebuilt via the .sentinel file.

The funny @: command in the a b rule just means that there are commands to build a and b but they do nothing.

Of course, it would be nice to make this transparent, and that's where the atomic function comes in.  The atomic function sets up the sentinel file automatically based on the names of the targets to be built and creates the necessary rules:

sp :=

sp +=

sentinel = .sentinel.$(subst $(sp),_,$(subst /,_,$1))

atomic = $(eval $1: $(call sentinel,$1) ; @:)$(call sentinel,$1): $2 ; touch $$@

.PHONY: all

all: a b

$(call atomic,a b,c d)

    touch a b

All that's been done here is that the previous rule has been replaced by a call to atomic. 
The first argument is the list of targets that need to be built atomically and the second argument is the list of prerequisites.

atomic uses the sentinel function to create a unique sentinel file name (in the case of a b the sentinel file name is .sentinel.a_b) and then sets up the necessary rules.

Expanding atomic in this Makefile would be the same as doing:

sp :=

sp +=

sentinel = .sentinel.$(subst $(sp),_,$(subst /,_,$1))

atomic = $(eval $1: $(call sentinel,$1) ; @:)$(call sentinel,$1): $2 ; touch $$@

.PHONY: all

all: a b

a b: .sentinel.a_b ; @:

.sentinel.a_b: c d ; touch $@

    touch a b

There's only one flaw with this technique.  If you delete a or b you must also delete the related sentinel file otherwise the files won't get rebuilt.

That can be worked around by having the Makefile delete the sentinel file if necessary by checking to see if any of the targets being built is missing.  Here's the updated code:

sp :=

sp +=

sentinel = .sentinel.$(subst $(sp),_,$(subst /,_,$1))

atomic
= $(eval $1: $(call sentinel,$1) ; @:)$(call sentinel,$1): $2 ; touch
$$@ $(foreach t,$1,$(if $(wildcard $t),,$(shell rm -f $(call
sentinel,$1))))

.PHONY: all

all: a b

$(call atomic,a b,c d)

   touch a b

Now atomic runs through the targets and if any are missing (detected by the $(wildcard)) the sentinel file is deleted.

About the author

John Graham-Cumming's picture John Graham-Cumming

John Graham-Cumming is Co-Founder 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.

AgileConnection is one of the growing communities of the TechWell network.

Featuring fresh, insightful stories, TechWell.com is the place to go for what is happening in software development and delivery.  Join the conversation now!