Reputation: 7566
Given the following 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)
the output is:
[0] % make all
_build/bin/aprogram_linux_386/aprogram
_build/bin/aprogram_linux_amd64/aprogram
_build/bin/aprogram_windows_386/aprogram
If I add another architecture fakearch
, the output is:
[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
Which makes me think is is just not performing the last iteration. How do I correct that?
Upvotes: 1
Views: 301
Reputation: 100926
The double-eval will work. But the more common method is to defer the expansion of the internal variable CUR_PROG
by escaping it via $$
, like this:
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
The reason for this is you're using call
first, then eval
. The call
function will expand its arguments before eval
sees them.
You have this inside your loops:
$(eval $(call PROGRAM_template,$(prog),$(sys),$(arch)))
In order to expand this make first will expand the inner function:
$(call PROGRAM_template,$(prog),$(sys),$(arch))
This expands the PROGRAM_template
as a straightforward string expansion: remember this is not eval
so it's not interpreting the text as if it were a makefile, it's just expanding values. So the assignment in the first line is not in effect because we haven't run the eval
yet. In your original implementation the first time through the loop, CUR_PROG
will have no value before the call
, so the call
expands to:
CUR_PROG := _build/bin/aprogram_linux_386/aprogram
: export GOOS = linux
: export GOARCH = 386
:
@echo ""
PROG_TARGETS +=
then that string is given to eval
for evaluation, but it's basically a no-op except for setting CUR_PROG
.
The next time through the loop, CUR_PROG
still has the previous value so when call expands the string you get:
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
etc. Basically, every time through the loop you're using the value of the CUR_PROG
value from the previous loop, because the expansion happens during the call
function, but the reassignment of the variable doesn't happen until the eval
function.
By escaping the CUR_PROG
it ensure that call
will not expand it, which means it will be left for eval
to expand. E.g., with my version above after the call
expansion is complete the result will be this:
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)
which is what you wanted.
A useful debugging tool for understanding eval
is to replace it with the info
function; this will cause make to print out the string that eval sees and it helps to visualize what's happening:
$(foreach ...,$(info $(call PROGRAM_template,$(prog),$(sys),$(arch))))))
Another option for a solution here is to not use a CUR_PROG
variable at all. You could, in this example, take the recipe out of the define
altogether. This would work just as well:
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 "$@"
Upvotes: 2
Reputation: 21000
You need to eval
the temp variable inside the define because make expands all references simultaneously inside a function call.
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)
Upvotes: 2