Common Syntax of CMake (Cache Variables)

Previous exciting content:CMake Hello, WorldCMake VariablesCMake Official Tutorial (Basic Project Setup)CMake Official Tutorial (Library Creation)CMake Official Tutorial (Usage Requirements)CMake Official Tutorial (Installation and Testing)CMake Common Syntax (if Statement)

In the previous article on CMake Variables, we briefly mentioned cache variables but did not elaborate on them. Today, we will organize the common syntax for cache variables.

First, we present the two main differences between cache variables and ordinary variables:

  1. 1. Scope: The scope of cache variables is global, accessible from anywhere in the CMake project, while the scope of ordinary variables is local, valid only within the current scope where they are defined (such as the current <span>CMakeLists.txt</span> file, function, or macro) and its sub-scopes.
  2. 2. Persistence: Cache variables are stored in the <span>CMakeCache.txt</span> file, retaining their values persistently, even after reconfiguring the project (without deleting the <span>build</span> directory), while ordinary variables are stored in memory and exist only during the CMake configuration phase, not persisted to a file.

Basic Syntax

The basic form for setting a cache variable is:

set(<variable> <value>... CACHE <type> <docstring> [FORCE])

<> represents required options, [] represents optional options

The type is the type of the cache variable, with optional values being:

  1. 1. BOOL: Values are ON or OFF
  2. 2. FILEPATH: File path
  3. 3. PATH: Path
  4. 4. STRING: String
  5. 5. INTERNAL: Internal cache variable, not exposed to users

In practice, it is also acceptable to set the type as STRING without needing to differentiate too finely.

The docstring is the description content, similar to comments in code, but the description content is required.

FORCE is used to force an update. Generally, if this option is not present, resetting an existing cache variable will not take effect. However, for INTERNAL type cache variables, the FORCE behavior is automatically enabled without explicit declaration, as shown below:

set(cache_var1 "hello" CACHE STRING "Test")
set(cache_var1 "hellox" CACHE STRING "Test")
message(${cache_var1})  #hello
set(cache_var1 "hellox" CACHE STRING "Test" FORCE)
message(${cache_var1})  #hellox

set(cache_var "hello" CACHE INTERNAL "Test")
set(cache_var "hellox" CACHE INTERNAL "Test")
message(${cache_var})   #hellox

Scope

In the previous article, the scope was not specifically mentioned, so here we will introduce the scope rules for ordinary variables.

When a parent directory includes a subdirectory through <span>add_subdirectory()</span>, the ordinary variables of the parent directory are passed to the subdirectory by value copy. The subdirectory’s CMakeLists.txt can directly access these variables, but modifications to the variables in the subdirectory only affect its own scope and do not impact the parent directory:

# Parent directory CMakeLists.txt
set(PARENT_VAR "Hello from parent")
add_subdirectory(subdir)
message("Parent directory PARENT_VAR = ${PARENT_VAR}")  # Outputs "Hello from parent"
# Subdirectory subdir/CMakeLists.txt
message("Subdirectory PARENT_VAR = ${PARENT_VAR}")  # Outputs "Hello from parent"
set(PARENT_VAR "Modified by child")           # Only modifies the variable in the subdirectory scope
message("Subdirectory PARENT_VAR = ${PARENT_VAR}")  # Outputs "Modified by child"

If the subdirectory needs to modify the ordinary variable of the parent directory, it must explicitly declare the scope using the <span>PARENT_SCOPE</span> option. At this point, the variable in the parent directory will be updated, but the variable in the subdirectory scope will still retain its original value:

# Subdirectory subdir/CMakeLists.txt
set(PARENT_VAR "Modified by child" PARENT_SCOPE)
message("Subdirectory PARENT_VAR = ${PARENT_VAR}")  # Still outputs "Hello from parent"

The scope of ordinary variables is limited to the current CMakeLists.txt and its subdirectories (passed by copy), and cannot be shared between sibling directories. However, since the scope of cache variables is global, they can be shared throughout the project. Here is a more comprehensive example (including ordinary variables and cache variables), with the directory structure as follows:

├── .
│   └── CMakeLists.txt    # Top level
├── sub1
│   └── CMakeLists.txt    # Subdirectory sub1
└── sub2  
    └── CMakeLists.txt    # Subdirectory sub2

Top-level CMake file:

