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。每一步都依赖上一步的成功。
8.8 实战建议
最后,分享几个我在实际项目里总结的经验:
- workflow 不要太大。一个 workflow 最好只做一件事。比如「构建+测试」是一个 workflow,「打包+发布」是另一个。分开更容易维护。
- 命名要清晰。用
ci、release、nightly这样的名字,一看就知道用途。 - 配合 --fresh 使用。如果你需要从头开始构建,可以加
--fresh参数:cmake --workflow --preset ci --fresh。这会清空之前的构建目录。 - 版本兼容性。workflowPresets 从 CMake 3.20 开始支持,3.23 之后才稳定。如果你的团队还在用老版本,记得升级。
一句话总结:workflowPresets 就是把你的「手动操作」变成「自动流水线」。它不复杂,但用好了能省下大量重复劳动。
公众号:蓝海资料掘金营,微信deep3321