I wonder why often variables in CMake are wrapped with a dollar sign and curly brackets. For example, I saw this call in a CMake tutorial.
include_directories(${PROJECT_BINARY_DIR})
But from what I tried, this does the same thing.
include_directories(PROJECT_BINARY_DIR)
When is the wrapping with ${...} needed and what does it mean? Why are variables often wrapped with this even if it makes no difference?
Quoting the CMake documentation:
A variable reference has the form ${variable_name} and is evaluated
inside a Quoted Argument or an Unquoted Argument. A variable reference
is replaced by the value of the variable, or by the empty string if
the variable is not set.
In other words, writing PROJECT_BINARY_DIR refers, literally, to the string "PROJECT_BINARY_DIR". Encapsulating it in ${...} gives you the contents of the variable with the name PROJECT_BINARY_DIR.
Consider:
set(FOO "Hello there!")
message(FOO) # prints FOO
message(${FOO}) # prints Hello there!
As you have probably guessed already, include_directories(PROJECT_BINARY_DIR) simply attempts to add a subdirectory of the name PROJECT_BINARY_DIR to the include directories. On most build systems, if no such directory exists, it will simply ignore the command, which might have tricked you into the impression that it works as expected.
A popular source of confusion comes from the fact that if() does not require explicit dereferencing of variables:
set(FOO TRUE)
if(FOO)
message("Foo was set!")
endif()
Again the documentation explains this behavior:
if(<constant>)
True if the constant is 1, ON, YES, TRUE, Y, or a non-zero number. False if the constant is 0, OFF, NO, FALSE, N, IGNORE, NOTFOUND, the
empty string, or ends in the suffix -NOTFOUND. Named boolean constants
are case-insensitive. If the argument is not one of these constants,
it is treated as a variable.
if(<variable>)
True if the variable is defined to a value that is not a false constant. False otherwise. (Note macro arguments are not variables.)
In particular, one can come up with weird examples like:
unset(BLA)
set(FOO "BLA")
if(FOO)
message("if(<variable>): True")
else()
message("if(<variable>): False")
endif()
if(${FOO})
message("if(<constant>): True")
else()
message("if(<constant>): False")
endif()
Which will take the TRUE branch in the variable case, and the FALSE branch in the constant case. This is due to the fact that in the constant case, CMake will go look for a variable BLA to perform the check on (which is not defined, hence we end up in the FALSE branch).
it's per-case. it's poorly defined. You just have to look it up.
there are other places where you don't have to use {}'s to use the contents of the variable, besides IF. Yikes.
Related
I am not sure the CMake if() command will treat a symbol in the condition clause as a variable or a string literal. So I did some experiments.
Script1.cmake
cmake_minimum_required(VERSION 3.15)
set(XXX "YYY") #<========== HERE!!
if(XXX STREQUAL "XXX")
message("condition 1 is true") # If reach here, XXX is treated as string
elseif(XXX STREQUAL "YYY")
message("condition 2 is true") # If reach here, XXX is treated as variable
endif()
The output is:
condition 2 is true
So I come to below conclusion 1.
For a symbol in the condition clause:
If the symbol is defined as a variable before, CMake will treat it as variable and use its value for evaluation.
If the symbol is not defined as a variable before, CMake will treat it literally as a string.
Then I did another experiment.
set(ON "OFF")
if(ON)
message("condition 3 is true") # If reach here, ON is treated as a constant.
else()
message("condition 4 is true") # If reach here. ON is treated as a variable.
endif()
The output is:
condition 3 is true
So, though ON is explicitly defined as a variable, the if command still treat it as a constant of TRUE value. This directly contradicts to my previous conclusion 1.
So how can I know for sure the CMake if() command will treat a symbol as string or variable??
ADD 1 - 11:04 AM 7/11/2019
It seems the if(constant) form precedes other forms of if() statement. (src)
if(<constant>)
True if the constant is 1, ON, YES, TRUE, Y, or a non-zero number.
False if the constant is 0, OFF, NO, FALSE, N, IGNORE, NOTFOUND, the
empty string, or ends in the suffix -NOTFOUND. Named boolean constants
are case-insensitive. If the argument is not one of these specific
constants, it is treated as a variable or string and the following
signature is used.
So for now, I have to refer to the above rule first before applying my conclusion 1.
(This may be an answer, but I am not sure enough yet.)
Welcome to the wilderness of CMake symbol interpretation.
If the symbol exists as a variable, then the expression is evaluated with the value of the variable. Otherwise, the name of the variable (or literal, as you said) is evaluated instead.
The behavior becomes a little more consistent if you add the ${ and } sequences. Then the value of the variable is used in the evaluation every single time. If the variable doesn't exist or has not been assigned a value, then CMake uses several placeholder values that evaluate to "false". These are the values you mentioned in the latter part to your post.
I believe this is done this way for backwards compatibility, which CMake is really good about. For most of the quirky things CMake does, it's usually in the name of backwards compatibility.
As for the inconsistent behavior you mentioned in the "ON" variable, this is probably due to the precedence in which CMake processes the command arguments. I would have to figure that the constants are parsed before the symbol lookup occurs.
So when it comes to knowing/predicting how an if statement will evaluate, my best answer is experience. The CMake source tree and logic is one magnificent, nasty beast.
There's been discussions on adding an alternative language (one with perhaps a functional paradigm), but it's a quite large undertaking.
CMake's if command [1] supports several signatures, starting with
if(<constant>)
if(<variable|string>)
if(NOT <expression>)
How to negate the first two?
If the CMake documentation is correct (which in my experience is far from certain), then my question boils down to:
How to convert a constant, a variable, or a string X into an expression, with the additional requirement that X is to be evaluated as a boolean?
[1] https://cmake.org/cmake/help/latest/command/if.html
Actually, <expression> is just a placeholder for any parameter, which can be passed to if. Even the list of possible if constructions is titled as "Possible expressions are".
if(NOT <constant>) # Revert 'if(<constant>)'
if(NOT <variable|string>) # Revert 'if(NOT <variable|string>)'
I want to implement a macro that works like cmake_parse_arguments(PARSE_ARGV ...), i.e. one that accesses the arguments of the enclosing function. Unfortunately, ${ARGN}, ${ARGC} and ${ARGVn} have a special meaning within a macro and they invoke macro parameter string substitutions. I would want to access the ARGN and ARGC and ARGVn variables of the enclosing function's scope.
The way around it is to use an intervening variable reference as an escape. Only the innermost substitution is a macro substition, thus ${${}ARGV0} is macro-substituted to ${${}ARGV0}, then first variable substitution gives ${ARGV0} and the second variable substitution yields the value of first argument in the innermost function scope enclosing the macro.
Thus:
macro (show_arg1)
message("1:${${}ARGV1}")
endmacro()
function (test_function)
show_arg1()
endfunction()
test_function(foo bar baz)
Output:
1:bar
This allows to implement macros that behave like cmake_parse_arguments but do something else while still parsing the arguments of the enclosing functions.
Should the ${} variable substitution ever trigger an error, one could use any unset variable, eg. ${${mgUXpKW8srYnwnSP}ARGV1}.
What was the reason to allow numeric only variable names in CMake?
It makes the next code frustrative (if's condition becomes true):
set(1 3)
set(2 3)
if (1 EQUAL 2)
MESSAGE( "hi there" )
endif()
And even more likely usage (if's condition becomes true also):
set(1 2)
... # later on, or even in the other file:
set(var1 1)
if (${var1} EQUAL 2)
MESSAGE( "hi there" )
endif()
PS I understand why variable references without ${} used inside IF/WHILE. But the possibility of numeric only variable names makes using IFs more error-prone...
Answer from Brad King at CMake issue tracker:
For reference, variable names are arbitrary strings, e.g.
set(var "almost anything here")
set("${var}" value)
message(STATUS "${${var}}")
Allowing numeric-only names is a side effect of that.
Certainly they can be used in confusing ways. Disallowing them, even
if only for if() evaluation, would require a policy.
Is there any differences in invoking variables with syntax ${var} and $(var)? For instance, in the way the variable will be expanded or anything?
There's no difference – they mean exactly the same (in GNU Make and in POSIX make).
I think that $(round brackets) look tidier, but that's just personal preference.
(Other answers point to the relevant sections of the GNU Make documentation, and note that you shouldn't mix the syntaxes within a single expression)
The Basics of Variable References section from the GNU make documentation state no differences:
To substitute a variable's value, write a dollar sign followed by the
name of the variable in parentheses or braces: either $(foo) or
${foo} is a valid reference to the variable foo.
As already correctly pointed out, there is no difference but be be wary not to mix the two kind of delimiters as it can lead to cryptic errors like in the GNU make example by unomadh.
From the GNU make manual on the Function Call Syntax (emphasis mine):
[…] If the arguments themselves contain other function calls or variable references, it is wisest to use the same kind of delimiters for all the references; write $(subst a,b,$(x)), not $(subst a,b,${x}). This is because it is clearer, and because only one type of delimiter is matched to find the end of the reference.
The ${} style lets you test the make rules in the shell, if you have the corresponding environment variables set, since that is compatible with bash.
Actually, it seems to be fairly different:
, = ,
list = a,b,c
$(info $(subst $(,),-,$(list))_EOL)
$(info $(subst ${,},-,$(list))_EOL)
outputs
a-b-c_EOL
md/init-profile.md:4: *** unterminated variable reference. Stop.
But so far I only found this difference when the variable name into ${...} contains itself a comma. I first thought ${...} was expanding the comma not as part as the value, but it turns out i'm not able to hack it this way. I still don't understand this... If anyone had an explanation, I'd be happy to know !
It makes a difference if the expression contains unbalanced brackets:
${info ${subst ),(,:-)}}
$(info $(subst ),(,:-)))
->
:-(
*** insufficient number of arguments (1) to function 'subst'. Stop.
For variable references, this makes a difference for functions, or for variable names that contain brackets (bad idea)