When should I quote CMake variable references?
- When you need to
- When you feel like doing it and it technically makes no difference
I have a hard time understanding how variables work. Most specifically, ${a}
seems to have a different meaning than "${a}"
.
There are a couple of things you need to learn here with respect to the mechanics of CMake: How variable references work, how lists work, and how command arguments are handled.
There are a couple scenarios I can think of where this matters: cammand calls/invocations, and some specific commands which have special behaviour for arguments that variable names, but are not evaluated variable references (Ex. if(...)
).
For Command Calls in General
The docs for variable referencing can be found here. Note that commands in CMake include functions and macros (related docs). TL;DR is that if you reference a non-existant variable without passing --warn-uninitialized
, CMake will evaluate the reference to an empty string. That's why the first message
call above prints "foo: ", and why the first call to set(foo ${foo} abc)
(which references foo
in the second argument) will not error (as long as you don't use --warn-uninitialized
)
In CMake, lists are just strings where each entry of the list is separated by a semicolon character. semicolons can be escaped with a backslash. You can find the full docs on lists here.
The docs for command arguments can be found here.
That's why I say "[quote variables] when you need to".
The rest of the deciding factor on what you need to quote to get the behaviour you want when invoking commands will depend on how the command's body handles the arguments it has been passed (after CMake has done un-escaping, variable reference evaluation, etc.). For those details, refer to the documentation for the specific command, or the documentation or implementation of the function/macro (see also the related docs for function arguments, macro arguments, and macro argument caveats).
Okay, so why did I also say "when you feel like doing it and it technically makes no difference"? Because of the if(...)
command's extra behaviours.
For any place in the signature of a if(...)
"sub-command" you see "<condition>
", refer to the condition syntax documentation, which states:
if(<constant>)
True if the constant is 1
, ON
, YES
, TRUE
, Y
, or a non-zero number (including floating point numbers). 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 (see Variable Expansion further below) and one of the following two forms applies.
if(<variable>)
True if given a variable that is defined to a value that is not a false constant. False otherwise, including if the variable is undefined. Note that macro arguments are not variables. Environment Variables also cannot be tested this way, e.g. if(ENV{some_var})
will always evaluate to false.
if(<string>)
A quoted string always evaluates to false unless:
The string's value is one of the true constants, or
Policy CMP0054
is not set to NEW and the string's value happens to be a variable name that is affected by CMP0054
's behavior.
Also, many of the signatures / "sub-commands" of the if(...)
command accept arguments of the form <variable|string>
Note that these are not talking about variable-references! Just variable names, which can lead to funky things that people might find unexpected, such as seen in Numeric only variable name in CMake.
If you want to be sure that something you intend to be a string in a if(...)
command that will attempt to "auto-reference" variables by name in unquoted strings, then prevent that from happening by quoting it. So this covers both reasons: both "when you need to", and also "when you feel like doing it and it technically makes no difference" (I like to quote things that I intend to be strings in these contexts just for peace of mind).
Note that this behaviour isn't something necessarily special to if(...)
. You can equally write a function that does this kind of thing (Ex. ${${ARGV0}}
(de-reference once to get the argument value, then de-reference again to treat the value as the name of another variable or additionally do a if(DEFINED "${ARGV0}")
first to check if such a variable is defined first)). So if you want to be safe, always read docs (I'd just encourage reading docs in general).