The CMake Cache

Discover the CMake Cache, the mechanism for storing persistent user-configurable options, and learn how to create and manage cached variables.

Greg Filak
Updated

In the last lesson, we learned about variables in CMake. We saw that they are the primary way we storing and reuse information, like a project's version number or a file path. However, these "normal" variables have a limitation: they are transient. Every time you run cmake to configure your project, they are reset and recalculated from scratch.

This is usually what you want, but what about configuration that should persist? What if a developer wants to enable a specific feature, like building with tests enabled, and have that choice remembered for all future builds? Re-typing a command-line flag every single time would be incredibly annoying.

This is where the CMake Cache comes in. The cache is CMake's memory. It's a special set of variables that are stored on disk and persist between runs, allowing you to create truly configurable projects.

What is the CMake Cache?

When you successfully run the configure command (cmake ..), you may have noticed that CMake creates a file named CMakeCache.txt inside your build directory. This file is a simple key-value store that saves the values of cached variables.

Think of it like this:

  • Normal variables are like local variables inside a function. Once the function (the cmake run) finishes, they're gone.
  • Cached variables are like a .ini or .json configuration file. The program writes to it once, and then reads from it on every subsequent startup.

The cache's primary purpose is to store information that is either:

  1. Expensive to compute and rarely changes: Things like the path to the C++ compiler or the results of system checks. CMake figures these out once and then stores them in the cache so it doesn't have to re-do the work every time.
  2. User-configurable options: Settings that a user of your project might want to change, like BUILD_TESTS or USE_CUSTOM_FEATURE.

If you open CMakeCache.txt (though you should rarely edit it by hand), you'll see something like this:

# ... lots of internal variables ...

//The name of the CXX compiler
CMAKE_CXX_COMPILER:FILEPATH=D:/msys64/ucrt64/bin/c++.exe

//The CXX compiler identification
CMAKE_CXX_COMPILER_ID:STRING=GNU

# ... more variables ...

Each entry has a KEY:TYPE=VALUE format, along with a comment that serves as documentation. This file is the physical manifestation of CMake's memory.

Creating Cached Variables

So how do we create a variable that gets saved to this file? We use a special form of the set() command.

Using set() with the CACHE keyword

To create a cached variable, you provide the CACHE keyword to the set() command, along with a type and a documentation string. The syntax looks like this:

set(name value CACHE type "description")

Let's break down the arguments:

  • name: The name of the variable. By convention, user-configurable options are often in ALL_CAPS.
  • value: The default value for the variable. This is only used the first time CMake encounters this command, which we'll explain later in the lesson.
  • CACHE: The magic keyword that tells CMake this is a cached variable.
  • type: A type hint specifying what type of variable this is, which can be useful for GUI tools like cmake-gui. For example, if we set this to BOOL, which is the most common type in real-world use cases, a GUI might render this option as an ON/OFF checkbox. Other options include STRING for arbitrary text, PATH for a directory picker, FILEPATH for a file picker, and INTERNAL for a private variable that should be hidden from GUI tools.
  • description: A short, helpful description of what the variable is for. This is sometimes referred to as a docstring (documentation string)

A cached variable that configures whether CMake creates a build of our software that says goodbye to the user might look something like this, where some optional white space has been added:

set(
  SAY_GOODBYE OFF CACHE BOOL
  "Should the program say goodbye before exiting"
)

We'll see how to react to this option later in the lesson.

The option() Command

For boolean options (ON/OFF switches), there's a more convenient shorthand command called option():

option(name docstring value)

Using option() is preferred for boolean flags as it's more concise and clearly expresses your intent to create a toggleable feature.

Rewriting our SAY_GOODBYE variable to use option() instead of set() would look like this:

option(
  SAY_GOODBYE
  "Should the program say goodbye before exiting"
  OFF
)

A Practical Example

Let's update our GreeterApp component's CMakeLists.txt file (in the /app directory) with this SAY_GOODBYE option. If this variable is set to ON, we'll have CMake define a preprocessor macro, and we'll update our source code to check for it.

To set a preprocessor definition for a target, we use the target_compile_definitions() command. We want to do this only if SAY_GOODBYE is enabled, so we'll place this command in an if() block.

