The CMake Cache
Discover the CMake Cache, the mechanism for storing persistent user-configurable options, and learn how to create and manage cached variables.
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:
- 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.
- User-configurable options: Settings that a user of your project might want to change, like
BUILD_TESTS
orUSE_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 inALL_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 likecmake-gui
. For example, if we set this toBOOL
, which is the most common type in real-world use cases, a GUI might render this option as an ON/OFF checkbox. Other options includeSTRING
for arbitrary text,PATH
for a directory picker,FILEPATH
for a file picker, andINTERNAL
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:
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 theoption(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.
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.