Makefile won't assign variable values in `ifeq` statement within function definition - makefile

I'm trying to assign a value to a variable in make when a condition is met, inside a function:
Without $() around the ifeq statements:
VARIABLE=true
define test_ifeq
FOO := foo
ifeq (${VARIABLE}, true)
FOO := true
else
FOO := false
endif
echo "value: ${FOO}"
endef
all:
$(call test_ifeq)
Result:
FOO := foo
make: FOO: No such file or directory
make: *** [Makefile:15: all] Error 127
With $() around the ifeq and variable assignment statements:
VARIABLE=true
define test_ifeq
$(FOO := foo)
$(ifeq (${VARIABLE}, true)
FOO := true
else
FOO := false
endif)
echo "value: ${FOO}"
endef
all:
$(call test_ifeq)
Result:
echo "value: "
value:
Why does this not work?

The ifeq and the likes are evaluated at the moment when make reads the makefile, that means there is no actual ifeq present in the assigned value of your test_ifeq variable.
To achieve what you need you have to use $(if ...) and $(filter ...) built in functions along with $(eval ...).
It should look something like this:
VARIABLE=true
define test_ifeq
$(eval FOO := $(if $(filter $(VARIABLE),true), true, false))
echo "value: $(FOO)"
endef
all:
$(call test_ifeq)
NOTE: I haven't tested it, wrote it straight out of head...

To do it with Make:
VARIABLE=true
define test_ifeq
FOO := foo
ifeq (${VARIABLE}, true)
FOO := true
else
FOO := false
endif
$$(info "value: $${FOO}")
endef
$(eval $(call test_ifeq))
To do it on the command line (assuming bash):
VARIABLE=true; FOO=foo; if [ $VARIABLE = true ]; then FOO=true; else FOO=false; fi; echo $FOO
To do it within a rule in a makefile:
all:
VARIABLE=true; \
FOO=foo; \
if [ $$VARIABLE = true ]; then \
FOO=true; else \
FOO=false; \
fi; \
echo $$FOO
To do it within a rule, using a function:
VARIABLE=true
define test_ifeq2
FOO=foo; \
if [ $(VARIABLE) = true ]; then \
FOO=true; else \
FOO=false; \
fi; \
echo $$FOO
endef
all:
$(call test_ifeq2)

Related

Makefile test if variable is not empty

In a makefile I'm trying to
run a shell command and capture the output in a make variable
do something if the variable is not empty
I've created this simplified makefile to demonstrate my problem. Neither make a or make b executes the body of the if, I don't understand why not.
.PHONY: a b
a:
$(eval MY_VAR = $(shell echo whatever))
#echo MY_VAR is $(MY_VAR)
$(info $(MY_VAR))
ifneq ($(strip $(MY_VAR)),)
#echo "should be executed"
endif
#echo done
b:
$(eval MY_VAR = $(shell echo ''))
#echo MY_VAR is $(MY_VAR)
$(info $(MY_VAR))
ifneq ($(strip $(MY_VAR)),)
#echo "should not be executed"
endif
#echo done
I'm using
$ make --version
GNU Make 3.81
Edit: as pointed out, the vars don't need to be make vars
If you want to dynamically test the content of MY_VAR, you may have to :
a:
$(eval MY_VAR = $(shell echo ''))
$(if $(strip $(MY_VAR)),echo ok,echo no)
if evaluation will become echo ok if MY_VAR is not empty, otherwise it will become echo no
Note that, due to the time of evaluation, make conditionals (ifeq, ifneq...) cannot be used in recipes the way you tried. Use shell conditionals, instead, as shown below.
As your MY_VAR variable is used only in recipes, is target-dependent and you want it to be computed only when needed, why don't you use shell variables, instead of make variables?
$ cat Makefile
.PHONY: a b
a:
MY_VAR=$$(echo 'whatever') && \
echo '$#: MY_VAR is $$MY_VAR' && \
if [ -n "$$MY_VAR" ]; then \
echo '$#: should be executed'; \
fi && \
echo '$#: done'
b:
MY_VAR=$$(echo '') && \
echo '$#: MY_VAR is $$MY_VAR' && \
if [ -n "$$MY_VAR" ]; then \
echo '$#: should not be executed'; \
fi && \
echo '$#: done'
$ make a
a: MY_VAR is whatever
a: should be executed
a: done
$ make b
b: MY_VAR is
b: done
In case you absolutely need MY_VAR to be a target-specific make variable, but want to execute only once (per target) the shell command that produces its value, MadScientist has a wonderful trick that you should probably look at. Applied to your case, it should look like:
$ make --version
GNU Make 4.1
...
$ cat Makefile
a: MY_VAR = $(eval a: MY_VAR := $$(shell echo 'whatever'))$(MY_VAR)
b: MY_VAR = $(eval b: MY_VAR := $$(shell echo ''))$(MY_VAR)
a:
#echo '$#: MY_VAR is $(MY_VAR)' && \
if [ -n "$(MY_VAR)" ]; then \
echo '$#: should be executed'; \
fi && \
echo '$#: done'
b:
#echo '$#: MY_VAR is $(MY_VAR)' && \
if [ -n "$(MY_VAR)" ]; then \
echo '$#: should not be executed'; \
fi && \
echo '$#: done'
$ make a
a: MY_VAR is whatever
a: should be executed
a: done
$ make b
b: MY_VAR is
b: done
$ make b a
b: MY_VAR is
b: done
a: MY_VAR is whatever
a: should be executed
a: done
It may look extremely strange but it guarantees that MY_VAR is computed if and only if targets a or b are invoked, and only at most once for each. Have a look at MadScientist's post for detailed explanations. Go, it's brilliant.
The ifeq and family of conditionals are evaluated when parsing the Makefile. If you want a conditional for a Make variable when expanding a rule, you'll want to use the $(if ) function:
.PHONY: a b
a b:
#$(if $(strip $(MY_VAR)),echo "MY_VAR isn't empty",)
#echo done
a: MY_VAR =
b: MY_VAR = something
A bit left field, but was useful in my case so I guess it's worth a share: pipe the value to xargs with the --no-run-if-empty option:
echo $(POSSIBLY_EMPTY) | xargs --no-run-if-empty echo
Note that this will not work on OSX. See How to ignore xargs commands if stdin input is empty? for more details on xargs --no-run-if-empty