We'll explain both target_compile_definitions() and if() in detail later in the course, but a basic example would look like this:

app/CMakeLists.txt

cmake_minimum_required(VERSION 3.16)

option(
  SAY_GOODBYE
  "Should the program say goodbye before exiting"
  OFF
)

add_executable(GreeterApp src/main.cpp)
target_compile_features(GreeterApp PUBLIC cxx_std_20)
target_link_libraries(GreeterApp GreeterLib)

if(SAY_GOODBYE) 
  target_compile_definitions(GreeterApp PUBLIC SAY_GOODBYE) 
endif()

Finally, let's modify our C++ code to react to this definition:

app/src/main.cpp

#include <iostream>
#include <greeter/Greeter.h>

int main() {
  std::cout << get_greeting();
#ifdef SAY_GOODBYE
  std::cout << "\nGoodbye";
#endif
  return 0;
}

Our project is now configurable! By default, nothing has changed. If we build and run it, we'll just get the "Hello" message. But now we have a knob we can turn to add additional behavior.

Logging Updates Using message()

Remember, we can liberally scatter message() commands through our CMakeLists.txt files to keep track of what's going on, and to help others do the same.

If we think some update is relatively important, we might use the STATUS mode:

app/CMakeLists.txt

cmake_minimum_required(VERSION 3.16)

option(
  SAY_GOODBYE
  "Should the program say goodbye before exiting"
  OFF
)

add_executable(GreeterApp src/main.cpp)
target_compile_features(GreeterApp PUBLIC cxx_std_20)
target_link_libraries(GreeterApp GreeterLib)

if(SAY_GOODBYE)
  message(STATUS "Defining SAY_GOODBYE") 
  target_compile_definitions(GreeterApp PUBLIC SAY_GOODBYE)
endif()

If something is less important, we can use the VERBOSE, DEBUG, or TRACE modes to hide it by default, but still make it available for anyone who wants to increase their --log-level.

Modifying the Cache

There are two primary ways a user can change the value of a cached variable: from the command line, or using a GUI tool.

Command-Line with -D

The -D flag allows you to define a cached variable's value during the configure step. The flag sets a variable, and does not care if it already has a value.

Let's try building our project with our new option enabled. As usual, we'll run our command from the /build directory:

cmake -DSAY_GOODBYE=ON ..

When you run this, CMake configures the project. It sees our option() command, but because we provided a value for USE_GOODBYE on the command line, it uses ON instead of the default OFF. It then saves USE_GOODBYE=ON into the CMakeCache.txt file. Our output will look something like this:

-- Configuring Greeter Version: 1.0
-- Defining SAY_GOODBYE
-- Configuring done (0.8s)
-- Generating done (0.1s)
-- Build files have been written to: D:/Projects/cmake/build

Now, let's build our application:

cmake --build .

And let's run it it from the ./app directory, remembering to add .exe if you're on Windows:

./app/GreeterApp
Hello from the modular Greeter library!
Goodbye

Success! Our configuration change worked.

Updating Cached Values

Remember, with a cached variable, the choice is persistent. If you now run the configure command again without the -D flag, CMake will read the existing CMakeCache.txt, see that USE_GOODBYE is already set to ON, and continue to use that value:

cmake ..

If we've added a message() command when SAY_GOODBYE is ON, we'll see that reflected in our output:

-- Configuring Greeter Version: 1.0
-- Defining SAY_GOODBYE
-- Configuring done (0.1s)
-- Generating done (0.1s)
-- Build files have been written to: D:/Projects/cmake/build

Alternatively, we can just build our program and see that we're still using the cached value:

cmake --build .
./app/GreeterApp
Hello from the modular Greeter library!
Goodbye

To change a cached value, you simply run the configure step again, using the -D flag as before to provide a new value:

cmake -DSAY_GOODBYE=OFF ..
-- Configuring Greeter Version: 1.0
-- Configuring done (0.1s)
-- Generating done (0.1s)
-- Build files have been written to: D:/Projects/cmake/build

Deleting Cached Values

We can unset a specific variable from the cache, causing it to adopt its default value, using the -U option:

