User Presets and Inheritance
Learn to structure your CMake presets with inheritance and customize them for your local environment using CMakeUserPresets.json
.
In the , we took our first step into the world of CMake Presets. We saw how a CMakePresets.json
file can transform a long, cumbersome command line into a single, memorable name, making our build workflows easier to use and share.
This is a great start, but as a project grows, so does its configuration complexity. You'll need presets for different operating systems, compilers, build types, and architectures. A simple, flat list of presets quickly becomes a repetitive, unmaintainable mess. Furthermore, how do you handle a developer's personal preferences without cluttering the shared, team-wide configuration?
This lesson builds on the fundamentals by introducing two powerful concepts: user presets for personal customization and inheritance for creating a scalable, layered preset architecture.
As a reminder, the example CMakePresets.json
from the previous lesson is provided below:
CMakePresets.json
{
"version": 3,
"configurePresets": [{
"name": "windows-x64-debug",
"displayName": "Windows x64 Debug",
"description": "Build for 64-bit Windows with MinGW (Debug)",
"binaryDir": "${sourceDir}/build/windows-x64-debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_TOOLCHAIN_FILE":
"${sourceDir}/toolchains/mingw-windows-x64.cmake"
}
}],
"buildPresets": [{
"name": "windows-x64-debug",
"configurePreset": "windows-x64-debug",
"displayName": "Build Windows x64 (Debug)"
}]
}
User Presets
This system works perfectly, but what if a developer on the team wants to create a preset that isn't likely to be useful to the wider team?
Or what if they have a personal preference that doesn't match what is in the shared presets that everyone else is using? Maybe they want to use a different generator, or they want to output the build in a different directory.
This is solved by a CMake recognizing two different preset files:
CMakePresets.json
: This file is for shared, team-wide configurations. It should be maintained and shared across the whole team and checked into your version control system (like Git, which we cover later). It defines the canonical ways to build your project that should work for everyone.CMakeUserPresets.json
: This file is for personal, machine-specific presets. Each developer can create their own to customize the build for their local environment.
A Practical Example
Let's imagine we prefer our build outputs to be placed in an /output
directory instead of /build
. This is just a personal preference - not something we want to force on the entire team. To implement this, we'd create a CMakeUserPresets.json
file, and set up our values just like we did in the shared CMakePresets.json
. For example:
CMakeUserPresets.json
{
"version": 3,
"configurePresets": [{
"name": "windows-x64-debug-out",
"displayName": "Windows x64 Debug (Out)",
"description": "Build for 64-bit Windows with MinGW (Debug)",
"binaryDir": "${sourceDir}/out/windows-x64-debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_TOOLCHAIN_FILE":
"${sourceDir}/toolchains/mingw-windows-x64.cmake"
}
}]
}
Now, when we list our presets, we will see both the shared and personal preset. We can use our personal preset without affecting the rest of the team:
cmake --list-presets
Available configure presets:
"windows-x64-debug" - Windows x64 Debug
"windows-x64-debug-out" - Windows x64 Debug (Out)
Preset Inheritance
We probably noticed something was off about the previous example. We only wanted to override one setting (binaryDir
) of a preset that already exists (windows-x64-debug
in the shared CMakePresets.json
file).
Duplicating the entire preset to change one setting isn't ideal. CMake saves us from this with inheritance. Just like a C++ class can inherit the values of some other class, a CMake preset can inherit the values of some other preset. We set this up using the inherits
key, passing the name of the base preset we want to inherit from:
CMakeUserPresets.json
{
"version": 3,
"configurePresets": [{
"name": "windows-x64-debug-out",
"displayName": "Windows x64 Debug (Out)",
"inherits": "windows-x64-debug",
"binaryDir": "${sourceDir}/out/windows-x64-debug"
}]
}
Now, we only need to specify the things we want to override - displayName
and binaryDir
in this example. Every other value will be inherited from the shared windows-x64-debug
preset. When the shared preset gets updated with new or changed values, that update will propagate to our personal user preset too.
The Need for Structure
Inheritance isn't just for user presets; it's the key to creating a scalable and maintainable CMakePresets.json
for the whole team.
As a project grows, the number of build configurations can explode. You might need:
- Windows, Linux, and macOS builds.
- Debug and Release configurations for each.
- Maybe a special "Address Sanitizer" build for debugging memory errors.
A flat list of presets for every combination quickly becomes a maintenance nightmare, full of duplicated JSON.
CMakePresets.json
{
"version": 3,
"configurePresets": [{
"name": "windows-debug",
"binaryDir": "${sourceDir}/build/win-dbg",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_TOOLCHAIN_FILE":
"${sourceDir}/toolchains/mingw-windows-x64.cmake"
}
}, {
"name": "windows-release",
"binaryDir": "${sourceDir}/build/win-rel",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_TOOLCHAIN_FILE":
"${sourceDir}/toolchains/mingw-windows-x64.cmake"
}
}, {
"name": "linux-debug",
"binaryDir": "${sourceDir}/build/linux-dbg",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_TOOLCHAIN_FILE":
"${sourceDir}/toolchains/linux.cmake"
}
}, {
"name": "linux-release",
"binaryDir": "${sourceDir}/build/linux-rel",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_TOOLCHAIN_FILE":
"${sourceDir}/toolchains/linux.cmake"
}
}]
}
The solution is to think in layers. We can create abstract "base" presets that define a common set of options, and then compose them together to create our final, user-visible presets.
A common convention is to create abstract presets that are not meant to be used directly by a developer. We can signal this by setting the "hidden"
property to true
. By convention, hidden presets also have their names prefixed with an underscore (e.g., "_base"
) but this is not strictly necessary.
Let's start refactoring our "bad" example by creating a base preset that defines a standard directory structure for all our builds.
CMakePresets.json
{
"version": 3,
"configurePresets": [{
"name": "_base",
"hidden": true,
"binaryDir": "${sourceDir}/build/${presetName}"
}]
}
The ${presetName}
variable automatically expanded by CMake to the name
of the final, user-visible preset. So, when we create a windows-debug
preset that inherits from this, the binaryDir
will automatically become ".../build/windows-debug"
.
CMakePresets.json
{
"version": 3,
"configurePresets": [{
"name": "_base",
"hidden": true,
"binaryDir": "${sourceDir}/build/${presetName}"
}, {
"name": "windows-debug",
"inherits": "_base",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE":
"${sourceDir}/toolchains/mingw-windows-x64.cmake"
}
}]
}
This single base preset has already eliminated the binaryDir
duplication from all our final presets. If we list our presets, our hidden
preset should not be listed:
cmake --list-presets
Available configure presets:
"windows-debug"
Layering with Multi-Inheritance
Inheritance becomes even more useful when we realize that the inherits
key can take an array of strings. This allows a preset to inherit from multiple parents.
inherits: ["_parent1", "_parent2", "_parent3"]
Settings from presets that appear later in the array will override settings from earlier ones. This lets us build up our final configuration by layering a series of base presets.
Let's complete our refactor. We'll create base presets for the OS and for the build type, and then compose them.
CMakePresets.json
{
"version": 3,
"configurePresets": [{
"name": "_base",
"hidden": true,
"binaryDir": "${sourceDir}/build/${presetName}"
}, {
"name": "_windows",
"hidden": true,
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE":
"${sourceDir}/toolchains/mingw-windows-x64.cmake"
}
}, {
"name": "_linux",
"hidden": true,
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE":
"${sourceDir}/toolchains/linux.cmake"
}
}, {
"name": "_debug",
"hidden": true,
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
}, {
"name": "_release",
"hidden": true,
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}, {
"name": "windows-debug",
"inherits": ["_base", "_windows", "_debug"]
}, {
"name": "windows-release",
"inherits": ["_base", "_windows", "_release"]
}, {
"name": "linux-debug",
"inherits": ["_base", "_linux", "_debug"]
}, {
"name": "linux-release",
"inherits": ["_base", "_linux", "_release"]
}]
}
Each piece of configuration (binaryDir
, toolchain, build type) is now defined in exactly one place.
The final, user-visible presets are just simple one-liners that compose these building blocks
cmake --list-presets
Available configure presets:
"windows-debug"
"windows-release"
"linux-debug"
"linux-release"
Defining a new configuration is now trivial:
{
"name": "windows-asan",
"inherits": ["_base", "_windows", "_debug"],
"cacheVariables": {
"ENABLE_ASAN": "ON"
}
}
This ENABLE_ASAN
variable enables AddressSanitizer, which we'll cover in more detail later in the course.
User Presets as the Final Layer
This layered approach fits perfectly with the CMakeUserPresets.json
file we saw earlier. The shared CMakePresets.json
defines the team-wide standard configurations. The user presets file acts as the final, outermost layer for personal, machine-specific overrides.
This is the standard solution to a very common problem: how to handle paths to external tools that might be installed in different locations on different machines.
Let's imagine we were using vcpkg, and our build expects the location of vcpkg toolchain file to be provided in a variable called VCPKG_TOOLCHAIN_FILE
:
toolchains/mingw-windows-x64.cmake
# 1. Set the target system
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
# 2. Specify the cross-compilers
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
set(CMAKE_C_FLAGS_INIT "-static -static-libgcc")
set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
set(CMAKE_CXX_FLAGS_INIT
"-static -static-libgcc -static-libstdc++")
set(CMAKE_EXE_LINKER_FLAGS_INIT
"-static-libgcc -static-libstdc++")
# 3. Configure search for external dependencies
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(MINGW TRUE)
set(VCPKG_TARGET_TRIPLET x64-mingw-static)
include("${VCPKG_TOOLCHAIN_FILE}")
The team's standard convention might be to install vcpkg in C:/vcpkg
, and the shared CMakePresets.json
could reflect this:
CMakePresets.json
{
"version": 3,
"configurePresets": [{
"name": "_base",
"hidden": true,
"binaryDir": "${sourceDir}/build/${presetName}"
"cacheVariables": {
"VCPKG_TOOLCHAIN_FILE":
"C:/vcpkg/scripts/buildsystems/vcpkg.cmake"
}
}]
}
However, on our machine, we've installed vcpkg to D:/vcpkg
. Remember, we can still override preset values on the command line as needed:
cmake \
--preset=windows-vcpkg-debug \
-DVCPKG_TOOLCHAIN_FILE= \
"D:/vcpkg/scripts/buildsystems/vcpkg.cmake"
But always remembering to do this can get annoying, so we'd prefer to use a preset instead. Instead of modifying the shared CMakePresets.json
and causing issues for the rest of the team, we can create a personal preset in CMakeUserPresets.json
:
CMakeUserPresets.json
{
"version": 3,
"configurePresets": [{
"name": "user-windows-debug",
"inherits": ["windows-debug"],
"cacheVariables": {
"VCPKG_TOOLCHAIN_FILE":
"D:/vcpkg/scripts/buildsystems/vcpkg.cmake"
}
}]
}
By using inherits
, we keep all the other settings from the shared preset and only change the one thing we needs to: the path to the toolchain file.
Hardcoded Paths are Still not Ideal
While this pattern works, hardcoding paths, even in a user preset, is not the most robust solution. Creating a load of new presets to handle variations in a single variable is quite a lot of work. This is particularly true when working with build servers, which we'll cover later.
A more flexible and standard approach is to use environment variables. In the next lesson, we'll see how to use the $env{...}
syntax in our presets to read paths from the system environment, making our configurations even simpler.
Summary
In this lesson, we expanded our understanding of CMake Presets from a simple shortcuts to an architectural tool for managing build complexity.
- Shared vs. User Presets: Use
CMakePresets.json
for team-wide configurations that go into version control. UseCMakeUserPresets.json
for personal, machine-specific overrides. - Inheritance is Key: Use the
inherits
key to avoid duplication. This is the fundamental mechanism for creating maintainable presets. - Layered Architecture: Structure your presets by creating hidden base presets (e.g.,
_base
,_windows
,_debug
) that are responsible for small, discrete blocks of configuration. Then, compose them into final, user-visible presets using multi-inheritance. - User Presets as the Final Override: A user can create a personal preset just for themselves. User presets can inherit from shared presets.
Environment Variables and Conditional Presets
Learn to make your CMake presets portable by using environment variables and conditional logic to adapt to different machine configurations.