setting variable with eval in Makefile recipe - makefile

I was looking at another stack overflow question, and they were using, what looks to be undocumented behavior for makefiles... If I have the following Makefile:
X=X
all:
#echo $#: $(X)
$(eval X=Y)
#echo $# part2: $(X)
all2:
#echo $#: $(X)
Then I run:
~> make all2
all2: X
~> make all all2
running all
all: X
all part2: Y
all2: Y
I would have expected the $(eval X=Y) to expand at Makefile parse time, and set the shell variable X to be Y for that line of the recipe (i.e. do nothing). Instead, it seems to be evaluated when the all recipe is run, plus, it seems to set the make variable. I've looked through the make man page, and for online manuals, but I can't find anything that describes this behavior (I'm using GNU Make 4.0). Can someone point me to some documentation describing this, or explain what's going on?

I'm not sure why you would expect either of the things you mention to be true. Maybe you're thinking that this is using the shell eval, somehow? That's not what it's doing, it's using the GNU make eval function, which is discussed here.
Just like any other variable or function, eval appearing in a recipe is not expanded until (and unless) the recipe is invoked because the target is out of date. See How make Reads a Makefile for full details on when variables and functions are expanded.
Second, all make variable assignments set make variables, and only if you export them will they be sent to the shell.

Related

GNU Make - Variable Expansion in Recipes

Say in the working directory, I have:
$ find . | grep testfile
./testfile1
This is my Makefile:
list_files:
#echo "Show Files..."
#echo $(shell find . | grep testfile)
#touch testfile2
#echo $(shell find . | grep testfile)
#rm testfile2
With make, I got this:
$ make list_files
Show Files...
./testfile1
./testfile1
Why this happened? I expected it to be something like this:
Show Files...
./testfile1
./testfile1 ./testfile2
Then my question is:
Why all the variables/function in recipes inside a rule, are expanded likely simultaneously after target is invoked?
I have found an explanation from this answer that is pretty close to the truth:
The reason your attempt doesn't work is that make will evaluate all lines of the recipe before it starts the first line.
But there is no references provided there, I just cannot convinced myself of this working mechanism of GNU Make.
Could anyone give some clues? Thanks!
Why this happened?
Because, with one caveat that does not apply to your case, make functions such as $(shell ...) are evaluated when the makefile is parsed, not during the execution of recipes.
Why all the variables/function in recipes inside a rule, are expanded likely simultaneously after target is invoked?
They're not. They are expanded before the target's recipe runs. In fact, before make even determines whether the recipe should be run.
But there is no references provided
This is covered in the manual. See in particular section 8.14, The shell function:
The commands run by calls to the shell function are run when the function calls are expanded (see How make Reads a Makefile).
... which refers to section 3.7, How make Reads a Makefile, in particular:
GNU make does its work in two distinct phases. During the first phase it reads all the makefiles, included makefiles, etc. and internalizes all the variables and their values and implicit and explicit rules, and builds a dependency graph of all the targets and their prerequisites. During the second phase, make uses this internalized data to determine which targets need to be updated and run the recipes necessary to update them.
It also relies on section 8.1, Function Call Syntax:
A function call resembles a variable reference. It can appear anywhere a variable reference can appear, and it is expanded using the same rules as variable references.
"The same rules" of course includes the rules for when expansions are performed.
Your recipes should be written in the language of the target shell, usually /bin/sh. All make functions and variable references in each recipe will be expanded before any recipe runs, so their expansions cannot reflect the results of running any recipe during the current make run. It's particularly peculiar to try to use the $(shell ...) function to do that, because you can just use shell code directly in a recipe.
As explained in the comments, all $(shell ...) and other functions are executed (expanded) before executing any lines from the recipe. But you can delay the expansion:
list_files:
#echo "Show Files..."
#echo $$(shell find . | grep testfile)
#touch testfile2
#echo $$(shell find . | grep testfile)
#rm testfile2
This yields the expected output. Note the double $$. Make will still expand all variables/functions first before executing the recipe, and remove one of the dollar signs. The expressions will be expanded again, when executing the recipe lines.
Of course, you're better off without the $(shell) in this example. However, it's not inherently a bad idea. Sometimes you need to execute shell commands before expanding other variables, and then $(shell) is the trivial solution.

Makefile expanding variables inside conditionals depends on order of definition

I want to define a variable differently depending on another variables value in a makefile. I thought using conditionals would solve the problem, like this in the makefile:
ifeq ($(BOOT_FLAG),installed)
BOOT_TEST=$(BOOT_FLAG)
else
BOOT_TEST=no
endif
BOOT_DEFINE=$(BOOT_FLAG)
BOOT_FLAG=installed
.PHONY: all
all:
#echo $(BOOT_TEST)
#echo $(BOOT_DEFINE)
I expected the output to be:
installed
installed
but I got this instead:
no
installed
apparently the ifeq does not expand the BOOT_FLAG to installed
but setting of the BOOT_DEFINE variable manages to expand it correctly.
I read in the manual that:
"make evaluates conditionals when it reads a makefile. Consequently, you cannot use automatic variables in the tests of conditionals because they are not defined until commands are run"
but the BOOT_FLAG is not an automatic variable. Also if I move the definition of BOOT_FLAG to before the ifeq, then it works as I want it. However, I want to keep the current order of the definitions (and I don't understand why make does an exception to the order independence of the definitions when using conditions)
The answer is right there in the statement you quoted:
make evaluates conditionals when it reads a makefile.
Since make has evaluated the conditional when it read that line in the makefile, and the variable has not been defined when it read that line, there's no way that variables set after the conditional can take effect.
Just because the documentation lists one consequence of this behavior (the one that most people get confused by) doesn't mean that this is the only consequence of this behavior.
However, I want to keep the current order of the definitions
You can't.
(and I don't understand why make does an exception to the order independence of the definitions when using conditions)
It would be virtually impossible, and even if it could be done the resulting behavior would be almost indecipherable except in the most trivial situations. If you don't believe me, try to write down an algorithm describing how that could work. Remember to consider things like simple variable assignments, nested conditionals, variables used in target and prerequisite lists, variables that are intentionally reset in different parts of makefiles, etc.
ETA You could do it, by putting the ifeq into a define variable then using eval later, after BOOT_FLAG is set, to expand it. Seems gross to me but...
This is because makefile is evaulating the ifeq as it parses the file.
So when it gets to the ifeq..., then BOOT_FLAG is yet not set, so BOOT_TEST = no
Then you set BOOT_FLAG.
Then once all the variables are parsed, makefile will go through and run your rule - so in this case BOOT_DEFINE is evaluated to $(BOOT_FLAG) final value of installed
Try this:
$(info start - BOOT_FLAG=$(BOOT_FLAG))
ifeq ($(BOOT_FLAG),installed)
BOOT_TEST=$(BOOT_FLAG)
else
BOOT_TEST=no
endif
$(info after if - BOOT_FLAG=$(BOOT_FLAG))
BOOT_DEFINE=$(BOOT_FLAG)
BOOT_FLAG=installed
$(info after assignment - BOOT_FLAG=$(BOOT_FLAG))
.PHONY: all
all:
#echo $(BOOT_TEST)
#echo $(BOOT_DEFINE)
You will see various values printed at different times during the makefile parsing. On the first pass it evaluates the variables (and if's) and then on the second pass it can do the target rules.
As others noted the problem is that ifeq is expanded and evaluated in-place.
If you want to postpone the evaluation until some late moment, you must keep the whole expression inside of a recursive variable. Then the conditional could be implemented by $(if ...) function, instead of ifeq (okay, $(eval ifeq...) should also be doable, but... well, gross).
Of course, this is quite an overhead for such simple case, but nonetheless it could be done like this:
BOOT_TEST=$(if $(subst _installed,,_$(BOOT_FLAG)),no,installed)
BOOT_DEFINE=$(BOOT_FLAG)
BOOT_FLAG=installed
.PHONY: all
all:
#echo $(BOOT_TEST)
#echo $(BOOT_DEFINE)

How to perform a string replacement on a path in a Makefile?

I am trying to remove the path prefix. Here is a small example showing just the issue.
Makefile
dist_directory = ./dist
default: build
build: $(patsubst %.md, $(dist_directory)/%.html, $(wildcard *.md))
$(dist_directory)/%.html: %.md
#echo start
#echo $#
#echo ${$#//$(dist_directory)/}
#echo end
Create a file: touch stuff.md
Then build: make
The output is:
start
dist/stuff.html
end
The expected output is:
start
dist/stuff.html
/stuff.html
end
There are similar posts on Stack Exchange. However, they have not worked for me in a Makefile for some reason. I'm probably doing something wrong.
https://unix.stackexchange.com/questions/311758/remove-specific-word-in-variable
Remove a fixed prefix/suffix from a string in Bash
Remove substring matching pattern both in the beginning and the end of the variable
You have many issues here. The most fundamental one is that if you want to use shell variables you have to escape the dollar sign so that make doesn't interpret it. And, you can only use shell variable substitutions on shell variables, while $# is a make variable, so you need:
#foo='$#' ; echo $${foo//$(dist_directory)/}
The more subtle one is that make always uses /bin/sh (POSIX standard shell) when it invokes recipes, and the above syntax is specific to bash. One way around that would be to explicitly set SHELL := /bin/bash in your makefile to force make to use bash. Luckily that is not necessary because POSIX sh can also do this, as mentioned by Reda in another answer:
#foo='$#' ; echo $${###*/}
But even more, you don't need any of this because make sets the automatic variable $* to the part of the target that matches the stem (the %):
#echo $*.html
It also sets $(#F) to the filename part of the $# variable:
#echo $(#F)
ETA
If you want to do something very similar to your shell variable expansion using GNU make you can use:
#echo $(patsubst $(dist_directory)/%,%,$#)

make rule that invokes another rule several times with different values for a variable

I have a rule something, that works on the variable VAR. I also have another rule something-all, that needs to run something, with VAR set to each value in vars.
vars = hello world
something:
echo $(VAR)
something-all:
$(foreach VAR,$(vars),something)
This doesn't quite work, I get
noob#work:~/Desktop$ make something-all
something something
make: something: No such file or directory
make: *** [something-all] Error 1
It should probably print hello\nworld.
I used to do this with wildcard rules by retrieving VAR from %, but got the feeling that was the wrong way to do it. This looked like this:
vars = hello world
all: $(foreach VAR,$(vars),something-$(VAR))
something-%:
echo $*
The below should fix your problem
Using foreach (Tried on GNU Make 3.80 on sparc-solaris 2.8 and windows)
vars = hello world
something:
echo $(VAR)
something-all:
$(foreach i, $(vars), $(MAKE) something VAR=$i || exit 1;)
Using shell for-loop (Tried on GNU Make 3.80 and cc make on sparc-solaris 2.8)
vars = hello world
something:
echo $(VAR)
something-all:
for i in $(vars); do $(MAKE) something VAR=$$i || exit 1; done
TL;DR: If you want to program make, drop GNU Make in favor of BSD Make.
This is a personal recommendation. While BSD Make seems more limited than GNU Make, as it offers less programming facilities, it is much easier to program and has a few unique killer features. This is why I propose a solution with GNU Make and another solution for BSD Make:
Doing it in GNU Make
Using GNU Make, you can write a macro to define a target. The canonical way to define a sequence in a Makefile is to add the steps of the sequence as dependencies to a target, as reflected by the snippet below:
vars= hello world
define something_t =
something: something-$(1)
something-$(1):
#echo $(1)
endef
$(foreach _,$(vars),$(eval $(call something_t,$_)))
It is recommended to use this organisation (rather than defining just one target), because you can work on it to make the task easily resumable if you interrupt the sequence. A Makefile describes a job whose advancement is entirely described by the state of the file system. A task is then easily resumable, if each step is associated to a file, usually a compilation object but sometimes also an empty file which is touch'ed to indicate that important checkpoints have been passed.
Using an auxiliary macro is a flexible solution that can be adapted to more complicated tasks than just echoing a name. Note that this does work with newest versions of GNU Make (4.1). On GNU Make 3.81, you should remove the equal sign from the macro definition.
Adapting your example for BSD Make
If this is an option for you, I recommand dropping the use of GNU Make and replace it by BSD Make, which is way easier to program: it has a short and to the point documentation, while the documentation of GNU Make is very verbose and somewhat unclear, BSD Make has industrial-strength examples of complex rulesets (FreeBSD Build system or BSD Owl), and it has a simple and predictable macro language.
vars= hello world
something:
.for _var in ${vars}
echo ${_var}
.endfor
This can evolve to support more complicated tasks, just by replacing the echo by the adapted commands, or using intermediary steps.
Allow the user to override some tasks, also in BSD Make
In this slightly more advanced variation, we allow the user to override our own recipes for building targets something-hello and something-world.
For each item in our list, a target something-* is created it if it does not already exist, and added to the dependencies of something. The whole operation of defining these targets only happens if something has been left undefined. Therefore, users of these macros can:
Override the recipes for something-hello and something-world
Override the full procedure bound to something.
Implementing such customisation possibilities is mandatory if we want to write useful, reusable, macros for Make. Unluckily, customisation of this sort is nearly impossible in GNU Make.
vars = hello world
.if!target(depend)
.for _var in ${vars}
.if!target(something-${_var})
something-${_var}:
echo ${_var}
.endif
something: something-${_var}
.endfor
.endif
Here's one way to do it:
VARS := hello world
THINGS := $(addprefix something-, $(VARS))
allthings: $(THINGS)
something-%:
echo $*
It should be no surprise that
vars := hello world
something-all:
$(foreach VAR,$(vars),something)
tries to run something something. That's exactly what the foreach expands to, since you don't reference VAR in the third expression.
All you need to do is reference VAR and use a command such as echo:
vars := hello world
something-all:
$(foreach VAR,$(vars),echo $(VAR);)
$ make
echo hello; echo world;
hello
world
Note how chaining the commands with a semicolon avoids forking several shells or -- GASP! -- recursive make invocations. It doesn't get more performant than that.
Alternatively, if your command accepts several somethings as arguments,
vars := hello world
something-all:
echo $(foreach VAR,$(vars),$(VAR))
$ make
echo hello world
hello world
But that is equivalent to the super simple echo $(vars). So it might pay off to think outside the box trying to change your requirements to make this simple solution work.

How to print out a variable in makefile

In my makefile, I have a variable 'NDK_PROJECT_PATH', my question is how can I print it out when it compiles?
I read Make file echo displaying "$PATH" string and I tried:
#echo $(NDK_PROJECT_PATH)
#echo $(value NDK_PROJECT_PATH)
Both gives me
"build-local.mk:102: *** missing separator. Stop."
Any one knows why it is not working for me?
You can print out variables as the makefile is read (assuming GNU make as you have tagged this question appropriately) using this method (with a variable named "var"):
$(info $$var is [${var}])
You can add this construct to any recipe to see what make will pass to the shell:
.PHONY: all
all: ; $(info $$var is [${var}])echo Hello world
Now, what happens here is that make stores the entire recipe ($(info $$var is [${var}])echo Hello world) as a single recursively expanded variable. When make decides to run the recipe (for instance when you tell it to build all), it expands the variable, and then passes each resulting line separately to the shell.
So, in painful detail:
It expands $(info $$var is [${var}])echo Hello world
To do this it first expands $(info $$var is [${var}])
$$ becomes literal $
${var} becomes :-) (say)
The side effect is that $var is [:-)] appears on standard out
The expansion of the $(info...) though is empty
Make is left with echo Hello world
Make prints echo Hello world on stdout first to let you know what it's going to ask the shell to do
The shell prints Hello world on stdout.
As per the GNU Make manual and also pointed by 'bobbogo' in the below answer,
you can use info / warning / error to display text.
$(error text…)
$(warning text…)
$(info text…)
To print variables,
$(error VAR is $(VAR))
$(warning VAR is $(VAR))
$(info VAR is $(VAR))
'error' would stop the make execution, after showing the error string
from a "Mr. Make post"
https://www.cmcrossroads.com/article/printing-value-makefile-variable
Add the following rule to your Makefile:
print-% : ; #echo $* = $($*)
Then, if you want to find out the value of a makefile variable, just:
make print-VARIABLE
and it will return:
VARIABLE = the_value_of_the_variable
If you simply want some output, you want to use $(info) by itself. You can do that anywhere in a Makefile, and it will show when that line is evaluated:
$(info VAR="$(VAR)")
Will output VAR="<value of VAR>" whenever make processes that line. This behavior is very position dependent, so you must make sure that the $(info) expansion happens AFTER everything that could modify $(VAR) has already happened!
A more generic option is to create a special rule for printing the value of a variable. Generally speaking, rules are executed after variables are assigned, so this will show you the value that is actually being used. (Though, it is possible for a rule to change a variable.) Good formatting will help clarify what a variable is set to, and the $(flavor) function will tell you what kind of a variable something is. So in this rule:
print-% : ; $(info $* is a $(flavor $*) variable set to [$($*)]) #true
$* expands to the stem that the % pattern matched in the rule.
$($*) expands to the value of the variable whose name is given by by $*.
The [ and ] clearly delineate the variable expansion.
You could also use " and " or similar.
$(flavor $*) tells you what kind of variable it is. NOTE: $(flavor)
takes a variable name, and not its expansion.
So if you say make print-LDFLAGS, you get $(flavor LDFLAGS),
which is what you want.
$(info text) provides output.
Make prints text on its stdout as a side-effect of the expansion.
The expansion of $(info) though is empty.
You can think of it like #echo,
but importantly it doesn't use the shell,
so you don't have to worry about shell quoting rules.
#true is there just to provide a command for the rule.
Without that,
make will also output print-blah is up to date. I feel #true makes it more clear that it's meant to be a no-op.
Running it, you get
$ make print-LDFLAGS
LDFLAGS is a recursive variable set to [-L/Users/...]
All versions of make require that command lines be indented with a TAB (not space) as the first character in the line. If you showed us the entire rule instead of just the two lines in question we could give a clearer answer, but it should be something like:
myTarget: myDependencies
#echo hi
where the first character in the second line must be TAB.
#echo $(NDK_PROJECT_PATH) is the good way to do it.
I don't think the error comes from there.
Generally this error appears when you mistyped the intendation : I think you have spaces where you should have a tab.
No need to modify the Makefile.
$ cat printvars.mak
print-%:
#echo '$*=$($*)'
$ cd /to/Makefile/dir
$ make -f ~/printvars.mak -f Makefile print-VARIABLE
Run make -n; it shows you the value of the variable..
Makefile...
all:
#echo $(NDK_PROJECT_PATH)
Command:
export NDK_PROJECT_PATH=/opt/ndk/project
make -n
Output:
echo /opt/ndk/project
This makefile will generate the 'missing separator' error message:
all
#echo NDK_PROJECT_PATH=$(NDK_PROJECT_PATH)
done:
#echo "All done"
There's a tab before the #echo "All done" (though the done: rule and action are largely superfluous), but not before the #echo PATH=$(PATH).
The trouble is that the line starting all should either have a colon : or an equals = to indicate that it is a target line or a macro line, and it has neither, so the separator is missing.
The action that echoes the value of a variable must be associated with a target, possibly a dummy or PHONEY target. And that target line must have a colon on it. If you add a : after all in the example makefile and replace the leading blanks on the next line by a tab, it will work sanely.
You probably have an analogous problem near line 102 in the original makefile. If you showed 5 non-blank, non-comment lines before the echo operations that are failing, it would probably be possible to finish the diagnosis. However, since the question was asked in May 2013, it is unlikely that the broken makefile is still available now (August 2014), so this answer can't be validated formally. It can only be used to illustrate a plausible way in which the problem occurred.
The problem is that echo works only under an execution block. i.e. anything after "xx:"
So anything above the first execution block is just initialization so no execution command can used.
So create a execution blocl
If you don't want to modify the Makefile itself, you can use --eval to add a new target, and then execute the new target, e.g.
make --eval='print-tests:
#echo TESTS $(TESTS)
' print-tests
You can insert the required TAB character in the command line using CTRL-V, TAB
example Makefile from above:
all: do-something
TESTS=
TESTS+='a'
TESTS+='b'
TESTS+='c'
do-something:
#echo "doing something"
#echo "running tests $(TESTS)"
#exit 1
This can be done in a generic way and can be very useful when debugging a complex makefile. Following the same technique as described in another answer, you can insert the following into any makefile:
# if the first command line argument is "print"
ifeq ($(firstword $(MAKECMDGOALS)),print)
# take the rest of the arguments as variable names
VAR_NAMES := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
# turn them into do-nothing targets
$(eval $(VAR_NAMES):;#:))
# then print them
.PHONY: print
print:
#$(foreach var,$(VAR_NAMES),\
echo '$(var) = $($(var))';)
endif
Then you can just do "make print" to dump the value of any variable:
$ make print CXXFLAGS
CXXFLAGS = -g -Wall
You could create a vars rule in your make file, like this:
dispvar = echo $(1)=$($(1)) ; echo
.PHONY: vars
vars:
#$(call dispvar,SOMEVAR1)
#$(call dispvar,SOMEVAR2)
There are some more robust ways to dump all variables here: gnu make: list the values of all variables (or "macros") in a particular run.
if you use android make (mka) #echo $(NDK_PROJECT_PATH) will not work and gives you error *** missing separator. Stop."
use this answer if you are trying to print variables in android make
NDK_PROJECT_PATH := some_value
$(warning $(NDK_PROJECT_PATH))
that worked for me
I usually echo with an error if I wanted to see the variable value.(Only if you wanted to see the value. It will stop execution.)
#echo $(error NDK_PROJECT_PATH= $(NDK_PROJECT_PATH))
The following command does it for me on Windows:
Path | tr ; "\n"

Resources