GNU Make documentation: contradiction in ifdef? - makefile

The following is the documentation for the ifdef conditional in GNU Make. What is this suppose to be telling me?
conditional-directive
text-if-true
else
text-if-false
endif
The ifdef form takes the name of a variable as its argument, not a reference to a variable. The value of that variable has a non-empty value, the text-if-true is effective; otherwise, the text-if-false, if any, is effective. Variables that have never been defined have an empty value. The text variable-name is expanded, so it could be a variable or function that expands to the name of a variable.
Note that ifdef only tests whether a variable has a value. It does not expand the variable to see if that value is nonempty. Consequently, tests using ifdef return true for all definitions except those likefoo =. To test for an empty value, use ifeq ($(foo),).

The ifdef form takes the name of a variable as its argument, not a reference to a variable. The value of that variable has a non-empty value, the text-if-true is effective; otherwise, the text-if-false, if any, is effective. Variables that have never been defined have an empty value.
Lets say we've a variable foo, then it having a value assigned is good enough for ifdef to deem it true, it doesn't care about foo's actual value. Some may consider 0 or an empty string to be falsey, but not ifdef.
foo = 0 # or even ''
ifdef foo # true, bar will be set to 1
bar = 1
else
bar = 0
endif
Note that ifdef only tests whether a variable has a value. It does not expand the variable to see if that value is nonempty.
So, when it already has a variable, no further expansions are made (like above).
The text variable-name is expanded, so it could be a variable or function that expands to the name of a variable.
However, when we pass a reference to a variable or a function, then expansion is done. The example given in the same document clarifies this:
bar = true
foo = bar
ifdef $(foo)
frobozz = yes
endif
The variable reference $(foo) is expanded, yielding bar, which is considered to be the name of a variable. The variable bar is not expanded, but its value is examined to determine if it is non-empty.
The confusion is due to this line:
Note that ifdef only tests whether a variable has a value. It does not expand the variable to see if that value is nonempty.
This would've been better:
Note that ifdef only tests whether a variable has a value. It does not test the variable to see if that value is nonempty.

