8、工作流预设(workflowPresets):串联多个预设步骤实现自动化流水线

说实话,前面几章讲的那些预设,不管是 configurePreset 还是 buildPreset,本质上都是单个动作。你配置好了,然后手动一步步执行:先 cmake configure,再 cmake build,再 cmake test……

但实际项目里,谁愿意这么干?

我当年在一个嵌入式团队里,每次发布版本都要手动敲七八条命令。后来实在受不了,写了个 shell 脚本。但脚本跟 CMake 版本绑死,换个人换个环境就炸。直到 CMake 3.20 引入了 workflowPresets,我才觉得——嗯,这才是正经的自动化方案。

8.1 什么是 workflowPresets?

说白了,workflowPresets 就是一组预设的「执行步骤」。每个步骤可以是一个 configure、build、test 或者 package 预设。你定义好顺序,CMake 会按顺序自动执行。

它的核心价值在于:

  • 标准化流程:团队所有人都用同一个流水线,不会有人漏掉某一步
  • 一键执行:一条命令跑完整个流程
  • 可组合:同一个 configure 预设可以被多个 workflow 复用

关键点:workflowPresets 本身不定义编译参数,它只是「编排」已有的预设。你之前定义的 configurePreset、buildPreset 等,在这里被串联起来。

8.2 基本结构:三步走

来看一个最简单的例子。假设我们有三个预设:

{
  "version": 6,
  "configurePresets": [
    {
      "name": "default",
      "displayName": "默认配置",
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/build"
    }
  ],
  "buildPresets": [
    {
      "name": "release",
      "displayName": "Release 构建",
      "configurePreset": "default",
      "configuration": "Release"
    }
  ],
  "testPresets": [
    {
      "name": "quick",
      "displayName": "快速测试",
      "configurePreset": "default",
      "configuration": "Release"
    }
  ],
  "workflowPresets": [
    {
      "name": "ci",
      "displayName": "CI 流水线",
      "steps": [
        {
          "type": "configure",
          "name": "default"
        },
        {
          "type": "build",
          "name": "release"
        },
        {
          "type": "test",
          "name": "quick"
        }
      ]
    }
  ]
}

执行的时候,只需要一条命令:

cmake --workflow --preset ci

CMake 会依次执行:configure → build → test。每一步如果失败,流水线就停下来。嗯,这跟 CI 里的 pipeline 是一个思路。

8.3 步骤类型:不止 configure/build/test

workflow 的 steps 支持四种类型:

类型 说明 对应的预设
configure 执行 cmake configure configurePreset
build 执行 cmake build buildPreset
test 执行 ctest testPreset
package 执行 cpack packagePreset

我个人的习惯是,把 package 步骤放在最后。因为只有测试通过了,才应该打包发布。你想想看,要是测试挂了还打包,那不是坑人吗?

8.4 串联多个配置:矩阵构建

有时候你需要用不同的配置跑同一套流程。比如 Debug 和 Release 都要构建并测试。

你当然可以定义两个 workflow,但那样太啰嗦。更好的做法是——利用 workflow 的继承和组合能力

{
  "workflowPresets": [
    {
      "name": "debug-ci",
      "displayName": "Debug 流水线",
      "steps": [
        { "type": "configure", "name": "debug" },
        { "type": "build", "name": "debug-build" },
        { "type": "test", "name": "debug-test" }
      ]
    },
    {
      "name": "release-ci",
      "displayName": "Release 流水线",
      "steps": [
        { "type": "configure", "name": "release" },
        { "type": "build", "name": "release-build" },
        { "type": "test", "name": "release-test" }
      ]
    },
    {
      "name": "full-ci",
      "displayName": "完整流水线",
      "steps": [
        { "type": "workflow", "name": "debug-ci" },
        { "type": "workflow", "name": "release-ci" }
      ]
    }
  ]
}

看到了吗?full-ci 的 steps 里引用了另外两个 workflow。这就是嵌套编排。CMake 会先跑 debug-ci 的全部步骤,再跑 release-ci 的全部步骤。

小技巧:嵌套 workflow 时,被引用的 workflow 里的步骤会「展开」到当前 workflow 中。所以你可以把公共流程抽出来,避免重复定义。

8.5 条件执行与错误处理

workflow 默认是「失败即停止」。但有些场景下,你可能希望某个步骤失败后继续执行后续步骤。比如:你想收集所有测试结果,而不是第一个失败就停掉。

目前 CMake 的 workflowPresets 还没有原生的「忽略错误」选项。但你可以通过脚本包装来实现。我在项目中用过这样的方式:

{
  "workflowPresets": [
    {
      "name": "collect-results",
      "displayName": "收集测试结果",
      "steps": [
        { "type": "configure", "name": "default" },
        { "type": "build", "name": "all" },
        { "type": "test", "name": "unit-tests" },
        { "type": "test", "name": "integration-tests" }
      ]
    }
  ]
}

这里我把单元测试和集成测试分成两个 testPreset。即使单元测试挂了,集成测试也不会被执行——因为默认是失败即停止。如果你想继续跑,目前只能通过外部脚本调用 cmake --workflow 并捕获返回值。

注意:workflowPresets 不支持条件判断(比如「如果上一步成功才执行」)。所有步骤都是顺序执行,没有分支逻辑。复杂的流水线建议交给 CI 工具(如 Jenkins、GitLab CI)来编排。

8.6 我踩过的坑

我曾经在一个跨平台项目里,把 workflow 定义得很复杂,嵌套了四五层。结果在 Windows 上跑的时候,路径分隔符出了问题——某个步骤的 binaryDir 用了正斜杠,但 Windows 需要反斜杠。

排查了半天,发现是 workflow 里引用的 configurePreset 的 binaryDir 写死了路径。后来我改成用 ${sourceDir}${sourceParentDir} 变量,问题就解决了。

所以我的建议是:workflow 里尽量用变量,不要写死路径。尤其是团队协作时,每个人的目录结构可能不一样。

8.7 可视化:workflow 的执行流程

下面这张图展示了一个典型的 workflow 执行流程。从 configure 开始,到 build,再到 test,最后 package。每一步都依赖上一步的成功。

workflowPresets 执行流程 步骤 1: configure cmake --preset default 步骤 2: build cmake --build --preset release 步骤 3: test ctest --preset quick 步骤 4: package cpack --preset release 执行说明 • 顺序执行,不可跳过 • 任一步失败则终止 • 支持嵌套 workflow • 支持 4 种步骤类型 常用命令: cmake --workflow --preset ci # 或指定步骤范围 cmake --workflow --preset ci --fresh

8.8 实战建议

最后,分享几个我在实际项目里总结的经验:

  • workflow 不要太大。一个 workflow 最好只做一件事。比如「构建+测试」是一个 workflow,「打包+发布」是另一个。分开更容易维护。
  • 命名要清晰。用 cireleasenightly 这样的名字,一看就知道用途。
  • 配合 --fresh 使用。如果你需要从头开始构建,可以加 --fresh 参数:cmake --workflow --preset ci --fresh。这会清空之前的构建目录。
  • 版本兼容性。workflowPresets 从 CMake 3.20 开始支持,3.23 之后才稳定。如果你的团队还在用老版本,记得升级。

一句话总结:workflowPresets 就是把你的「手动操作」变成「自动流水线」。它不复杂,但用好了能省下大量重复劳动。


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