CMake Gotchas

When working with CMake, never assume you know how something works. Guessing can easily lead you into a subtle “feature” trap.

Important Notes

Common Pitfalls When Testing Environment Variables or Strings

Refer to the official documentation section on :

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.

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.

First, as the documentation states, strings are always treated as false. So if you need to check whether an environment variable is defined and non-empty, you should write:

1
if (DEFINED ENV{VAR} AND NOT "$ENV{VAR}" STREQUAL "")

In this case, ENV{VAR} is an empty string, while $ENV{VAR} is the string value of the set environment variable — both are treated as false.

You need to use string comparison to handle this: "$ENV{VAR}" STREQUAL "".

As for why quotes are necessary, this relates to the exception case CMP0054 mentioned in the documentation. I won’t go into full detail here, but the gist is that older CMake versions had a feature where if the argument to if matched an existing variable name, it would be expanded and evaluated as a string — and this expansion was recursive, meaning if the result still matched a variable name it would keep expanding.

CMake 3.1 introduced a new policy: expansion only happens once. See CMake compare to empty string with STREQUAL failed for details.

Using CMAKE_TOOLCHAIN_FILE

See Tsyvarev’s answer in CMake ignore custom toolchain file.:

The variable CMAKE_TOOLCHAIN_FILE should be set before the first project() call:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
cmake_minimum_required(VERSION <..>)

# set up the Microchip cross toolchain
set(CMAKE_TOOLCHAIN_FILE ./external/cmake-microchip/toolchain.cmake)

project(Test)

# set the default MCU model
set(MICROCHIP_MCU PIC18F97J60)

add_executable(main main.c)

When the first project() call is processed, CMake automatically calls the script specified in CMAKE_TOOLCHAIN_FILE.

Note that the preferred way is to not hardcode the path to the toolchain in the CMakeLists.txt, but to pass the -DCMAKE_TOOLCHAIN_FILE=/path/to/toolchain option to cmake.

In short:

  • The toolchain must be set before calling project()
  • The preferred method is to pass -DCMAKE_TOOLCHAIN_FILE=/path/to/toolchain to cmake

Here is an example:

1
2
3
4
5
6
if (NOT DEFINED CMAKE_TOOLCHAIN_FILE)
    # The toolchain must be set before calling `project()`
    # The preferred method is to pass `-DCMAKE_TOOLCHAIN_FILE=/path/to/toolchain` to `cmake`
    set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/toolchain/toolchain.cmake)
    message(STATUS "CMAKE_TOOLCHAIN_FILE not defined, using default value: " ${CMAKE_TOOLCHAIN_FILE})
endif ()

Official documentation on CMAKE_TOOLCHAIN_FILE:

This is initialized by the CMAKE_TOOLCHAIN_FILE environment variable if it is set when a new build tree is first created.

The CMAKE_TOOLCHAIN_FILE environment variable specifies a default value for the CMAKE_TOOLCHAIN_FILE variable when there is no explicit configuration given on the first run while creating a new build tree. On later runs in an existing build tree the value persists in the cache as CMAKE_TOOLCHAIN_FILE.

If the CMAKE_TOOLCHAIN_FILE environment variable is set when a new build tree is first created, it is used for initialization.

When creating a new build tree, if no explicit configuration is provided on the first run, the CMAKE_TOOLCHAIN_FILE environment variable specifies the default value for the CMAKE_TOOLCHAIN_FILE variable. On subsequent runs in an existing build tree, the value is persisted in the cache as CMAKE_TOOLCHAIN_FILE.

References