1

I have been trying to use John Graham-Cumming's excellent article about "Atomic Rules in GNU Make" at http://www.cmcrossroads.com/article/atomic-rules-gnu-make?page=0%2C0

However, I sometimes have to specify a dependency between two files using a separate rule with no recipe that looks simply like:

a : b

This has always worked as expected for me, but it fails miserably when specifying a dependency between two atomic rules. Here is a simple Makefile with 3 testcases:

atomic = $(eval $1: $(firstword $1).sentinel ; @:) \
         $(firstword $1).sentinel : $2 ; touch $$@ \
         $(foreach _, $1, $(if $(wildcard $_), , $(shell rm -f $(firstword $1).sentinel)))

d1 d2 d3:
    touch $@

## Test case 1 using sentinel and dependency (u1 u2 : t1 t2) specified in call to atomic
$(call atomic, t1 t2, d1)
    touch t1 t2

$(call atomic, u1 u2, t1 t2)
    touch u1 u2

## Test case 2 using sentinel and dependency (w1 w2 : v1 v2) specified as another rule does not work
$(call atomic, v1 v2, d2)
    touch v1 v2

$(call atomic, w1 w2, )
    touch w1 w2

w1 w2 : v1 v2

## Test case 3 showing that specifying a dependency (y1 : x1) with another rule does work
x1 : d3
    touch x1

y1 :
    touch y1

y1 : x1

##

clean :
    rm -f {d,t,u,v,w,x,y}{1,2,3}{,.sentinel} test{1,2,3}

test1 : u1
    touch test1

test2 : w1
    touch test2

test3 : y1
    touch test3

The first testcase does the right thing and builds everything in the expected order

> make test1
touch d1
touch t1.sentinel      
touch t1 t2
touch u1.sentinel      
touch u1 u2
touch test1

The second testcase fails miserably for atomic rules by building things in the wrong order

> make test2
touch w1.sentinel      
touch w1 w2
touch d2
touch v1.sentinel      
touch v1 v2
touch test2

The third simple testcase proves that using a separate rule to specify dependencies does work

> make test3
touch d3
touch x1
touch y1
touch test3

Can anyone explain why this is happening? Better yet, can anybody come up with a work-around so that test2 works the same as test1? I'm really stuck.

Thanks, -Tom

tvarga
  • 11
  • 3

1 Answers1

0

You have your manually added dependencies on the wrong targets.

Recall what the atomic construct is doing. It is collapsing the set of original targets down to a single sentinel target and using that as a sequencing point.

You are adding your prerequisites to the individual original targets (the exact thing the atomic workaround was designed to allow/fix in the first place).

You need to add the prerequisites to the sentinel file: w1.sentinel : v1 v2

