Comprehensive Guide to CMake Variable Syntax and Scoping: From Basics to Advanced Applications

Nov 22, 2025 · Programming · 13 views · 7.8

Keywords: CMake variables | scoping | cache variables | syntax | debugging

Abstract: This article provides an in-depth exploration of CMake's complete variable syntax system, covering string and list operations, detailed analysis of variable scoping mechanisms (including normal variables, cache variables, and environment variables), examination of common pitfalls in variable usage and debugging methods, and introduction of advanced features like generator expressions and recursive substitution. Through rich code examples and practical scenario analysis, it helps developers master the correct usage of CMake variables comprehensively.

Basic CMake Variable Syntax

CMake provides multiple ways to set and manipulate variables, with the set() command being the most fundamental method for variable assignment. For string variables, the following syntax can be used:

set(MyString "Some Text")
set(MyStringWithVar "Some other Text: ${MyString}")
set(MyStringWithQuot "Some quote: \"${MyStringWithVar}\"")

In addition to the set() command, the string() command can be used for string operations:

string(APPEND MyStringWithContent " ${MyString}")

For list operations, CMake provides two main approaches. Using the set() command:

set(MyList "a" "b" "c")
set(MyList ${MyList} "d")

It's more recommended to use the dedicated list() command:

list(APPEND MyList "a" "b" "c")
list(APPEND MyList "d")

When handling file name lists, special attention should be paid to space handling:

set(MySourcesList "File.name" "File with Space.name")
list(APPEND MySourcesList "File.name" "File with Space.name")
add_executable(MyExeTarget ${MySourcesList})

Variable Scoping Mechanism

CMake's variable scoping system is crucial for understanding variable behavior. Normal variables are visible in the CMakeLists.txt file where they are set and to everything called from there via add_subdirectory(), include(), macro(), and function().

The add_subdirectory() and function() commands create new scopes, where variables set within these scopes are only visible within that scope. They make a copy of all normal variables from the parent scope that called them. To modify an existing variable in the parent scope from within a subdirectory or function, the set(... PARENT_SCOPE) syntax can be used:

function(xyz _resultVar)
    set(${_resultVar} 1 PARENT_SCOPE)
endfunction()

In contrast, everything set in include() or macro() scripts directly modifies variables in the scope from which they are called.

Global Cache Variables

CMake's cache variable system provides persistent storage mechanism. If no normal variable with the given name is defined in the current scope, CMake will look for a matching cache entry.

Cache values are stored in the CMakeCache.txt file in your binary output directory. These values can be modified in CMake's GUI application before generation. Compared to normal variables, cache variables have a type and a docstring. The typical syntax for setting cache variables:

set(MY_CACHE_VARIABLE "default_value" CACHE STRING "Description of the variable")

In a CMake script, existing cache entries can only be changed using the set(... CACHE ... FORCE) syntax. Command-line syntax for setting cache entries includes: cmake -D var:type=value, cmake -D var=value, or using cmake -C CMakeInitialCache.cmake.

Entries in the cache can be unset using unset(... CACHE). For non-persistent global variables, it's recommended to use set_property(GLOBAL PROPERTY ...) and set_property(GLOBAL APPEND PROPERTY ...) syntax.

Variable Pitfalls and Debugging

Understanding CMake variable pitfalls is essential for avoiding common errors:

It's recommended to always use quotation marks, with the only exception being when you want to give a list as a list. Generally, prefer the list() command for handling lists.

When debugging variables, the following methods can be used:

Special Syntax and Advanced Features

Environment variables are read and written using special syntax: $ENV{...} for reading, set(ENV{...} ...) for setting.

Generator expressions $<...> are only evaluated when CMake's generator writes the make environment, in contrast to normal variables that are replaced in-place by the parser. These are particularly useful in compiler/linker command lines and multi-configuration environments.

Using ${${...}} allows referencing content through variable names stored in variables, often used when passing variable names as function/macro parameters.

In the if() command, variables can be directly checked for true/false values without needing the enclosing ${...}. True if the constant is 1, ON, YES, TRUE, Y, or a non-zero number; false if the constant is 0, OFF, NO, FALSE, N, IGNORE, NOTFOUND, the empty string, or ends in the suffix -NOTFOUND.

Recursive substitution allows constructing variable names using variables. After CMake substitutes the variables, it checks again if the result is a variable itself. This powerful feature is used in CMake itself, for example as a sort of template: set(CMAKE_${lang}_COMPILER ...).

Policy CMP0054 introduced in CMake 3.1 addressed some confusion in if() commands. It's recommended to always set cmake_policy(SET CMP0054 NEW) to only interpret if() arguments as variables or keywords when unquoted.

The option() command is mainly for cached strings that can only be ON or OFF and allows some special handling like dependencies. Note that the value given to option is really only the initial value (transferred once to the cache during the first configuration step) and is afterwards meant to be changed by the user through CMake's GUI.

Variable Definition Checking and Command Line Arguments

Checking if a variable is defined in CMake requires using the DEFINED keyword. Unlike the C preprocessor, CMake variables don't have a defined-but-unassigned state; they either have a value or are undefined. The value can be empty, which might be what you're looking for.

The command-line syntax for adding a cache entry is -DVAR=$value, where $value may be empty. The following example demonstrates how to check if a variable is defined:

PROJECT(cmake_defined)
IF(DEFINED variable)
    MESSAGE(STATUS "variable is defined")
ELSE()
    MESSAGE(STATUS "variable is not defined")
ENDIF()

It's important to note that different versions of CMake may have subtle differences in variable usage checking. In some cases, setting empty value variables using -Dvariable= might produce unused variable warnings in older versions but not in newer ones.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.