3

I have a setup where the files I want to process with make are dependent on the output of another program. Building the program and all its prerequisites is also a complicated task so I would like to use make for this as well. Now my problem is, that it doesn't seem that one can generate rules and prerequisites in Makefile recipes. Consider the following code:

bar:
    echo target1 target2 target3 > bar

foo: bar
    $(eval BAR := $(shell cat bar))

define FUN
$(1):
    touch a$(1)
endef

ifdef BAR
$(foreach i,$BAR,$(eval $(call FUN,$(i))))
endif

blub: foo $(BAR)

I replaced a big set of complicated recipes that lead to the generation of the list of files I want to have in the end by the bar recipe. In reality, producing the content of bar is very complicated and should be done by a set of Makefile recipes and cannot just be done by (as the above suggests):

BAR:=$(shell echo target1 target2 target3)

I would like to put the foreach loop into the recipe for foo but that fails with prerequisites cannot be defined in recipes which makes sense and is also explained in function define in makefile

But it seems that when I do make blub that at the time when foo eval's BAR to a different value, the prerequisites for blub are not re-evaluated.

So I think ultimately I'm looking for two things:

  • how do I generate recipes dynamically at runtime, based on (and dependent on) what another recipe (bar in this case) outputs?

  • how do I update the prerequisites of a target (blub in this case) dynamically at runtime, based on (and dependent on) what another recipe (bar in this case) outputs?

Thank you!

EDIT: SOLUTION

With the help of @user657267 the following seems to solve my problem:

.PHONY: all
all: blub

-include bar.make

.PHONY: blub
blub: $(BAR)
    echo $^

bar.make: Makefile
    printf 'BAR=target1 target2 target3\n' > $@
    printf 'target1 target2 target3:\n' >>$@
    printf '\ttouch $$@' >> $@

.PHONY: clean
clean:
    rm -f target1 target2 target3 bar.make
Community
  • 1
  • 1
josch
  • 6,716
  • 3
  • 41
  • 49
  • 1
    FWIW, this use of echo is very non-portable. You should use printf (the program) to print anything other than simple text followed by a newline. Also FYI, you can use eval to set variables from within recipes, but you can't use it to create new rules. This is because after make reads in all the makefiles and before it starts to run recipes, it performs a process that aligns all the makefile data into its internal graph. Once that happens, you can't change the graph by adding new rules. – MadScientist Aug 28 '16 at 15:43
  • @MadScientist ah, thanks I think your commet wrt `eval()` answers my other question I asked you in the answer to this question, namely that the use of `include` is really required for this to work and that there cannot exist a `eval`-only solution. Which part of how I use `echo` here is not portable? – josch Aug 28 '16 at 20:54
  • Not all echo commands will turn the `\t` character sequence into a TAB character. – MadScientist Aug 28 '16 at 23:21
  • @MadScientist Indeed according to http://www.in-ulm.de/~mascheck/various/echo+printf/ I would not have support for the BSDs. I modified my answer accordingly. Thanks! – josch Aug 29 '16 at 06:12

1 Answers1

4

Sounds like you should be using make's self-remaking features

-include bar.make

blub: $(BAR)
    @echo $^

bar.make:
    @echo BAR := target1 target2 target3 > $@
    @echo target1 target2 target3: ; touch $$@ >> $@

Obviously the recipes for bar.make are contrived, in the real world they'd probably invoke some kind of script that outputs a valid makefile.

user657267
  • 20,568
  • 5
  • 58
  • 77
  • What magic is this??? Wow, thanks a lot! Your answer helped me to find the right solution (I yet have to apply it to my real problem). I had to make some changes to your solution: 1. `bar.make` needs to depend on the `Makefile` itself or otherwise it will not be regenerated if `Makefile` changes 2. There seems to have been some missing escaping for the arguments of `touch` 3. The order of `-include` and `blub` is important. If left as in your answer, the default target will become `target1` which is undesirable. But if `-include` is put after `blub` then it seems that `$(BAR)` doesn't evaluate – josch Aug 28 '16 at 12:58
  • 1
    For more info on this see: https://www.gnu.org/software/make/manual/html_node/Remaking-Makefiles.html Also there's a set of blog posts on doing stuff like this here: http://make.mad-scientist.net/category/metaprogramming/ – MadScientist Aug 28 '16 at 15:41
  • @MadScientist Thanks, the articles on make.mad-scientist.net made me understand the topic *much* better than the official make docs did after having read them several times! I wonder though: in http://make.mad-scientist.net/the-eval-function/ the `eval()` function is said to be the "most powerful". Does this mean that this question could also be solved by using `eval()` instead of the `include`-based technique suggested in this answer? – josch Aug 28 '16 at 19:58
  • @MadScientist Unrelated, but while I was messing around with this I noticed that if the new `file` function was used in place of `echo` then the makefiles wouldn't be reparsed, is this intended? – user657267 Aug 28 '16 at 22:40
  • Hm. Well, `file` happens outside the purview of the "run a recipe" engine. It's a side-effect of expanding the recipe. Then the recipe is empty and so make doesn't actually run any commands. It surprises me somewhat that an empty rule doesn't force the makefile to be reread. – MadScientist Aug 29 '16 at 15:31
  • @MadScientist Ah ok, and yes that does seem a bit odd. – user657267 Aug 29 '16 at 22:28