The docs explanation also makes me confused.
I try to explain it by the following steps (not sure if it's always correct):
If the right side of equal sign is empty, then consider the variable as undefined, otherwise, defined.
If the right side of ifdef is a variable name, then I look up the name to see if it is defined.
If the right side of ifdef is a variable reference, I evaluate its value and see its result as the actual variable name that ifdef wants to test, then I look up the name to see if it is defined.
e.g.
bar = # the right side of equal sign is empty (nothing), it's undefined
foo1 = bar # the right side of equal sign is not empty(bar), it's defined
abc1 = foo1 # the right side of equal sign is not empty(foo1), it's defined
foo2 = $(abc1) # the right side of equal sign is not empty($(abc1)), it's defined
all:
ifdef foo1 # variable name is foo1, defined
echo "1. foo1 is defined"
endif
ifdef abc1 # variable name is abc1, defined
echo "2. abc1 is defined"
endif
ifdef foo2 # variable name is foo2, defined
echo "3. foo2 is defined"
endif
ifdef $(foo1) # variable name is bar, undefined
echo "4. $(foo1) is defined"
endif
ifdef $(abc1) # variable name is foo1, defined
echo "5. $(abc1) is defined"
endif
ifdef $(foo2) # variable name is foo1, defined
echo "6. $(foo2) is defined"
endif
result:
echo "1. foo1 is defined"
1. foo1 is defined
echo "2. abc1 is defined"
2. abc1 is defined
echo "3. foo2 is defined"
3. foo2 is defined
echo "5. foo1 is defined"
5. foo1 is defined
echo "6. foo1 is defined"
6. foo1 is defined

Related

Makefile do not reevaluate variable with every call [duplicate]

Can anybody give a clear explanation of how variable assignment really works in Makefiles.
What is the difference between :
VARIABLE = value
VARIABLE ?= value
VARIABLE := value
VARIABLE += value
I have read the section in GNU Make's manual, but it still doesn't make sense to me.
Lazy Set
VARIABLE = value
Normal setting of a variable, but any other variables mentioned with the value field are recursively expanded with their value at the point at which the variable is used, not the one it had when it was declared
Immediate Set
VARIABLE := value
Setting of a variable with simple expansion of the values inside - values within it are expanded at declaration time.
Lazy Set If Absent
VARIABLE ?= value
Setting of a variable only if it doesn't have a value. value is always evaluated when VARIABLE is accessed. It is equivalent to
ifeq ($(origin VARIABLE), undefined)
VARIABLE = value
endif
See the documentation for more details.
Append
VARIABLE += value
Appending the supplied value to the existing value (or setting to that value if the variable didn't exist)
Using = causes the variable to be assigned a value. If the variable already had a value, it is replaced. This value will be expanded when it is used. For example:
HELLO = world
HELLO_WORLD = $(HELLO) world!
# This echoes "world world!"
echo $(HELLO_WORLD)
HELLO = hello
# This echoes "hello world!"
echo $(HELLO_WORLD)
Using := is similar to using =. However, instead of the value being expanded when it is used, it is expanded during the assignment. For example:
HELLO = world
HELLO_WORLD := $(HELLO) world!
# This echoes "world world!"
echo $(HELLO_WORLD)
HELLO = hello
# Still echoes "world world!"
echo $(HELLO_WORLD)
HELLO_WORLD := $(HELLO) world!
# This echoes "hello world!"
echo $(HELLO_WORLD)
Using ?= assigns the variable a value iff the variable was not previously assigned. If the variable was previously assigned a blank value (VAR=), it is still considered set I think. Otherwise, functions exactly like =.
Using += is like using =, but instead of replacing the value, the value is appended to the current one, with a space in between. If the variable was previously set with :=, it is expanded I think. The resulting value is expanded when it is used I think. For example:
HELLO_WORLD = hello
HELLO_WORLD += world!
# This echoes "hello world!"
echo $(HELLO_WORLD)
If something like HELLO_WORLD = $(HELLO_WORLD) world! were used, recursion would result, which would most likely end the execution of your Makefile. If A := $(A) $(B) were used, the result would not be the exact same as using += because B is expanded with := whereas += would not cause B to be expanded.
I suggest you do some experiments using "make". Here is a simple demo, showing the difference between = and :=.
/* Filename: Makefile*/
x := foo
y := $(x) bar
x := later
a = foo
b = $(a) bar
a = later
test:
#echo x - $(x)
#echo y - $(y)
#echo a - $(a)
#echo b - $(b)
make test prints:
x - later
y - foo bar
a - later
b - later bar
Check more elaborate explanation here
When you use VARIABLE = value, if value is actually a reference to another variable, then the value is only determined when VARIABLE is used. This is best illustrated with an example:
VAL = foo
VARIABLE = $(VAL)
VAL = bar
# VARIABLE and VAL will both evaluate to "bar"
When you use VARIABLE := value, you get the value of value as it is now. For example:
VAL = foo
VARIABLE := $(VAL)
VAL = bar
# VAL will evaluate to "bar", but VARIABLE will evaluate to "foo"
Using VARIABLE ?= val means that you only set the value of VARIABLE if VARIABLE is not set already. If it's not set already, the setting of the value is deferred until VARIABLE is used (as in example 1).
VARIABLE += value just appends value to VARIABLE. The actual value of value is determined as it was when it was initially set, using either = or :=.
In the above answers, it is important to understand what is meant by "values are expanded at declaration/use time". Giving a value like *.c does not entail any expansion. It is only when this string is used by a command that it will maybe trigger some globbing. Similarly, a value like $(wildcard *.c) or $(shell ls *.c) does not entail any expansion and is completely evaluated at definition time even if we used := in the variable definition.
Try the following Makefile in directory where you have some C files:
VAR1 = *.c
VAR2 := *.c
VAR3 = $(wildcard *.c)
VAR4 := $(wildcard *.c)
VAR5 = $(shell ls *.c)
VAR6 := $(shell ls *.c)
all :
touch foo.c
#echo "now VAR1 = \"$(VAR1)\"" ; ls $(VAR1)
#echo "now VAR2 = \"$(VAR2)\"" ; ls $(VAR2)
#echo "now VAR3 = \"$(VAR3)\"" ; ls $(VAR3)
#echo "now VAR4 = \"$(VAR4)\"" ; ls $(VAR4)
#echo "now VAR5 = \"$(VAR5)\"" ; ls $(VAR5)
#echo "now VAR6 = \"$(VAR6)\"" ; ls $(VAR6)
rm -v foo.c
Running make will trigger a rule that creates an extra (empty) C file, called foo.c but none of the 6 variables has foo.c in its value.
The most upvoted answer can be improved.
Let me refer to GNU Make manual "Setting variables" and "Flavors", and add some comments.
Recursively expanded variables
The value you specify is installed verbatim; if it contains references to other variables, these references are expanded whenever this variable is substituted (in the course of expanding some other string). When this happens, it is called recursive expansion.
foo = $(bar)
The catch: foo will be expanded to the value of $(bar) each time foo is evaluated, possibly resulting in different values. Surely you cannot call it "lazy"! This can surprise you if executed on midnight:
# This variable is haunted!
WHEN = $(shell date -I)
something:
touch $(WHEN).flag
# If this is executed on 00:00:00:000, $(WHEN) will have a different value!
something-else-later: something
test -f $(WHEN).flag || echo "Boo!"
Simply expanded variable
VARIABLE := value
VARIABLE ::= value
Variables defined with ‘:=’ or ‘::=’ are simply expanded variables.
Simply expanded variables are defined by lines using ‘:=’ or ‘::=’ [...]. Both forms are equivalent in GNU make; however only the ‘::=’ form is described by the POSIX standard [...] 2012.
The value of a simply expanded variable is scanned once and for all, expanding any references to other variables and functions, when the variable is defined.
Not much to add. It's evaluated immediately, including recursive expansion of, well, recursively expanded variables.
The catch: If VARIABLE refers to ANOTHER_VARIABLE:
VARIABLE := $(ANOTHER_VARIABLE)-yohoho
and ANOTHER_VARIABLE is not defined before this assignment, ANOTHER_VARIABLE will expand to an empty value.
Assign if not set
FOO ?= bar
is equivalent to
ifeq ($(origin FOO), undefined)
FOO = bar
endif
where $(origin FOO) equals to undefined only if the variable was not set at all.
The catch: if FOO was set to an empty string, either in makefiles, shell environment, or command line overrides, it will not be assigned bar.
Appending
VAR += bar
Appending:
When the variable in question has not been defined before, ‘+=’ acts just like normal ‘=’: it defines a recursively-expanded variable. However, when there is a previous definition, exactly what ‘+=’ does depends on what flavor of variable you defined originally.
So, this will print foo bar:
VAR = foo
# ... a mile of code
VAR += $(BAR)
BAR = bar
$(info $(VAR))
but this will print foo:
VAR := foo
# ... a mile of code
VAR += $(BAR)
BAR = bar
$(info $(VAR))
The catch is that += behaves differently depending on what type of variable VAR was assigned before.
Multiline values
The syntax to assign multiline value to a variable is:
define VAR_NAME :=
line
line
endef
or
define VAR_NAME =
line
line
endef
Assignment operator can be omitted, then it creates a recursively-expanded variable.
define VAR_NAME
line
line
endef
The last newline before endef is removed.
Bonus: the shell assignment operator ‘!=’
HASH != printf '\043'
is the same as
HASH := $(shell printf '\043')
Don't use it. $(shell) call is more readable, and the usage of both in a makefiles is highly discouraged. At least, $(shell) follows Joel's advice and makes wrong code look obviously wrong.

Make args: is there a variable that contains all of them?

In make, I can define a file as follows:
.PHONY: echo-foo
echo-foo:
echo ${foo}
Usage:
make echo-foo foo=foo
Now suppose I would like to do the following:
make echo-vars foo=foo bar=bar
And I start by writing:
echo-vars:
echo ${???}
Is there a variable automatically defined which will output:
// either
foo=foo bar=bar
// or
foo bar
In other words: is there a MAKEARGS or some variable along those lines defined as part of the make env?
There's a special variable just for that:
echo-vars:
echo ${MAKEOVERRIDES}
.VARIABLES is somewhat close.
This prints only command-line variables:
test:
echo "$(foreach VAR_NAME,$(.VARIABLES),\
$(if $(filter command line,$(origin $(VAR_NAME))),$(VAR_NAME)))"
$(filter command line,$(origin VAR_NAME)) equals to "command line" if it's the value of $(origin VAR_NAME), and is empty otherwise. $(filter <needle>,<haystack>) is a make's boolean test for "<needle> is in or equals to <haystack> and is not empty".
$(if <condition>,<value>) returns <value> if <condition> is nonempty, otherwise an empty string.
$(foreach VAR_NAME,SET,EXPRESSION) returns a joined result of EXPRESSION applied to each element of a (space-separated) SET, where VAR_NAME is substituted with each element of the SET.
Add $(strip) to get rid of excess spaces.

"define" and "endef" syntax-rules in makefile

From the docs:
Note that lines beginning with the recipe prefix character are
considered part of a recipe, so any define or endef strings
appearing on such a line will not be considered make directives.
My worry is, that is not always the case.
As evident, by the following makfile:
ifeq "x" "y"
define xxx
else
foo = 1
endef
else
bar = 2
endif
all ::
#echo 'foo is: "$(foo)"'
#echo 'bar is: "$(bar)"'
Running, we get:
$ make
foo is: ""
bar is: "2"
Going back to the makefile, it is evident that the lines 3 and 4 inside the ifeq directive, i.e.
ifeq "x" "y"
define xxx
else
foo = 1
endef
else
bar = 2
endif
are ignored by Make.
Put simply, make ignores these 2 lines, within the define xx
else
foo = 1
Because they are inside a define, and therefore, Make does not evaluate the else as a directive (i.e. as a else part, for ifeq "x" "y").
But, Make clearly parses that endef line (5th line in the ifeq block), as a directive to end the define directive.
Hence, after ending the define block with the endef line, Make will parse later lines, that is:
else
bar = 2
endif
As part of the ifeq block, and because the opening directive ifeq "x" "y" evaluated to false, Make will take the else part as a true statement, and assign the value 2 to the variable bar, and so on.
Why is all this important?
Because, clearly, Make allows here a endef line to be parsed as a directive, even though, it is prefixed with a <tab> character.
That is in clear violation of the documentation, quoted above:
Note that lines beginning with the recipe prefix character are
considered part of a recipe, so any define or endef strings
appearing on such a line will not be considered make directives.
Agree?

:= and ?= in Makefile [duplicate]

Can anybody give a clear explanation of how variable assignment really works in Makefiles.
What is the difference between :
VARIABLE = value
VARIABLE ?= value
VARIABLE := value
VARIABLE += value
I have read the section in GNU Make's manual, but it still doesn't make sense to me.
Lazy Set
VARIABLE = value
Normal setting of a variable, but any other variables mentioned with the value field are recursively expanded with their value at the point at which the variable is used, not the one it had when it was declared
Immediate Set
VARIABLE := value
Setting of a variable with simple expansion of the values inside - values within it are expanded at declaration time.
Lazy Set If Absent
VARIABLE ?= value
Setting of a variable only if it doesn't have a value. value is always evaluated when VARIABLE is accessed. It is equivalent to
ifeq ($(origin VARIABLE), undefined)
VARIABLE = value
endif
See the documentation for more details.
Append
VARIABLE += value
Appending the supplied value to the existing value (or setting to that value if the variable didn't exist)
Using = causes the variable to be assigned a value. If the variable already had a value, it is replaced. This value will be expanded when it is used. For example:
HELLO = world
HELLO_WORLD = $(HELLO) world!
# This echoes "world world!"
echo $(HELLO_WORLD)
HELLO = hello
# This echoes "hello world!"
echo $(HELLO_WORLD)
Using := is similar to using =. However, instead of the value being expanded when it is used, it is expanded during the assignment. For example:
HELLO = world
HELLO_WORLD := $(HELLO) world!
# This echoes "world world!"
echo $(HELLO_WORLD)
HELLO = hello
# Still echoes "world world!"
echo $(HELLO_WORLD)
HELLO_WORLD := $(HELLO) world!
# This echoes "hello world!"
echo $(HELLO_WORLD)
Using ?= assigns the variable a value iff the variable was not previously assigned. If the variable was previously assigned a blank value (VAR=), it is still considered set I think. Otherwise, functions exactly like =.
Using += is like using =, but instead of replacing the value, the value is appended to the current one, with a space in between. If the variable was previously set with :=, it is expanded I think. The resulting value is expanded when it is used I think. For example:
HELLO_WORLD = hello
HELLO_WORLD += world!
# This echoes "hello world!"
echo $(HELLO_WORLD)
If something like HELLO_WORLD = $(HELLO_WORLD) world! were used, recursion would result, which would most likely end the execution of your Makefile. If A := $(A) $(B) were used, the result would not be the exact same as using += because B is expanded with := whereas += would not cause B to be expanded.
I suggest you do some experiments using "make". Here is a simple demo, showing the difference between = and :=.
/* Filename: Makefile*/
x := foo
y := $(x) bar
x := later
a = foo
b = $(a) bar
a = later
test:
#echo x - $(x)
#echo y - $(y)
#echo a - $(a)
#echo b - $(b)
make test prints:
x - later
y - foo bar
a - later
b - later bar
Check more elaborate explanation here
When you use VARIABLE = value, if value is actually a reference to another variable, then the value is only determined when VARIABLE is used. This is best illustrated with an example:
VAL = foo
VARIABLE = $(VAL)
VAL = bar
# VARIABLE and VAL will both evaluate to "bar"
When you use VARIABLE := value, you get the value of value as it is now. For example:
VAL = foo
VARIABLE := $(VAL)
VAL = bar
# VAL will evaluate to "bar", but VARIABLE will evaluate to "foo"
Using VARIABLE ?= val means that you only set the value of VARIABLE if VARIABLE is not set already. If it's not set already, the setting of the value is deferred until VARIABLE is used (as in example 1).
VARIABLE += value just appends value to VARIABLE. The actual value of value is determined as it was when it was initially set, using either = or :=.
In the above answers, it is important to understand what is meant by "values are expanded at declaration/use time". Giving a value like *.c does not entail any expansion. It is only when this string is used by a command that it will maybe trigger some globbing. Similarly, a value like $(wildcard *.c) or $(shell ls *.c) does not entail any expansion and is completely evaluated at definition time even if we used := in the variable definition.
Try the following Makefile in directory where you have some C files:
VAR1 = *.c
VAR2 := *.c
VAR3 = $(wildcard *.c)
VAR4 := $(wildcard *.c)
VAR5 = $(shell ls *.c)
VAR6 := $(shell ls *.c)
all :
touch foo.c
#echo "now VAR1 = \"$(VAR1)\"" ; ls $(VAR1)
#echo "now VAR2 = \"$(VAR2)\"" ; ls $(VAR2)
#echo "now VAR3 = \"$(VAR3)\"" ; ls $(VAR3)
#echo "now VAR4 = \"$(VAR4)\"" ; ls $(VAR4)
#echo "now VAR5 = \"$(VAR5)\"" ; ls $(VAR5)
#echo "now VAR6 = \"$(VAR6)\"" ; ls $(VAR6)
rm -v foo.c
Running make will trigger a rule that creates an extra (empty) C file, called foo.c but none of the 6 variables has foo.c in its value.
The most upvoted answer can be improved.
Let me refer to GNU Make manual "Setting variables" and "Flavors", and add some comments.
Recursively expanded variables
The value you specify is installed verbatim; if it contains references to other variables, these references are expanded whenever this variable is substituted (in the course of expanding some other string). When this happens, it is called recursive expansion.
foo = $(bar)
The catch: foo will be expanded to the value of $(bar) each time foo is evaluated, possibly resulting in different values. Surely you cannot call it "lazy"! This can surprise you if executed on midnight:
# This variable is haunted!
WHEN = $(shell date -I)
something:
touch $(WHEN).flag
# If this is executed on 00:00:00:000, $(WHEN) will have a different value!
something-else-later: something
test -f $(WHEN).flag || echo "Boo!"
Simply expanded variable
VARIABLE := value
VARIABLE ::= value
Variables defined with ‘:=’ or ‘::=’ are simply expanded variables.
Simply expanded variables are defined by lines using ‘:=’ or ‘::=’ [...]. Both forms are equivalent in GNU make; however only the ‘::=’ form is described by the POSIX standard [...] 2012.
The value of a simply expanded variable is scanned once and for all, expanding any references to other variables and functions, when the variable is defined.
Not much to add. It's evaluated immediately, including recursive expansion of, well, recursively expanded variables.
The catch: If VARIABLE refers to ANOTHER_VARIABLE:
VARIABLE := $(ANOTHER_VARIABLE)-yohoho
and ANOTHER_VARIABLE is not defined before this assignment, ANOTHER_VARIABLE will expand to an empty value.
Assign if not set
FOO ?= bar
is equivalent to
ifeq ($(origin FOO), undefined)
FOO = bar
endif
where $(origin FOO) equals to undefined only if the variable was not set at all.
The catch: if FOO was set to an empty string, either in makefiles, shell environment, or command line overrides, it will not be assigned bar.
Appending
VAR += bar
Appending:
When the variable in question has not been defined before, ‘+=’ acts just like normal ‘=’: it defines a recursively-expanded variable. However, when there is a previous definition, exactly what ‘+=’ does depends on what flavor of variable you defined originally.
So, this will print foo bar:
VAR = foo
# ... a mile of code
VAR += $(BAR)
BAR = bar
$(info $(VAR))
but this will print foo:
VAR := foo
# ... a mile of code
VAR += $(BAR)
BAR = bar
$(info $(VAR))
The catch is that += behaves differently depending on what type of variable VAR was assigned before.
Multiline values
The syntax to assign multiline value to a variable is:
define VAR_NAME :=
line
line
endef
or
define VAR_NAME =
line
line
endef
Assignment operator can be omitted, then it creates a recursively-expanded variable.
define VAR_NAME
line
line
endef
The last newline before endef is removed.
Bonus: the shell assignment operator ‘!=’
HASH != printf '\043'
is the same as
HASH := $(shell printf '\043')
Don't use it. $(shell) call is more readable, and the usage of both in a makefiles is highly discouraged. At least, $(shell) follows Joel's advice and makes wrong code look obviously wrong.

get a default value when variable is unset in make

(edit: question more accurate based on #Michael feedback)
In bash, I often use parameter expansion: the following commands print "default value" when $VARNAME is unset, otherwise it prints the VARNAME content.
echo ${VARNAME:-default value} #if VARNAME empty => print "default value"
echo ${VARNAME-default value} #if VARNAME empty => print "" (VARNAME string)
I did not find a similar feature on GNU make.
I finally wrote in my Makefile:
VARNAME ?= "default value"
all:
echo ${VARNAME}
But I am not happy with this solution: it always creates the variable VARNAME and this may change the behavior on some makefiles.
Is there a simpler way to get a default value on unset variable?
If you want to use the expansion of a GNU make variable if it is non-empty and a default value if it is empty, but not set the variable, you can do something like this:
all:
echo $(or $(VARNAME),default value)
If you want to test if a variable has a non-empty value, you can use:
ifeq ($(VARNAME),)
VARNAME="default value"
else
do_something_else
endif
For checking if a variable has been defined or not, use ifdef.
Refer to Syntax of Conditionals in the manual for more.
I have a similar case where the result of filtering a shell command could be a single word or empty string. When empty, it should fallback to the default word. In the example below APPLE_LINUX will be 'apple' on macOS or 'linux' on other platforms. MSG will be set to the message for the appropriate platform. The example intentionality avoids using ifeq.
MACHINE := $(shell $(COMPILE.cpp) -dumpmachine)
MACHINE_APPLE := $(findstring apple,$(MACHINE))
APPLE_LINUX := $(firstword $(MACHINE_APPLE) linux)
apple.MSG := You are building on macOS
linux.MSG := You are building on Linux or another OS
MSG := $($(APPLE_LINUX).MSG)
Just remove the colon. If you use :- in your substitution the default value will be used if the variable is null, an empty string or it does not exist, but just using - on its own will only substitute the default value if the variable has not been defined.
# var1=default
# var2=
# echo var2 is ${var2:-$var1}
var2 is something
# echo var3 is ${var3:-$var1}
var3 is something
# echo var2 is ${var2-$var1}
var2 is
# echo var3 is ${var3-$var1}
var3 is something

Resources