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.
Related
I want to declare a variable using a for from bash in a makefile. That's what I tried:
SRCS="path/to/foo.c path/to/boo.c path/to/bar.c"
OBJS=$(for file in $SRCS; do TEMP="$TEMP $(basename $file .c).o"; done; echo $TEMP)
This command:
for file in $SRCS; do TEMP="$TEMP $(basename $file .c).o"; done
works on bash when echoing TEMP. But OBJS in makefile is empty. What am I missing here?
Desired output:
foo.o boo.o bar.o
First of all there's better ways to do this without using bash's for. You could use the make constructs to generate these lists. See the documentation on $(notdir), $(addprefix...) and $(addsuffix ...)
However, to answer your question on this particular example:
First, you are putting quotes around sources. Make does not interpret quotes as special characters, and thus SRCS will expand to "path/to/foo.c path/to/boo.c path/to/bar.c" (including the quotes). This will mess up your for loop later on.
The next thing is the reference to $SRCS -- make will interpret this as $S followed by the literal RCS (which is not what you want). You have to use braces around multi0letter variables in bash as so -- $(SRCS)
Next, TEMP=$TEMP.... When make sees $TEMP, it will immediately attempt to expand it. Because TEMP is not set to anything in the make context, it will expand to a null string -- this is before it invokes the bash shell... What you wanted to do in this case is use $$TEMP, which make will expand to $TEMP.
The following does what you want: note the $(info) lines are just for debugging.
SRCS:=path/to/foo.c path/to/boo.c path/to/bar.c
OBJS:=$(shell for file in $(SRCS); do TEMP="$$TEMP $$(basename $$file .c).o"; done; echo $$TEMP)
$(info for file in $(SRCS); do TEMP="$$TEMP $$(basename $$file .c).o"; done; echo $$TEMP)
$(info OBJS=$(OBJS))
If you wanted to do this in makefile without bash or sh, you might try:
OBJS:=$(notdir $(SRCS:.c=.o))
Note: this is gnu-make specific syntax and may not work on other makes.
I want to use the bash timing variables in my makefile
for example in my terminal I can do this and it works
MY_TIME=$SECONDS
echo $MY_TIME
but when I write this on my makefile it does not work
how can I use these two lines in my make file?
this is what I'm doing
.PHONY: myProg
myProg:
MY_TIME=$SECONDS
echo $MY_TIME
After Etan Reisner' answer
This is what I have now
.PHONY: myProg
myProg:
MY_TIME= date; echo $MY_TIME
but the result of my echo is an empty line, it does not look like it is storing the date
the dollar sign ($MY_TIME) refers to make variables, which are not the same as bash variables.
To access a bash variable you must escape the dollar using the double dollar notation ($$MY_TIME).
.PHONY: myProg
myProg:
MY_TIME=$$SECONDS ; echo $$MY_TIME
As already mentioned in Etan answer you can't split the code into multiple lines (unless you are using the backslash) since each command executes in a different subshell, making variables inaccessible to other lines.
In the following example the value of SECONDS will be always 0, since it get reset by the spawn of the shell for the second line.
.PHONY: myProg
myProg: # WRONG
MY_TIME=$$SECONDS
echo $$MY_TIME
By default make uses /bin/sh as the shell which executes recipe lines.
Presumably /bin/sh doesn't support the SECONDS variable.
You can tell make to use a different shell by assigning a value to the SHELL variable (i.e. SHELL := /bin/bash).
Doing that will make SECONDS available but will still not allow you to carry a variable value between recipe lines as each recipe line is run in its own shell.
So to do what you want you would need to write both of those lines on one line or continue the line over the newline.
.PHONY: myProg
myProg:
MY_TIME=$SECONDS; echo $MY_TIME
or
.PHONY: myProg
myProg:
MY_TIME=$SECONDS; \
echo $MY_TIME
That being said you would almost certainly be better off not doing this and instead using something like date invoked at the start/end of the recipe or time invoked on the command to be timed directly instead.
.PHONY: myProg
myProg:
date
# Do something
date
or
.PHONY: myProg
myProg:
time some_command
PROGRAM_NAME = myLittleProgram
...
$(PROGRAM_NAME) : myLittleProgram.o
I know the above works, as it is in my own makefile (program names and object names changed to protect the innocent).
"Variable references can be used in any context: targets, dependencies, commands, most directives, and new variable values. Here is an example of a common case, where a variable holds the names of all the object files in a program:"
objects = program.o foo.o utils.o
program : $(objects)
cc -o program $(objects)
$(objects) : defs.h
http://web.mit.edu/gnu/doc/html/make_6.html
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.
I have a bash shell script which I usually source into my shell, with lots of environment variables defined, which are not exported. I do not want to:
Export the variables, because this would make the exportable environment too big, and eventually make the whole system slower (it must be exported when running every command from the shell)
Redefine those variables in the makefile (DRY)
I would like to source the same shell script into the environment of the makefile, so that I can access those variables. Is this possible? How can I do that? Ideally I would do in the makefile:
source setup-env.sh
There is not source command for makefiles, but maybe something equivalent? Any special hack I can use to simulate the same effect?
As per the additional question in the comment, here is one way to effectively mark the whole environment as exported:
for var in $(compgen -v); do export $var; done
compgen -v simply outputs all variable names, as per the bash manual, section 8.7 Programmable Completion Builtins. Then we simply loop over this list and export each one.
Credit to https://stackoverflow.com/a/16337687/2113226 - compgen is new to me.
There are two ways I can think of to integrate this into your make workflow:
- Shell script wrapper
Simply write a shell script which sources your setup-env.sh, exports all variables as above, then calls make itself. Something like:
#!/bin/bash
./source setup-env.sh
for var in $(compgen -v); do export $var; done
make $#
- Recursive make
It may be that you don't want a shell script wrapper, and want to directly invoke make for whatever reason. You can do this all in one Makefile which calls itself recursively:
$(info MAKELEVEL=$(MAKELEVEL) myvar=$(myvar))
ifeq ($(MAKELEVEL), 0)
all:
bash -c "source ./setup-env.sh; \
for var in \$$(compgen -v); do export \$$var; done; \
$(MAKE) $#"
else
all: myprog
myprog:
echo "Recipe for myprog. myvar=$(myvar)"
endif
Output for this Makefile is:
$ make
MAKELEVEL=0 myvar=
bash -c "source ./setup-env.sh; \
for var in \$(compgen -v); do export \$var; done; \
make all"
MAKELEVEL=1 myvar=Hello World
make[1]: Entering directory `/home/ubuntu/makesource'
echo "Recipe for myprog. myvar=Hello World"
Recipe for myprog. myvar=Hello World
make[1]: Leaving directory `/home/ubuntu/makesource'
$
We check the GNU Make builtin variable MAKELEVEL to see what level of recursion we are at. if the level is 0, then we recursively call make for all targets, but first source ./setup-env.sh and export all variables. If the recursion level is anything else, we just do the normal makefile stuff, but you see that the variables you need are now available. This is highlighted by the $(info ) line at the top of the Makefile, which shows the recursion level, and the value (or not) of myvar.
Notes:
We have to use bash -c because compgen is strictly a bash builtin, and not available in Posix mode - i.e. when make invokes the shell as sh -c by default.
The $ in the first all: recipe need to be escaped very carefully. The $$ escapes the $ from being expanded by make, and the \$$ escapes the $ from being expanded by the implicit sh
There is plenty of literature arguing that "Recursive make is considered harmful". E.g. http://aegis.sourceforge.net/auug97.pdf
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
...