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:指定打包输出目录,方便后续上传

我的经验:如果项目依赖第三方库,建议用vcpkgconan来管理。在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.txtbuild/*.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示意图

下面这张图展示了从代码提交到制品发布的完整流程:

CI/CD中CMake与CPack完整流程 代码提交 git push / PR CMake配置 cmake -B build 构建 cmake --build build CTest测试 ctest --test-dir build CPack打包 cpack -B package 制品发布 上传到Release / 制品库 测试失败,回退 构建阶段 测试阶段 打包阶段 失败回退路径

29.7 一些实用技巧

最后分享几个我在实际项目中积累的经验:

  • 缓存策略:CMake的构建目录可以缓存,但要注意CMakeCache.txt里的绝对路径。建议只缓存build/libbuild/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