I have input files with a certain pattern that generates files with that pattern repeated.
my input files are:
inputs/<pattern>.py
e.g.
inputs/cfg1.py
inputs/cfg2.py
calling python inputs/<pattern>.py
generates:
outputs/<pattern>/<pattern>.rpt
outputs/<pattern>/<pattern>.html
outputs/<pattern>/<pattern>.txt
e.g. python inputs/cfg1.py
generates
outputs/cfg1/cfg1.rpt
outputs/cfg1/cfg1.html
outputs/cfg1/cfg1.txt
How do I write a make rule for that?
I tried
outputs/%/%.rpt: inputs/%.py
python $<
but it doesn't work
With GNU make >= 4.3, using grouped targets (&:
) and secondary expansion:
I := inputs
O := outputs
in := $(patsubst $I/%.py,%,$(wildcard $I/*.py))
out := $(foreach i,$(in),$(foreach s,rpt html txt,$O/$i/$i.$s))
.PHONY: all
all: $(out)
$O:
mkdir -p $@
.SECONDEXPANSION:
$O/%.rpt $O/%.html $O/%.txt &: $I/$$(word 1,$$(subst /, ,$$*)).py | $O
python $<
Grouped targets (a b c &: p
) is a way to indicate that the same run of the recipe creates several targets. It is needed to avoid running the same recipe 3 times for your rpt
, html
and txt
targets.
After the special .SECONDEXPANSION:
all rules have their list of prerequisites expanded twice, and the second time the automatic variables, like $*
for the stem of a pattern rule, are defined. So, this is a way to add some processing to the list of prerequisites. In your case to transform x/x
into x
.
After the first expansion the rule above becomes:
outputs/%.rpt outputs/%.html outputs/%.txt &: inputs/$(word 1,$(subst /, ,$*)).py | outputs
python $<
During the second expansion, for each inputs/x.py
, it becomes (step by step):
# step 1:
outputs/x/x.rpt outputs/x/x.html outputs/x/x.txt &: inputs/$(word 1,$(subst /, ,x/x)).py | outputs
python $<
# step 2:
outputs/x/x.rpt outputs/x/x.html outputs/x/x.txt &: inputs/$(word 1,x x).py | outputs
python $<
# step 3:
outputs/x/x.rpt outputs/x/x.html outputs/x/x.txt &: inputs/x.py | outputs
python $<
For completeness, as suggested in another answer, you could obtain the same with eval
, the make function that programmatically instantiates make statements. Here is a slightly different version of an eval
-based solution:
I := inputs
O := outputs
in := $(patsubst $I/%.py,%,$(wildcard $I/*.py))
.PHONY: all
define my_rule
all: $O/$1/$1.rpt $O/$1/$1.html $O/$1/$1.txt
$O/$1/$1.rpt $O/$1/$1.html $O/$1/$1.txt &: $I/$1.py | $O/$1
python $$<
endef
$(foreach i,$(in),$(eval $(call my_rule,$i)))
$(addprefix $O/,$(in)):
mkdir -p $@
Note the $$<
instead of $<
, it is important, again because of double expansion.