持续集成:GitHub Actions, GitLab CI, Jenkins 配置跨平台编译与测试
跨平台开发最头疼的是什么?我个人觉得,不是代码怎么写,而是怎么保证代码在 Windows、Linux、macOS 上都能跑起来。你想想看,本地开发环境再完美,换台机器就翻车的情况我见得太多了。
持续集成(CI)就是来解决这个问题的。说白了,每次你提交代码,CI 自动帮你拉代码、编译、跑测试,一气呵成。今天我就聊聊三个主流工具:GitHub Actions、GitLab CI 和 Jenkins。它们各有各的脾气,但核心思路是一样的。
核心要点:CI 的本质是「自动化验证」。跨平台 CI 的核心是「矩阵构建」—— 同一份代码,在多个操作系统、多个编译器下同时跑。
GitHub Actions:最省心的选择
如果你的代码托管在 GitHub 上,GitHub Actions 是最直接的选择。它不需要额外搭建服务器,直接在 GitHub 的虚拟机里跑。我个人习惯把跨平台编译写成矩阵策略,一行配置就能搞定。
来看一个实际例子。这是一个 C 语言项目的 CI 配置,支持 Linux、macOS、Windows 三平台编译:
# .github/workflows/cross-platform.yml
name: Cross-Platform Build & Test
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
compiler: [gcc, clang]
exclude:
- os: macos-latest
compiler: gcc
- os: windows-latest
compiler: clang
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Setup Compiler
run: |
if [ "${{ matrix.compiler }}" = "gcc" ]; then
echo "CC=gcc" >> $GITHUB_ENV
echo "CXX=g++" >> $GITHUB_ENV
else
echo "CC=clang" >> $GITHUB_ENV
echo "CXX=clang++" >> $GITHUB_ENV
fi
shell: bash
- name: Build
run: |
mkdir build && cd build
cmake .. -DCMAKE_C_COMPILER=${{ env.CC }}
cmake --build .
shell: bash
- name: Test
run: |
cd build
ctest --output-on-failure
shell: bash
小技巧:exclude 字段用来排除不合理的组合。比如 macOS 上 GCC 其实是 Clang 的别名,Windows 上 Clang 需要额外配置。我刚开始没注意这个,结果 macOS 的 GCC 跑出来一堆奇怪警告,排查了半天才发现是编译器版本问题。
GitLab CI:自托管玩家的首选
GitLab CI 和 GitHub Actions 思路类似,但配置语法略有不同。它用 .gitlab-ci.yml 文件,支持更灵活的 Runner 管理。如果你有自己的服务器,可以注册自定义 Runner,跑在物理机上性能更好。
我曾经在一个嵌入式项目里用过 GitLab CI。那项目需要在 ARM 交叉编译环境下测试,GitHub 的公共 Runner 不支持,但 GitLab 的自定义 Runner 完美解决了这个问题。
# .gitlab-ci.yml
stages:
- build
- test
variables:
BUILD_DIR: build
.cross_platform_template: &cross_platform
before_script:
- apt-get update -qq
- apt-get install -y -qq cmake gcc g++ clang
linux-gcc:
stage: build
image: ubuntu:22.04
script:
- mkdir -p $BUILD_DIR && cd $BUILD_DIR
- cmake .. -DCMAKE_C_COMPILER=gcc
- cmake --build .
artifacts:
paths:
- $BUILD_DIR/
tags:
- linux-runner
linux-clang:
stage: build
image: ubuntu:22.04
script:
- mkdir -p $BUILD_DIR && cd $BUILD_DIR
- cmake .. -DCMAKE_C_COMPILER=clang
- cmake --build .
artifacts:
paths:
- $BUILD_DIR/
tags:
- linux-runner
test:
stage: test
image: ubuntu:22.04
needs: ["linux-gcc", "linux-clang"]
script:
- cd $BUILD_DIR
- ctest --output-on-failure
tags:
- linux-runner
注意:GitLab CI 的 artifacts 机制很实用。编译产物可以传递给后续 job,避免重复编译。但要注意 artifacts 过期时间,默认是 30 天,太大会占磁盘空间。我有个项目忘了设置过期时间,结果 Runner 磁盘被撑爆了……
Jenkins:老牌劲旅,灵活但重
Jenkins 是 CI 领域的老大哥。它不绑定任何代码托管平台,什么都能接。但配置起来相对复杂,需要自己维护 Jenkins 服务器。说实话,小项目用 Jenkins 有点杀鸡用牛刀的感觉,但大型企业项目里它依然是主流。
Jenkins 的跨平台编译通常用 Pipeline 脚本(Jenkinsfile)来实现。下面是一个声明式 Pipeline 的例子:
// Jenkinsfile
pipeline {
agent none
stages {
stage('Build on Linux') {
agent {
label 'linux-node'
}
steps {
checkout scm
sh '''
mkdir -p build && cd build
cmake .. -DCMAKE_C_COMPILER=gcc
cmake --build .
'''
stash includes: 'build/**', name: 'linux-build'
}
}
stage('Build on Windows') {
agent {
label 'windows-node'
}
steps {
checkout scm
bat '''
if not exist build mkdir build
cd build
cmake .. -DCMAKE_C_COMPILER=cl
cmake --build .
'''
stash includes: 'build/**', name: 'win-build'
}
}
stage('Test') {
agent {
label 'linux-node'
}
steps {
unstash 'linux-build'
sh '''
cd build
ctest --output-on-failure
'''
}
}
}
post {
always {
junit 'build/test-results/**/*.xml'
}
}
}
关键区别:Jenkins 的 agent 机制允许你指定不同的执行节点。Linux 编译在 Linux 节点上跑,Windows 编译在 Windows 节点上跑。stash/unstash 用于在节点间传递文件,相当于 GitLab 的 artifacts。
跨平台编译的常见坑
配置 CI 只是第一步,真正头疼的是跨平台编译本身。我踩过不少坑,挑几个典型的说说:
- 路径分隔符:Windows 用反斜杠
\,Linux/macOS 用正斜杠/。代码里硬编码路径会直接炸。解决方案是用#include <filesystem>(C++17)或 CMake 的file(TO_CMAKE_PATH)。 - 动态库后缀:Linux 是
.so,macOS 是.dylib,Windows 是.dll。CMake 的MODULE和SHARED要分清。 - 编译器差异:MSVC 对 C99/C11 的支持比较「有个性」。我遇到过 MSVC 不支持
strdup()函数,后来用_strdup()加宏定义绕过去的。 - 换行符:Windows 是
\r\n,Linux 是\n。Git 的autocrlf设置要统一,否则编译时会出现莫名其妙的语法错误。
我的经验:在项目根目录放一个 CMakeLists.txt,用 CMake 管理构建。CMake 本身跨平台,能自动检测编译器、路径、库依赖。配合 CI 的矩阵构建,一套配置跑遍所有平台。我最近三个项目都是这么干的,省心不少。
测试策略:不止是编译通过
编译通过不代表程序能跑。跨平台测试要覆盖几个层面:
| 测试类型 | 说明 | CI 中的实现 |
|---|---|---|
| 单元测试 | 验证函数级逻辑 | CTest + 单元测试框架(如 Unity、Criterion) |
| 集成测试 | 验证模块间交互 | 自定义测试脚本,模拟实际调用链 |
| 平台特性测试 | 验证平台相关代码 | 条件编译 + 平台标记,只在对应平台运行 |
| 性能回归测试 | 防止性能退化 | 基准测试框架(如 Google Benchmark),对比历史数据 |
举个例子,如果你的代码里有 #ifdef _WIN32 的分支,那 Windows 上的测试必须覆盖这个分支。Linux 上跑再多测试也发现不了 Windows 的 bug。所以 CI 的矩阵策略里,每个平台都要跑完整的测试套件。
曾经踩过的坑:我有一个网络库,Linux 上一切正常,Windows 上却频繁超时。排查了半天,发现是 Windows 的 socket 默认超时时间和 Linux 不一样。后来在测试代码里显式设置了超时参数,问题才解决。所以平台特性测试一定要做细。
总结一下
GitHub Actions 适合 GitHub 用户,配置简单,开箱即用。GitLab CI 适合自托管场景,Runner 灵活可控。Jenkins 适合大型企业,高度可定制但维护成本高。
不管你选哪个工具,核心思路是一样的:用矩阵构建覆盖多平台,用自动化测试保证质量。跨平台开发没有银弹,但 CI 能帮你把问题提前暴露出来,而不是等到用户反馈才手忙脚乱。
嗯,今天就聊到这里。配置 CI 的时候遇到具体问题,欢迎随时交流。