24、预设(Presets):CMakePresets.json,配置预设、构建预设、测试预设,CI/CD集成

说实话,CMake 的命令行参数我背了好几年。每次开新项目,都得翻文档查 -DCMAKE_BUILD_TYPE 怎么写。直到 CMake 3.19 引入了 Presets 机制,我才算真正解脱。

Presets 是什么?说白了,就是把常用的 CMake 配置、构建、测试参数,写进一个 JSON 文件里。团队里所有人都用同一套参数,再也不用担心「我机器上能编译啊」这种鬼话了。

CMakePresets.json 的结构

这个文件必须放在项目的根目录。文件名是固定的,就叫 CMakePresets.json。我习惯把它和 CMakeLists.txt 放在一起。

一个典型的文件结构长这样:

{
  "version": 3,
  "configurePresets": [],
  "buildPresets": [],
  "testPresets": []
}

version 目前推荐用 3。version 2 也还能用,但少了些新特性。我个人建议直接上 version 3,省得以后升级麻烦。

配置预设(configurePresets)

配置预设是最核心的部分。它决定了 CMake 怎么生成构建系统。

来看一个实际例子:

{
  "version": 3,
  "configurePresets": [
    {
      "name": "default",
      "displayName": "默认配置",
      "description": "Debug 模式,开启测试",
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/build/${presetName}",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Debug",
        "CMAKE_CXX_STANDARD": "17",
        "BUILD_TESTING": "ON"
      }
    },
    {
      "name": "release",
      "displayName": "发布配置",
      "description": "Release 模式,优化全开",
      "inherits": "default",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Release",
        "BUILD_TESTING": "OFF"
      }
    }
  ]
}

这里有几个关键点:

  • binaryDir:构建目录。我用 ${sourceDir}/build/${presetName},这样不同预设的构建产物不会混在一起。
  • inherits:继承机制。release 预设继承了 default,只覆盖了需要改的变量。避免重复写同样的东西。
  • generator:生成器。我推荐 Ninja,跨平台且快。Windows 上也可以用 Visual Studio。
小技巧:${presetName} 变量来命名构建目录。这样你执行 cmake --preset release 时,产物自动进 build/release 目录,清爽得很。

构建预设(buildPresets)

配置预设搞定了,构建预设就简单多了。它只是告诉 CMake 怎么编译:

{
  "buildPresets": [
    {
      "name": "default",
      "configurePreset": "default",
      "jobs": 4
    },
    {
      "name": "release",
      "configurePreset": "release",
      "jobs": 8
    }
  ]
}

注意 configurePreset 字段。它必须指向一个已经定义好的配置预设。构建预设不能独立存在,它依赖于配置预设生成的构建系统。

jobs 是并行编译的线程数。我一般设成 CPU 核心数。但如果你在 CI 环境里,记得别设太高,免得把服务器搞崩了。我曾经在 4 核的 CI 机器上设了 16 个 jobs,结果编译到一半机器直接 OOM 了……嗯,从那以后我就老实了。

测试预设(testPresets)

测试预设控制 ctest 的行为:

{
  "testPresets": [
    {
      "name": "default",
      "configurePreset": "default",
      "output": {
        "outputOnFailure": true
      },
      "execution": {
        "noTestsAction": "error",
        "stopOnFailure": false
      }
    }
  ]
}

我个人最常用的是 outputOnFailure。测试失败时直接输出详细信息,省得再跑一遍。还有 noTestsAction 设成 error,如果没找到测试用例就直接报错——这能帮你发现测试没注册的问题。

注意: 测试预设的 configurePreset 必须和构建预设指向同一个配置预设。否则 ctest 找不到构建产物,会报错。我见过有人把测试预设指向了 release,但构建用的是 default,结果折腾了半天。

CI/CD 集成

Presets 最大的价值,其实在 CI/CD 里。你想想看,本地开发和 CI 用同一套配置,多省心。

在 GitHub Actions 里,可以这样用:

# .github/workflows/build.yml
- name: Configure
  run: cmake --preset default

- name: Build
  run: cmake --build --preset default

- name: Test
  run: ctest --preset default

就这么三行。不需要在 CI 配置文件里重复写 -DCMAKE_BUILD_TYPE=Debug 之类的参数了。所有配置都在 CMakePresets.json 里统一管理。

GitLab CI 也类似:

build:
  script:
    - cmake --preset release
    - cmake --build --preset release

Jenkins 的话,直接在 Pipeline 里调用同样的命令就行。

核心思路: Presets 把 CMake 的配置从 CI 脚本里解耦出来。CI 脚本只负责调用 preset,具体的编译参数、测试选项,全在 JSON 文件里。改配置时,只需要改一个文件,不用去翻 CI 的 YAML。

避坑指南

用 Presets 时,有几个坑我踩过:

  • 路径问题binaryDir 里的 ${sourceDir} 是 CMake 自动识别的源码根目录。别自己拼路径,容易出错。
  • 版本兼容:CMake 3.19 才开始支持 Presets。如果你的 CI 镜像里 CMake 版本太老,记得升级。我见过有人用 Ubuntu 18.04 默认的 CMake 3.10,结果 Presets 完全不认。
  • JSON 语法:JSON 不允许注释。别想着在文件里写 ///* */,CMake 会直接报错。如果非要写说明,用 description 字段。
  • 继承覆盖:子预设的 cacheVariables 会完全覆盖父预设的同名变量,不是合并。如果你只想改一个变量,记得把其他变量也带上,或者用 cacheVariables${inherits} 机制。

知识体系总览

下面这张图,帮你理清 Presets 的整体结构:

CMake Presets 知识体系 CMakePresets.json 配置预设 构建预设 测试预设 generator / binaryDir cacheVariables inherits 继承机制 configurePreset 关联 jobs 并行编译 target / cleanFirst outputOnFailure stopOnFailure noTestsAction CI/CD 集成:GitHub Actions / GitLab CI / Jenkins

Presets 这东西,说白了就是「一次配置,到处使用」。本地开发用 --preset default,CI 用 --preset release,测试用 --preset test。团队里所有人都用同一个 JSON 文件,参数统一,再也不用争论「你那个编译选项不对」了。

我个人觉得,Presets 是 CMake 近年来最实用的功能之一。它把配置从 CMakeLists.txt 和 CI 脚本里解放出来,让项目管理变得更清晰。如果你还没用上,我建议你从下个项目开始试试。嗯,相信我,用了就回不去了。


公众号:蓝海资料掘金营,微信 deep3321