CMakePresets.json 文件结构:顶层结构解析

好,咱们正式开始聊 CMakePresets 的核心——那个 JSON 文件到底长什么样。

我第一次接触 CMakePresets 时,说实话有点懵。一个 JSON 文件,要管配置、构建、测试,还得兼容不同编译器、不同平台。但摸清楚顶层结构后,你会发现它其实很规整。说白了,就是四个顶层字段撑起整个体系。

核心要点:CMakePresets.json 的顶层结构由四个字段组成——schemaVersion、configurePresets、buildPresets、testPresets。其中只有 schemaVersion 是必填项,其他三个按需使用。

顶层结构总览

先看一个最简的完整示例,感受一下骨架:

{
  "version": 6,
  "schemaVersion": 6,
  "configurePresets": [
    {
      "name": "default",
      "displayName": "Default Config",
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/build/${presetName}"
    }
  ],
  "buildPresets": [
    {
      "name": "default",
      "configurePreset": "default"
    }
  ],
  "testPresets": [
    {
      "name": "default",
      "configurePreset": "default"
    }
  ]
}

嗯,这里要注意:version 和 schemaVersion 在 CMake 3.23 之后其实是同一个东西。我个人习惯只写 version,省得混淆。

下面这张图能帮你快速建立整体认知:

CMakePresets.json 顶层结构 CMakePresets.json schemaVersion configurePresets buildPresets testPresets 各字段说明: • schemaVersion(必填):声明 Presets 规范版本,当前推荐 6 • configurePresets(可选):定义 cmake 配置阶段的参数集合 • buildPresets(可选):定义 cmake --build 构建阶段的参数集合 • testPresets(可选):定义 ctest 测试阶段的参数集合 • 三者通过 configurePreset 字段串联,形成配置→构建→测试流水线

schemaVersion:版本声明

这个字段最简单,但也最容易踩坑。

你想想看,CMake 版本在迭代,Presets 规范也在变。CMake 3.21 支持 version 4,3.23 支持 version 6。如果你写了个 version 6 的 Presets,但团队里有人用 CMake 3.20,那直接报错。

我曾经遇到过:一个项目在 CI 上跑得好好的,换到同事的本地环境就崩了。查了半天,发现 CI 的 CMake 是 3.25,同事的是 3.19。Presets 文件里写了 "version": 6,3.19 根本不认识。从那以后,我养成了习惯——在项目 README 里明确标注最低 CMake 版本要求。

版本对应关系如下:

CMake 版本 最高支持 schemaVersion 关键变化
3.19 - 3.20 3 基础支持,功能有限
3.21 - 3.22 4 新增 vendor 字段、条件支持
3.23 - 3.24 6 引入 workflowPresets,version 与 schemaVersion 统一
3.25+ 6 稳定,推荐使用

我的建议:新项目直接用 "version": 6。如果必须兼容旧版 CMake,降到 version 4 也够用。version 3 就别用了,少了很多好东西。

configurePresets:配置阶段的指挥中心

这是三个 Presets 里最复杂的,也是我花时间最多的部分。

configurePresets 是一个数组,每个元素定义一套配置方案。你想想看,一个项目可能有 Debug、Release、RelWithDebInfo、MinSizeRel 四种配置,再加上不同的编译器、不同的平台,组合起来十几个 Preset 很正常。

每个 configurePreset 的核心字段:

  • name:唯一标识符,其他 Preset 通过它来引用
  • displayName:给人看的名字,CMake GUI 里会显示这个
  • description:描述这个 Preset 的用途
  • generator:指定构建系统生成器,比如 Ninja、Visual Studio 17 2022
  • binaryDir:构建目录,支持 ${sourceDir}、${presetName} 等变量
  • cacheVariables:设置 CMake 缓存变量,相当于 -D 参数
  • environment:设置环境变量
  • inherits:继承其他 Preset,实现复用

看个实际例子:

