第28章 自动化构建CI:Jenkins/GitLab CI配置、多设备构建、版本发布
说实话,做嵌入式Android系统开发,最怕什么?不是代码写不出来,而是每次手动编译、打包、刷机,一套流程下来大半天就没了。我早年在一家IoT公司带队做平板固件,那时候团队就三个人,每次发版都得熬夜盯着终端,生怕哪个环节断了没人知道。后来我实在受不了了,花了两周把Jenkins流水线搭起来,从那以后,晚上十点之后我基本不看手机——构建挂了会自动发邮件,第二天早上来修就行。
这一章,我就把CI/CD在AOSP定制中的实战经验掰开揉碎讲给你听。说白了,就是让机器替你干那些重复、枯燥、容易手滑的活儿。
为什么AOSP构建特别需要CI?
你想想看,一个完整的AOSP工程,从repo sync到生成刷机包,快的也要一两个小时。如果每次改个驱动或者加个系统服务,都要手动敲命令等结果,那效率太低了。更关键的是,多人协作时,你没法保证每个人本地的编译环境完全一致。我见过最离谱的一次,同事的Ubuntu是中文版,locale没设对,编译出来的系统UI全是乱码——这种问题在CI环境里根本不会出现。
所以,CI的核心价值就两条:自动化和标准化。自动化解放人力,标准化消灭玄学。
CI工具选型:Jenkins vs GitLab CI
这两个我都深度用过。简单说下我的感受:
| 对比维度 | Jenkins | GitLab CI |
|---|---|---|
| 部署方式 | 需要单独服务器,安装配置较复杂 | 集成在GitLab中,开箱即用 |
| 插件生态 | 极其丰富,几乎什么都能干 | 相对精简,但够用 |
| Pipeline语法 | Declarative / Scripted Pipeline | YAML格式,学习成本低 |
| 多设备支持 | 通过节点标签灵活分配 | 需要注册多个Runner |
| 适合场景 | 大型团队,复杂构建矩阵 | 中小团队,与GitLab深度绑定 |
我个人习惯是:如果公司已经用了GitLab,那就直接用GitLab CI,省心。如果团队超过20人,或者需要同时维护多个Android版本(比如Android 12和13并行),Jenkins的矩阵构建能力会更顺手。
Jenkins Pipeline实战:AOSP多设备构建
先看一个我实际用过的Jenkinsfile片段。这个流水线支持同时构建三个设备:平板、车机、电视棒。
pipeline {
agent any
parameters {
choice(name: 'DEVICE', choices: ['all', 'tablet', 'car', 'tvstick'], description: '选择目标设备')
string(name: 'BUILD_TAG', defaultValue: 'daily', description: '构建标签')
}
stages {
stage('初始化环境') {
steps {
sh '''
export PATH=/opt/android-sdk/platform-tools:$PATH
source build/envsetup.sh
'''
}
}
stage('并行构建') {
parallel {
stage('平板') {
when { expression { params.DEVICE == 'all' || params.DEVICE == 'tablet' } }
steps {
sh 'lunch aosp_tablet-userdebug && make -j$(nproc) 2>&1 | tee build_tablet.log'
}
}
stage('车机') {
when { expression { params.DEVICE == 'all' || params.DEVICE == 'car' } }
steps {
sh 'lunch aosp_car_x86-userdebug && make -j$(nproc) 2>&1 | tee build_car.log'
}
}
stage('电视棒') {
when { expression { params.DEVICE == 'all' || params.DEVICE == 'tvstick' } }
steps {
sh 'lunch aosp_tvstick-arm64-userdebug && make -j$(nproc) 2>&1 | tee build_tvstick.log'
}
}
}
}
stage('打包与发布') {
steps {
script {
def devices = ['tablet', 'car', 'tvstick']
devices.each { device ->
sh """
cd out/target/product/${device}
zip -r ${BUILD_TAG}_${device}_ota.zip *
cp ${BUILD_TAG}_${device}_ota.zip /mnt/nfs/releases/
"""
}
}
}
}
}
post {
failure {
emailext(
subject: "AOSP构建失败: ${env.BUILD_TAG}",
body: "设备: ${params.DEVICE}\n请查看构建日志: ${env.BUILD_URL}",
to: 'team@company.com'
)
}
}
}
GitLab CI配置:轻量级方案
如果你团队用的是GitLab,那配置起来更简洁。下面是一个.gitlab-ci.yml的例子,它会在每次推送代码时自动构建指定设备,并把产物上传到Release页面。
variables:
DEVICE_LIST: "tablet car tvstick"
stages:
- build
- package
- release
before_script:
- source build/envsetup.sh
build:tablet:
stage: build
script:
- lunch aosp_tablet-userdebug
- make -j$(nproc)
artifacts:
paths:
- out/target/product/tablet/
expire_in: 1 week
only:
- tags
build:car:
stage: build
script:
- lunch aosp_car_x86-userdebug
- make -j$(nproc)
artifacts:
paths:
- out/target/product/car/
expire_in: 1 week
only:
- tags
package:
stage: package
needs: ["build:tablet", "build:car"]
script:
- for device in tablet car; do
cd out/target/product/$device;
zip -r ${CI_COMMIT_TAG}_${device}_ota.zip *;
mv *.zip ../../..;
done
artifacts:
paths:
- "*.zip"
expire_in: 1 month
release:
stage: release
needs: ["package"]
script:
- echo "发布版本 ${CI_COMMIT_TAG}"
release:
name: "Release ${CI_COMMIT_TAG}"
tag_name: ${CI_COMMIT_TAG}
description: "自动构建的AOSP固件包"
assets:
links:
- name: "平板固件"
url: "${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/file/tablet_ota.zip"
- name: "车机固件"
url: "${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/file/car_ota.zip"
多设备构建的坑与解法
我在项目中遇到过最头疼的问题,是不同设备对编译工具链的依赖冲突。比如平板用clang-14,车机必须用clang-12,但AOSP的prebuilt工具链是全局的。怎么解?
我的做法是:为每个设备创建独立的编译容器。用Docker封装好各自的环境,CI里直接拉取对应镜像。这样既隔离了依赖,又方便扩展新设备。
另一个常见问题是磁盘空间。AOSP一次完整构建能吃掉200GB以上,如果同时构建三个设备,磁盘很快就爆了。我建议在Jenkins里配置构建后清理策略:只保留最近两次的out目录,其余的自动删除。GitLab CI则可以利用artifacts的expire_in字段来控制保留时间。
版本发布:自动化签名与OTA
构建完只是第一步,怎么把固件安全地发出去才是关键。我习惯在CI流水线里集成签名步骤:
stage('签名') {
steps {
withCredentials([file(credentialsId: 'release-key', variable: 'KEYSTORE')]) {
sh '''
java -jar signapk.jar -w $KEYSTORE password \
out/target/product/tablet/system.img \
out/target/product/tablet/system_signed.img
'''
}
}
}
签名用的密钥绝对不能硬编码在脚本里。Jenkins用Credentials Binding插件,GitLab CI用CI/CD Variables的File类型,都是安全的方式。
发布时,我还会自动生成OTA差分包。这样用户升级时只需要下载几十MB的增量包,而不是整个系统镜像。生成差分包的脚本我放在AOSP源码的build/tools/releasetools/下,CI里直接调用ota_from_target_files即可。
知识体系总览
下面这张图是我自己整理的CI/CD在AOSP定制中的核心流程,你可以把它当作搭建流水线的路线图:
避坑指南:我踩过的三个大坑
- 缓存问题:我曾经因为ccache没配置好,导致每次构建都全量编译,白白浪费了三个小时。后来我在CI脚本里加了
export USE_CCACHE=1 && ccache -M 50G,构建时间直接缩短了60%。 - 并发冲突:多个构建同时写同一个out目录,会出现奇怪的链接错误。我的解决方案是让每个构建使用独立的输出目录,比如
make OUT_DIR=out_${DEVICE}_${BUILD_ID}。 - 密钥泄露:有一次我不小心把签名密钥打印到了构建日志里,虽然马上撤销了,但想想都后怕。从那以后,所有敏感信息我都用CI的secret变量管理,日志里用
set +x关闭命令回显。
总结
自动化构建CI这件事,投入产出比非常高。你花一周时间搭好流水线,后面每个月能省下几十个小时的手动操作。而且,标准化流程能大幅减少「在我机器上能跑啊」这种扯皮问题。
我个人建议,先从单设备CI开始跑通,再逐步扩展到多设备并行。别一上来就想搞个大而全的矩阵,容易把自己绕晕。记住,CI的核心是稳定可靠,不是花里胡哨。
核心要点回顾:
- Jenkins适合复杂矩阵和多节点调度,GitLab CI适合与代码仓库深度集成
- 多设备构建一定要做环境隔离(Docker或独立输出目录)
- 签名密钥永远不要硬编码,用CI的凭据管理功能
- 构建产物要设置自动清理策略,防止磁盘写满
- OTA差分包能大幅降低用户升级成本,值得集成到流水线中