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?
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 "$#"
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)
Related
Target_2 is unable to get value of eval set in target_1,
The destination eval turns out empty handed, i.e no value is assigned to it
.PHONY:target_1
target_1:
ifeq ($(RQM_SETUP),ci)
$(eval efs-mount-path := $(shell mktemp -d))
else
#echo "$(VAR)"
endif
.PHONY:target_2
target_2:
$(MAKE) target_1 VAR=dags
$(eval destination := $(efs-mount-path))
define func1
include $(shell pwd)/test/$(strip $1)/component.mk
$(info :::::::${NAME} ::::::::::::::: )
endef
INCLUDES := a b c
$(foreach dir, $(INCLUDES), $(eval $(call func1, $(dir)) ))
all : $(objs)
Contents of each makefile:
cat test/a/component.mk
NAME := AA
cat test/b/component.mk
NAME := BB
cat test/c/component.mk
NAME := CC
Output is
::::::: :::::::::::::::
:::::::AA :::::::::::::::
:::::::BB :::::::::::::::
It looks like first time NAME is empty.
Let's look at the expansion of $(foreach dir, ${INCLUDES}, $(eval $(call func1, ${dir}) )) in painful detail.
${INCLUDES} is expanded, giving $(foreach dir,a b c,$(eval $(call func1,${dir})))
Next dir is set to a
$(call func1,a) is expanded
1 is set to a
func1 is expanded:
include $(shell pwd)/test/$(strip $1)/component.mk
$(info :::::::${NAME} ::::::::::::::: )
$(shell pwd) becomes HERE, say (N.B. Use ${CURDIR} instead)
$(strip $1) becomes $(strip a) becomes a
${NAME} expands to nothing
$(info ::::::: ::::::::::::::: ) expands to nothing
As a side effect ::::::: ::::::::::::::: appears on stdout
$(eval $(call func1,a)) expands to $(eval include HERE/test/a/component.mk), expands to nothing
As a side effect, the include is processed by make
Presumably HERE/test/a/component.mk exists and contains valid make syntax,
and the variable NAME gets a value.
1 is set to b. Lather, rinse, repeat.
Tip
To get a hint of problems in code like this, always run make with --warn:
$ make --warn -Rr
Makefile:8: warning: undefined variable 'NAME'
::::::: :::::::::::::::
⋮
Fix
To get some insight, replace the $(eval stuff) with $(error [stuff])
$ make
::::::: :::::::::::::::
Makefile:8: *** [ include /cygdrive/c/Users/somewhere/a/component.mk
]. Stop.
Here we see the $(info …) has disappeared even before it has got to the eval.
The naive fix is pretty horrible.
define func1
include $(shell pwd)/test/$(strip $1)/component.mk
$$(info :::::::$${NAME} ::::::::::::::: )
endef
Running this with the $(error …) in place gives
$ make
Makefile:8: *** [ include /cygdrive/c/Users/somewhere/a/component.mk
$(info :::::::${NAME} ::::::::::::::: )]. Stop.
That stuff between the [ and ] is valid make syntax.
Tidied up it looks like:
include /cygdrive/c/Users/somewhere/a/component.mk
$(info :::::::${NAME} ::::::::::::::: )
Job done. There are cleaner ways, but you need to understand the pain first!
I have something like this in my makefile:
exit_code := $(shell some_script.py; echo $$?)
ifneq ($(exit_code),0)
$(error Error occured)
endif
and it works properly - the echo $$? returns exit code of python script
I need to put that code into define like that:
define run-python-script
exit_code := $(shell some_script.py; echo $$?)
ifneq ($(exit_code),0)
$(error Error occured)
endif
endef
$(call run-python-script)
but then exit_code does not contain the exit code. And $(error Error occured) is always executed.
How to make work the version with define?
I need to put that code into define like that:
The if()/endif are processed and evaluated when the Makefile is parsed. You can't use them inside a variable definition.
The exit_code := ... in first snippet is a definition of a make variable, while in the second it is just a string, part of the make's variable called run-python-script.
You can try this (your script is replaced with false for test purposes):
eq = $(and $(findstring $(1),$(2)),$(findstring $(2),$(1)))
the-command = false; echo $$?
run-python-script = $(if $(call eq,0,$(shell $(the-command))),,$(error Error occured))
$(run-python-script)
(The $(call) is redundant in that case.)
My Makefile is based on multiple variables defined in a configuration file or ENV vars. My current solution is to test all of them manually:
NOGOAL = help clean distclean mrproper
ifeq ($(strip $(filter $(NOGOAL), $(MAKECMDGOALS))),)
VAR1 ?= $(error VAR1 undefined)
VAR2 ?= $(error VAR2 undefined)
VAR3 ?= $(error VAR3 undefined)
...
VARn ?= $(error VARn undefined)
endif
I would like to use a foreach loop instead:
ifeq ($(strip $(filter $(NOGOAL), $(MAKECMDGOALS))),)
TESTVAR = TEST1 TEST2 TEST3
$(foreach v, $(TESTVAR), $(eval $v ?= $$(warning Error: $v undefined)))
endif
Unfortunately eval doesn't work as I expected. Did I miss something?
Here a full test of my tests with 2 implementations of the tests. Even if TEST3 is not defined I don't get any error
TEST1 = 1
TEST2 = 1
#TEST3 = 1 # NOT DEFINED
TESTVAR := TEST1 TEST2 TEST3
# First implementation
$(foreach v, $(TESTVAR), $(eval $v ?= $$(warning Error: $v undefined)))
# Second implementation
$(foreach v, $(TESTVAR), $(eval $(call TESTER,$v)))
define TESTER
ifndef $1
$(warning $1 not defined)
endif
endef
# Dummy rule
all:
#echo Hello World
However, my first implementation works if I use $(TEST3) somewhere.
EDIT
Here I get no error but TEST3 is not defined:
~$ cat Makefile
TEST1 = 1
TEST2 = 1
#TEST3 = 1 # NOT DEFINED
TESTVAR := TEST1 TEST2 TEST3
# First implementation
$(foreach v, $(TESTVAR), $(eval $v ?= $$(warning Error: $v undefined)))
# Dummy rule
all:
#echo Hello World
~$ make
Hello World
Well, I guess I don't get it. Your original version, that you say works the way you want it, will not print any warnings unless you USE one of the variables which is not defined. Your first alternative with foreach works the same way: it will print a warning but only when you use the variable that's undefined.
If you want it that way, then testing for clean, etc. doesn't really make much sense since presumably those rules won't use the variables that are not defined so you won't get any errors (and if they did use the variables that weren't defined, presumably you'd want those rules to fail as well).
But in your second edit, you say that you want the make to fail immediately if the variables are not defined, regardless of whether or not they're used (in your last example you don't define TEST3, but you don't use TEST3 for anything either so no warning is printed). If that's what you want I don't see why you are assigning values to the variables with ?= at all, or using eval. Just write something like:
ifeq ($(strip $(filter $(NOGOAL), $(MAKECMDGOALS))),)
$(foreach v,$(TESTVAR),$(if $($v),,$(error Error: $v undefined))
endif
(In this version you do need to check MAKECMDGOALS since it fails immediately on an unset variable).
here is a snippet from my makefile:
main_DEPS = $(TARGETS_$(d)/classes/player) $(TARGETS_$(d)/classes/monster)
It sets main_DEPS to the expanded versions of the other two variables.
This works as it should.
How can I replace:
$(TARGETS_$(d)/classes/player) $(TARGETS_$(d)/classes/monster)
with a program that gives the same output?
I tried:
main_DEPS = $(shell program)
but it appeared to set main_DEPS equal to the string value $(TARGETS_$(d)/classes/player) $(TARGETS_$(d)/classes/monster), not the expanded versions.
I've also tried:
main_DEPS = $(eval $(shell program))
main_DEPS = $(value $(shell program))
main_DEPS = $(value $(eval $(shell program)))
main_DEPS = $(eval $(value $(shell program)))
Did you try this:
$(eval main_DEPS = $(shell program))
Here, the argument of eval (inner expression with shell) is expanded to get the statement to evaluate:
main_DEPS = $(TARGETS_$(d)/classes/player) $(TARGETS_$(d)/classes/monster)
If I properly understand, this should be exactly what you want.