为什么 GNU Make 会错过 foreach 的最后一次迭代?
Why would GNU Make miss last iteration of foreach?
给定以下 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
这让我觉得只是没有执行最后一次迭代。我该如何纠正?
您需要 eval
定义中的临时变量,因为 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)
double-eval 会起作用。但更常见的方法是通过 $$
转义来延迟内部变量 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
,然后是 eval
。 call
函数将在 eval
看到它们之前展开它的参数。
你的循环中有这个:
$(eval $(call PROGRAM_template,$(prog),$(sys),$(arch)))
为了扩展这个make首先会扩展内部函数:
$(call PROGRAM_template,$(prog),$(sys),$(arch))
这将 PROGRAM_template
扩展为一个简单的字符串扩展:请记住这不是 eval
,因此它不会像生成文件一样解释文本,它只是扩展值。所以第一行的赋值没有生效,因为我们还没有运行 eval
。在您第一次通过循环的原始实现中,CUR_PROG
在 call
之前没有值,因此 call
扩展为:
CUR_PROG := _build/bin/aprogram_linux_386/aprogram
: export GOOS = linux
: export GOARCH = 386
:
@echo ""
PROG_TARGETS +=
然后该字符串被提供给eval
进行评估,但它基本上是一个no-op,除了设置CUR_PROG
。
下一次循环,CUR_PROG
仍然有以前的值,所以当调用扩展字符串时,您得到:
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 "$@"
给定以下 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
这让我觉得只是没有执行最后一次迭代。我该如何纠正?
您需要 eval
定义中的临时变量,因为 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)
double-eval 会起作用。但更常见的方法是通过 $$
转义来延迟内部变量 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
,然后是 eval
。 call
函数将在 eval
看到它们之前展开它的参数。
你的循环中有这个:
$(eval $(call PROGRAM_template,$(prog),$(sys),$(arch)))
为了扩展这个make首先会扩展内部函数:
$(call PROGRAM_template,$(prog),$(sys),$(arch))
这将 PROGRAM_template
扩展为一个简单的字符串扩展:请记住这不是 eval
,因此它不会像生成文件一样解释文本,它只是扩展值。所以第一行的赋值没有生效,因为我们还没有运行 eval
。在您第一次通过循环的原始实现中,CUR_PROG
在 call
之前没有值,因此 call
扩展为:
CUR_PROG := _build/bin/aprogram_linux_386/aprogram
: export GOOS = linux
: export GOARCH = 386
:
@echo ""
PROG_TARGETS +=
然后该字符串被提供给eval
进行评估,但它基本上是一个no-op,除了设置CUR_PROG
。
下一次循环,CUR_PROG
仍然有以前的值,所以当调用扩展字符串时,您得到:
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 "$@"