2. 第2例

基于CMake Presets的多平台构建配置——为同一C++工程编写CMakePresets.json,定义Debug、Release及不同编译器的预设,实现一条命令切换构建类型与工具链,并验证预设继承和缓存变量生效。 学习卡片

为什么需要 CMake Presets

同一个 C++ 工程,在开发机上要 Debug + GCC,在 CI 上要 Release + Clang,在 Windows 上要 MSVC。传统做法是给每种组合写一条 cmake -S . -B build-xxx -DCMAKE_BUILD_TYPE=... -DCMAKE_CXX_COMPILER=... 命令,参数散落在 README、脚本和 CI 配置里,容易写错、难以继承、无法被 IDE 直接识别。CMake Presets(CMake 3.19+,CMakePresets.json)把这些组合固化成可继承的 JSON 预设,一条 cmake --preset=xxx 即可完成配置。

预设的三层结构

Presets 文件由三个顶层数组组成,它们通过 inherits 形成继承链,子预设只写差异字段:

  • configurePresets:定义生成器、二进制目录、工具链、缓存变量(cacheVariables)。
  • buildPresets:通过 configurePreset 绑定某个配置预设,指定构建目标与并行度。
  • testPresets:通过 configurePreset 绑定配置预设,指定 CTest 的输出与过滤条件。

关键字段:hidden: true 的预设只作为基类,不出现在 cmake --list-presets 中;binaryDir 支持 ${presetName} 等宏,让每个预设拥有独立构建目录,互不污染。

CMakePresets.json 继承链与命令映射 base(hidden: true) generator / binaryDir / cacheVariables debug-gcc CMAKE_BUILD_TYPE=Debug CMAKE_CXX_COMPILER=g++ release-gcc CMAKE_BUILD_TYPE=Release CMAKE_CXX_COMPILER=g++ release-clang CMAKE_BUILD_TYPE=Release CMAKE_CXX_COMPILER=clang++ build-debug configurePreset: debug-gcc build-release configurePreset: release-gcc build-clang configurePreset: release-clang 一条命令切换: cmake --preset=debug-gcc && cmake --build --preset=build-debug cmake --preset=release-gcc && cmake --build --preset=build-release cmake --preset=release-clang && cmake --build --preset=build-clang 构建目录:build/debug-gcc、build/release-gcc、build/release-clang(由 ${presetName} 展开)

完整 CMakePresets.json

下面是一个可直接使用的示例,工程根目录下有一个 CMakeLists.txt 定义目标 app。预设文件放在工程根目录,与 CMakeLists.txt 同级。

{
  "version": 6,
  "cmakeMinimumRequired": {
    "major": 3,
    "minor": 23,
    "patch": 0
  },
  "configurePresets": [
    {
      "name": "base",
      "hidden": true,
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/build/${presetName}",
      "cacheVariables": {
        "CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
        "CMAKE_CXX_STANDARD": "17",
        "CMAKE_CXX_STANDARD_REQUIRED": "ON"
      }
    },
    {
      "name": "debug-gcc",
      "inherits": "base",
      "displayName": "Debug (GCC)",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Debug",
        "CMAKE_CXX_COMPILER": "g++",
        "CMAKE_C_COMPILER": "gcc"
      }
    },
    {
      "name": "release-gcc",
      "inherits": "base",
      "displayName": "Release (GCC)",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Release",
        "CMAKE_CXX_COMPILER": "g++",
        "CMAKE_C_COMPILER": "gcc"
      }
    },
    {
      "name": "release-clang",
      "inherits": "base",
      "displayName": "Release (Clang)",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Release",
        "CMAKE_CXX_COMPILER": "clang++",
        "CMAKE_C_COMPILER": "clang"
      }
    }
  ],
  "buildPresets": [
    {
      "name": "build-debug",
      "configurePreset": "debug-gcc",
      "jobs": 8
    },
    {
      "name": "build-release",
      "configurePreset": "release-gcc",
      "jobs": 8
    },
    {
      "name": "build-clang",
      "configurePreset": "release-clang",
      "jobs": 8
    }
  ],
  "testPresets": [
    {
      "name": "test-debug",
      "configurePreset": "debug-gcc",
      "output": { "outputOnFailure": true }
    },
    {
      "name": "test-release",
      "configurePreset": "release-gcc",
      "output": { "outputOnFailure": true }
    }
  ]
}

验证预设继承与缓存变量生效

配置完成后,用以下方式确认继承链和缓存变量确实写入了 CMakeCache.txt:

  1. 列出所有可见预设:cmake --list-presets,应只看到 debug-gcc、release-gcc、release-clang,base 因 hidden: true 不出现。
  2. 执行配置:cmake --preset=debug-gcc,观察输出中生成器为 Ninja,构建目录为 build/debug-gcc。
  3. 检查缓存变量:grep -E "CMAKE_BUILD_TYPE|CMAKE_CXX_COMPILER|CMAKE_CXX_STANDARD" build/debug-gcc/CMakeCache.txt,应分别得到 Debug、g++ 路径、17。
  4. 验证继承:CMAKE_EXPORT_COMPILE_COMMANDS 来自 base,在 debug-gcc 中未重复声明,但缓存中应为 ON。
  5. 构建并测试:cmake --build --preset=build-debug,再 ctest --preset=test-debug。
如果使用 CMake 3.21+,可以在 configurePresets 中加 "toolchainFile" 指向交叉编译工具链文件,实现嵌入式平台的预设切换。CMake 3.25+ 还支持 packagePresets 和 workflowPresets,可把配置、构建、测试、打包串成一条工作流。
常见问题:binaryDir 中若使用 ${presetName},不同预设会生成不同目录,切换预设时不会互相覆盖缓存;但如果两个预设共用同一 binaryDir 且编译器不同,CMake 会报缓存冲突,必须删除旧目录或改用独立目录。另外,CMAKE_CXX_COMPILER 只在首次配置时生效,修改预设后需清空对应构建目录。

快速执行卡片

操作命令预期结果
查看可用预设cmake --list-presets列出 3 个配置预设,不含 base
Debug 配置cmake --preset=debug-gcc生成 build/debug-gcc,缓存含 Debug/g++/17
Release 配置cmake --preset=release-gcc生成 build/release-gcc,缓存含 Release/g++
Clang 配置cmake --preset=release-clang生成 build/release-clang,缓存含 clang++
构建cmake --build --preset=build-debug在对应目录完成编译
测试ctest --preset=test-debug运行 CTest,失败时输出详情