How to abort makefile if variable not set? - makefile

How could I abort a make/makefile execution based on a makefile's variable not being set/valued?
I came up with this, but works only if caller doesn't explicitly run a target (i.e. runs make only).
ifeq ($(MY_FLAG),)
abort: ## This MUST be the first target :( ugly
#echo Variable MY_FLAG not set && false
endif
all:
#echo MY_FLAG=$(MY_FLAG)
I think something like this would be a good idea, but didn't find anything in make's manual:
ifndef MY_FLAG
.ABORT
endif

TL;DR: Use the error function:
ifndef MY_FLAG
$(error MY_FLAG is not set)
endif
Note that the lines must not be indented. More precisely, no tabs must precede these lines.
Generic solution
In case you're going to test many variables, it's worth defining an auxiliary function for that:
# Check that given variables are set and all have non-empty values,
# die with an error otherwise.
#
# Params:
# 1. Variable name(s) to test.
# 2. (optional) Error message to print.
check_defined = \
$(strip $(foreach 1,$1, \
$(call __check_defined,$1,$(strip $(value 2)))))
__check_defined = \
$(if $(value $1),, \
$(error Undefined $1$(if $2, ($2))))
And here is how to use it:
$(call check_defined, MY_FLAG)
$(call check_defined, OUT_DIR, build directory)
$(call check_defined, BIN_DIR, where to put binary artifacts)
$(call check_defined, \
LIB_INCLUDE_DIR \
LIB_SOURCE_DIR, \
library path)
This would output an error like this:
Makefile:17: *** Undefined OUT_DIR (build directory). Stop.
Notes:
The real check is done here:
$(if $(value $1),,$(error ...))
This reflects the behavior of the ifndef conditional, so that a variable defined to an empty value is also considered "undefined". But this is only true for simple variables and explicitly empty recursive variables:
# ifndef and check_defined consider these UNDEFINED:
explicitly_empty =
simple_empty := $(explicitly_empty)
# ifndef and check_defined consider it OK (defined):
recursive_empty = $(explicitly_empty)
As suggested by #VictorSergienko in the comments, a slightly different behavior may be desired:
$(if $(value $1) tests if the value is non-empty. It's sometimes OK if the variable is defined with an empty value. I'd use $(if $(filter undefined,$(origin $1)) ...
And:
Moreover, if it's a directory and it must exist when the check is run, I'd use $(if $(wildcard $1)). But would be another function.
Target-specific check
It is also possible to extend the solution so that one can require a variable only if a certain target is invoked.
$(call check_defined, ...) from inside the recipe
Just move the check into the recipe:
foo :
#:$(call check_defined, BAR, baz value)
The leading # sign turns off command echoing and : is the actual command, a shell no-op stub.
Showing target name
The check_defined function can be improved to also output the target name (provided through the $# variable):
check_defined = \
$(strip $(foreach 1,$1, \
$(call __check_defined,$1,$(strip $(value 2)))))
__check_defined = \
$(if $(value $1),, \
$(error Undefined $1$(if $2, ($2))$(if $(value #), \
required by target `$#')))
So that, now a failed check produces a nicely formatted output:
Makefile:7: *** Undefined BAR (baz value) required by target `foo'. Stop.
check-defined-MY_FLAG special target
Personally I would use the simple and straightforward solution above. However, for example, this answer suggests using a special target to perform the actual check. One could try to generalize that and define the target as an implicit pattern rule:
# Check that a variable specified through the stem is defined and has
# a non-empty value, die with an error otherwise.
#
# %: The name of the variable to test.
#
check-defined-% : __check_defined_FORCE
#:$(call check_defined, $*, target-specific)
# Since pattern rules can't be listed as prerequisites of .PHONY,
# we use the old-school and hackish FORCE workaround.
# You could go without this, but otherwise a check can be missed
# in case a file named like `check-defined-...` exists in the root
# directory, e.g. left by an accidental `make -t` invocation.
.PHONY : __check_defined_FORCE
__check_defined_FORCE :
Usage:
foo :|check-defined-BAR
Notice that the check-defined-BAR is listed as the order-only (|...) prerequisite.
Pros:
(arguably) a more clean syntax
Cons:
One can't specify a custom error message
Running make -t (see Instead of Executing Recipes) will pollute your root directory with lots of check-defined-... files. This is a sad drawback of the fact that pattern rules can't be declared .PHONY.
I believe, these limitations can be overcome using some eval magic and secondary expansion hacks, although I'm not sure it's worth it.

Use the shell function test:
foo:
test $(something)
Usage:
$ make foo
test
Makefile:2: recipe for target 'foo' failed
make: *** [foo] Error 1
$ make foo something=x
test x

You can use an IF to test:
check:
#[ "${var}" ] || ( echo ">> var is not set"; exit 1 )
Result:
$ make check
>> var is not set
Makefile:2: recipe for target 'check' failed
make: *** [check] Error 1

For simplicity and brevity:
$ cat Makefile
check-%:
#: $(if $(value $*),,$(error $* is undefined))
bar:| check-foo
echo "foo is $$foo"
With outputs:
$ make bar
Makefile:2: *** foo is undefined. Stop.
$ make bar foo="something"
echo "foo is $$foo"
foo is something

Use the shell error handling for unset variables (note the double $):
$ cat Makefile
foo:
echo "something is set to $${something:?}"
$ make foo
echo "something is set to ${something:?}"
/bin/sh: something: parameter null or not set
make: *** [foo] Error 127
$ make foo something=x
echo "something is set to ${something:?}"
something is set to x
If you need a custom error message, add it after the ?:
$ cat Makefile
hello:
echo "hello $${name:?please tell me who you are via \$$name}"
$ make hello
echo "hello ${name:?please tell me who you are via \$name}"
/bin/sh: name: please tell me who you are via $name
make: *** [hello] Error 127
$ make hello name=jesus
echo "hello ${name:?please tell me who you are via \$name}"
hello jesus

Another option:
MY_FLAG = $(error Please set this flag)
Attempting to use this variable anywhere will cause an error, unless it's overriden from the command line.
To accept environment variables as well, use ?=:
MY_FLAG ?= $(error Please set this flag)

Related

Use of eval in a target recipe

In a Makefile I'm writing I had an interest in cleaning up some of the CC prints and centralizing some of the build preparations (like creating directories in the build tree). I figured macros would be a good fit for this task. This is effectively what im trying to do, used all over various Makefiles:
define func
#mkdir -p $$(dir $(1))
#printf "%-5s $(2)\n" $(3)
endef
test:
#echo Run
$(eval $(call func,a,b,c))
My thought was that after first expansion I'd get something like (less any tabs maybe, I'm not exactly sure how the expansion works within eval):
test:
#echo Run
$(eval #mkdir -p $(dir a)\n#printf "%-5s b\n" c
and of course finally the commands would be executed. However, what I get is this:
# make
Makefile:7: *** recipe commences before first target. Stop.
I changed eval to info and got this:
#mkdir -p $(dir a)
#printf "%-5s b\n" c
Run
So I thought maybe my explicit tabs in the macro definition were causing trouble, so I removed them and tried again:
# make
Makefile:7: *** missing separator. Stop.
So it still does not quite work. If it is indeed possible at all, it seems some function of indentions in the macro, or maybe I'm defining the macros incorrectly. I thought perhaps the two commands in the macro was causing trouble (since the complaint is regarding a separator), but reducing the macro to a single line did not help either.
You don't want eval here. Eval is used to evaluate makefile syntax. That is, the thing you're evaluation has to be a valid, complete makefile. You can see that what info prints is not a valid makefile. If you put that into a file and ran make -f <file>, you'd get a syntax error.
You are just trying to expand a variable for shell syntax. Just remove the eval.

Put Very Long Dependency List at the END of the Makefile

I'm trying to put a very long list of objects at the END of my Makefile. I cannot get this code to work. I can see $(LIST) getting assigned in the ASSIGN_LIST macro, however, it seems that $(LIST) is blank for stuff.txt. I'm a newbie at Make so maybe I'm way off the mark. (Using Red Hat Linux, GNU Make v3.81.)
> cat makefile
define ASSIGN_LIST
LIST = $1
endef
stuff.txt: $(LIST)
echo $(LIST)
# MUCH MORE CODE HERE
# THIS CODE CONTAINING THE LIST IS AT THE VERY END OF MAKEFILE (i.e. OUT OF THE WAY)
define VERY_LONG_LIST_OF_OBJECTS
aaa \
bbb \
ccc \
ddd \
eee \
fff \
etc
endef
$(info $(call ASSIGN_LIST, $(VERY_LONG_LIST_OF_OBJECTS)))
When I run the makefile:
> make
make: `stuff.txt' is up to date.
I use 'touch' to make the prereqs newer than stuff.txt. If I swapout "eval" with "info" (last line), I can see this
> make
LIST = aaa bbb ccc ddd eee fff etc
make: `stuff.txt' is up to date.
Is this even possible? Thanks for the help.
Variables that appear in targets or prerequisites are expanded immediately when read by make during the parsing of the makefile. So, this cannot work:
stuff.txt: $(LIST)
echo $(LIST)
...
LIST = ...
Here, the prerequisite variable is expanded when make reads that line but the variable inside the recipe is not expanded until later when make wants to build the target. So the variable is not set yet when parsing the prerequisite, then it gets set, then the recipe is run.
If you want to see what make sees you should use automatic variables instead:
stuff.txt: $(LIST)
echo $^
The best way to do this is delay the prerequisites until after the variable is set. If you really, really don't want to do that you can use secondary expansion:
.SECONDEXPANSION:
stuff.txt: $$(LIST)
echo $^
Using eval on ASSIGN_LIST will only set LIST to the 1st file (because of LIST = $1). As a result, stuff.txt only depends on aaa.
As an alternative, add the dependency after setting the variable:
define VERY_LONG_LIST_OF_OBJECTS
aaa \
... \
etc
endef
stuff: $(VERY_LONG_LIST_OF_OBJECTS)

How to make conditional expansion without subshell?

I wrote following function in GNU Make, that checks whether first argument belongs is found in some list, and expands to second or third argument accordingly:
FLAGS := foo bar
use = $(shell { echo $(FLAGS) | grep -qw $(1) ; } && echo $(2) || echo $(3))
all:
» $(info $(call use, foo, have-foo, no-foo))
» $(info $(call use, baz, have-baz, no-baz))
It behaves as I want:
$ make all
have-foo
no-baz
make: 'all' is up to date.
Is there any way to implement same functionality only with GNU Make,
without subshell?
Is there any way to add more syntax sugar at call sites?
I need it work on GNU Make built without Guile support.
I'm not sure I fully understand but why not this?
use = $(if $(filter $1,$(FLAGS)),$2,$3)
??
While MadScientists answer may be all you need, it looks like you are doing the configuration management in make. While this is IMO the place where it should happen, the heritage has prevented make from becoming a full-flegded tool for that purpose. However, there is a library which helps in this case: The GNUmake Table Toolkit lets you select from a table those lines which fulfill certain freely defined condition (read the documentation to select).
include gmtt/gmtt.mk
# original definition of the flag table
define FLAG-TABLE
2
foo have-foo-1
foo have-foo-2
bar have-bar-1
endef
# GNUmake even allows to write an addendum to a define:
define FLAG-TABLE +=
bar have-bar-2
foo have-foo-3
endef
# the $1 is where the parameter of the call to MY-SELECTION is placed; the $$1 is
# where the first column of the table is put before calling `str-eq`
MY-SELECTION = $(call select,2,$(FLAG-TABLE),$$(call str-eq,$$1,$1))
$(info $(FLAG-TABLE))
$(info ------------------)
$(info $(call MY-SELECTION,foo))
$(info ------------------)
$(info $(call MY-SELECTION,bar))
Output:
$ make
2
foo have-foo-1
foo have-foo-2
bar have-bar-1
bar have-bar-2
foo have-foo-3
------------------
have-foo-1 have-foo-2 have-foo-3
------------------
have-bar-1 have-bar-2

Makefiles - How do I use an eval'd variables with ifndef/ifdef?

I'm trying to figure out how to get an eval'd variable (using output from the shell) to pass conditional checks like ifndef or ifdef. I need to use the shell because I'm actually using a script that returns some output.
foo::
$(eval var := $(shell echo 'hello'))
ifndef var
#printf 'ifndef is true. var is ${var}'
else
#printf 'ifndef is false. var is ${var}'
endif
Running "make foo" will output the following:
'ifndef is true. var is hello'
As you can see, ifndef doesn't find anything in var, even though printf is showing that var contains the string "hello".
What am I missing here?
You can't do this because ifdef et. al. are parsed when the makefile is read in (note that they do not begin with a TAB character so they are not part of the recipe), and the recipe (which includes $(eval ...)) is not parsed until much later when make wants to build the target foo.
And, you cannot put ifdef et. al. into the recipe because if you prefix them with TAB then they get passed to the shell, not parsed by make. In general if you want a conditional inside a recipe you have to use shell conditionals, NOT make conditionals, because the recipe is a shell script.
You can use the $(if ...) function:
foo::
$(eval var := $(shell echo 'hello'))
#printf 'ifndef is $(if $(var),true,false). var is ${var}'
But to me this looks like a mistake and you might reconsider what you're trying to do at a more fundamental level.

Recursive make: correct way to insert `$(MAKEFLAGS)`

How can I use $(MAKEFLAGS) (or another way of passing variables defined on the command line to sub-make) in a way that supports invocation from shell with both make VAR=val and make -args?
I need my subprojects configurable, but I hate autotools, so I'm using make variables for this, e.g. invoking from shell:
$ make USE_SSE3=1
and USE_SSE3 needs to apply to all builds in all sub-makefiles.
The manual states that:
if you do ‘make -ks’ then MAKEFLAGS gets the value ‘ks’.
Therefore I'm using -$(MAKEFLAGS) (with a dash prefix) in my Makefile.
However, that expands into invalid arguments when variables with no flags are used. If I run:
$ make FOO=bar
then sub-make gets invalid -FOO=bar. OTOH without the dash prefix variable definitions work, then but make -s, etc. don't.
Is there a syntax/variable/hack that makes passing of arguments and lone variable definitions work with sub-makefiles?
The legacy $(MKFLAGS) doesn't have the weird dash prefix problem, but it doesn't include variable definitions either. I've tried fixing the variable with $(patsubst), but that only made things worse by trimming whitespace.
I need the solution to be compatible with the outdated GNU Make 3.81 shipped with Mac OS X Mavericks.
foo:
$(MAKE) -C subproject -$(MAKEFLAGS)
$ make foo -s # MAKEFLAGS = 's'
$ make foo BAR=baz # MAKEFLAGS = 'BAR=baz'
$ make foo -j8 # MAKEFLAGS = ' --job-server=…'
You shouldn't set MAKEFLAGS at all. Why do you want to? You didn't give any reason to do so.
MAKEFLAGS is intended, really, to be an internal implementation passing arguments from a parent make to a child make. It's not intended, generally, to be modified by a makefile. About the only thing you can usefully do to it is add new flags.
If you just run the recursive make using the $(MAKE) variable rather than hardcoding make, it will Just Work:
recurse:
#$(MAKE) all FOO=bar
or whatever.
Years too late I got your answer if I got it right.
You can construct $(MAKEARGS) manually yourself like:
MAKEARGS := $(strip \
$(foreach v,$(.VARIABLES),\
$(if $(filter command\ line,$(origin $(v))),\
$(v)=$(value $(v)) ,)))
MAKEARGS := assign static
strip cleans leading and trailing whitespaces.
foreach v iterate over all variable names.
origin $(v) check if variable origin is "command line".
$(v)=$(value $(v)) output env assignment string.
Alternatively you can unpick the $(MAKEFLAGS) like:
MAKEARGS := $(wordlist 2,$(words $(MAKEFLAGS)),$(MAKEFLAGS))
MAKEFLAGS := $(firstword $(MAKEFLAGS))
Which can leave you with cleaner code for further recursions IMHO. I say this because I sometimes need to keep apart arguments and flags in certain cases. Especially as you get caught in debugging a recursion djungle.
But for any specific case one should consult the manual about recursive options processing.
Changing the $(MAKEFLAGS) can lead to unwanted malfunction.
Another useful information for the willing user could be that the $(MAKEFLAGS) variable is basically the whole argument list passed to make, not only the flag characters. So $(info MAKEFLAGS = $(MAKEFLAGS)) can give you something like:
MAKEFLAGS = rRw -- VAR=val
Cheers
To check if -B is present in make flags i do :
BB_CLOBBER := $(shell echo $(MAKEFLAGS) | grep wB)
ifeq (,$(BB_CLOBBER))
# also force clobber make if these files are missing
BB_CLOBBER := $(shell (test -e $(bb_gen)/minimal/.config && test -e $(bb_gen)/full/.config) || echo "B")
endif
bb_prepare:
ifneq (,$(BB_CLOBBER))
#rm -rf $(bb_gen)/full
...

Resources