CMake set function - cmake

I am trying to learn CMake from a tutorial. I am not crystal clear about how this set function works.
set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
According to CMake document:
set(<variable> <value> [[CACHE <type> <docstring> [FORCE]] | PARENT_SCOPE])
If in this specific case Variable is EXTRA_LIBS, Value is ${EXTRA_LIBS}, then CACHE is MathFunctions?
Correct?

What the command is trying to do is to append the string MathFunctions to whatever value is already stored in the variable EXTRA_LIBS.
To break it down:
set(VARNAME VALUE)
sets the variable VARNAME to the string VALUE. Note that you can assign multiple values to a single variable, which will effectively assign a list value to the variable:
set(VARNAME VALUE1 VALUE2 VALUE3)
To access the value of that variable later, you have to dereference it using ${}, as in
message(${VARNAME})
If the value assigned is a list, you might want to access the different elements separately instead:
foreach(element ${VARNAME})
message(${element})
endforeach()
The command from your question does both the dereferencing of the old value and the assignment of the new value in one line.
A more descriptive way of performing the same assignment is offered by the list command:
list(APPEND EXTRA_LIBS MathFunctions)
On a related note: Note that there is a subtle difference between appending to a list and string concatenation:
set (EXTRA_LIBS "${EXTRA_LIBS} MathFunctions")
Instead of appending, this command will assign a new singular string value to the variable, that is equal to the concatenation of the previous values with the string MathFunctions.

Related

How to use empty entries in CMake lists as arguments

When using a CMake list to specify multiple arguments to a function, empty arguments are not passed as arguments to the list. In some cases an empty string is needed as an argument. Is there a way to achieve this?
If I run this
set(CMAKE_EXECUTE_PROCESS_COMMAND_ECHO STDOUT)
set(LIST_VAR
"ONE"
"TWO"
""
"FOUR"
)
execute_process(
COMMAND
${CMAKE_COMMAND} -E echo
${LIST_VAR}
)
execute_process(
COMMAND
${CMAKE_COMMAND} -E echo
"ONE"
"TWO"
""
"FOUR"
)
with
cmake -P test.cmake
I get:
'cmake' '-E' 'echo' 'ONE' 'TWO' 'FOUR'
ONE TWO FOUR
'cmake' '-E' 'echo' 'ONE' 'TWO' '' 'FOUR'
ONE TWO FOUR
In the first variant the third, empty argument is swallowed, which is really annoying if there are cases, where an empty argument may be possible/needed and arguments are prepared as CMake lists.
In my application I need to call a script and not cmake -E echo, which expects empty arguments in certain situations.
Also, the empty entries are - of course - not put in literally as in this simplified example. Instead of "" I have something like "${MIGHT_BE_EMPTY}".
Is there a way to safely transport empty strings as list entries to function arguments?
If not, is there a good work-around for this problem?
E.g. transform unset variables to something like a space (" ") which might be equivalent to an empty argument for the called script?
The problem is that there are no lists in CMake, only strings. And there is a convention that some CMake commands understand: if there are unquoted ; (semicolon) in the variable then it is a list, where each element of the list separated from another by ;. And there is a rule which allows to create lists out of multiple strings separated by a whitespace.
So this command:
set(LIST_VAR
"ONE"
"TWO"
""
"FOUR"
)
Creates a variable with the following content: ONE;TWO;;FOUR which is a list in the CMake book. Now there is another rule (reverse) which CMake uses when it expands unquoted variables:
Each non-empty element is given to the command invocation as an
argument.
So you can't use empty elements and have CMake lists propagate them across the script. You can use something non-empty like an empty element, though. For example, you could use the space character to mean the element is empty if it wouldn't mess with your other data:
set(EMPTY " ")
set(LIST_VAR
"ONE"
"TWO"
"${EMPTY}"
"FOUR"
)
And if you can't modify the list with explicitly providing an ${EMPTY} element you can add it to the existing list like this: string(REPLACE ";;" ";${EMPTY};" LIST_VAR "${LIST_VAR}").
Finally, if you need to modify not a list but some particular, potentially empty variable then you can use the following:
if(MIGHT_BE_EMPTY STREQUAL "")
set(MIGHT_BE_EMPTY "${EMPTY}")
endif()
I use the EMPTY variable because it is convenient, you can drop it and replace with whatever symbol you likeā€”it won't make a difference.

CMake - How does the if() command treat a symbol? As string or as variable?

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.

How to access enclosing function's arguments from within a macro?

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}.

When should I wrap variables with ${...} in CMake?

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.

CMake: difference between ${} and "${}"

What is the difference, in cmake, between something like:
set(any_new_var ${old_var})
and
set(any_new_var "${old_var}")
Any important difference? When have I to use one or the other form?
For example, I try with the next mini test
# test.cmake
# Variable 'a' isn't defined.
set(hola "${a}")
# message(${hola})
message("${hola}")
The output of this mini-test (cmake -P test.cmake) is a empty line (because 'a' isn't defined). If I uncomment the first message, cmake throws an message error:
CMake Error at prueba.cmake:6 (message):
message called with incorrect number of arguments
Why in the second case it doesn't throw and error but an empty line?
In CMake strings can be interpreted as lists. The rule is simple: to form the list split the string at semicolons. For example, the string value one;two;three can be thought of as a list of three elements: one, two, and three.
To invoke a command you write the command name and some words between parentheses. However, these words do not correspond to the arguments the command receive in a one-to-one fashion. Each word become zero or more arguments, and all the arguments get concatenated together.
Unless a word is quoted, it is treated as a list and is expanded to multiple arguments. A quoted word always becomes a single argument.
For example, assume that X is bound to one;two;three, Y is bound to the empty string, and Z is bound to foo. The following command invocation has three words, but the command receives four arguments:
some_command(${X} ${Y} ${Z})
# The command receives four arguments:
# 1. one
# 2. two
# 3. three
# 4. foo
If we would have quoted the words, the command would have received three arguments:
some_command("${X}" "${Y}" "${Z}")
# The command receives three arguments:
# 1. one;two;three
# 2. (the empty list)
# 3. foo
To return to your original question: the message command can receive a varying number of arguments. It takes all its arguments, concatenates them together into one string, and then prints that string. For some unknown reason it does not accept zero arguments, though.
The behavior message has with multiple arguments is not very useful, so you tend to use a single quoted argument with it:
set(SOURCES foo.c hoo.h)
message(${SOURCES}) # prints foo.cfoo.h
message("${SOURCES}") # prints foo.c;foo.h
Also, when set receives multiple arguments it builds a string of the arguments separated by semicolons. The variable is then set to that string.