How to get "at most once" semantics in variable assignments?

Shell commands sometimes take a long time to run, so you may not want to do VAR = $(shell slow-cmd) (with =, the slow-cmd will be run every time the variable is referenced). Using VAR := $(shell slow-cmd) can be useful, but if you are building a target that does not ever need the variable expanded, you will get one more invocation of the slow-cmd than is needed. In the following makefile (with gnu-make), you can get the desired behavior: the shell command to define a value for V2 is never invoked more than once, and for the target foo it is not invoked at all. But this is a heinous kludge. Is there a more reasonable way to ensure that a variable is only defined when needed, but never evaluated more than once?
V1 = $(shell echo evaluating V1 > /dev/tty; echo V1 VALUE)
all: foo bar V2
#echo $(V1) $#
#echo $(V2) $#
foo:
#echo $(V1) $#
bar: V2
#echo $(V1) $#
#echo $(V2) $#
V2:
$(eval V2 := $(shell echo evaluating V2 > /dev/tty; echo V2 VALUE))
.PHONY: all foo bar
There's no way to do it without tricks, but there's a cleaner way (maybe) than you're using. You can use:
V1 = $(eval V1 := $$(shell some-comand))$(V1)
For more details and explanation of exactly how this works see this page.
Target-specific deferred variables are an option:
host> cat Makefile
foo: VFOO = $(shell echo "VFOO" >> log.txt; echo "VFOO")
foo:
#echo '$(VFOO)' > $#
bar: VBAR = $(shell echo "VBAR" >> log.txt; echo "VBAR")
bar:
#echo '$(VBAR)' > $#
host> make foo
host> cat log.txt
VFOO
host> make foo
make: 'foo' is up to date.
host> cat log.txt
VFOO
host> make bar
host> cat log.txt
VFOO
VBAR
host> make bar
make: 'bar' is up to date.
host> cat log.txt
VFOO
VBAR

Choosing Arguments with GNU Make

Consider this short Makefile:
ARGS := -foo
my_ARGS := -bar
their_ARGS :=
all: your.foo my.foo their.foo
%.foo:
#echo $*: $(call _choose_,ARGS,$*)
_isndef_ = $(findstring undefined,$(origin $1))
_choose_ = $(value $(if $(call _isndef_,$2_$1),$1,$2_$1))
It correctly outputs:
your: -foo
my: -bar
their:
My Questions:
is this the way that automake does it? (LDADD and rmt_LDADD)
is there a shorter or better way of doing this?
Why not use variable name construction? It would be just simply:
ARGS := -foo
my_ARGS := -bar
their_ARGS :=
all: your.foo my.foo their.foo
%.foo:
#echo $*: $(or $($*_ARGS),$(ARGS))
More info here for example: http://make.mad-scientist.net/constructed-macro-names/
If you want to "override with empty" you can use target-specific variables:
ARGS := -foo
my.foo: ARGS := -bar
their.foo: ARGS :=
all: your.foo my.foo their.foo
%.foo:
#echo $*: $(ARGS)

