if
, And Conditions¶
if
Syntax¶
Much like in other languages
set(some_variable "value1") if ("${some_variable}" STREQUAL "value1") message("case 1") elseif ("${some_variable}" STREQUAL "value2") message("case 2") else() message("default") endif()
$ cmake -P if-good.cmake case 1
Much complexity in the evaluation of the condition
The following holds true for
if()
,elseif()
, andwhile()
Predefined Conditions: String Comparison¶
Lexicographic comparison, e.g.
STRLESS
set(some_name "Faschingbauer") if ("${some_name}" STRLESS "Zeilinger") message("yes") endif()
$ cmake -P if-strless.cmake yes
Numerical comparison, e.g.
LESS
set(some_number 7) # <--- unquoted is ok for numbers :-) if ("${some_number}" LESS 8) message("yes") endif()
$ cmake -P if-less.cmake yes
Pitfalls: Variables Are Strings, And CMake Does Not Care¶
Comparing apples and oranges: no runtime error
CMake just says “no”
set(some_number "Faschingbauer")
if ("${some_number}" LESS 8)
message("yes")
else()
message("no")
endif()
if ("${some_number}" GREATER 8)
message("yes")
else()
message("no")
endif()
if ("${some_number}" EQUAL 8)
message("yes")
else()
message("no")
endif()
$ cmake -P if-less-mismatch.cmake
no
no
no
Danger
Be careful! Test!!
Predefined Conditions: File System¶
File existence, etc.
set(filename "/etc/passwd") if (EXISTS "${filename}") message("${filename} exists") if (NOT IS_DIRECTORY "${filename}") message("${filename} is not a directory") endif() endif()
$ cmake -P if-file-exists-and-is-directory.cmake /etc/passwd exists /etc/passwd is not a directory
Path comparison
set(filename "/etc/passwd") if (IS_ABSOLUTE "${filename}") message("${filename} is an absolute path") endif() cmake_policy(SET CMP0139 NEW) # <--- argh! set(alternative_filename "/etc/blah/../passwd") if ("${alternative_filename}" PATH_EQUAL "${filename}") message("${alternative_filename} and ${filename} are the same path") endif()
$ cmake -P if-path-comparison.cmake /etc/passwd is an absolute path
Policies?¶
Concrete problem: in the above code snippet, CMake version 3.27.7
(cmake --version
) failed to recognise the PATH_EQUAL
operator,
with a message saying something about policy CMP0139
.
$ cmake --help-policy CMP0139
CMP0139
-------
.. versionadded:: 3.24
The ``if()`` command supports path comparisons using ``PATH_EQUAL``
operator.
The ``OLD`` behavior for this policy is to ignore the ``PATH_EQUAL`` operator.
The ``NEW`` behavior is to interpret the ``PATH_EQUAL`` operator.
This policy was introduced in CMake version 3.24.
It may be set by ``cmake_policy()`` or ``cmake_minimum_required()``.
If it is not set, CMake warns, and uses ``OLD`` behavior.
.. note::
The ``OLD`` behavior of a policy is
``deprecated by definition``
and may be removed in a future version of CMake.
Solution: set the damn policy
cmake_policy(SET CMP0139 NEW) # <--- argh!
Note
Try not to set policies in any of the 100
CMakeLists.txt
files in your projectReserve the toplevel
CMakeLists.txt
for such administrative tasks
Miscellaneous Checks (Some Rather Advanced)¶
Is a variable defined? Note: a variable may be defined but empty
set(some_variable "value") if (DEFINED some_variable) message("some_variable is a defined variable") endif() if (NOT DEFINED another_variable) message("another_variable is not a defined variable") endif()
$ cmake -P if-defined.cmake some_variable is a defined variable another_variable is not a defined variable
Does a target exist?
set(libname "a-library-that-is-optionally-built") if (NOT TARGET "${libname}") message("${libname} is not a known target") endif()
$ cmake -P if-target.cmake a-library-that-is-optionally-built is not a known target
Note
Targets are architecturally relevant
Properties are probably a better way to communicate optionality.
Condition Evaluation (Where The Mess Begins): What’s Boolean?¶
So far, we only used strings in the examples
Attention
Rule: use strings explicitly as much as you can in your CMake code
This keeps you mentally sane
This will help colleagues understand your code
Rule: set policy
CMP0012
toNEW
if
(andwhile
, for that matter) exists in CMake for a long time⟶ long before
${NAME}
has been considered the way to go for variable references⟶ confusion
if
understands “literals”/”constants” ⟶ there is no such definition in the language, it just happened
Dereferencing Unquoted Variable Names¶
set(some_name "Faschingbauer")
if (some_name STRLESS "Zeilinger") # <--- better: "${some_name}" or ${some_name}
message("yes")
endif()
$ cmake -P if-unquoted.cmake
yes
“Boolean” Variables: Weirdly Interpreted Strings¶
First: there is no boolean type
⟶ only strings that are interpreted somehow
1
,ON
,YES
,TRUE
,Y
(case-insensitively) are interpreted as boolean true0
,OFF
,NO
,FALSE
,N
,IGNORE
,NOTFOUND
, the empty string, or ends in the suffix-NOTFOUND
(case-insensitively) are interpreted as boolean false.
⟶ please listen, and suffer
A naive approach would be to pretend to not know all that …
set(boolean_variable "TRUE") if ("${boolean_variable}" STREQUAL "TRUE") message("true") endif()
$ cmake -P if-bool-naive.cmake true
But this is not how CMake wants it
set(boolean_variable 1) if (boolean_variable) # <--- unquoted variables # <--- preferred for bool message("boolean_variable (${boolean_variable}) evaluated as boolean true") endif() set(boolean_variable 0) if (NOT boolean_variable) # <--- unquoted variables # <--- preferred for bool message("boolean_variable (${boolean_variable}) evaluated as boolean false") endif()
$ cmake -P if-bool-cmake.cmake boolean_variable (1) evaluated as boolean true boolean_variable (0) evaluated as boolean false
Attention
All would be well if that was all. But we have no such luck.
Pitfall: Evaluation Until There Is No More¶
When CMake evaluates (“interpolates”, “expands”) strings, it does so until there’s nothing left.
… which can lead to surprising results
The following evaluates to the plain string
"boolean_variable"
, which is turn is recognized by CMake as a variable reference
set(boolean_variable 0)
set(another_variable "boolean_variable") # <--- name of boolean_variable
if ("${another_variable}") # <--- evaluates to unquoted boolean_variable
# which evaluates to 0
message("quoted: true")
else()
message("quoted: false")
endif()
$ cmake -P if-boolshit-expand-loop.cmake
CMake Warning (dev) at if-boolshit-expand-loop.cmake:4 (if):
Policy CMP0054 is not set: Only interpret if() arguments as variables or
keywords when unquoted. Run "cmake --help-policy CMP0054" for policy
details. Use the cmake_policy command to set the policy and suppress this
warning.
Quoted variables like "boolean_variable" will no longer be dereferenced
when the policy is set to NEW. Since the policy is not set the OLD
behavior will be used.
This warning is for project developers. Use -Wno-dev to suppress it.
quoted: false
Pitfall: Evaluation Of Unquoted Arguments¶
Undefined variables evaluate to boolean false. Just like before, quoting evaluates until there is no more
"${foo}"
evaluates to"bar"
… which is then taken by
if
as an unquoted reference to variablebar
bar
is not defined which evaluates to boolean false
set(foo "bar") if ("${foo}") # <--- unquoted bar -> undefined message("true") else() message("false") endif()
$ cmake -P if-boolshit-par-excellence-ok.cmake false
Biggest crap ever seen: unquoted variable references are special
foo
evaluates to"bar"
"bar"
is not equal to one of the false literals (remember “Boolean” Variables: Weirdly Interpreted Strings)⟶ hence it can only mean boolean true
set(foo "bar") if (foo) message("true") else() message("false") endif()
$ cmake -P if-boolshit-par-excellence.cmake true