构建预设(buildPresets):定义构建类型、生成器、构建目录等

好,咱们接着聊。前面我们把configurePresets讲透了,说白了就是给CMake喂参数、定工具链。那配置完了之后呢?当然是开始编译了。这就是buildPresets的活儿。

我个人习惯把构建预设看作是「编译阶段的配置清单」。你想想看,配置阶段决定了「用什么编译器、开哪些选项」,而构建阶段决定了「怎么编译、编译成什么、产物放哪儿」。这两者配合起来,才是一个完整的构建流程。

buildPresets 的核心字段

先看一个最基础的例子,感受一下:

{
  "version": 6,
  "buildPresets": [
    {
      "name": "debug",
      "configurePreset": "debug",
      "configuration": "Debug",
      "jobs": 8,
      "targets": ["my_app", "my_tests"]
    }
  ]
}

这里每个字段都有它的用意。我一个个说。

name 与 configurePreset

name 是预设的名字,这个不用多讲。configurePreset 是关键——它指定了当前构建预设要「绑定」哪个配置预设。说白了,你得先配置好,才能构建。这个字段就是建立这种依赖关系。

重要:一个构建预设必须关联一个配置预设。CMake 会先确保对应的配置预设已经执行过,然后再执行构建。

configuration:构建类型

这个字段对应的是 CMAKE_BUILD_TYPE。常见的值有 DebugReleaseRelWithDebInfoMinSizeRel

我在项目中遇到过一个问题:团队里有人用 Debug 构建,有人用 Release,结果联调的时候符号对不上,折腾了半天。后来我强制在 buildPresets 里固定了构建类型,再也没出过这种幺蛾子。

构建类型 优化级别 调试信息 典型用途
Debug -O0 完整 开发调试
Release -O3 正式发布
RelWithDebInfo -O2 性能分析
MinSizeRel -Os 嵌入式/空间受限

generator:生成器指定

这个字段可以覆盖配置预设里指定的生成器。比如你配置时用了 Ninja,但某个构建预设想用 Visual Studio 17 2022,可以在这里改。

{
  "name": "vs-release",
  "configurePreset": "base",
  "generator": "Visual Studio 17 2022",
  "configuration": "Release"
}

小技巧:我个人建议尽量在配置预设里统一指定生成器,构建预设里少覆盖。除非你有特殊需求,比如跨平台测试时需要切换生成器。

buildDirectory:构建目录

这个字段控制编译产物的存放位置。默认情况下,CMake 会把构建目录放在源码目录下的 build 文件夹里。但你可以自定义。

{
  "name": "out-of-source",
  "configurePreset": "base",
  "buildDirectory": "${sourceDir}/_build/${presetName}"
}

嗯,这里要注意:${sourceDir} 是 CMake Presets 支持的宏,会自动展开成源码路径。${presetName} 则是当前预设的名字。用这种组合,你可以轻松实现「每个预设一个独立构建目录」,互不干扰。

构建预设的继承机制

和配置预设一样,构建预设也支持 inherits。你可以定义一个基础预设,然后派生出多个变体。

{
  "buildPresets": [
    {
      "name": "base",
      "configurePreset": "base",
      "configuration": "Debug",
      "jobs": 4
    },
    {
      "name": "release-x64",
      "inherits": ["base"],
      "configuration": "Release",
      "environment": {
        "ARCH": "x64"
      }
    },
    {
      "name": "release-arm",
      "inherits": ["base"],
      "configuration": "Release",
      "environment": {
        "ARCH": "arm64"
      }
    }
  ]
}

你看,base 预设定义了通用的配置,release-x64release-arm 只改了自己需要改的部分。这样既减少了重复代码,又保持了清晰的结构。

注意:继承时,子预设会覆盖父预设的同名字段。但像 environment 这种字典类型,默认是合并而不是覆盖。如果你想要完全替换,需要显式设置。

构建目标与并行度

两个很实用的字段:targetsjobs

  • targets:指定要编译的目标列表。不指定的话,默认编译 all target。
  • jobs:并行编译的线程数。相当于 make -jninja -j

我曾经在 CI 环境里踩过一个坑:机器有 32 核,我设了 jobs: 32,结果内存爆了。后来我改成 jobs: 8,编译时间虽然长了一点,但系统稳定多了。所以啊,jobs 不是越大越好,得看你的机器扛不扛得住。

环境变量与缓存变量

构建预设里也可以设置环境变量和缓存变量,作用和配置预设类似。但要注意作用域:构建预设的环境变量只在构建阶段生效,不会影响配置阶段。

{
  "name": "test-env",
  "configurePreset": "base",
  "environment": {
    "MY_BUILD_FLAG": "1"
  },
  "cacheVariables": {
    "ENABLE_TESTS": "ON"
  }
}

知识体系总览

下面这张图,是我梳理的 buildPresets 核心逻辑。你看一眼,基本就全明白了。

buildPresets 核心结构 buildPresets configurePreset 绑定配置预设 configuration Debug/Release等 buildDirectory 构建目录路径 jobs / targets 并行度与目标 继承机制:inherits 实现预设复用 环境变量:environment 构建阶段生效 缓存变量:cacheVariables 覆盖 CMake 缓存

实际项目中的用法

最后,我分享一个我在实际项目中用过的 buildPresets 配置。这个项目是个跨平台的 C++ 库,需要同时支持 Windows、Linux 和 macOS。

{
  "buildPresets": [
    {
      "name": "dev",
      "configurePreset": "dev",
      "configuration": "Debug",
      "jobs": 4,
      "targets": ["all"]
    },
    {
      "name": "ci-linux",
      "configurePreset": "ci-linux",
      "configuration": "RelWithDebInfo",
      "jobs": 8,
      "targets": ["my_lib", "my_tests"]
    },
    {
      "name": "ci-windows",
      "configurePreset": "ci-windows",
      "configuration": "RelWithDebInfo",
      "generator": "Visual Studio 17 2022",
      "jobs": 4
    },
    {
      "name": "release",
      "inherits": ["ci-linux"],
      "configuration": "Release",
      "environment": {
        "ENABLE_LOGGING": "0"
      }
    }
  ]
}

你看,dev 是给本地开发用的,ci-linuxci-windows 是给 CI 用的,release 则继承了 ci-linux 并覆盖了构建类型和环境变量。每个预设各司其职,清晰明了。

我的建议:刚开始用 buildPresets 时,不要贪多。先定义两三个最常用的预设(比如 debug 和 release),等团队用顺手了再慢慢加。一口吃不成胖子,构建配置也一样。

好了,关于构建预设的核心内容就这些。说白了,它就是让你把「怎么编译」这件事标准化、可复用。下一节我们聊聊测试预设,那个也挺有意思的。


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