Remove target from MAKECMDGOALS?

I have the following in my makefile. Its a GNUmakefile, so the additional make features are supported:
# Undefined Behavior Sanitzier (Clang and G++)
ifeq ($(findstring ubsan,$(MAKECMDGOALS)),ubsan)
CXXFLAGS += -fsanitize=undefined
MAKECMDGOALS := $(subst ubsan,,$(MAKECMDGOALS))
endif # UBsan
Running it results in:
make ubsan
make: *** No rule to make target 'ubsan'. Stop.
I know the code path is being executed (by introducing an error in the block). How do I remove the target from the command goals?
You cannot do what you want here as far as I know.
You can modify the variable value and you will see the changes if you check the value yourself but modifying the value of MAKECMDGOALS will not affect make in any way.
If you look at Appendix A Quick Reference in the GNU Make Manual you will see that it says:
MAKECMDGOALS
The targets given to make on the command line. Setting this variable has no effect on the operation of make.
See Arguments to Specify the Goals.
The closest you could get to what you are trying to do here, I think, would be to re-execute make on the Makefile (or whatever) manually.
That being said this sort of thing is probably better done as variables instead of targets.
$ cat Makefile
$(info $(origin UBSAN))
$ make
undefined
$ make UBSAN=
command line
So something like this.
# Undefined Behavior Sanitzier (Clang and G++)
ifneq (undefined,$(origin UBSAN))
CXXFLAGS += -fsanitize=undefined
endif # UBsan
Yes, you can. Based on my recent answer to this other question Force Makefile to execute script after building any target (just before exiting), it was very easy to do what you would like.
This has only a drawback, you will lose any command-line arguments you pass directly to make as make all --silent. However, you can look into the question: How to detect if the makefile `--silent/--quiet` command line option was set?, and recapture the command-line arguments you would like to repass to the second make call command on make -f ${MAKEFILE_JUSTNAME} ${MAKECMDGOALS}.
ECHOCMD:=/bin/echo -e
SHELL := /bin/bash
ifeq (${IS_MAKEFILE_RUNNING_TARGETS},)
MAKEFILE_JUSTNAME := $(firstword ${MAKEFILE_LIST})
# UBsan
ifeq ($(findstring ubsan,${MAKECMDGOALS}),ubsan)
MAKECMDGOALS := $(subst ubsan,,${MAKECMDGOALS})
USELESS := $(eval export IS_UBSAN_CXXFLAGS_SET=1)
endif
define DEFAULTTARGET :=
printf 'Calling "%s" "%s"\n\n' "${MAKEFILE_JUSTNAME}" "${MAKECMDGOALS}"
make -f ${MAKEFILE_JUSTNAME} ${MAKECMDGOALS}
printf '\n'
printf 'Running something after all rules finished\n'
endef
%:
#:
# printf 'IS_MAKEFILE_RUNNING_TARGETS="%s"\n' "${IS_MAKEFILE_RUNNING_TARGETS}"
$(if ${IS_MAKEFILE_RUNNING_TARGETS},,${DEFAULTTARGET})
$(eval export IS_MAKEFILE_RUNNING_TARGETS=1)
all:
#:
# printf 'IS_MAKEFILE_RUNNING_TARGETS="%s"\n' "${IS_MAKEFILE_RUNNING_TARGETS}"
$(if ${IS_MAKEFILE_RUNNING_TARGETS},,${DEFAULTTARGET})
$(eval export IS_MAKEFILE_RUNNING_TARGETS=1)
else
# UBsan
ifneq (${IS_UBSAN_CXXFLAGS_SET},)
CXXFLAGS += -fsanitize=undefined
endif
all:
printf 'Calling my all rule, CXXFLAGS="%s"\n' "${CXXFLAGS}"
foo:
printf 'Calling my foo rule, CXXFLAGS="%s"\n' "${CXXFLAGS}"
bar:
printf 'Calling my bar rule, CXXFLAGS="%s"\n' "${CXXFLAGS}"
xyzzy:
printf 'Calling my xyzzy rule, CXXFLAGS="%s"\n' "${CXXFLAGS}"
endif
Usage examples:
make
printf 'Calling "%s" "%s"\n\n' "Makefile" ""
Calling "Makefile" ""
make -f Makefile
make[1]: Entering directory '/cygdrive/d/User/Downloads'
printf 'Calling my all rule, CXXFLAGS="%s"\n' ""
Calling my all rule, CXXFLAGS=""
make[1]: Leaving directory '/cygdrive/d/User/Downloads'
printf '\n'
printf 'Running something after all rules finished\n'
Running something after all rules finished
make foo bar
printf 'Calling "%s" "%s"\n\n' "Makefile" "foo bar"
Calling "Makefile" "foo bar"
make -f Makefile foo bar
make[1]: Entering directory '/cygdrive/d/User/Downloads'
printf 'Calling my foo rule, CXXFLAGS="%s"\n' ""
Calling my foo rule, CXXFLAGS=""
printf 'Calling my bar rule, CXXFLAGS="%s"\n' ""
Calling my bar rule, CXXFLAGS=""
make[1]: Leaving directory '/cygdrive/d/User/Downloads'
printf '\n'
printf 'Running something after all rules finished\n'
Running something after all rules finished
make foo bar ubsan
printf 'Calling "%s" "%s"\n\n' "Makefile" "foo bar "
Calling "Makefile" "foo bar "
make -f Makefile foo bar
make[1]: Entering directory '/cygdrive/d/User/Downloads'
printf 'Calling my foo rule, CXXFLAGS="%s"\n' "-fsanitize=undefined"
Calling my foo rule, CXXFLAGS="-fsanitize=undefined"
printf 'Calling my bar rule, CXXFLAGS="%s"\n' "-fsanitize=undefined"
Calling my bar rule, CXXFLAGS="-fsanitize=undefined"
make[1]: Leaving directory '/cygdrive/d/User/Downloads'
printf '\n'
printf 'Running something after all rules finished\n'
Running something after all rules finished
I am using the following pattern to pass parameters around, it has its limitations (only [a-z,0-9] characters) but can become quite handy:
ifeq (console,$(firstword $(MAKECMDGOALS)))
CONSOLE_ARGS := $(wordlist 2,$(words $(MAKECMDGOALS))
$(eval $(CONSOLE_ARGS):;#:)
endif
console::
run-some-command $(CONSOLE_ARGS)
For your case the following works:
# Undefined Behavior Sanitzier (Clang and G++)
ifeq (ubsan,$(firstword $(MAKECMDGOALS)))
CXXFLAGS += -fsanitize=undefined
$(eval $(CONSOLE_ARGS):;#:)
endif # UBsan

Makefile rule always been processed

My recipe $(HDAIMG) is always been processed, even when already there is a $(HDAIMG) file in the folder. What am I doing wrong?
HDAIMG := $(TESTDIR)/$(PROJECT)-hda.img
HDAIMG value, actually, is test/project-hda.img
PHONY: $(PROJECT)
all: $(PROJECT) $(HDAIMG)
$(PROJECT): check-env
$(call v_exec, 1, $(MAKE) -C $(SRCDIR) $#)
$(HDAIMG): $(PROJECT) check-env
$(call print_white_init, HDAIMG)
$(call print, Creating $#)
$(call v_exec, 2, dd if=/dev/zero of=$# count=0 bs=1 seek=$(HDAIMGSIZE) &> /dev/null)
$(call print, Partitioning $#)
$(call v_exec, 2, parted --script $# mklabel msdos mkpart primary ext4 1 100%)
$(call print, Creating $# device maps)
$(call v_exec, 2, sudo kpartx -a $# -s)
$(call v_exec, 2, sudo mkfs.ext4 /dev/mapper/loop0p1 -q)
$(call v_exec, 2, sudo mount /dev/mapper/loop0p1 $(TESTDIR)/mnt)
$(call v_exec, 2, sudo umount $(TESTDIR)/mnt)
$(call v_exec, 2, sudo kpartx -d $#)
$(call print_white_done, HDAIMG)
check-env:
ifneq ($(ERROR),)
$(call print_error, $(ERROR))
exit 1
endif
That called functions are used to print with color or to execute with choosed verbose; there are in my Makeconfig.mk already included. Some:
v_exec = $(V$(strip $(1)))$(strip $(2))
print = #echo -e '$(LEAD_SUB_STR) $(strip $(1))'
print_white_init= #echo -e '$(subst PATTERN,$(strip $(1)),$(WHITE_INIT)) $(strip $(2))'
print_white_done= #echo -e '$(subst PATTERN,$(strip $(1)),$(WHITE_DONE)) $(strip $(2))'
$(HDAIMG) has check-env as a prerequisite, and Make always thinks that check-env must be rebuilt, because check-env is not actually a file that exists. Therefore Make decides that $(HDAIMG) must be rebuilt.
It would make more sense to perform the check as the first command in the rule, rather than as a prerequisite.

Resources