第二章 工具链选型:版本控制、持续集成、构建工具与固件打包
说实话,选工具链这事儿,我踩过的坑比走过的路还多。
刚开始做嵌入式CI/CD那会儿,我总觉得工具越新越好。结果呢?团队里一半人用GitHub,一半人用SVN,构建脚本写得跟天书似的。后来我学乖了——工具链选型,说白了就是找最顺手的家伙事儿,而不是最时髦的。
2.1 版本控制:Git 是唯一的选择吗?
嗯,这个问题其实不用纠结。Git 现在就是事实标准。我见过有人还在用 SVN,理由是「简单」。但你想想看,嵌入式开发越来越复杂,固件版本、配置管理、分支策略……SVN 那套集中式模型,真的扛不住。
我个人习惯:所有嵌入式项目,不管大小,一律上 Git。哪怕是个人的小玩具项目,也值得用 Git 管理。为什么?因为你能随时回溯,能打标签,能搞分支实验。
核心要点:Git 不是选项,是基础设施。
我在项目中遇到过最头疼的事:一个同事用 SVN 提交了二进制固件,结果仓库直接炸了。Git 对二进制文件也不友好,但至少你有 LFS(Large File Storage)可以救场。
小技巧:嵌入式项目里,.gitignore 一定要写好。别把编译生成的 .o 文件、.hex 文件、IDE 的临时文件都提交上去。我见过有人把整个 build 目录都 push 上去了……那画面太美我不敢看。
2.2 持续集成:Jenkins 还是 GitLab CI?
这个问题,我当年也纠结过。Jenkins 老牌、稳定、插件多;GitLab CI 新潮、配置简单、跟代码仓库深度绑定。
我的建议:看团队规模。
- 小团队(1-5人):直接用 GitLab CI。配置在 .gitlab-ci.yml 里,跟代码一起管理,省心。
- 中大型团队(10人以上):Jenkins 更灵活。Pipeline as Code 用 Jenkinsfile 写,插件生态丰富,什么嵌入式工具都能集成。
我曾经在一个项目里,团队用 GitLab CI 跑固件编译,结果每次提交都要等 20 分钟。后来换成 Jenkins,配合分布式构建节点,时间砍了一半。为什么会这样?因为 GitLab CI 的 Runner 资源调度不如 Jenkins 灵活。
注意:不管选哪个,一定要把 CI 配置也纳入版本控制。别手动在网页上点来点去,那叫「人肉 CI」,迟早出问题。
2.3 构建工具:CMake 还是 Make?
这个问题,我敢说每个嵌入式工程师都纠结过。
Make 简单直接,但跨平台能力弱。CMake 学习曲线陡,但能生成各种平台的构建系统。
我个人习惯:新项目一律用 CMake。为什么?
- 跨平台:Windows、Linux、macOS 都能跑。
- 支持 IDE:能生成 Visual Studio、Eclipse、CLion 的项目文件。
- 模块化:用 add_subdirectory 组织代码,比 Make 的 include 清晰多了。
但我也不是说 Make 一无是处。如果你只是编译一个简单的单片机固件,Makefile 几十行就能搞定。CMake 反而显得杀鸡用牛刀。
避坑指南:我曾经在一个项目里,CMakeLists.txt 写得跟屎一样,各种全局变量乱飞。后来重构了一次,用 target_include_directories 和 target_link_libraries 明确依赖关系,世界清净了。
给你看个简单的 CMakeLists.txt 示例:
cmake_minimum_required(VERSION 3.10)
project(my_firmware C ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
# 添加源文件
add_executable(firmware.elf
src/main.c
src/gpio.c
src/uart.c
)
# 指定链接脚本
target_link_options(firmware.elf PRIVATE
-T ${CMAKE_SOURCE_DIR}/linker.ld
)
# 添加编译选项
target_compile_options(firmware.elf PRIVATE
-mcpu=cortex-m4
-mthumb
-O2
)
嗯,这样写,清晰明了。谁接手都能看懂。
2.4 固件打包工具:从 .hex 到发布包
固件编译出来只是第一步。怎么打包成可发布的格式?怎么加版本号?怎么生成校验和?
我见过最原始的做法:手动复制 .hex 文件,改个名,用 WinRAR 压缩……嗯,那叫一个酸爽。
我建议:用脚本自动化打包。Python 脚本或者 Shell 脚本都行。
举个例子,一个简单的打包脚本:
#!/bin/bash
# 固件打包脚本
VERSION=$(git describe --tags --always)
BOARD_TYPE=$1
# 生成固件包
mkdir -p output
cp build/firmware.hex output/${BOARD_TYPE}_firmware_${VERSION}.hex
# 生成校验和
sha256sum output/${BOARD_TYPE}_firmware_${VERSION}.hex > output/${BOARD_TYPE}_firmware_${VERSION}.sha256
# 生成发布说明
echo "Firmware version: ${VERSION}" > output/RELEASE_NOTES.md
echo "Board: ${BOARD_TYPE}" >> output/RELEASE_NOTES.md
echo "Build date: $(date)" >> output/RELEASE_NOTES.md
echo "Package generated: output/${BOARD_TYPE}_firmware_${VERSION}.hex"
你看,这样每次 CI 构建完,自动生成带版本号的固件包,还有校验和和发布说明。多省心。
小技巧:版本号一定要用 Git tag 管理。别手动写版本号,人总会犯错。我见过有人把 v1.0 写成了 v1.0.0.0.0……
2.5 工具链整合:一个完整的流水线示例
说了这么多,咱们看看一个完整的流水线长什么样。
| 阶段 | 工具 | 产出 |
|---|---|---|
| 代码提交 | Git | 触发 CI |
| 代码检查 | cppcheck / clang-tidy | 质量报告 |
| 单元测试 | Ceedling / Unity | 测试报告 |
| 固件编译 | CMake + GCC | .hex / .bin 文件 |
| 固件打包 | Shell / Python 脚本 | 发布包 + 校验和 |
| 固件部署 | SCP / OTA 服务器 | 固件上线 |
这个流水线,我用了好几年。每次提交代码,自动触发编译、测试、打包、部署。你想想看,以前手动搞这些,至少半小时。现在?几分钟搞定。
核心思想:工具链选型不是技术问题,是工程问题。选最合适的,不是最炫酷的。
好了,这一章就聊到这儿。下一章咱们深入讲讲 Jenkins Pipeline 怎么写,怎么跟嵌入式工具链集成。到时候我会分享一些我踩过的坑,保证让你少走弯路。