CMake Language Pitfalls

From Qt Wiki
Jump to navigation Jump to search

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