A TL;DR about CMake Presets
Prolog
In my never-ending quest to master CMake, I'm coming across more and more handy tools for project setup and management. Trying to maintain a good starter template for my projects and having developed a few opinions on how I want my projects to work, I finally looked at "CMake Presets" I had already heard much about.
One of those opinions is that I want CMake to do as much as possible without relying on other tools to minimize what is needed for a project setup. If I have a setting that CMake already supports through an option, I define it, reducing what is needed for execution further.
With this in mind, I always thought I wouldn't really need CMake presets, but it turned out some settings I always configure were missing.
Expectations
What will I cover in this article? It won't be every setting with every property, and in general, the official CMake documentation about presets is quite extensive. If this is what you're looking for, you can follow the link.
Even though the official docs are great, I prefer a different format when it comes to presentation and personal taste, and that's why this article exists.
Besides, some of the settings that can be configured with presets I would rather see directly in a CMake file. This will also be the general approach, what can live in a CMake file will, everything else goes to a preset.
What are CMake Presets?
With time and more knowledge about CMake, certain project settings appear again and again. For me, I found a set of settings I tend to use in every project. I always set up Ninja on Windows and Linux, Xcode on macOS, define the same build directories, or even run the same CTest commands.
Presets enable defining exactly those standard or repeated project settings for CMake and give the option to easily override them per project if needed — things like the build directory, generator, or target architecture.
I can then take this file, throw it in a CMake project, and be ready to go. Especially considering tooling support, where many IDEs like CLion detect those presets and automatically set up a project.
Create a preset
A preset is, at its core, a CMakePresets.json
in the root of the
project. It gets checked into source control and defines
project-wide settings. The minimum requirement to be a valid preset file is
the version
field that defines the version of the used JSON
schema.
{
"version": 6
}
CMakePresets.json
with schema version 6.Version 6 is not the latest, that be version 8. I picked version 6 as it is the latest CLion supported version of CMake presets.
One benefit of using version 8 is the support of the
$schema
field, which defines a URL to the JSON schema to validate
the file contents against.
{
"version": 8,
"$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json"
}
The URL for the JSON schema is from the official CMake documentation.
Another thing I see often used in projects is the
cmakeMinimumRequired
property, though I'd rather define this
version through CMake itself, e.g., in the root CMake file of the project.
# CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
Basic configuration
The base of most CMake presets is the
configurePresets
property, which defines configurations that are applied to the
configuration phase, like defining the build directory or the
used generator.
The minimum required setting is a name for the preset, but a display name is recommended to be shown when interacting with the preset through a GUI or the CMake CLI, increasing usability.
{
"version": 6,
"configurePresets": [
{
"name": "debug",
"displayName": "Debug",
"generator": "Ninja",
"binaryDir": "build/debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
}
]
}
CMakePresets.json
.
This example of a configuration preset defines a debug preset. Using
Ninja
as the generator, the build directory will be build/debug
and the
CMake build type Debug
.
Defining a release preset follows the same pattern.
{
"version": 6,
"configurePresets": [
{ /* ... */ },
{
"name": "release",
"displayName": "Release",
"generator": "Ninja",
"binaryDir": "build/release",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}
]
}
CMakePresets.json
.
Run configuration preset
With at least one configuration presets defined, it's time to run it. To view
all defined presets, run cmake --list-presets
.
$ cmake --list-presets
Available configure presets:
"debug" - Debug
"release" - Release
$
is used to show a command will be entered.
Run one of those configuration presets with cmake --preset
.
$ cmake --preset debug
User presets
Working on a project with CMake presets, there can always be the case where a
user needs or wants to define their own presets for any
reason. This can be done through the CMakeUserPresets.json
file.
This file has the same structure as the CMakePresets.json
to
override or extend any options and should always be
excluded from any source control — I usually add this
to my global .gitignore
file.
For example, defining a release build with debug information as a user preset.
{
"version": 6,
"configurePresets": [
{
"name": "rel-with-deb-info",
"displayName": "Release with Debug information",
"generator": "Ninja",
"binaryDir": "build/relwithdebinfo",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
}
}
]
}
CMakeUserPresets.json
Conditional configuration
A very handy feature, especially when it comes to multi-platform configurations, are conditional configurations. With this, it is possible to define when a preset is available, for example, to configure Xcode as generator only on macOS.
{
"version": 6,
"configurePresets": [
{ /* ... */ },
{
"name": "xcode-debug",
"displayName": "Debug (Xcode)",
"generator": "Xcode",
"binaryDir": "build/xcode-debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Darwin"
}
}
]
}
CMakePresets.json
The condition reads as: compare two values (equals
), the
left-hand string (lhs
) is the variable
hostSystemName
, the right-hand string (rhs
) is
"Darwin" (aka. macOS).
There are many different options for conditionals; different types, multiple conditions to match any or all of; best taken from the official CMake documentation about preset conditions.
Inherit settings
Any preset can inherit another to override or add to it. It is common to
define a base preset, hide it with the hidden
property, and
derive specific presets from it. Another typical use case is to have a local
and a derived continuous integration (CI) preset.
With this, let's first create a common, hidden base preset using Ninja as a generator.
{
"version": 6,
"configurePresets": [
{
"name": "common",
"hidden": true,
"generator": "Ninja",
"binaryDir": "build/${presetName}"
}
]
}
The binary directory is predefined, using the variable
presetName
to set the directory based on the derived preset. Via
inherits
the base preset will be inherited by name.
{
"version": 6,
"configurePresets": [
{
"name": "common",
"hidden": true,
"generator": "Ninja",
"binaryDir": "build/${presetName}"
},
{
"name": "debug",
"displayName": "Debug",
"inherits": "common",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
}
]
}
Common variable setup
Even though I prefer to set as much as possible from inside a CMake file, it can come in very handy to define some cache variables through a preset. One of those is to run a project against multiple compilers, e.g., for CI.
Using inheritance, a CI preset can be derived from an already existing preset. Given the following base preset:
{
"version": 6,
"configurePresets": [
{
"name": "debug",
"displayName": "Debug",
"generator": "Ninja",
"binaryDir": "build/debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
}
]
}
Setting the compiler as a cache variable for CI to create different configuration scenarios.
{
"version": 6,
"configurePresets": [
{ /* Base preset ... */ },
{
"name": "debug-ci-gcc",
"inherits": "debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_C_COMPILER": "gcc",
"CMAKE_CXX_COMPILER": "g++"
}
},
{
"name": "debug-ci-clang",
"inherits": "debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_C_COMPILER": "clang-cl",
"CMAKE_CXX_COMPILER": "clang-cl"
}
}
]
}
Build preset
With a properly defined configuration step, it is just a small jump to also define a build preset. As the name implies, build presets are used for CMake's build phase and are directly related to a configuration preset, supporting many of the same properties.
A great advantage of using build presets is that they can predetermine what targets are built and when. For example, only building tests, everything for a debug build, or just the main target for a release, all while having a simple, explorable* command.
Let's take a debug configuration preset and create a build preset based on it.
All build presets go into an array under the property
buildPresets
.
{
"version": 6,
"configurePresets": [
{ /* ... */ },
],
"buildPresets": [
{
"name": "app-debug",
"displayName": "App Debug Build",
"configurePreset": "debug",
"configuration": "Debug"
}
]
}
To view all defined build presets, run
cmake --build --list-presets
.
$ cmake --build --list-presets
Available build presets:
"app-debug"
The build preset can then be used to build the project using the defined configure preset.
$ cmake --build --preset app-debug
By default, this will build all the defined targets. This can
be changed via the targets
property, defining what targets
specifically to build. For example, only building the main target without
tests for a release.
{
"version": 6,
"configurePresets": [
{ /* ... */ },
],
"buildPresets": [
{ /* ... */ },
{
"name": "app-release",
"displayName": "App Release Build",
"configurePreset": "release",
"configuration": "Release",
"targets": ["App"]
}
]
}
Test presets
When using
CTest, presets can be used similarly to build presets. Any defined test preset can
be listed via ctest --list-preset
, and run with
ctest --preset
, using the ctest
CLI instead of
cmake
.
Using test presets gives the option to execute all or a predefined set of tests, define test fixtures, change environments, set different output options, and more. Nevertheless, I try to define possible or sensible defaults directly in a CMake file for CTest.
Same as build presets, test presets are related to a configuration preset and
defined under testPresets
.
{
"version": 6,
"configurePresets": [ /* ... */ ],
"buildPresets": [ /* ... */ ],
"testPresets": [
{
"name": "test-all",
"displayName": "Test All",
"configurePreset": "debug"
}
]
}
All the CTest CLI options can also be defined through this preset, as well as filtering for specific tests.
{
"version": 6,
"configurePresets": [ /* ... */ ],
"buildPresets": [ /* ... */ ],
"testPresets": [
{
"name": "test-all",
"displayName": "Test All",
"configurePreset": "debug",
"output": {
"outputOnFailure": true
},
"execution": {
"stopOnFailure": true
},
"filter": {
"include": {
"name": "SomeTestName"
}
}
}
]
}
Full example
Here it goes, a full CMakePresets.json
example with debug and
release builds, giving a quick overview of the mentioned features of CMake
presets.
{
"version": 6,
"configurePresets": [
{
"name": "debug",
"displayName": "Debug",
"generator": "Ninja",
"binaryDir": "build/debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "release",
"displayName": "Release",
"generator": "Ninja",
"binaryDir": "build/release",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "debug-ci-gcc",
"inherits": "debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_C_COMPILER": "gcc",
"CMAKE_CXX_COMPILER": "g++"
}
},
{
"name": "xcode-debug",
"displayName": "Debug (Xcode)",
"generator": "Xcode",
"binaryDir": "build/xcode-debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Darwin"
}
}
],
"buildPresets": [
{
"name": "app-debug",
"displayName": "App Debug Build",
"configurePreset": "debug",
"configuration": "Debug"
},
{
"name": "app-release",
"displayName": "App Release Build",
"configurePreset": "release",
"configuration": "Release",
"targets": ["App"]
}
],
"testPresets": [
{
"name": "test-all",
"displayName": "Test All",
"configurePreset": "debug",
"output": {
"outputOnFailure": true
},
"execution": {
"stopOnFailure": true
}
}
]
}
Epilogue
There is much more to CMake presets than I showed here, but as said in the beginning, I did not try to replicate the great official CMake documentation about presets and rather give a ready-to-start overview.
To see CMake presets on a real project, my GUI starter template with SDL2 and Dear ImGUI has a set of CMake presets defined, including package presets and workflow presets.
Until then 👋🏻