Reputation: 43
I have a Makefile with user-specified input files in the variable INPUT_FILES
.
For each input file, I need to create an input file prime. Some notes:
$(OUTPUT_DIR)
My basic strategy has been to generate the set of targets based INPUT_FILES
and then try to determine which input file is the actual dependency of the target.
A few variations I've tried:
# Create a list of targets
OUTPUT_FILES = $(foreach file,$(notdir $(INPUT_FILES)),$(OUTPUT_DIR)/$(file))
# This doesn't work, because all input files are dependencies of each output file
$(OUTPUT_FILES): $(INPUT FILES)
program --input $^ --output $@
# This doesn't work because $@ hasn't been resolved yet
$(OUTPUT_FILES): $(filter,$(notdir $@),$(INPUT FILES))
program --input $^ --output $@
# This doesn't work, I think because $@ is evaluated too late
.SECONDEXPANSION:
$(OUTPUT_FILES): $(filter,$(notdir $$@),$(INPUT FILES))
program --input $^ --output $@
# This doesn't work either
.SECONDEXPANSION:
$(OUTPUT_FILES): $$(filter,$(notdir $@),$(INPUT FILES))
program --input $^ --output $@
I've looked into static pattern rules as well, but I'm not sure if it can help with what I need.
Upvotes: 1
Views: 790
Reputation: 100781
When asking for help please provide some kind of actual example names so we can understand more clearly what you have. It also helps us use terminology which is not confusing.
You really want to use $<
in your recipes, not $^
, I expect.
IF your "input files" are truly input-only (that is, they are not themselves generated by other make rules) then you can easily solve this problem with VPATH.
Just use this:
VPATH := $(sort $(dir $(INPUT_FILES)))
$(OUTPUT_DIR)/% : %
program --input $< --output $@
Upvotes: 1
Reputation: 28910
In your case .SECONDEXPANSION:
works because you can use make functions (filter
) to compute the prerequisite of each output file. In other circumstances it could be impossible. But there is another GNU make feature that can be used in cases like yours: if you use GNU make you can programmatically instantiate make statements using foreach-eval-call
. Just remember that the macro that is used as the statements pattern gets expanded twice, reason why you must double some $
signs (more on this later):
OUTPUT_DIR := dir
OUTPUT_FILES := $(addprefix $(OUTPUT_DIR)/,$(notdir $(INPUT_FILES)))
.PHONY: all
all: $(OUTPUT_FILES)
# The macro used as statements pattern where $(1) is the input file
define MY_RULE
$(1)-output-file := $(OUTPUT_DIR)/$$(notdir $(1))
$$($(1)-output-file): $(1)
@echo program --input $$^ --output $$@
endef
$(foreach i,$(INPUT_FILES),$(eval $(call MY_RULE,$(i))))
Demo:
$ mkdir -p a/a b
$ touch a/a/a b/b c
$ make INPUT_FILES="a/a/a b/b c"
program --input a/a/a --output dir/a
program --input b/b --output dir/b
program --input c --output dir/c
Explanation:
When make parses the Makefile it expands $(foreach ...)
: it iterates over all words of $(INPUT_FILES)
, for each it assigns the word to variable i
and expands $(eval $(call MY_RULE,$(i)))
in this context. So for word foo/bar/baz
it expands $(eval $(call MY_RULE,$(i)))
with i = foo/bar/baz
.
$(eval PARAMETER)
expands PARAMETER
and instantiates the result as new make statements. So, for foo/bar/baz
, make expands $(call MY_RULE,$(i))
with i = foo/bar/baz
and considers the result as regular make statements. The expansion of $(eval ...)
has no other effect, the result is the empty string. This is why in our case $(foreach ...)
expands as the empty string. But it does something: create new make statements dynamically for each input file.
$(call NAME,PARAMETER)
expands PARAMETER
, assigns it to temporary variable 1
and expands the value of make variable NAME
in this context. So, $(call MY_RULE,$(i))
with i = foo/bar/baz
expands as the expanded value of variable MY_RULE
with $(1) = foo/bar/baz
:
foo/bar/baz-output-file := dir/$(notdir foo/bar/baz)
$(foo/bar/baz-output-file): foo/bar/baz
@echo program --input $^ --output $@
which is what is instantiated by eval
as new make statements. Note that we had a first expansion here and the $$
became $
. Note also that call
can have more parameters: $(call NAME,P1,P2)
will do the same with $(1) = P1
and $(2) = P2
.
When make parses these new statements (as any other statements) it expands them (second expansion) and finally adds the following to its list of variables:
foo/bar/baz-output-file := dir/baz
and the following to its list of rules:
dir/baz: foo/bar/baz
@echo program --input $^ --output $@
This may look complicated but it is not if you remember that the make statements added by eval
are expanded twice. First when $(eval ...)
is parsed and expanded by make, and a second time when make parses and expands the added statements. This is why you frequently need to escape the first of these two expansions in your macro definition by using $$
instead of $
.
And it is so powerful that it is good to know.
Upvotes: 1
Reputation: 43
I finally found a permutation that works - I think the problem was forgetting that filter
requires a %
for matching patterns. The rule is:
.SECONDEXPANSION:
$(OUTPUT_FILES): $$(filter %$$(@F),$(INPUT_FILES))
program --input $^ --output $@
I also realized I can use @F
(equivalent to $$(notdir $$@)
) for cleaner syntax.
The rule gets the target's filename on its second expansion ($$(@F)
) and then gets the input file (with path) that corresponds to it on second expansion ($$(filter %$$(@F),$(INPUT_FILES))
).
Of course, the rule only works if filenames are unique. If someone has a cleaner solution, feel free to post.
Upvotes: 2