rookie
rookie

Reputation: 1208

Read variable from file in target recipe enclosed in foreach

I have a top level Makefile that can create multiple targets, for which I have a for loop in place. In this file I am trying to read a text file with a version number that is created only when a sub makefile runs. I have got it to work but it looks very ugly, so I'm thinking there is possibly a better way to do it. Here is the top level Makefile:

T1=_release
T2=_debug
T3=_test


all: bin/mybin_release.bin
debug: bin/mybin_debug.bin
debug: export DBG:=1
test: bin/mybin_test.bin
test: export TST:=1

define build_foo

bin/mybin$$($(1)).bin: bin/abc$$($(1)).bin bin/xyz$$($(1)).bin
    cat $$^ > $$@
    VER=$$$$(cat bin/rev.txt) && mv bin/mybin$$($(1)).bin bin/mybin$$($(1))_$$$${VER}.bin

bin/abc$$($(1)).bin:
    $(MAKE) -C mod1    # <--- rev.txt is produced here

bin/xyz$$($(1)).bin:
    $(MAKE) -C mod2

endef

$(foreach suffix, T1 T2 T3, $(eval $(call build_foo,$(suffix))))

clean:
    rm bin/*.bin bin/*.txt

Notice the attempt to grab file contents in VER. Is there a better/right way to do this? I cannot use eval since it runs at the beginning and rev.txt only gets created once the sub make runs. Also, is this a decent way to build multiple targets(using foreach)? I use the exported variables to modify the target built by the sub makefile.

Upvotes: 0

Views: 630

Answers (1)

Renaud Pacalet
Renaud Pacalet

Reputation: 29167

As far as I understand, this bin/rev.txt file, and all the bin/abcXXX.bin are produced when running $(MAKE) -C mod1. So, it is the same for all. What about:

include version.mk

version.mk: bin/rev.txt
    { printf 'VER = '; cat $<; } > $@

bin/rev.txt:
    $(MAKE) -C mod1

Demo:

$ ls
Makefile
$ cat Makefile
include version.mk

all:
    touch $(VER).txt

version.mk: rev.txt
    { printf 'VER = '; cat $<; } > $@

rev.txt:
    echo "1.2.3" > $@
$ make --quiet
Makefile:1: version.mk: No such file or directory
$ ls
1.2.3.txt  Makefile  rev.txt  version.mk

Explanation:

version.mk is a second makefile that make will look for. As per GNU make documentation, section 3.5:

To this end, after reading in all makefiles, make will consider each as a goal target and attempt to update it. If a makefile has a rule which says how to update it (found either in that very makefile or in another one) or if an implicit rule applies to it (see Using Implicit Rules), it will be updated if necessary. After all makefiles have been checked, if any have actually been changed, make starts with a clean slate and reads all the makefiles over again. (It will also attempt to update each of them over again, but normally this will not change them again, since they are already up to date.)

Note: as you wrote your makefile, $(MAKE) -C mod1 will be run several times, which is a waste. You could instead exploit a specificity of GNU make pattern rules: when they have multiple targets, make considers that all targets are produced by one single invocation of the recipe. Example:

$ cat Makefile
all: a.bin b.bin c.bin

a.%in b.%in c.%in:
    @echo 'building a.bin b.bin c.bin'
$ make all
building a.bin b.bin c.bin

See? The recipe is executed only once to build the 3 targets. The only problem is that the % wildcard must match at least one character. So, in your case you could do something like (the character that % matches is the b of bin/):

T1        := _release
T2        := _debug
T3        := _test
suffixes  := T1 T2 T3
pattern   := $(foreach suffix,$(suffixes),%in/abc$($(suffix))) %in/rev.txt

$(pattern):
    $(MAKE) -C mod1

This will tell make that $(MAKE) -C mod1 builds all the bin/abcXXX.bin and bin/rev.txt at once. Same with $(MAKE) -C mod2.

All in all, you could probably get completely rid of your build_foo generic rule by gluing all these features together. Something like:

T1         := _release
T2         := _debug
T3         := _test
suffixes   := T1 T2 T3
patternabc := $(foreach suffix,$(suffixes),%in/abc$($(suffix)).bin) %in/rev.txt
patternxyz := $(foreach suffix,$(suffixes),%in/xyz$($(suffix)).bin)

include version.mk

all:   bin/mybin_release_$(VER).bin
debug: bin/mybin_debug_$(VER).bin
debug: export DBG:=1
test:  bin/mybin_test_$(VER).bin
test:  export TST:=1

version.mk: bin/rev.txt
    { printf 'VER = '; cat $<; } > $@

$(patternabc):
    $(MAKE) -C mod1

$(patternxyz):
    $(MAKE) -C mod2

bin/mybin%_$(VER).bin: bin/abc%.bin bin/xyz%.bin
    cat $^ > $@

Upvotes: 1

Related Questions