29、CI/CD中的CMake与CPack:在GitHub Actions/GitLab CI中配置CMake构建、自动运行CTest、发布CPack制品
说实话,很多团队在本地开发时用CMake用得挺溜,一到CI/CD就乱了阵脚。要么是构建脚本写得太死,换个平台就崩;要么是测试跑完了没人看结果;更常见的是——打包出来的东西不知道往哪放。
我个人习惯把CI/CD里的CMake流程拆成三步:配置→测试→打包。每一步都有对应的工具,CMake负责配置,CTest负责测试,CPack负责打包。今天我们就拿GitHub Actions和GitLab CI来演示,怎么把这套流程跑通。
核心思路:CI/CD里的CMake,本质上就是把你在本地敲的命令,自动化地跑一遍。但要注意环境差异、缓存策略、制品管理这些细节。
29.1 为什么要在CI/CD里用CMake?
你想想看,一个项目可能有十几个开发者,每个人用的系统、编译器、依赖版本都不一样。如果全靠人工去协调,迟早要出问题。
我在项目中遇到过最典型的情况:开发者在macOS上编译通过,一提交代码,Linux CI直接报错。查了半天,原来是大小写敏感的问题。嗯,这种坑,用CI/CD就能提前发现。
CMake在CI/CD里的优势很明显:
- 跨平台统一:一套CMakeLists.txt,Windows/Linux/macOS都能跑
- 测试自动化:CTest可以集成单元测试、集成测试,甚至性能测试
- 打包标准化:CPack支持生成deb、rpm、zip、dmg等多种格式
- 缓存友好:CMake的构建目录可以缓存,加速后续构建
29.2 GitHub Actions中的CMake配置
GitHub Actions是我用得最多的CI平台。它的YAML语法比较直观,而且有现成的CMake action可以用。但我个人建议,还是自己写命令更可控。
先看一个基础的构建流程:
# .github/workflows/cmake.yml
name: CMake Build and Test
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure CMake
run: cmake -B build -DCMAKE_BUILD_TYPE=Release
- name: Build
run: cmake --build build --config Release
- name: Run CTest
run: ctest --test-dir build --output-on-failure
- name: Package with CPack
run: cpack --config build/CPackConfig.cmake -B package
这段代码看着简单,但有几个细节要注意:
--output-on-failure:CTest默认只显示失败的测试名,加上这个参数才能看到具体错误信息--config Release:多配置生成器(如Visual Studio)需要指定配置类型-B package:指定打包输出目录,方便后续上传
我的经验:如果项目依赖第三方库,建议用vcpkg或conan来管理。在CI里安装依赖时,记得开启缓存,不然每次都要重新编译,太浪费时间了。
29.3 GitLab CI中的CMake配置
GitLab CI的配置方式和GitHub Actions不太一样。它用的是.gitlab-ci.yml,而且默认使用Docker runner。我个人觉得GitLab CI的流水线可视化做得更好,适合复杂的多阶段构建。
来看一个典型的配置:
# .gitlab-ci.yml
stages:
- configure
- build
- test
- package
variables:
BUILD_DIR: build
CMAKE_FLAGS: "-DCMAKE_BUILD_TYPE=Release"
configure:
stage: configure
script:
- cmake -B $BUILD_DIR $CMAKE_FLAGS
artifacts:
paths:
- $BUILD_DIR/
expire_in: 1 hour
build:
stage: build
script:
- cmake --build $BUILD_DIR --config Release
dependencies:
- configure
test:
stage: test
script:
- ctest --test-dir $BUILD_DIR --output-on-failure
dependencies:
- build
package:
stage: package
script:
- cpack --config $BUILD_DIR/CPackConfig.cmake -B package
artifacts:
paths:
- package/
expire_in: 1 week
这里我用了artifacts来传递构建产物。为什么要这么做?因为GitLab CI的每个job是独立运行的,如果不传递,下一个job就得重新编译。我曾经踩过这个坑,白白浪费了半小时的构建时间。
注意:GitLab CI的artifacts默认会压缩上传,如果构建目录很大,建议只保留关键文件。比如只传递build/CMakeCache.txt和build/*.cmake,而不是整个build目录。
29.4 自动运行CTest的最佳实践
CTest本身不跑测试,它只是测试的调度器。你需要先在CMakeLists.txt里注册测试:
# CMakeLists.txt
enable_testing()
# 添加单元测试
add_test(NAME test_math COMMAND test_math)
add_test(NAME test_string COMMAND test_string)
# 添加自定义测试
add_test(NAME integration_test
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/cmake/run_integration_test.cmake
)
在CI里,我建议这样配置CTest:
- 并行运行:
ctest -j4,充分利用多核CPU - 超时控制:
--timeout 60,防止测试卡死 - 失败重试:
--repeat until-pass:3,对不稳定测试有用 - 输出格式:
--output-junit test-results.xml,方便集成到CI的测试报告
避坑指南:我曾经遇到过CTest在CI里跑得好好的,但本地就是过不了。后来发现是环境变量的问题。建议在CTest命令前加上export CTEST_OUTPUT_ON_FAILURE=1,这样能保证错误信息完整输出。
29.5 发布CPack制品
打包是CI/CD的最后一步,也是最容易被忽视的一步。很多人觉得「能编译通过就行」,但用户拿到的是一个安装包,不是源码。
CPack支持多种打包格式,我常用的有:
| 格式 | 适用平台 | 生成器 |
|---|---|---|
| DEB | Debian/Ubuntu | CPack DEB Generator |
| RPM | Red Hat/Fedora | CPack RPM Generator |
| NSIS | Windows | CPack NSIS Generator |
| DMG | macOS | CPack DragNDrop Generator |
| ZIP/TGZ | 跨平台 | CPack Archive Generator |
在CMakeLists.txt里配置CPack:
# CMakeLists.txt
set(CPACK_PACKAGE_NAME "MyApp")
set(CPACK_PACKAGE_VERSION "1.0.0")
set(CPACK_PACKAGE_VENDOR "My Company")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A sample application")
# 根据平台选择打包格式
if(WIN32)
set(CPACK_GENERATOR "NSIS")
elseif(APPLE)
set(CPACK_GENERATOR "DragNDrop")
else()
set(CPACK_GENERATOR "DEB;RPM;TGZ")
endif()
include(CPack)
在CI里,打包完成后需要上传制品。GitHub Actions用actions/upload-artifact,GitLab CI用artifacts关键字。我个人建议设置一个合理的保留时间,比如7天,太长了浪费存储空间。
29.6 完整流程的SVG示意图
下面这张图展示了从代码提交到制品发布的完整流程:
29.7 一些实用技巧
最后分享几个我在实际项目中积累的经验:
- 缓存策略:CMake的构建目录可以缓存,但要注意
CMakeCache.txt里的绝对路径。建议只缓存build/lib和build/bin,不要缓存整个build目录。 - 矩阵构建:GitHub Actions支持矩阵策略,可以一键测试多个编译器、多个平台。比如
matrix: {os: [ubuntu-latest, windows-latest, macos-latest], compiler: [gcc, clang]}。 - 版本号管理:我习惯用Git标签来触发发布流程。当打上
v1.0.0这样的标签时,CI自动执行Release构建,并上传制品到GitHub Release。 - 测试报告:CTest可以生成JUnit格式的测试报告,GitHub Actions和GitLab CI都支持直接展示。这样团队成员不用看原始日志,一眼就能知道测试结果。
一个小建议:刚开始配置CI/CD时,不要追求一步到位。先跑通最简单的构建流程,再逐步加入测试、打包、发布。我见过太多人一开始就想搞全自动发布,结果卡在某个细节上,反而拖慢了进度。
好了,关于CI/CD中的CMake和CPack,今天就聊到这里。这套流程我已经在多个项目中验证过,稳定性和效率都还不错。如果你在配置过程中遇到什么问题,欢迎随时交流。
公众号:蓝海资料掘金营,微信deep3321