Android 自动化编译之 Github Actions

发布于:

编程

Actions 是 Github 2019年推出的自动化集成编译工具,在漫长的 Beta 等待后我也在去年获得了优先体验,当时也写了 actions-android-ci 的第一个版本,用来编译 CallerInfo 项目。

最近在写一个私有项目,自动化 CI 自然是必不可少的,为了简单方便,托管在了 Github。使用去年编写的 [email protected] 很快完成了编译,同时增加了对每次编译 apkmapping.txt 上传到 artifacts 的支持。

但是每次编译都需要将近 8 分钟,而每个月只有 2000 分钟的免费编译时长,因此减少编译时间很重要。查看编译的日志,发现有 30 秒在编译 docker 镜像,还有 6 分钟都在编译,其中大部分时间都在下载 gradle 依赖。

所以通过预编译 docker 镜像和增加 sdk 及 gradle 依赖缓存可以大幅减少编译运行时间。

actions build

集成编译优化过程

1. 使用预编译 docker 镜像

在 v1.2.1 版本, Github Actions 在每次运行时都会从 actions-android-ci 仓库拉取最新的代码,然后根据 action.yml 文件中的配置,编译新的 docker 镜像。由于这个镜像包括 openjdk 等依赖,每次编译都会去拉取大量数据并重新编译 docker image:

runs:
  using: 'docker'
  image: 'Dockerfile'

直接从 docker hub 拉起最新的镜像则可以减少每次制作新镜像的耗时:

runs:
  using: 'docker'
  image: 'docker://xdtianyu/actions-android-ci'

2. 增加 sdk 及 gradle 缓存

这个 docker 复用了 Gitlab CI,而当时为了优化在 Gitlab runner 中编译,已经增加了缓存的支持,只是存储在了 /opt/cache/gradle /opt/sdk /opt/ndk 几个目录下,在 Gitlab Runner 运行时是预先挂载了宿主机的这几个目录来实现 runner 缓存的。

而 Github 目前是不支持使用 docker volume 挂载参数的,所以需要将这几个目录设定在可以访问的目录下,最后缓存这几个目录。所以重新编写了下载 sdk 的脚本和环境变量:

echo $GITHUB_WORKSPACE

SDK=/opt/sdk
NDK=/opt/ndk
GRADLE=/opt/cache/gradle

if [ ! -z "$GITHUB_WORKSPACE" ]; then
    SDK="$GITHUB_WORKSPACE/.opt/sdk"
    NDK="$GITHUB_WORKSPACE/.opt/ndk"
    GRADLE="$GITHUB_WORKSPACE/.opt/cache/gradle"
fi
# ... setup sdk ...
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$SDK/emulator/:$SDK/tools/bin:$SDK/tools:$SDK/platform-tools:$NDK:$SDK/cmake/3.10.2.4988404/bin
export ANDROID_HOME=$SDK
export ANDROID_SDK=$SDK
export ANDROID_SDK_ROOT=$SDK
export GRADLE_USER_HOME=$GRADLE

当有 GITHUB_WORKSPACE 环境变量时,说明当前运行环境为 Github Actions 环境,需要将 sdk 目录设定在 $GITHUB_WORKSPACE/.opt/sdk 目录下。完整内容请阅读代码 setup-android-sdk.sh

编译新的镜像并上传到 docker hub: xdtianyu/actions-android-ci (这里使用了 docker hub 的自动编译)。

然后在 .github/workflows/android.yml 编译前增加缓存配置:

    - name: Cache gradle and sdk
      uses: actions/cache@v2
      env:
        cache-name: cache-gradle-and-sdk
      with:
        path: |
          ${{ github.workspace }}/.opt/cache/gradle/wrapper
          ${{ github.workspace }}/.opt/cache/gradle/caches
          ${{ github.workspace }}/.opt/sdk
        key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/wrapper/gradle-wrapper.properties', '**/build.gradle') }}
        restore-keys: |
          ${{ runner.os }}-build-${{ env.cache-name }}-
          ${{ runner.os }}-build-
          ${{ runner.os }}-

这样,每次编译时都会下载 .opt/sdk 等目录下的缓存文件,并在编译完成后检查缓存是否需要更新,如需更新则上传新的目录。

这里的缓存校验使用了 build.gradlegradle-wrapper.properties ,如果这些文件被修改则说明需要更新缓存,否则跳过新建缓存的过程。可以根据自己需要调整这里的校验配置。

3. 加密的环境变量 Secrets

可以在项目 Settings -> Secrets 中配置加密的环境变量,这里配置的环境变量很安全,有特殊处理,不会在日志中显示。同时也不会传递由 Fork 发起的 Pull Request

secrets

我习惯性的使用了上图中的5个环境变量,用来将发布签名加密保存在代码仓库中。其中 ENCRYPTED_IVENCRYPTED_KEY 是使用 openssl 加密的 secrets.tar.enc 文件相关密钥。通过如下命令解密:

openssl aes-256-cbc -K $ENCRYPTED_KEY -iv $ENCRYPTED_IV -in secrets.tar.enc -out secrets.tar -d

如果不需要,可以在参考代码脚本中移除这部分内容。

在这个脚本中有详细的说明可以参考学习: .travis/env.sh

4. 增加 artifacts 上传

每次自动编译都会生成 apk 文件,可以将其和混淆文件一起发布出来提供下载。只需要增加如下 artifacts 配置即可完成对每次编译 apkmapping.txt 文件上传。类似于 Gitlab job,在每个 Action 编译成功后都会有一个 Artifacts 链接能用来下载:

- name: Upload artifacts
  uses: actions/upload-artifact@v2
  with:
    name: artifacts
    path: |
      app/**/apk/release/*
      app/**/mapping/release/mapping.txt

artifacts

完整的配置文件请阅读 xdtianyu/actions-android-ci README 文件。

总结

我从事开发过程的一大乐趣就是写各种各样的自动化脚本,在工作中也承担了内部 Gitlab 及 CI 的搭建维护工作。在 CallerInfo 项目中,已经集成了各式各样的 CI 服务如:Travis-ciGitlab runnerJenkinsAppVeyorGithub Actions,也集成过 CircleCI 等服务,但是担心过多的权限要求导致我的其他私有仓库泄漏,所以没有集成。

本文主要是对 Gtihub Actions Android 应用编译缓存方面的实现,附带的介绍了加密环境变量及上报 artifacts ,没有涉及版本发布、自动化发布报告(sha1/commits/changes)和其他更多好玩的内容。这些内容已经在 Travis ci 上实现过了,就不再赘述,感兴趣的读者可以参考学习我的开源项目 CallerInfo 中的 .travis 目录和 .travis.yml 文件。

CI 平台大同小异,常见的几种平台中,AppVeyor 是在 Windows 宿主机上编译的,如果你也喜欢写自动化CI,建议也不要错过 AppVeyor