4、持续集成(CI)基础:Jenkins/GitLab CI/CD概念、Pipeline即代码、触发策略
持续集成,说白了就是让代码每次提交都能自动编译、自动测试。我早年做嵌入式项目时,团队还在用手动编译、手动烧录的老办法。每次联调前光合并代码就要花半天,还经常漏掉依赖冲突。后来上了CI,才真正体会到什么叫「机器能干的活,别让人干」。
4.1 CI/CD 核心概念:为什么嵌入式项目更需要它
嵌入式开发和纯软件有个很大的不同——你离不开硬件。代码写得再好,烧到板子上跑不起来就是零。CI/CD 在嵌入式领域的价值,我总结为三点:
- 尽早发现集成问题:硬件驱动、中间件、应用层代码,任何一层改了,其他层可能就崩了。CI 能在合并前就把问题暴露出来。
- 自动化回归测试:每次提交都跑一遍单元测试、编译检查、静态分析。我见过太多「改了一行宏定义,整个工程编译不过」的惨案。
- 可追溯的构建产物:每次 CI 构建都会生成带版本号的固件。出了问题,能精确回退到某个 commit 对应的二进制。
我的经验:在某个车载项目里,我们引入了 CI 后,集成测试周期从两周缩短到两天。关键不是工具多牛,而是「每次提交都验证」这个习惯。
4.2 Jenkins vs GitLab CI/CD:选型对比
这两个工具我都深度用过。Jenkins 是老牌选手,插件生态极其丰富;GitLab CI/CD 则胜在与代码仓库深度集成,配置更轻量。下面这张表是我在实际项目中的对比感受:
| 对比维度 | Jenkins | GitLab CI/CD |
|---|---|---|
| 配置方式 | Web界面 + Jenkinsfile | .gitlab-ci.yml 纯代码 |
| Pipeline即代码 | Declarative / Scripted Pipeline | YAML 语法,原生支持 |
| 触发策略 | Webhook + 插件 | 内置 Push / MR / Tag 规则 |
| 嵌入式适配 | 需自行配置交叉编译环境 | 可用 Docker runner 封装工具链 |
| 维护成本 | 较高,需管理 Master/Slave | 较低,SaaS 或自托管均可 |
我个人习惯:如果团队规模小、追求快速上手,GitLab CI/CD 是首选。如果项目复杂、需要高度定制(比如多架构并行构建),Jenkins 更灵活。
4.3 Pipeline即代码:把构建流程写成配置
Pipeline即代码,就是把整个 CI 流程——从拉代码、编译、测试到打包——全部写在一个文件里。这样做的好处很明显:版本可控、可审查、可复用。
4.3.1 GitLab CI/CD 的 Pipeline 结构
一个典型的 .gitlab-ci.yml 包含 stages、jobs 和 scripts。看个例子:
stages:
- build
- test
- package
build_firmware:
stage: build
script:
- make clean
- make all
artifacts:
paths:
- firmware.bin
run_unit_tests:
stage: test
script:
- make test
dependencies:
- build_firmware
package_release:
stage: package
script:
- cp firmware.bin release/
- tar -czf release.tar.gz release/
only:
- tags
这里有个细节:artifacts 会把编译产物传递给后续 job。我在项目中遇到过一个问题——测试 job 拿不到编译产物,后来发现是 dependencies 没写对。嗯,这个坑踩过一次就不会忘了。
4.3.2 Jenkins Declarative Pipeline
Jenkins 的 Pipeline 写法类似,但用 Groovy 语法:
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'make clean'
sh 'make all'
}
}
stage('Test') {
steps {
sh 'make test'
}
}
stage('Package') {
when {
tag "v*"
}
steps {
sh 'tar -czf release.tar.gz firmware.bin'
}
}
}
}
避坑指南:我曾经在 Jenkins 里用 Scripted Pipeline 写了一个超长的脚本,结果维护起来痛苦不堪。后来全部改成 Declarative Pipeline,结构清晰多了。建议新手从 Declarative 开始。
4.4 触发策略:Push / MR / Tag
触发策略决定了「什么时候跑 Pipeline」。选对了,效率翻倍;选错了,服务器天天空转。
4.4.1 Push 触发
最简单的策略:只要有人 push 代码,就触发 CI。适合开发分支的快速验证。但要注意——如果团队频繁 push,可能会造成资源浪费。我建议只在 develop 和 feature/* 分支上启用。
4.4.2 Merge Request 触发
这是我最推荐的策略。只有当你发起 MR(或 PR)时,才触发完整的 CI 流水线。这样做的好处是:
- 代码审查和 CI 验证同步进行
- MR 合并前必须通过所有测试
- 避免「push 即触发」带来的碎片化构建
在 GitLab CI/CD 中配置 MR 触发很简单:
workflow:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
4.4.3 Tag 触发
Tag 触发通常用于发布流程。当你打了一个版本标签(如 v1.2.0),CI 自动执行打包、签名、生成 release notes 等操作。我在项目中是这样用的:
release_job:
stage: release
script:
- ./sign_firmware.sh
- ./upload_to_server.sh
only:
- tags
except:
- branches
注意:Tag 触发的 job 通常需要更高的权限(如访问密钥、签名证书)。务必在 CI 变量中安全存储这些敏感信息,不要硬编码在 yml 文件里。
4.5 嵌入式 CI 的典型触发流程
下面这张 SVG 图展示了我常用的嵌入式 CI 触发策略组合:
4.6 嵌入式 CI 的特殊考量
嵌入式 CI 和普通 Web 项目的 CI 有个本质区别——你没法在 CI 服务器上直接插一块开发板。怎么办?我分享几个实践:
- 使用模拟器或 QEMU:对于 ARM Cortex-M 系列,QEMU 可以模拟大部分指令。至少能跑通编译和基础逻辑测试。
- 硬件在环(HIL):如果预算允许,搭建一个硬件集群,通过 USB/串口与 CI 服务器通信。我在一个工业项目中就是这么做的——每次 CI 通过后,自动烧录到 5 块不同型号的板子上验证。
- 静态分析先行:在真正烧录前,先用 MISRA 检查、代码规范扫描、内存泄漏检测把低级问题过滤掉。
我的建议:不要试图在 CI 里覆盖所有硬件测试。把 CI 定位为「快速反馈环」,硬件相关的深度测试留给 nightly build 或专门的系统联调阶段。
4.7 小结
持续集成的核心不是工具,而是「自动化验证」这个习惯。Jenkins 和 GitLab CI/CD 各有千秋,选哪个取决于你的团队规模和项目复杂度。Pipeline即代码让构建流程变得可追溯、可复用。触发策略则决定了 CI 的执行效率——Push 用于快速反馈,MR 用于质量门禁,Tag 用于发布管理。
你想想看,如果每次提交都能自动编译、自动跑测试、自动生成固件,集成测试还会是噩梦吗?