Prateek Joshi
Prateek Joshi

Reputation: 4067

Makefile :Store argument of inside a variable in user defined function and print it

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

Answers (1)

Renaud Pacalet
Renaud Pacalet

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.

Side notes

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

Related Questions