I have a makefile, with two variables like this
OS = foo.o bar.o baz.o
WS = -DWITH_FOO -DWITH_BAR -DWITH_BAZ
And so on. Instead of writing this out manually I want to generate these two when the makefile is executed based on an environment variable called WITH containing something like foo bar baz. If this environment variable is not set, or is empty, the makefile should use some hard-coded fallback instead.
How would I do that? I'm not too good at makefiles, all I can think is some kind of 'foreach` call but the specifics elude me.
Assuming you are using GNU Make on a UNIX-like operating system, here is a possible solution:
afineman#hotdog:/tmp$ cat Makefile
WITH = foo bar baz
WITH_UPPER = $(shell echo $(WITH) | tr a-z A-Z)
OS = $(WITH:%=%.o)
WS = $(WITH_UPPER:%=-DWITH_%)
.PHONY: env
env:
#echo WITH=$(WITH)
#echo WITH_UPPER=$(WITH_UPPER)
#echo OS=$(OS)
#echo WS=$(WS)
afineman#hotdog:/tmp$ make
WITH=foo bar baz
WITH_UPPER=FOO BAR BAZ
OS=foo.o bar.o baz.o
WS=-DWITH_FOO -DWITH_BAR -DWITH_BAZ
afineman#hotdog:/tmp$
You can supply WITH in your environment if you wish, but in general it is better to write your Makefiles so that they are self-contained. If you do have a requirement that WITH comes from the environment, just leave out the first line of the above Makefile, and $(WITH) will come from the environment.
You can also override $WITH by running Make with the -e switch, i.e.,
afineman#hotdog:/tmp$ WITH="bing bang buzz" make # Not overridden
WITH=foo bar baz
WITH_UPPER=FOO BAR BAZ
OS=foo.o bar.o baz.o
WS=-DWITH_FOO -DWITH_BAR -DWITH_BAZ
afineman#hotdog:/tmp$ WITH="bing bang buzz" make -e # Overridden
WITH=bing bang buzz
WITH_UPPER=BING BANG BUZZ
OS=bing.o bang.o buzz.o
WS=-DWITH_BING -DWITH_BANG -DWITH_BUZZ
Well, I would write something like this:
ifdef WITH
OS := $(WITH:%=%.o)
WS := $(WITH:%=-DWITH_%)
else
# Fallback.
endif
This is the most straightforward solution I see, however, it is not 100% good because WS would be -DWITH_foo ... instead of -DWITH_FOO ....
If such behavior does not fit your needs, you can use tr command to convert WS to uppercase:
WS := $(shell echo '$(WS)' | tr 'a-z' 'A-Z')
Or, as more portable option, use tr function from GMSL:
WS := $(call tr,$([a-z]),$([A-Z]),$(WS))
Related
I have a makefile that executes some shell command and I want to store the output to a global variable:
GLOBVAR = a
all:
GLOBVAR=$(shell echo 'X')
$(info $(GLOBVAR))
GLOBVAR is empty. What am I doing wrong?
You are mixing up make and shell variables. In GLOBVAR=$(shell echo 'X') it is a shell variable that you assign, while in $(info $(GLOBVAR)) it is a make variable that you expand.
Try this, instead:
GLOBVAR = $(shell echo 'X')
all:
$(info $(GLOBVAR))
But there are several other issues with your Makefile that you should probably consider.
Using $(shell...) in recipes is not recommended because recipes are already shell scripts. So, if you want to assign a shell variable in a recipe, just:
all:
GLOBVAR="$$(echo 'X')"
Note the $$ to escape the expansion that make performs before passing the recipes to the shell.
The different lines of the recipe are executed in different shells. So, if you want to use in a line a shell variable that was assigned in a previous line you must join them:
all:
GLOBVAR="$$(echo 'X')"; echo $$GLOBVAR
(same remark as before about $$). You can use line continuation if you prefer:
all:
GLOBVAR="$$(echo 'X')"; \
echo $$GLOBVAR
And finally, if you want to assign make variables in recipes you can, with the eval make function, but I strongly discourage you to do so until you perfectly understand when make does what:
$ cat Makefile
.PHONY: all lla
all:
$(eval GLOBVAR = $(shell echo 'X'))
#echo all: $(GLOBVAR)
lla:
#echo lla: $(GLOBVAR)
$ make all
all: X
$ make lla
lla:
$ make all lla
all: X
lla: X
$ make lla all
lla:
all: X
And I let you imagine what the results could be with parallel make... In summary, if you start using make functions in recipes you are probably wandering into dangerous areas.
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
I have the following code:
foo:
touch foo
$(foreach f, $(shell ls | grep foo), \
echo $f; \
)
it will not list the file foo created by the touch foo above, will list if the foo file already exists before the task starts, like this:
$ make foo # first time call, file 'foo' doesn't exists yet
$ make foo # second time call, file 'foo' already exists
foo
Is there a way to evaluate the ls after all the commands above are executed?
That's how Make works. The Makefile is parsed and any Makefile functions are called, then one or more recipes are evaluated.
Is there a reason you're not simply using a shell loop?
foo:
touch foo
for f in *foo*; do \
echo "$$f"; \
done
Notice how the dollar sign needs to be doubled to escape it from being evaluated by make, and also how shell variables should generally be double-quoted unless you specifically require the shell to perform whitespace tokenization and wildcard expansion on the value.
On the other hand, a more "make-ish" approach is to explicitly document any dependencies.
.PHONY: all
all: foo
printf '%s\n' $^
foo:
touch $#
Now all depends on foo, so Make knows it must create foo before it can perform the all recipe if foo doesn't exist, or is out of date in relation to its own dependencies (of which of course there are currently none).
The make variable $^ refers to the dependencies of the current target, and $# expands to the current recipe target. The printf shell script is just a more economical way to print one thing per line without a loop.
This question already has answers here:
Define make variable at rule execution time
(4 answers)
Closed 4 years ago.
How can one use the variable defined inside a make target
.PHONY: foo
VAR_GLOBAL=$(shell cat /tmp/global)
foo:
echo "local" > /tmp/local
VAR_LOCAL=$(shell cat /tmp/local)
echo ${VAR_GLOBAL}
echo ${VAR_LOCAL}
here is the execution output:
$ echo global > /tmp/global
$ make foo
echo "local" > /tmp/local
VAR_LOCAL=local
echo global
global
echo
As #KelvinSherlock pointed out this is a duplicate of another question
here is the specific solution for my question:
.PHONY: foo
VAR_GLOBAL=$(shell cat /tmp/global)
foo:
echo "local" > /tmp/local
$(eval VAR_LOCAL := $(shell cat /tmp/local))
echo ${VAR_GLOBAL}
echo ${VAR_LOCAL}
You probably want to use the override directive in a target-specific variable assignment, so try:
foo: override LS_LOCAL=$(shell ls /var | tail -1)
echo ${LS_GLOBAL}
echo ${LS_LOCAL}
If LS_LOCAL is never defined (even by builtin-rules) you might not need the override keyword.
BTW, you might avoid $(shell ls /var | tail -1) by using the wildcard function combined with the lastword function (perhaps combined with notdir function), so you might code $(lastword $(wildcard /var/*)) or $(notdir $(lastword $(wildcard /var/*))) instead . However, beware of the order of expansion, and of filenames with spaces. At last the shell function probably uses your $PATH variable (so strange things could happen if some weird ls program appears there before /bin/ls). Perhaps using $(shell /bin/ls /var | /usr/bin/tail -1) might be better.
Look also into Guile-extended make; consider perhaps some other build-automation tool like ninja and/or generating your Makefile (or other build configuration) with something like a configure script generated via autoconf or cmake.
Notice also that a command in recipe can be made of several physical backslashed lines (hence executed in the same shell). Maybe you might consider something like
export MY_VAR=$$(ls /var | tail); \
dosomething; \
use $$MY_VAR
inside some recipe.
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
...