【问题标题】:Why would GNU Make miss last iteration of foreach?为什么 GNU Make 会错过 foreach 的最后一次迭代?
【发布时间】:2017-01-18 02:17:23
【问题描述】:

给定以下 Makefile:

PROGRAMS := aprogram
SYSTEMS := linux windows
ARCHS := 386 amd64

define PROGRAM_template =
CUR_PROG := _build/bin/$(1)_$(2)_$(3)/$(1)
$(CUR_PROG): export GOOS = $(2)
$(CUR_PROG): export GOARCH = $(3)
$(CUR_PROG):
    @echo "$(CUR_PROG)"
PROG_TARGETS += $(CUR_PROG)
endef

$(foreach prog,$(PROGRAMS),$(foreach sys,$(SYSTEMS),$(foreach arch,$(ARCHS),$(eval $(call PROGRAM_template,$(prog),$(sys),$(arch))))))

all: $(PROG_TARGETS)

输出是:

[0] % make all
_build/bin/aprogram_linux_386/aprogram
_build/bin/aprogram_linux_amd64/aprogram
_build/bin/aprogram_windows_386/aprogram

如果我添加另一个架构fakearch,输出是:

[0] % make all
_build/bin/aprogram_linux_386/aprogram
_build/bin/aprogram_linux_amd64/aprogram
_build/bin/aprogram_linux_fakearch/aprogram
_build/bin/aprogram_windows_386/aprogram
_build/bin/aprogram_windows_amd64/aprogram

这让我觉得只是没有执行最后一次迭代。我该如何纠正?

【问题讨论】:

    标签: makefile foreach build gnu-make


    【解决方案1】:

    双重评估将起作用。但更常见的方法是通过$$ 转义内部变量CUR_PROG 来推迟扩展,如下所示:

    PROG_TARGETS :=
    
    define PROGRAM_template =
    CUR_PROG := _build/bin/$(1)_$(2)_$(3)/$(1)
    $$(CUR_PROG): export GOOS = $(2)
    $$(CUR_PROG): export GOARCH = $(3)
    $$(CUR_PROG):
            @echo "$$(CUR_PROG)"
    PROG_TARGETS += $$(CUR_PROG)
    endef
    

    原因是您先使用call,然后使用evalcall 函数将在 eval 看到它们之前扩展其参数。

    你的循环中有这个:

    $(eval $(call PROGRAM_template,$(prog),$(sys),$(arch)))
    

    为了扩展这个make首先会扩展内部函数:

    $(call PROGRAM_template,$(prog),$(sys),$(arch))
    

    这会将PROGRAM_template 扩展为一个简单的字符串扩展:记住这不是eval,因此它不会将文本解释为一个makefile,它只是扩展值。所以第一行的赋值没有生效,因为我们还没有运行eval。在您第一次通过循环的原始实现中,CUR_PROGcall 之前将没有任何值,因此call 扩展为:

    CUR_PROG := _build/bin/aprogram_linux_386/aprogram
    : export GOOS = linux
    : export GOARCH = 386
    :
            @echo ""
    PROG_TARGETS += 
    

    然后将该字符串提供给eval 进行评估,但除了设置CUR_PROG 之外,它基本上是无操作的。

    下一次循环时,CUR_PROG 仍然有之前的值,所以当 call 扩展你得到的字符串时:

    CUR_PROG := _build/bin/aprogram_linux_amd64/aprogram
    _build/bin/aprogram_linux_386/aprogram: export GOOS = linux
    _build/bin/aprogram_linux_386/aprogram: export GOARCH = amd64
    _build/bin/aprogram_linux_386/aprogram:
            @echo "_build/bin/aprogram_linux_386/aprogram"
    PROG_TARGETS += _build/bin/aprogram_linux_386/aprogram
    

    等等。基本上,每次通过循环时,您都使用 previous 循环中的 CUR_PROG 值,因为扩展发生在 call 函数期间,但变量的重新分配不会在eval 函数之前不会发生。

    通过转义CUR_PROG 确保call 不会扩展它,这意味着它将留给eval 扩展。例如,在call 扩展完成后,使用上面的我的版本,结果将是这样的:

    CUR_PROG := _build/bin/aprogram_linux_386/aprogram
    $(CUR_PROG): export GOOS = linux
    $(CUR_PROG): export GOARCH = 386
    $(CUR_PROG):
            @echo "$(CUR_PROG)"
    PROG_TARGETS += $(CUR_PROG)
    

    这就是你想要的。

    理解eval的一个有用的调试工具是用info函数替换它;这将导致 make 打印出 eval 看到的字符串,它有助于可视化正在发生的事情:

    $(foreach ...,$(info $(call PROGRAM_template,$(prog),$(sys),$(arch))))))
    

    这里解决方案的另一个选项是根本不使用CUR_PROG 变量。在此示例中,您可以完全从 define 中取出配方。这也可以:

    define PROGRAM_template =
    PROG_TARGETS += _build/bin/$(1)_$(2)_$(3)/$(1)
    _build/bin/$(1)_$(2)_$(3)/$(1): export GOOS = $(2)
    _build/bin/$(1)_$(2)_$(3)/$(1): export GOARCH = $(3)
    endef
    
    $(foreach prog,$(PROGRAMS),$(foreach sys,$(SYSTEMS),$(foreach arch,$(ARCHS),$(eval $(call PROGRAM_template,$(prog),$(sys),$(arch))))))
    
    $(PROG_TARGETS):
            @echo "$@"
    

    【讨论】:

    • 感谢超级详细的回答。这有助于澄清很多。我会说,第一段代码没有按预期工作。当我进行转义扩展时,我只在 PROG_TARGETS 中得到一个目标。但是,没有 CUR_PROG 的最后一段代码可以完美运行,并且似乎是最干净的解决方案。
    • 如果它不起作用,则意味着您没有将PROG_TARGETS 定义为一个简单的变量。除非另有说明,否则所有变量都被认为是递归的,+= 运算符使用前一种类型。如果在第一次使用之前添加行PROG_TARGETS := 将其定义为简单变量,那么第一个示例将起作用。请参阅gnu.org/software/make/manual/html_node/Flavors.html 了解更多信息。
    【解决方案2】:

    您需要 eval 定义中的 temp 变量,因为 make 在函数调用中同时展开所有引用。

    PROGRAMS := aprogram
    SYSTEMS := linux windows
    ARCHS := 386 amd64
    
    define PROGRAM_template =
    $(eval CUR_PROG := _build/bin/$(1)_$(2)_$(3)/$(1))
    $(CUR_PROG): export GOOS = $(2)
    $(CUR_PROG): export GOARCH = $(3)
    $(CUR_PROG):
        @echo "$(CUR_PROG)"
    PROG_TARGETS += $(CUR_PROG)
    endef
    
    $(foreach prog,$(PROGRAMS),$(foreach sys,$(SYSTEMS),$(foreach arch,$(ARCHS),$(eval $(call PROGRAM_template,$(prog),$(sys),$(arch))))))
    
    all: $(PROG_TARGETS)
    

    【讨论】:

    • 哇,非常感谢。我仍然无法弄清楚这意味着什么,但它确实起到了作用。可以扩大一点吗?
    • 如果没有eval,make 将在定义中扩展$(CUR_PROG),然后再进行任何分配。
    猜你喜欢
    • 1970-01-01
    • 2012-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-03
    • 2011-01-17
    • 2014-12-16
    • 1970-01-01
    相关资源
    最近更新 更多