theherk
theherk

Reputation: 7566

Why would GNU Make miss last iteration of foreach?

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

Answers (2)

MadScientist
MadScientist

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

user657267
user657267

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

Related Questions