cmake_minimum_required(VERSION 3.16)
project(cache_var_test)
set(normal_var "normal_var, set in parent")
add_subdirectory(sub1)
add_subdirectory(sub2)
message("cache_var_sub1=${cache_var_sub1}")    # cache_var_sub1, set in sub1

Subdirectory sub1:

cmake_minimum_required(VERSION 3.16)
project(cache_var_test_sub1)
message("normal_var=${normal_var}")            # normal_var, set in parent
set(normal_var_sub1 "normal_var_sub1, set in sub1")
set(cache_var_sub1 "cache_var_sub1, set in sub1" CACHE STRING "Test")

Subdirectory sub2:

cmake_minimum_required(VERSION 3.16)
project(cache_var_test_sub2)
message("normal_var=${normal_var}")            # normal_var, set in parent
message("normal_var_sub1=${normal_var_sub1}")  # empty
message("cache_var_sub1=${cache_var_sub1}")    # cache_var_sub1, set in sub1

In practice, if you need to pass variables, it is generally recommended as follows:

  1. 1. Use ordinary variables within the current directory and subdirectories
  2. 2. Use cache variables when crossing directories or when user configuration is needed

Option

For the set command, defining BOOL cache variables can be quite cumbersome, as shown below:

set(VAR_BOOL "ON" CACHE BOOL "test")

To simplify the definition, CMake provides the option command to define BOOL variables, as follows:

option(VAR_BOOL "test" ON)

The variable defined using <span>option</span> is essentially a boolean cache variable, possessing globality and persistence.

Overriding Strategy

If an ordinary variable and a cache variable have the same name, which variable do we access when using <span>${var_name}</span>? For example, in the following code:

cmake_minimum_required(VERSION 3.12)
project(ShadowTest)
set(MY_VAR "local")
set(MY_VAR "cache" CACHE STRING "Global cache variable")
message("MY_VAR = ${MY_VAR}")

Here, we need to briefly mention CMake’s policy mechanism, which is mainly to address version compatibility issues.

For instance, when a new version of CMake changes certain behaviors, older projects may not build correctly. At this point, the policy mechanism comes into play, allowing users to choose whether to use the old behavior or the new behavior.

By setting policy CMP0126, we can control the handling logic of ordinary variables with the same name as cache variables.

When CMP0126 is set to NEW, ordinary variables will override cache variables, and to access the cache variable, one must explicitly use the <span>$CACHE{var_name}</span> syntax, while <span>${var_name}</span> still points to the ordinary variable, as shown below:

cmake_minimum_required(VERSION 3.12)
project(ShadowTest)
cmake_policy(SET CMP0126 NEW)
set(MY_VAR "local")
set(MY_VAR "cache" CACHE STRING "Global cache variable")
message("MY_VAR = ${MY_VAR}")          # Outputs local
message("MY_VAR = $CACHE{MY_VAR}")     # Outputs cache

When CMP0126 is set to OLD, the behavior becomes quite magical:

cmake_minimum_required(VERSION 3.12)
project(ShadowTest)
cmake_policy(SET CMP0126 OLD)
set(MY_VAR "local")
set(MY_VAR "cache" CACHE STRING "Global cache variable")
message("MY_VAR = ${MY_VAR}")         # First execution outputs cache, subsequent executions output local

According to the official description: When setting a cache variable (<span>CACHE</span>), if a same-named ordinary variable (non-cache variable) exists, CMake will automatically delete that ordinary variable. At this point, the value accessed directly through <span>${var_name}</span> will be overridden by the cache variable.

However, the actual effect is: the first execution outputs cache, and subsequent executions output local.

In practical use, it is recommended to prioritize using the NEW mode. If encountering projects that need to be compatible with older CMake behaviors, the OLD mode can be used, but it may lead to debugging difficulties due to variable overriding.

Variable Passing

Variables passed from the command line are all cache variables, as shown in the CMake file:

cmake_minimum_required(VERSION 3.12)
project(cacheTest)
message(${PARAM})

The first time running from the command line will output hello:

cmake -S . -B build -DPARAM="hello"

The second time running from the command line, without using -D to pass parameters, will still output hello:

cmake -S . -B build

Thus, variables passed from the command line are all cache variables.

If the parameters are reset from the command line, it will output a new value, i.e., hello world:

cmake -S . -B build -DPARAM="hello world"

It can be seen that passing parameters with -D will FORCE the value of the cache variable.

Leave a Comment