User Presets and Inheritance

Learn to structure your CMake presets with inheritance and customize them for your local environment using CMakeUserPresets.json.

Greg Filak
Published

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:

  1. 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.
  2. 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.

Creating Hidden Base 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. Use CMakeUserPresets.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.
Next Lesson
Lesson 39 of 39

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.

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