cmake -USAY_GOODBYE ..

We can also remove all cached variables by manually deleting the CMakeLists.txt file or, if you have CMake 3.24 or later installed, by using the --fresh option:

cmake --fresh ..

You may notice this option takes a little longer to run. This is because CMake has to reexamine our environment more thoroughly to recalculate all of the previously cached values, such as what compilers we have installed and where they are located.

Using a GUI (cmake-gui)

The -D flag is great for scripting, but it's not very user-friendly. This is where GUI tools shine. CMake comes with such a GUI tool by default.

If we run cmake-gui and open our /build directory within the tool, we'll see our SAY_GOODBYE option presented visually, alongside some others that are provided by default. Hovering over the option will also display the description we set as a tooltip:

cmake-gui-cache.png

A less technical user can now get a visual overview of the options they have, and simply check or uncheck the box. They can create a build without being familiar with the command line, or even familiar with CMake.

This is the power of adding types and docstrings to your variables: you are enabling GUI interfaces to be created for your build.

The Cache Precedence Rule and FORCE

There is a fundamental rule you must understand about the cache:

Once a variable is in the cache, its value will not be changed by a normal set() or option() command in the CMakeLists.txt file.

The cached value always takes precedence over the default value in the script. This is what makes the user's choice take precedence over the default values we may have set in the CMakeLists.txt file.

The -D flag is one way to override the cached value. But what if you, the project author, need to programmatically override a cached value? You can do this by adding the FORCE keyword to your set() command.

set(MY_VAR "New Value" CACHE STRING "Some docs" FORCE)

This command will overwrite whatever value for MY_VAR is currently in the cache.

Use FORCE with Caution

Using FORCE is generally considered bad practice for user-facing options. It's a "hammer" that stomps on the user's configuration without their knowledge, which can be incredibly confusing. Imagine setting an option with -D, only to have the CMakeLists.txt silently change it back.

FORCE should be reserved for situations where the build script must enforce a value in a specific situation, perhaps based on the detected operating system or other internal logic.

Even then it should be used sparingly and be well-documented, perhaps with message() commands using the WARNING mode. It might look something like this:

set(say_goodbye_doc "Should the program say goodbye")

if(WIN32 AND SAY_GOODBYE)
  message(
    WARNING
    "SAY_GOODBYE not supported on Windows - forcing it OFF"
  )
  set(SAY_GOODBYE OFF CACHE BOOL "${say_goodbye_doc}" FORCE)
endif()

Just throwing a FATAL_ERROR on an invalid configuration is usually more appropriate than forcing a different value than what was requested:

if(WIN32 AND SAY_GOODBYE)
  message(
    FATAL_ERROR
    "SAY_GOODBYE not supported on Windows"
  )
endif()
cmake -DSAY_GOODBYE=ON ..
-- Configuring Greeter Version: 1.0
CMake Error at app/CMakeLists.txt:17 (message):
  SAY_GOODBYE not supported on Windows

-- Configuring incomplete, errors occurred!

Summary

The CMake Cache is the mechanism that elevates CMake from a simple script runner to a powerful, configurable build framework. It provides the memory that makes user choices stick.

  • The Cache File: CMakeCache.txt is a file in your build directory that stores persistent key-value pairs.
  • Creating Cached Variables: Use set(var val CACHE type "doc") for general variables, and the option(var "doc" val) shorthand for boolean ON/OFF toggles.
  • Persistence: Once a variable is in the cache, its value is used for all subsequent runs, overriding the defaults in your CMakeLists.txt.
  • User Configuration: Users can change cached values from the command line (with -D) or with a GUI like cmake-gui. GUI tools can use the type and docstring information you provide to create a user-friendly interface.
  • Forcing a Value: The FORCE keyword allows a script to override a cached value, but it should be used with great care.

You now understand the two fundamental types of variables in CMake: normal variables for script logic and cached variables for persistent configuration.

Next Lesson
Lesson 15 of 18

CMake Commands, Arguments, and Lists

Exploring CMake's command syntax and its list data type, including how to create, manipulate, and use lists in your projects.

Have a question about this lesson?
Answers are generated by AI models and may not be accurate