Makefile:在用户定义的函数中存储变量内部的参数并打印 旁注

问题描述

我有一个简单的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),这可以在多核计算机上产生真正的影响。