Remove target from MAKECMDGOALS? - makefile

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

Related

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

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)

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

How do I get a makefile function to stop the current target?

I have a function in a makefile that I want to stop the entire make run if a file doesn't exist, or at least the target it is executed from:
vaultfile = ./vault
$(shell test -f $(1) || exit 1)
define get_token
$(shell test -f $1 && cat $1 || exit 2)
endef
a: token = $(call get_token,$(vaultfile),tokenname)
a:
echo ==== $(token)
.PHONY=a
The above doesn't work, silently failing when the file is missing
$ rm vault
$ make
echo ====
====
$ echo f > vault
$ make
echo ==== f
==== f
I want this to be in the function, because most targets do not call the function (which obviously does more IRL).
How do I make this work?
First a few things:
.PHONY=a
doesn't do anything: the variable .PHONY is not special to make. To declare a target phony you need to list it as a prerequisite of the .PHONY pseudo target:
.PHONY: a
Second, this line:
$(shell test -f $(1) || exit 1)
doesn't do anything: the make variable $(1) is not set here so the test always fails, but it doesn't matter because the exit code is ignored, see below.
The exit code from the make shell function won't cause make to fail, it's ignored. To cause make to think that a recipe failed you have to get the command line itself to exit with a non-zero value.
A good rule of thumb is, if you find yourself using the make shell function inside a recipe, you're doing something wrong and you aren't understanding how make expands variables and functions. A recipe is already going to be passed to a shell, so you don't need to use the shell function at all.
Let's look at what your recipe will be after the first step of expansion, for the token variable:
echo ==== $(call get_token,$(vaultfile),tokenname)
Now after the call function is expanded (note that the second argument to the function, tokenname, is completely ignored) you get:
echo ==== $(shell test -f ./vault && cat ./vault || exit 2)
Now make expands the shell function which invokes a shell to run the command and replace the expansion with the output... but the exit code is ignored. Let's say that ./vault doesn't exist: then this shell command outputs nothing, and make runs this rule:
echo ====
The best way to stop an entire make run is using the error function. You can use make functions to do all the work, like this:
vaultfile = ./vault
get_token = $(if $(wildcard $1),`cat $1`,$(error File $1 does not exist))
a: token = $(call get_token,$(vaultfile),tokenname)
a:
echo ==== $(token)
Let's look at what the results of the call expansion will be now:
echo ==== $(if $(wildcard ,/vault),`cat ./vault`,$(error File ./vault does not exist))
Now make evaluates the if function and the condition is the wildcard function which will expand to ./vault if it exists, and the empty string if not. The if function treats a non-empty string as "true" and an empty string as "false", so if the file exists it will expand to:
echo ==== `cat ./vault`
If the file doesn't exist it will run the error function which stops make immediately, printing that error message.
Thank you MadScientist!
The simplified version of what I ended up with is: (formatted for easier readability)
vaultfile = ./vault
define get_token
$(shell \
$(if $(wildcard $1), \
echo "Decoded $1" \
, \
$(error File $1 does not exist)
)
)
endef
a: token = $(call get_token,$(vaultfile))
a:
echo ==== $(token)

Force variable expansion once and when used only

In this example, I have a process that takes some time. Let's says 1 second. If I write the following Makefile, FOO will be expanded 3 times for make all and none for make clean.
If I want to save some execution time for all I can assign FOO using := instead of =. However this will cause FOO to be expanded for the target clean even if it doesn't use it.
FOO = $(shell echo -e "+1" >> foo && echo "Hello" && sleep 1)
all:
#echo $(FOO)
#echo $(FOO)
#echo $(FOO)
#cat foo
clean:
rm foo
The output:
$ make
Hello
Hello
Hello
+1
+1
+1
I would like to force Make to expand a variable only once only if required.
Is it possible to do it so?
The "best" solution I can come up with looks like this:
$ cat coin.mk
FOO = FOO:=$(shell echo -e "+1" >> foo && echo "Hello" && sleep 1)
defined = $(and $(filter-out undefined,$(origin $1)),$($1))
all:
echo $(eval $(FOO))$(FOO)
echo $(FOO)
echo $(FOO)
cat foo
clean:
rm foo
$ time make -f coin.mk clean
rm foo
rm: cannot remove `foo': No such file or directory
make: *** [clean] Error 1
real 0m0.003s
user 0m0.003s
sys 0m0.000s
$ time make -f coin.mk
echo Hello
Hello
echo Hello
Hello
echo Hello
Hello
cat foo
+1
real 0m1.009s
user 0m0.002s
sys 0m0.004s
$ time make -f coin.mk clean
rm foo
real 0m0.003s
user 0m0.000s
sys 0m0.003s
Which works but requires special-casing the first use of the variable in the make run ($(eval $(FOO)) run a second time will cause a make error).
I tried briefly to encapsulate the eval logic inside the value of FOO but most attempts were blocked by make complaining that *** Recursive variable 'FOO' references itself (eventually). Stop.
Try this:
FOO = $(eval FOO := $(shell echo "+1" >> foo && echo "Hello" && sleep 1))$(value FOO)
The first time make expands $(FOO) it will first expand the eval, which resets the variable FOO using :=. Then it resolves the value of the FOO variable. In subsequent expansions, due to the eval, FOO expands directly to the value.
I should also point out that if you have at least GNU make 4.0 you can use a new feature added to the POSIX standard for make recently, the != operator:
FOO != echo "+1" >> foo && echo "Hello" && sleep 1
which does exactly what you want here.

Compile several projects (with makefile), but stop on first broken build?

I want to do something like:
for i in *
do
if test -d $i
then
cd $i; make clean; make; cd -;
fi;
done
And this works fine, but I want "break" the for-loop in case of a broken build.
Is there a way to do this? Maybe some kind of if-statement, that can check for success of make?
You can use Make itself to achieve what you're looking for:
SUBDIRS := $(wildcard */.)
.PHONY : all $(SUBDIRS)
all : $(SUBDIRS)
$(SUBDIRS) :
$(MAKE) -C $# clean all
Make will break execution in case when any of your target fails.
UPD.
To support arbitrary targets:
SUBDIRS := $(wildcard */.) # e.g. "foo/. bar/."
TARGETS := all clean # whatever else, but must not contain '/'
# foo/.all bar/.all foo/.clean bar/.clean
SUBDIRS_TARGETS := \
$(foreach t,$(TARGETS),$(addsuffix $t,$(SUBDIRS)))
.PHONY : $(TARGETS) $(SUBDIRS_TARGETS)
# static pattern rule, expands into:
# all clean : % : foo/.% bar/.%
$(TARGETS) : % : $(addsuffix %,$(SUBDIRS))
#echo 'Done "$*" target'
# here, for foo/.all:
# $(#D) is foo
# $(#F) is .all, with leading period
# $(#F:.%=%) is just all
$(SUBDIRS_TARGETS) :
$(MAKE) -C $(#D) $(#F:.%=%)
You can check whether the make has exited successfully by examining its exit code via the $? variable, and then have a break statement:
...
make
if [ $? -ne 0 ]; then
break
fi

Resources