CMake Language Pitfalls
This is a collection of pitfalls you might encounter when writing CMake code for Qt's build system.
Initializing Empty Variables
Initialize variables that are supposed to be empty with set(foo "") never with set(foo).
Why? set(foo) is equivalent to unset(foo). A normal variable expansion ${foo} will fall back to the cache variable expansion $CACHE{foo} if foo is unset.
This is especially unpleasant for the variable name result which is cache-set by our feature system.
set(foo ON CACHE BOOL "") set(foo) message("\${foo}: ${foo}") # prints ${foo}: ON set(foo "") message("\${foo}: ${foo}") # prints ${foo}:
Brackets in Strings
Mind CMake's special handling of brackets in lists.
set(lines "foo" "bar" "baz") list(JOIN lines "\n" content) message("this is fine:\n${content}")
set(lines "foo" "bar [" "baz") list(JOIN lines "\n" content) message("----------") message("this is unexpected:\n${content}")
prints
this is fine: foo bar baz ---------- this is unexpected: foo bar [;baz
This happens, because
Although all values in CMake are stored as strings, a string may be treated as a list in certain contexts, such as during evaluation of an Unquoted Argument. In such contexts, a string is divided into list elements by splitting on ; characters not following an unequal number of [ and ] characters and not immediately preceded by a \. The sequence \; does not divide a value but is replaced by ; in the resulting element.
If you need to generate a string with brackets, for example JSON code, do not join lists. Directly append to a string.
Property names should start with an underscore
When creating new target property names, always prefix them with an underscore.
Popular prefixes are _qt_ and _qt_internal_
This is needed to avoid issues with INTERFACE_LIBRARY targets with CMake version 3.18 or lower.
Expand variable references in conditions if they might not be defined
An extension of the Initializing Empty Variables point.
If you have two possibly undefined variables and you want to compare their string values, prefer to evaluate them explicitly in an if command.
unset(UNSETVAR) set(EMPTYVAR "") if(EMPTYVAR) message(">> inside if(EMPTYVAR)") endif() if(UNSETVAR STREQUAL EMPTYVAR) # <------------------------------------- doesn't work if one variable is undefined message(">> inside if(UNSETVAR STREQUAL EMPTYVAR)") endif() if("${UNSETVAR}" STREQUAL "${EMPTYVAR}") # <--------------------------- prefer this style message(">> inside \"\${UNSETVAR}\" STREQUAL \"\${EMPTYVAR}\"") endif()
only shows
>> inside "${UNSETVAR}" STREQUAL "${EMPTYVAR}"
Reference: https://crascit.com/2022/01/25/quoting-in-cmake/#h-special-cases-for-the-if-command