AProgrammer
AProgrammer

Reputation: 52274

Ordering in makefiles

I've two targets foo and bar. Neither depend on the other, but if bar has to be rebuilt, it has to be done before foo. They are what gnu-make calls phony targets, their rules have always to be executed when they are specified.

Currently, we express a main target which depends on both like this:

# user level targets
all: bar
        @$(MAKE) foo
        @echo all

alt: foo
        @echo alt

# internal targets
foo:
        @echo foo

bar: qux
        @echo bar

qux:
        @echo qux
        @touch qux

and we have the required behavior: if qux is not up-to-date: make bar outputs qux bar foo all (in that order) and make alt outputs foo alt; if qux is up-to-date, make bar output bar foo all and make alt outputs foo alt.

This is increasingly uncomfortable as foo has to be handled specifically (all targets which depend on both have to be handled that way, foo can't be put in a variable describing dependencies if bar is also there, the submake is itself an issue and the command line has to be maintained to pass additional variables). We now have another target which has to be handled in the same way and I'm looking for other, more convenient, ways to handle the structure.


Note 1 : In practice, I'm currently using only gnu-make but the only known dependency on a gnu-make extension over POSIX is the possibility to include files (which is quite widely available). I'd prefer something which keep the current state (i.e. widely supported constructs), but if it is not possible, the use of a gnu-make only extension is acceptable.


Note 2: gnu-make has a notion of order-only-prerequisites, but it apparently doesn't provide what we need. With

# user level targets
all: bar foo 
        @echo all

alt: foo
        @echo alt

# internal targets
foo: | bar
        @echo foo

bar:
        @echo bar

make alt also build bar (if a file bar exist, its date doesn't influence the decision of rebuilding foo, which is the documented behavior).


Note 3: The more I think about it, the less I think it is possible to solve this problem with make without using a recursive call. It seems to me that it need two passes on the dependency graph, one to determine what has to be built, one to determine the ordering and I know nothing in make behavior which can't be done with a one pass algorithm.

Upvotes: 0

Views: 497

Answers (1)

bobbogo
bobbogo

Reputation: 15483

Hmmm, how about this hack (for a hack it undoubtedly is :-)).

Basically, you could run make -d -n plus your command arguments. The output will contain several lines like Must remake target 'clean'. This information tells you whether this run of make will attempt to build both foo and bar. If this turns out to be the case, just add a rule to cause the serialisation you want.

A sketch:

this := $(lastword ${MAKEFILE_LIST})

ifndef DONTRECURSE
  targets-that-will-get-remade := $(patsubst %',%,$(shell ${MAKE} -f ${this} ${MAKECMDGOALS} --debug=b -n DONTRECURSE=nosiree | grep -Po "Must remake target '\K.*'"))
endif

ifeq (bar foo,$(sort $(filter bar foo,${targets-that-will-get-remade})))
foo: bar
endif

.PHONY: foo bar
foo bar:
    sleep 3
    : $@

So, you run make. DONTRECURSE is not set so the $(shell …) runs. That runs make a second time with the same makefile and goals, but adds the -d (debug) and -n (don't actually run the recipes) flags. DONTRECURSE is set to prevent a third copy of make running.

The expansion of all that is a list of the targets this run of make will attempt to build on this run. (Extracting the target names is pretty tiresome—there is probably a cleaner way.)

If this list of targets includes both foo and bar, simply add a foo: bar dependency. Job done. The sleep 3 lines show this serialisation working when you use -j4 (say).

Upvotes: 1

Related Questions