Reputation: 4067
I have a simple Makefile where I want to pass a list of files and store them inside a variable in user-defined function and print the file names.
REQUESTS:=request/*.json
define my_func
file=${1} && \
echo ${file}
endef
run_test : $(REQUESTS)
for f in $^ ; \
do \
$(call my_func , $$f) ; \
done
I get this below error when I run make run_test
for f in request/temp.json ; \
do \
file= $f
/bin/sh: 3: Syntax error: end of file unexpected (expecting "done")
Makefile:53: recipe for target 'run_test' failed
make: *** [run_test] Error 2
I want it to return the below output
request/file1.json
request/file2.json
Upvotes: 0
Views: 220
Reputation: 29050
Several problems, here. First you put spaces where you should not:
$(call my_func , $$f)
substitutes the ${1}
of your macro with $f
(note the leading space) and you end up with file= $f
which is rejected by the shell as syntactically incorrect. Use:
$(call my_func,$$f)
instead and avoid useless spaces in Makefiles, they are sometimes meaningful.
Next (assuming you fix the first issue), you use ${file}
in your macro but as make expands it before passing the result to the shell and as there is no make variable named file
(it is a shell variable, not a make variable), it expands as empty space and the recipe that is executed is:
for f in request/foo.json request/bar.json ...; \
do \
file=$f && echo ; \
done
To escape this first expansion by make you must double this $
(not the other which corresponds to a real make variable):
define my_func
file=${1} && \
echo $${file}
endef
Now, the expanded recipe is:
for f in request/foo.json request/bar.json ...; \
do \
file=$f && echo ${file}; \
done
And it should behave about as you want.
You must remember that the recipe is expanded by make only once, just before passing the complete recipe to the shell, not at each iteration of the shell loop. In other words, when the shell starts executing your recipe it is to late to expect any extra processing from make; the $(call...)
has already been expanded and the result will not change any more; make already finished its job for this rule. If you expect a different effect of $(call...)
in different iterations you will be disappointed.
If you do not expect a different expansion of $(call...)
in different iterations, then what you wrote is (almost) OK. But if you use other make functions (patsubst
...) and variables in your macro you need another approach: you must also use a make iterator (foreach
):
REQUESTS := $(wildcard request/*.json)
define my_func
file="$(patsubst %.json,%,$(1))" && \
echo "$$file"
endef
run_test: $(REQUESTS)
$(foreach f,$^,$(call my_func,$(f));)
make will expand the recipe as:
file="request/foo" && echo "$file"; file="request/bar" && echo "$file" ...
before passing it to the shell. Make somehow unrolls the loop, pre-processes (patsubst
) each iteration, and the shell executes the result as a flat sequence of commands, without a shell loop.
Last but not least, each time I see these loops over lists of files in Makefiles, I wonder if they are not the sign that the authors do not know about pattern rules or multi-targets rules. Just in case, here is an example Makefile that uses a static pattern rule to process (in this example just copy) each request/xxx.json
file and produce a foo/xxx.foo
file:
REQUESTS := $(wildcard request/*.json)
FOOS := $(patsubst request/%.json,foo/%.foo,$(REQUESTS))
define my_func
cp "request/$(1).json" "foo/$(1).foo"
endef
.PHONY: run_test clean
run_test: $(FOOS)
$(FOOS): foo/%.foo: request/%.json | foo
$(call my_func,$*)
foo:
mkdir -p $@
clean:
rm -rf foo
A potential benefit is that all recipes can run in parallel (make -j
) which can make a real difference on a multi-core computer.
Upvotes: 2