为什么GNU Make会错过foreach的最后一次迭代?

时间:2017-01-18 02:17:23

标签: makefile foreach build gnu-make

给出以下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

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

2 个答案:

答案 0 :(得分:2)

你需要eval define中的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)

答案 1 :(得分:2)

双重评估将有效。但更常见的方法是通过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所以它不会将文本解释为make文件,它只是在扩展值。因此,第一行中的分配不起作用,因为我们还没有运行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

等。基本上,每次循环都会使用上一个循环中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 "$@"