Standardizing Build Workflows with CMake Presets
Eliminate "it works on my machine" build errors by moving CMake configuration flags from shell scripts into version-controlled CMakePresets.json files for reproducible CI and local builds.
06 Feb 2026, 09:36 UTC

The Problem: Command-Line Drift
In many C++ projects, the exact set of flags used to configure a build is often trapped in a developer's shell history or a fragile CI script. When a new engineer joins the team, they must manually reconstruct a long string of -D flags to match the production environment. This "command-line drift" leads to "it works on my machine" bugs where local builds differ from CI builds in critical ways, such as optimization levels or sanitizer settings.
The solution is CMake Presets. Introduced in CMake 3.19, presets move the configuration logic from the command line into a version-controlled JSON file, ensuring every environment uses the same generator, toolchain, and cache variables.
Prerequisites
- CMake Version: 3.21 or higher (required for preset inheritance).
- Build Directory: A clean project root with a valid
CMakeLists.txt. - Toolchain: Any installed compiler (GCC, Clang, or MSVC) that matches your intended target.
Implementing a Standardized Preset Hierarchy
The most effective engineering approach is to use a base preset for common settings and specialized presets for different environments (e.g., Local vs. CI). This prevents duplication and ensures that CI always inherits the core project logic.
1. Create the CMakePresets.json File
Create a CMakePresets.json file in your project root. Use forward slashes for all paths to ensure compatibility between Windows and Unix-like systems.
{
"version": 3,
"configurePresets": [
{
"name": "base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
"PROJECT_ENABLE_LOGGING": "ON"
}
},
{
"name": "dev-linux",
"inherits": "base",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "ci-linux",
"inherits": "base",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_CXX_FLAGS": "-Werror -Wall"
}
}
],
"buildPresets": [
{
"name": "dev-linux",
"configurePreset": "dev-linux"
},
{
"name": "ci-linux",
"configurePreset": "ci-linux"
}
],
"testPresets": [
{
"name": "dev-linux",
"configurePreset": "dev-linux",
"output": {
"outputOnFailure": true
}
}
]
}
2. Managing Local Overrides
Do not add machine-specific paths (like C:/Users/Name/bin) to CMakePresets.json, as this breaks the build for others. Instead, create a CMakeUserPresets.json file. This file is ignored by version control and can inherit from the main presets to add local tweaks.
Executing the Workflow
Run these commands from the project root. All commands require the permissions of the user owning the source directory.
Step 1: Configuration
List available presets to verify the file is parsed correctly:
cmake --list-presetsConfigure the project using a specific preset:
cmake --preset dev-linuxRisk: If you switch presets (e.g., from dev-linux to ci-linux) without deleting the build folder, some cache variables may persist, leading to a "hybrid" configuration. Always use a fresh build directory when switching presets.
Step 2: Building
Build the project using the associated build preset:
cmake --build --preset dev-linux
Step 3: Testing
Execute tests defined in the test preset:
ctest --preset dev-linux
Verification and Diagnostics
| Check | Method | Expected Result |
|---|---|---|
| Preset Recognition | cmake --list-presets |
Names dev-linux and ci-linux appear in output. |
| Cache Application | Inspect build/dev-linux/CMakeCache.txt |
PROJECT_ENABLE_LOGGING is set to ON. |
| Build Integrity | cmake --build --preset dev-linux |
Compilation completes without missing flags. |
Recovery and Rollback
Because presets primarily automate the generation of the build tree, "rolling back" a preset change involves clearing the state of the build directory.
- To reset a configuration: Delete the directory specified in
binaryDir(e.g.,rm -rf build/dev-linux). - To revert preset logic: Revert the
CMakePresets.jsonfile via Git and re-run the configure step.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.