(Or better, use a sentinel function like in the original post and use $(call sentinel,w1 w2): v1 v2.

As an aside, be careful with adding spaces around , in make function calls. It can have very damaging and somewhat bizarre effects on things.

atomic.mk:

atomic = $(eval $1: $(firstword $1).sentinel ; @:) \
         $(firstword $1).sentinel : $2 ; touch $$@ \
         $(foreach _,$1,$(if $(wildcard $_),,$(shell rm -f $(firstword $1).sentinel)))

d1 d2 d3 d4:
>-------touch $@

## Test case 1 using sentinel and dependency (u1 u2 : t1 t2) specified in call to atomic
$(call atomic, t1 t2, d1)
>-------touch t1 t2

$(call atomic, u1 u2, t1 t2)
>-------touch u1 u2

## Test case 2 using sentinel and dependency (w1 w2 : v1 v2) specified as another rule does not work
$(call atomic, v1 v2, d2)
>-------touch v1 v2

$(call atomic, w1 w2, )
>-------touch w1 w2

w1 w2 : v1 v2

## Test case 4 using manual dependencies added to sentinel explicitly.
$(call atomic, z1 z2, d4)
>-------touch z1 z2

$(call atomic, a1 a2, )
>-------touch a1 a2

a1.sentinel : z1 z2

##

clean :
>-------rm -f {d,t,u,v,w,x,y,z,a}{1,2,3,4}{,.sentinel} test{1,2,3,4}

test1 : u1
>-------touch test1

test2 : w1
>-------touch test2

test4: a1
>-------touch test4

Output from make -f ../atomic.mk -drR test1:

Reading makefiles...
Reading makefile `../atomic.mk'...
Updating makefiles....
 ....
Updating goal targets....
Considering target file `test1'.
 File `test1' does not exist.
  Considering target file `u1'.
   File `u1' does not exist.
    Considering target file `u1.sentinel'.
     File `u1.sentinel' does not exist.
      Considering target file `t1'.
       File `t1' does not exist.
        Considering target file `t1.sentinel'.
         File `t1.sentinel' does not exist.
          Considering target file `d1'.
           File `d1' does not exist.
           Finished prerequisites of target file `d1'.
          Must remake target `d1'.
touch d1
....
          Successfully remade target file `d1'.
         Finished prerequisites of target file `t1.sentinel'.
        Must remake target `t1.sentinel'.
touch t1.sentinel
....
touch t1 t2
....
        Successfully remade target file `t1.sentinel'.
       Finished prerequisites of target file `t1'.
      Must remake target `t1'.
      Successfully remade target file `t1'.
      Considering target file `t2'.
        Pruning file `t1.sentinel'.
       Finished prerequisites of target file `t2'.
       Prerequisite `t1.sentinel' is older than target `t2'.
      No need to remake target `t2'.
     Finished prerequisites of target file `u1.sentinel'.
    Must remake target `u1.sentinel'.
touch u1.sentinel
....
touch u1 u2
....
    Successfully remade target file `u1.sentinel'.
   Finished prerequisites of target file `u1'.
  Must remake target `u1'.
  Successfully remade target file `u1'.
 Finished prerequisites of target file `test1'.
Must remake target `test1'.
touch test1
....
Successfully remade target file `test1'.

Output from make -f ../atomic.mk -drR test2:

Reading makefiles...
Reading makefile `../atomic.mk'...
Updating makefiles....
 ....
Updating goal targets....
Considering target file `test2'.
 File `test2' does not exist.
  Considering target file `w1'.
   File `w1' does not exist.
    Considering target file `w1.sentinel'.
     File `w1.sentinel' does not exist.
     Finished prerequisites of target file `w1.sentinel'.
    Must remake target `w1.sentinel'.
touch w1.sentinel
....
touch w1 w2
....
    Successfully remade target file `w1.sentinel'.
    Considering target file `v1'.
     File `v1' does not exist.
      Considering target file `v1.sentinel'.
       File `v1.sentinel' does not exist.
        Considering target file `d2'.
         File `d2' does not exist.
         Finished prerequisites of target file `d2'.
        Must remake target `d2'.
touch d2
....
        Successfully remade target file `d2'.
       Finished prerequisites of target file `v1.sentinel'.
      Must remake target `v1.sentinel'.
touch v1.sentinel
....
touch v1 v2
....
      Successfully remade target file `v1.sentinel'.
     Finished prerequisites of target file `v1'.
    Must remake target `v1'.
    Successfully remade target file `v1'.
    Considering target file `v2'.
      Pruning file `v1.sentinel'.
     Finished prerequisites of target file `v2'.
     Prerequisite `v1.sentinel' is older than target `v2'.
    No need to remake target `v2'.
   Finished prerequisites of target file `w1'.
  Must remake target `w1'.
  Successfully remade target file `w1'.
 Finished prerequisites of target file `test2'.
Must remake target `test2'.
touch test2
....
Successfully remade target file `test2'.

Output from make -f ../atomic.mk -drR test4:

Reading makefiles...
Reading makefile `../atomic.mk'...
Updating makefiles....
 ....
Updating goal targets....
Considering target file `test4'.
 File `test4' does not exist.
  Considering target file `a1'.
   File `a1' does not exist.
    Considering target file `a1.sentinel'.
     File `a1.sentinel' does not exist.
      Considering target file `z1'.
       File `z1' does not exist.
        Considering target file `z1.sentinel'.
         File `z1.sentinel' does not exist.
          Considering target file `d4'.
           File `d4' does not exist.
           Finished prerequisites of target file `d4'.
          Must remake target `d4'.
touch d4
....
          Successfully remade target file `d4'.
         Finished prerequisites of target file `z1.sentinel'.
        Must remake target `z1.sentinel'.
touch z1.sentinel
....
touch z1 z2
....
        Successfully remade target file `z1.sentinel'.
       Finished prerequisites of target file `z1'.
      Must remake target `z1'.
      Successfully remade target file `z1'.
      Considering target file `z2'.
        Pruning file `z1.sentinel'.
       Finished prerequisites of target file `z2'.
       Prerequisite `z1.sentinel' is older than target `z2'.
      No need to remake target `z2'.
     Finished prerequisites of target file `a1.sentinel'.
    Must remake target `a1.sentinel'.
touch a1.sentinel
....
touch a1 a2
....
    Successfully remade target file `a1.sentinel'.
   Finished prerequisites of target file `a1'.
  Must remake target `a1'.
  Successfully remade target file `a1'.
 Finished prerequisites of target file `test4'.
Must remake target `test4'.
touch test4
....
Successfully remade target file `test4'.

See this graph to see the related prerequisite trees (manually created since I don't know of any good tools to do this from make output though I've wanted one for a while so apologies if I got something wrong).

Etan Reisner
  • 77,877
  • 8
  • 106
  • 148
  • Etan, thanks for your reponse! Your suggestion of adding the prerequisites to the sentinel and not the actual targets does work. However, what I find very strange is that the atomic methodology targets for a rule depend on the sentinel for THAT rule, and not the other way around. For example, using John's example pretty much verbatim results in the following output: touch d1 touch .sentinel.t1_t2 touch t1 t2 touch .sentinel.u1_u2 touch u1 u2 – tvarga Jul 12 '14 at 17:35
  • You can see that the sentinel is created before the targets. So, why doesn't simply adding a seperate dependency such as: u1 : t1 not behave just like : .sentinel.u1_u2 : t1 It certainly shouldn't flip the order of events completely around. -Tom – tvarga Jul 12 '14 at 17:35
  • The atomic call sets up a rule for the explicit targets depending on the sentinel target, which in turn depends on the original prereq targets. When you add a second manual prereq to the original targets it becomes a sibling prereq to the original sentinel target and then sequences as a sibling and not as a dependency. Looking at the `make -d` output from the various runs will be helpful here I think. I'll update my answer with it in a minute. – Etan Reisner Jul 13 '14 at 20:29