持续集成:CI 环境搭建、自动化编译、编译结果验证

各位同学,今天我们来聊聊持续集成。说实话,我刚入行那会儿,Android 编译还是件挺「手工」的事——改完代码,自己敲 make,等半小时,然后祈祷别报错。后来项目大了,团队人多了,这种搞法根本撑不住。于是,CI 就成了必需品。

CI 是什么?说白了就是:你提交代码,机器自动帮你编译、测试、出报告。你不用守着终端,也不用担心漏掉什么。我个人的经验是,CI 搭得好,能省下至少 30% 的调试时间。

CI 环境搭建:选工具、配机器

先说说环境。Android 编译对机器要求不低。我建议至少 16 核 CPU、64GB 内存、500GB 以上 SSD。磁盘空间尤其重要——一次完整编译可能吃掉 200GB。

工具方面,主流选择有 Jenkins、GitLab CI、GitHub Actions。我个人偏爱 GitLab CI,因为它和代码仓库集成得紧,配置也简单。不过 Jenkins 更灵活,适合定制需求多的团队。

搭建步骤大致如下:

  1. 准备编译服务器:装好 Ubuntu 20.04 或 22.04,安装 JDK、Python、Git、Repo 等基础工具。
  2. 安装 CI 工具:以 GitLab CI 为例,注册 Runner,配置 executor 为 shell 或 docker。
  3. 配置编译环境:下载 Android 源码,设置环境变量,确保 lunchmake 能跑通。
  4. 编写 CI 配置文件:在项目根目录创建 .gitlab-ci.yml
小提示: 我第一次搭 CI 时,忘了给 Runner 装足够的依赖,结果编译到一半报错找不到 ninja。嗯,从那以后我养成了习惯——先在服务器上手动跑一遍完整编译,再写 CI 脚本。

自动化编译:写脚本、定策略

自动化编译的核心是 CI 脚本。拿 GitLab CI 举例,一个典型的 .gitlab-ci.yml 长这样:

stages:
  - build
  - test
  - verify

build_aosp:
  stage: build
  script:
    - source build/envsetup.sh
    - lunch aosp_arm64-eng
    - make -j$(nproc) 2>&1 | tee build.log
  artifacts:
    paths:
      - out/
    expire_in: 1 week

这里有几个关键点:

  • 并行编译-j$(nproc) 用满所有 CPU 核心。我见过有人写死 -j8,结果换了台 32 核机器反而慢了——浪费啊。
  • 日志输出:用 tee 把日志存下来,方便排查问题。
  • 产物保留:编译出来的镜像、符号表等,设置过期时间,别把磁盘撑爆。

你可能会问:每次提交都全量编译?那太慢了。没错,所以 CI 里要加增量编译策略。比如只编译改动过的模块:

build_module:
  stage: build
  script:
    - source build/envsetup.sh
    - lunch aosp_arm64-eng
    - mmm packages/apps/Settings/  # 只编译 Settings 模块
    - make -j$(nproc) snod         # 重新打包 system.img

这样做的好处是快——全量编译可能要 1 小时,增量编译可能只要 5 分钟。但要注意,模块间的依赖关系要理清楚。我曾经踩过坑:只编译了 Settings,没编译它的依赖库,结果打包出来的镜像跑不起来。

编译结果验证:不只是「没报错」

编译通过了,是不是就万事大吉?当然不是。我见过太多「编译成功,但刷机就黑屏」的案例。所以,验证环节必不可少。

验证分几个层次:

验证类型 检查内容 常用工具
编译日志检查 是否有 warning、error、undefined reference grep、awk
产物完整性检查 镜像文件是否存在、大小是否合理 ls -lh、file 命令
签名验证 APK 是否已签名、签名是否匹配 apksigner、jarsigner
基本功能测试 刷机后能否开机、ADB 能否连接 fastboot、adb

举个例子,我写过一个简单的验证脚本:

#!/bin/bash
# 验证编译产物
OUT_DIR=out/target/product/generic_arm64

# 检查 boot.img 是否存在且大于 10MB
if [ ! -f "$OUT_DIR/boot.img" ] || [ $(stat -c%s "$OUT_DIR/boot.img") -lt 10485760 ]; then
  echo "ERROR: boot.img 缺失或太小"
  exit 1
fi

# 检查 system.img 是否存在
if [ ! -f "$OUT_DIR/system.img" ]; then
  echo "ERROR: system.img 缺失"
  exit 1
fi

# 检查 APK 签名
for apk in $OUT_DIR/system/app/*/*.apk; do
  if ! apksigner verify "$apk" &>/dev/null; then
    echo "WARNING: $apk 未签名"
  fi
done

echo "验证通过"
注意: 验证脚本一定要放在 CI 流程的最后一步。如果验证失败,CI 应该标记为失败,并阻止产物被部署。我曾经见过有人把验证结果只写进日志,不阻断流程——结果测试同学刷了个坏镜像,排查了半天。

SVG 流程图:CI 流水线概览

下面这张图展示了典型的 Android CI 流水线。你可以看到,从代码提交到最终验证,每一步都有明确的输入输出。

Android CI 流水线 1. 代码提交 (git push) 2. 触发 CI Runner 3. 环境准备 (source env) 4. 自动化编译 (make) 5a. 日志检查 (grep) 5b. 产物验证 (签名/大小) 两个验证并行执行,全部通过才算成功

核心要点:

  • CI 环境要稳定,最好用专用编译服务器,别跟开发机混用。
  • 自动化编译要支持增量,别每次都全量编译——时间就是金钱。
  • 验证环节不能省,至少检查产物是否存在、大小是否合理、签名是否有效。

好了,这一章就到这里。CI 这东西,搭起来不难,但想搭得好、用得顺,还是得在实践中慢慢调。你想想看,如果每次提交都能自动编译、自动验证,是不是省心多了?

公众号:蓝海资料掘金营,微信 deep3321