问题描述
我有一个简单的Makefile,我想在其中传递文件列表,并将其存储在用户定义的函数中的变量中,并打印文件名。
REQUESTS:=request/*.json
define my_func
file=${1} && \
echo ${file}
endef
run_test : $(REQUESTS)
for f in $^ ; \
do \
$(call my_func,$$f) ; \
done
运行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
我希望它返回以下输出
request/file1.json
request/file2.json
解决方法
这里有几个问题。首先,在不应该放置的位置放置
$(call my_func,$$f)
用${1}
替换宏的 $f
(注意前导空格),最后得到file= $f
,由于语法上的错误,它被shell拒绝。使用:
$(call my_func,$$f)
相反,避免在Makefile中使用无用的空格,有时它们是有意义的。
接下来(假设您已解决了第一个问题),您在宏中使用了${file}
,但是在将结果传递给外壳程序之前,make对其进行了扩展,并且没有名为file
的make变量(它是 shell 变量,而不是 make 变量),它会扩展为空白,执行的配方为:
for f in request/foo.json request/bar.json ...; \
do \
file=$f && echo ; \
done
要通过make退出第一个扩展,必须将此$
(而不是与实际make变量对应的另一个)加倍:
define my_func
file=${1} && \
echo $${file}
endef
现在,扩展的配方为:
for f in request/foo.json request/bar.json ...; \
do \
file=$f && echo ${file}; \
done
它应该表现出您想要的样子。
旁注
您必须记住,仅在将完整配方传递给外壳之前(而不是在外壳循环的每次迭代中),仅将配方扩展一次。换句话说,当外壳程序开始执行您的配方时,期待make进行任何额外处理的时机已经很晚了。 $(call...)
已经扩展,结果将不再更改;使该规则已经完成工作。如果您期望$(call...)
在不同的迭代中具有不同的效果,您将感到失望。
如果您不希望$(call...)
在不同的迭代中有不同的扩展,那么您写的(几乎)可以。但是,如果您在宏中使用其他make函数(patsubst
...)和变量,则需要另一种方法:您还必须使用make迭代器(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会将配方扩展为:
file="request/foo" && echo "$file"; file="request/bar" && echo "$file" ...
,然后再将其传递给外壳。以某种方式展开循环,对每次迭代进行预处理(patsubst
,然后shell以平坦的命令序列执行结果,而没有shell循环。
最后但并非最不重要的是,每次我看到这些遍历Makefiles中文件列表的循环时,我想知道它们是否不是作者不了解模式规则或多目标规则的标志。以防万一,下面是一个示例Makefile,它使用静态模式规则来处理(在此示例中只是复制)每个request/xxx.json
文件并生成一个foo/xxx.foo
文件:
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
潜在的好处是所有配方都可以并行运行(make -j
),这可以在多核计算机上产生真正的影响。