I have a makefile that extracts a tar file into a variable number of folders which are determined at runtime (in the example below, this is hard coded):
firstTarget: start
.PHONY: start
DIRS = d1 d2 d3
TAR_FILES = $(wildcard ?.tar)
TAR_FILE_NAMES := $(TAR_FILES:%.tar=%)
FILES = $(foreach _, $(DIRS), $(TAR_FILE_NAMES:%=$_/%.txt))
define AddRule
DIR = $(1)
$(DIR)/%.txt: %.tar
@echo $(1) $(DIR) $$@ $$< $$*
@mkdir -p $(DIR)
@tar -xvf $$< > /dev/null
@mv $$*.txt $(DIR)
endef
$(foreach _, $(DIRS), $(eval $(call AddRule, $_)))
#$(foreach _, $(DIRS), $(eval $(call AddRule, $_)))
start: $(FILES)
@echo "Finished"
setup:
@touch a.txt
@tar -cvf a.tar a.txt > /dev/null
@rm a.txt
clean:
@rm -rf d1/ d2/ d3/
Rules are generated at runtime for each of the variable cases and parsed as make instructions using eval
and call
.
Note the following lines:
$(foreach _, $(DIRS), $(eval $(call AddRule, $_)))
#$(foreach _, $(DIRS), $(eval $(call AddRule, $_)))
When the second line is commented out, I get the following output and error:
d2 d1 d1/a.txt a.tar a
d3 d2 d2/a.txt a.tar a
make: *** No rule to make target 'd3/a.txt', needed by 'start'. Stop.
When I run make with -pRr
I see the following output for the rule for d3/a.txt
:
# Not a target:
d3/a.txt:
# Implicit rule search has been done.
# File does not exist.
# File has not been updated.
Compare this to the rule for d1/a.txt
:
d1/a.txt: a.tar
# Implicit rule search has been done.
# Implicit/static pattern stem: 'a'
# Last modified 2016-02-18 09:36:24
# File has been updated.
# Successfully updated.
# automatic
# @ := d1/a.txt
# automatic
# % :=
# automatic
# * := a
# automatic
# + := a.tar
# automatic
# | :=
# automatic
# < := a.tar
# automatic
# ^ := a.tar
# automatic
# ? := a.tar
# variable set hash-table stats:
# Load=8/32=25%, Rehash=0, Collisions=1/25=4%
# recipe to execute (from 'Makefile', line 18):
@echo d2 d1 $@ $< $*
@mkdir -p d1
@tar -xvf $< > /dev/null
@mv $*.txt d1
With the second line added, meaning that each rule is called and evaluated twice, it works fine:
d2 d1 d1/a.txt a.tar a
d3 d2 d2/a.txt a.tar a
d1 d3 d3/a.txt a.tar a
Finished
The rule for d3/a.txt
is also similar to the above rule for d1/a.txt
when viewed through make -pRr
.
It's worth noting that in the rule I'm outputting the following:
@echo $(1) $(DIR) $$@ $$< $$*
And now the questions:
- Why doesn't the rule for
d3
get evaluated properly? - Why does $(1) not equal $(DIR) when in the rule? This is odd since $(1) is assigned to $(DIR).
- Why does calling and evaluating the rule make it work fine?
If you want to repeat the problem, run make setup
first, then make
.
Update 1: 18/02/2016
The answer by @DevSolar solves the problem above but has made me realise my test case wasn't a perfect representation of the real problem. In the real problem, the parameter to the dynamic rule isn't at the beginning of the target:
FILES = $(foreach _, $(DIRS), $(TAR_FILE_NAMES:%=somedir/$_/%.txt))
define AddRule
somedir/$(1)/%.txt: %.tar
@echo $(1) $$@ $$< $$*
@mkdir -p somedir/$(1)
@tar -xvf $$< > /dev/null
@mv $$*.txt $(1)
endef
Note that the target is now somedir/$(1)/%.txt: %.tar
. This results in the following error in make 3.81:
Makefile:17: warning: overriding commands for target `somedir'
Makefile:17: warning: ignoring old commands for target `somedir'
Makefile:17: warning: overriding commands for target `somedir'
Makefile:17: warning: ignoring old commands for target `somedir'
Interestingly, make 4.1 has something else to say:
Makefile:17: *** mixed implicit and normal rules: deprecated syntax
Makefile:17: warning: overriding recipe for target 'somedir/'
Makefile:17: warning: ignoring old recipe for target 'somedir/'
Makefile:17: *** mixed implicit and normal rules: deprecated syntax
Makefile:17: warning: overriding recipe for target 'somedir/'
Makefile:17: warning: ignoring old recipe for target 'somedir/'
Makefile:17: *** mixed implicit and normal rules: deprecated syntax
make: *** No rule to make target 'somedir/d1/a.txt', needed by 'start'. Stop.
Neither have helped me figure out the cause.