Brian
Brian

Reputation: 130

defer prerequisite expansion until after a (different) target creation

I want to be able use the result of a target created in a rule in the prerequisite of another rule in GNU make. So for example:

PREREQ = $(shell echo "reading target1" >&2; cat target1)

target1:
        echo "prereq" > $@

target2: target1 $(PREREQ)
        echo foo > $@

target2 should depend on prereq as read from the target1 file, but that is not in the file until the target1 recipe is executed.

Granted this is very contrived example with I am sure lots of suggestions about how to refactor this particular example but I'm not looking to refactor this example. This is just a simplified example of my more complicated problem where I need to derive prerequisites from the contents of a file that is not created until a recipe in the Makefile is executed.

The question is, [how] can I make expansion of $(PREREQ) (and therefore the execution of the $(shell cat target1) defer until after the target1 rule is actually executed?

Update: I tried .SECONDARYEXPANSION: but that doesn't seem to do the job:

$ make -d target2
...
reading target1
cat: target1: No such file or directory
...
Updating goal targets....
Considering target file 'target2'.
 File 'target2' does not exist.
  Considering target file 'target1'.
   File 'target1' does not exist.
   Finished prerequisites of target file 'target1'.
  Must remake target 'target1'.
echo "prereq" > target1
[ child management ]
  Successfully remade target file 'target1'.
 Finished prerequisites of target file 'target2'.
Must remake target 'target2'.
echo foo > target2
[ child management ]
Successfully remade target file 'target2'.

As you can see, "reading target" was only printed once at the very beginning demonstrating that PREREQ is not expanded again due to the .SECONDEXPANSION: and the list of targets Considered for target2 did not include prereq.

Upvotes: 5

Views: 1051

Answers (3)

vjalle
vjalle

Reputation: 935

There are several solutions:

  1. GNU make 4.4 has been released! Haven't tried yet, but the release notes claim that secondary expansion only expands the prerequisites when they're considered. Furthermore you can delay execution with the .WAIT special prerequisite. That works fine, I tested. If .WAIT really delays the second expansion of the prerequisites after .WAIT, you're good.

  2. Recursive make. Restart make after the prerequisites for the second rule were updated. This is a bit lame solution, can't recommend it.

  3. Produce the prerequisites into make include file(s). Make automatically restarts after updating include files (re-exec). I'm currently using this method, and it works great. Better than recursive make but still slow, as all the makefiles have to be parsed again. A possible solution is comparing the old and new prerequisites, and only updating the include file, if its content changed. The second rule also needs to be modified to do nothing, if the content changed. Make will run all rules before restarting, but if they don't update their targets, after the restart they'll be executed again, now with the proper prerequisites. An interesting feature of make is that you can define make variables inside a recipe, and use them in other recipes (but not in the prereq list, unless #1 above reall works).

Upvotes: 1

jfMR
jfMR

Reputation: 24738

Deferring the expansion of the prerequisite $(PREREQ) can be achieved by conditionally creating the target2 and relying on recursion:

ifndef expand-prereq
target2: target1
    $(MAKE) --no-print-directory -f $(lastword $(MAKEFILE_LIST)) $@ expand-prereq=y
else
target2: target1 $(PREREQ)
    echo foo > $@
endif

The first time make runs for this makefile, the variable expand-prereq is not defined and therefore, the first targe2 rule is generated as a result of the conditional. This kind of dummy rule makes possible to update target1 without expanding $(PREREQ). Matching this rule results in target1 being updated (since target1 is a prerequisite of this rule) and make being called recursively for the same makefile and with target2 as target.

The second time make is (recursively) invoked, the variable expand-prereq was defined by means of the command-line argument expand-prereq=y, so the second target2 rule is generated as a result of the else branch this time. This rule is the one that actually produces the target target2. Note that before this rule can be matched, target1 has been already created as a side effect of the first dummy rule, so the expansion of $(PREREQ) happens after target1 has been created (what you were looking for).

Upvotes: 4

Florian Weimer
Florian Weimer

Reputation: 33694

You could write the complete rule for target2 to a separate file and -include it:

The exact mechanics will depend on your specific use case, and it may well be impossible to achieve what we need using this approach, but it supports a variety of styles for automated dependency generation.

Upvotes: 0

Related Questions