When should I quote CMake variables?
VariablesCmakeMacrosVariables Problem Overview
I am writing CMake macros for the first time, and I have a hard time understanding how variables work. Most specifically, ${a}
seems to have a different meaning than "${a}"
.
For example here: https://stackoverflow.com/questions/5248749/passing-a-list-to-a-cmake-macro
I fail to understand when I am supposed to add quotes, and what are the bigger underlying principles.
Variables Solutions
Solution 1 - Variables
Two principles of CMake you have to keep in mind:
- CMake is a script language and arguments are evaluated after the variables are expanded
- CMake differentiates between normal strings and list variables (strings with semicolon delimiters)
Examples
set(_my_text "A B C")
withmessage("${_my_text}")
would giveA B C
set(_my_list A B C)
withmessage("${_my_list}")
would giveA;B;C
set(_my_list "A" "B" "C")
withmessage("${_my_list}")
would giveA;B;C
set(_my_list "A" "B" "C")
withmessage(${_my_list})
would giveABC
Some Rules of Thumb
There are some rules of thumb you should consider:
-
a) When your variable contains text - especially one that could contain semicolons - you should add quotes.
Reasoning: A semicolon is a delimiter for list elements in CMake. So put quotes around a text that is supposed to be one (it works everywhere and for me personally looks better with CMake syntax highlighting)
EDIT: Thanks for the hint from @schieferstapel
b) To be more precise: A variable content with spaces that already had quotes does keep those quotes (imagine as it getting part of the variable's content). This works everywhere also unquoted (normal or user-defined function parameters) with the prominent exception of
if()
calls, where CMake re-interprets the content of unquoted variables after variable expansion (see also rule of thumb #3 and policy CMP0054: Only interpretif()
arguments as variables or keywords when unquoted)Examples:
set(_my_text "A B C")
withmessage(${_my_text})
would also giveA B C
set(_my_text "A;B;C")
withif (${_my_text} STREQUAL "A;B;C")
would giveif given arguments: "A" "B" "C" "STREQUAL" "A;B;C" Unknown arguments specified
-
If your variable contains a list you normally don't add quotes.
Reasoning: If you give something like a file list to an CMake command it normally expect a list of strings and not one string containing a list. The difference you can see e.g. in the
foreach()
command acceptingITEMS
orLISTS
. -
if()
statements are a special case where you normally don't even put the braces.Reasoning: A string could - after expansion - evaluate again to a variable name. To prevent this it's recommended to just name the variable whose content you want to compare (e.g.
if (_my_text STREQUAL "A B C")
).
COMMAND
Examples
set(_my_text "A B C")
withCOMMAND "${CMAKE_COMMAND}" -E echo "${_my_text}"
would- call
cmake.exe -E echo "A B C"
on VS/Windows - call
cmake -E echo A\ B\ C
on GCC/Ubuntu - give
A B C
- call
set(_my_text "A B C")
withCOMMAND "${CMAKE_COMMAND}" -E echo "${_my_text}" VERBATIM
would- call
cmake.exe -E echo "A B C"
on VS/Windows - call
cmake -E echo "A B C"
on GCC/Ubuntu - give
A B C
- call
set(_my_list A B C)
withCOMMAND "${CMAKE_COMMAND}" -E echo "${_my_list}"
would- call
cmake.exe -E echo A;B;C
- give
A
,B: command not found
,C: command not found
- call
set(_my_list A B C)
withCOMMAND "${CMAKE_COMMAND}" -E echo "${_my_list}" VERBATIM
would- call
cmake.exe -E echo "A;B;C"
- give
A;B;C
- call
set(_my_list "A" "B" "C")
withCOMMAND "${CMAKE_COMMAND}" -E echo "${_my_list}" VERBATIM
would- call
cmake.exe -E echo "A;B;C"
- give
A;B;C
- call
set(_my_list "A" "B" "C")
withCOMMAND "${CMAKE_COMMAND}" -E echo ${_my_list} VERBATIM
would- call
cmake.exe -E echo A B C
- give
A B C
- call
set(_my_list "A + B" "=" "C")
withCOMMAND "${CMAKE_COMMAND}" -E echo ${_my_list} VERBATIM
would- call
cmake.exe -E echo "A + B" = C
- give
A + B = C
- call
Some Rules of Thumb with add_custom_target()
/add_custom_command()
/execute_process()
There are some rules of thumb you should consider when you use variables in COMMAND
calls:
-
a) Use quotes for the arguments that contain file paths (like the first argument containing the executable itself).
Reasoning: It could contain spaces and could be reinterpreted as separate arguments to the
COMMAND
callb) See above, works also if the variable
set()
did include quotes. -
Use quotes only if you want to concatenate something into a single parameter to be passed to executable that is called.
Reasoning: A variable could contain a list of parameters which - when using quotes - won't be correctly extracted (semicolons instead of spaces)
-
Always add the
VERBATIM
option withadd_custom_target()
/add_custom_command()
Reasoning: Otherwise the cross-platform behavior is undefined and you could get surprises with your quoted strings.
References
- https://stackoverflow.com/questions/13582282/cmake-difference-between-and
- https://stackoverflow.com/questions/31037882/whats-the-cmake-syntax-to-set-and-use-variables
- https://stackoverflow.com/questions/24986392/looping-over-a-string-list
- https://stackoverflow.com/questions/19982340/cmake-compare-to-empty-string-with-strequal-failed