Organizing build commands in cmake with profiles


When dealing with a C/C++ project that utilizes cmake one will sooner or later need to organize the available build options into profiles than enable multiple features in a convenient manners. Two of the ways to achieve that is the use of cmake cache files and cmake presets.

Let’s start with setting a common ground for this test using the vtk repository:

git clone https://gitlab.kitware.com/vtk/vtk
cd vtk/
git switch -d v9.1.0
git submodule init
git submodule update --recursive

VTK has already a number of documented build options on top of the ones that cmake provides for every project.

Let’s say we want to build VTK with some optional flags and enable caching and the generation of compile commands for this build.

cmake -DVTK_USE_MPI=ON -DVTK_BUILD_TESTING=ON  -DVTK_DEBUG_LEAKS=ON -DVTK_WRAP_PYTHON=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache

Quite a long command ! How do we keep track of the command flags used ? Keep looking at the history of our commands and hope that it is still there ? Have a untracked file BUILD.txt and record the command in there ? What about documenting our choices to our future selves? I admit to using all of the above in the past but thankfully there is a much better way to do it. Actually there are at least two of them!

CMake cache files

Consider the following cache file flags.cmake:

# common cmake commands
set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE BOOL "Generate compile_commands.json")
set(CMAKE_C_COMPILER_LAUNCHER ccache CACHE STRING "")
set(CMAKE_CXX_COMPILER_LAUNCHER ccache CACHE STRING "")

# common vtk commands
set(VTK_DEBUG_LEAKS ON CACHE BOOL "")

set(VTK_USE_MPI ON CACHE BOOL "Enable MPI")
set(VTK_WARP_PYTHON ON CACHE BOOL "")
set(VTK_BUILD_TESTING ON CACHE BOOL "")

To use it:

cmake -C flags.cmake -B <build-directory> -S vtk

Much better! We can now track flags.cmake with git, add documentation and have different profiles for different cases/environments.

We can even split the file into some base common flags and profile specific. Consider the following two files:

common.cmake:

# common cmake commands
set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE BOOL "Generate compile_commands.json")
set(CMAKE_C_COMPILER_LAUNCHER ccache CACHE STRING "")
set(CMAKE_CXX_COMPILER_LAUNCHER ccache CACHE STRING "")

flags.cmake:

include("${CMAKE_CURRENT_LIST_DIR}/common.cmake")
# common vtk commands
set(VTK_DEBUG_LEAKS ON CACHE BOOL "")

set(VTK_USE_MPI ON CACHE BOOL "Enable MPI")
set(VTK_WARP_PYTHON ON CACHE BOOL "")
set(VTK_BUILD_TESTING ON CACHE BOOL "")

This is in fact the way the VTK project organizes its compile options for ci.

CMake presets

An alternative way is to organize the flags info a CMakePresets.json file. As per cmake documentation it is recommended that CMakePresets.cmake lives in the project’s root directory and it is tracked with the project’s version control system while CMakeUserPresets.json is not and it is included in .gitignore so that users can drop their own files in the root directory without polluting their working directory.

Let’s organize the above into a CMakeUserPresets.json file:

{
  "version": 3,
    "configurePresets": [
    {
      "name": "vtk-common",
      "displayName": "Common vtk config",
      "description": "Common flags for project tha use VTK",
      "generator": "Ninja",
      "cacheVariables": {
        "CMAKE_EXPORT_COMPILE_COMMANDS": {
          "type": "BOOL",
          "value": "ON"
        },
        "VTK_DEBUG_LEAKS": {
          "type": "BOOL",
          "value": "ON"
        },
        "CMAKE_C_COMPILER_LAUNCHER": {
         "type": "STRING",
         "value": "ccache"
        },
        "CMAKE_CXX_COMPILER_LAUNCHER": {
         "type": "STRING",
         "value": "ccache"
        }
      }
    },
    {
      "name": "python",
      "inherits": ["vtk-common"],
      "displayName": "vtk with python enabled",
      "cacheVariables": {
        "VTK_WARP_PYTHON": {
          "type": "BOOL",
          "value": "ON"
        }
      }
    },
    {
      "name": "mpi",
      "inherits": ["vtk-common"],
      "displayName": "vtk with mpi enabled",
      "cacheVariables": {
        "VTK_USE_MPI": {
          "type": "STRING",
          "value": "ON"
        }
      }
    },
    {
      "name": "testing",
      "inherits": ["vtk-common"],
      "displayName": "vtk tests enabled",
      "cacheVariables": {
        "VTK_BUILD_TESTING": {
          "type": "BOOL",
          "value": "ON"
        }
      }
    },
    {
      "name": "my-config",
      "inherits": ["mpi","python","testing"],
      "displayName": "Experimental"
    },
    {
      "name": "python-tests",
      "inherits": ["python","testing"],
      "displayName": "Testing python config"
    }
  ]
}

Notice that we get the same composability options but this time in a single file. Documentation is more limited here since we cannot have free-text comments, also handling if/else blocks is impossible right now. It has however a nice user experience feature. We can list all available presets:

$ cmake --list-presets  -B build -S vtk
Available configure presets:

  "vtk-common"   - Common vtk config
  "python"       - vtk with python enabled
  "mpi"          - vtk with mpi enabled
  "testing"      - vtk tests enabled
  "my-config"    - Experimental
  "python-tests" - Testing python config

Also, when selecting one preset, cmake informs which options have been set via the preset:

$ cmake --preset vtk-common -B build -S vtk
Preset CMake variables:

  CMAKE_CXX_COMPILER_LAUNCHER:STRING="ccache"
  CMAKE_C_COMPILER_LAUNCHER:STRING="ccache"
  CMAKE_EXPORT_COMPILE_COMMANDS:BOOL="ON"
  VTK_DEBUG_LEAKS:BOOL="ON"

-- The C compiler identification is GNU 10.4.0
...

Some IDEs like Visual Studio Code can take advantage of CMakePresets file and present a dropdown menu for the user with the available options. Same goes for cmake-gui that comes with cmake.