"configurePresets": [
  {
    "name": "debug",
    "displayName": "Debug (Ninja)",
    "description": "Debug build using Ninja",
    "generator": "Ninja",
    "binaryDir": "${sourceDir}/build/debug",
    "cacheVariables": {
      "CMAKE_BUILD_TYPE": "Debug",
      "CMAKE_CXX_FLAGS_DEBUG": "-g -O0 -DDEBUG"
    }
  },
  {
    "name": "release",
    "displayName": "Release (Ninja)",
    "description": "Release build using Ninja",
    "generator": "Ninja",
    "binaryDir": "${sourceDir}/build/release",
    "cacheVariables": {
      "CMAKE_BUILD_TYPE": "Release",
      "CMAKE_CXX_FLAGS_RELEASE": "-O3 -DNDEBUG"
    }
  }
]

这里有个小技巧——binaryDir 里用 ${presetName} 变量,可以自动匹配 Preset 名字。我习惯写成 "${sourceDir}/build/${presetName}",这样新增 Preset 时不用手动改路径。

buildPresets:构建阶段的快捷方式

buildPresets 相对简单。它不定义构建参数,而是引用一个 configurePreset,然后附加构建阶段的选项。

核心字段:

  • name:唯一标识符
  • configurePreset:关联的 configurePreset 名字
  • jobs:并行任务数,相当于 -j 参数
  • targets:指定构建目标,相当于 --target
  • nativeToolOptions:传递给底层构建工具的参数

举个例子:

"buildPresets": [
  {
    "name": "debug",
    "configurePreset": "debug",
    "jobs": 8,
    "targets": ["my_app", "my_tests"]
  },
  {
    "name": "release",
    "configurePreset": "release",
    "jobs": 8
  }
]

使用方式很简单:cmake --build --preset debug。它会自动找到关联的 configurePreset,读取构建目录,然后执行构建。

我个人习惯:buildPreset 的名字和 configurePreset 保持一致。这样 cmake --preset debug 和 cmake --build --preset debug 一一对应,不容易搞混。

testPresets:测试阶段的标准化

testPresets 是三个 Presets 里最晚加入的,CMake 3.20 才开始支持。它的结构和 buildPresets 类似,也是引用一个 configurePreset。

核心字段:

  • name:唯一标识符
  • configurePreset:关联的 configurePreset
  • configuration:测试配置名称(多配置生成器用)
  • output:控制测试输出,比如 {"outputOnFailure": true}
  • filter:测试用例过滤,支持正则表达式

看个例子:

"testPresets": [
  {
    "name": "debug",
    "configurePreset": "debug",
    "output": {
      "outputOnFailure": true,
      "maxTestNameWidth": 60
    },
    "filter": {
      "include": {
        "name": "unit_*"
      }
    }
  },
  {
    "name": "release",
    "configurePreset": "release",
    "output": {
      "outputOnFailure": false
    }
  }
]

使用方式:ctest --preset debug。它会自动定位到 configurePreset 指定的构建目录,然后执行测试。

我曾经踩过的坑:testPreset 的 configurePreset 字段必须和 buildPreset 的 configurePreset 指向同一个 Preset,否则测试会找不到构建产物。说白了,配置、构建、测试这三个 Preset 通过 configurePreset 字段串成一条链,断了一环就全废了。

三个 Preset 的协作关系

你想想看,这三个 Preset 不是孤立的。它们通过 configurePreset 字段串联起来,形成一条完整的流水线:

  1. 配置阶段:cmake --preset debug → 读取 debug configurePreset,生成构建系统
  2. 构建阶段:cmake --build --preset debug → 读取 debug buildPreset,找到关联的 configurePreset,进入构建目录执行编译
  3. 测试阶段:ctest --preset debug → 读取 debug testPreset,找到关联的 configurePreset,进入构建目录执行测试

这种设计的好处很明显——你只需要记住 Preset 的名字,不用关心构建目录在哪、生成器是什么、测试过滤条件怎么写。一切都在 Presets 文件里定义好了。

总结一下:CMakePresets.json 的顶层结构其实就四个字段。schemaVersion 是版本声明,configurePresets 管配置,buildPresets 管构建,testPresets 管测试。后三个通过 configurePreset 字段串联,形成完整的构建流水线。掌握了这个骨架,后面的细节填充就水到渠成了。