Skip to content

26持续集成:如何实现无需人手的快速交付?

在前面的课程中,我们已经讲过如何使用 fastlane 来自动化常用的操作,例如管理私钥、证书和 Provisioning Profile 文件,打包和签名 App,以及把 App 上传到 App Store 等。有了这些自动化操作,我们就可以很方便地通过一条命令把 App 发布到 App Store。但有没有更好的办法做到不需要人手就能自动完成打包和上传等所有的操作呢?当然有!我们可以通过 CI,也就是持续集成(Continuous Integration)来完成这一任务。

那什么是 CI 呢?CI 是一种有效的工程实践,能帮助团队更频繁、更可靠地交付代码。我们可以利用 CI 来不断优化构建、测试和发布流程,从而保证产品的快速交付,推动工程化进程与最佳实践,并推进工程师文化的建设。

CI 的系统架构

要搭建一套完整的 CI,我们需要理解 CI 的构建流程和系统架构。首先,我们以 Moments App 为例子来看看一套完整 CI 的构建流程吧。

通过上图,我们可以看到一套 CI 流程主要由两大部分组成:GitHub 流程和 CI 管道。其中,GitHub 流程是 CI 流程的触发者,而 CI 管道是 CI 流程的执行者

我们在《06 | 代码管理:如何使用 Git 与 GitHub 统一代码管理流程?》里讲述过管理多个分支的代码流程,在此基础上,我们可以让分支在合并的时候自动触发 CI 流程,例如,在功能分支合并到主分支时触发 CI 管道 1,当主分支合并到发布分支时触发 CI 管道 2。

那 CI 管道到底是什么呢?CI 管道是自动化软件交付的流程定义,它把多个自动化操作串联起来,并按照一定的顺序执行,最终达到从源码到交付的全自动化。例如,CI 管道 1 执行编译、运行测试、打包和签名 AdHoc 版本的 App,以及上传到 Firebase 的 App 分发服务等操作。而 CI 管道 2 执行了打包和签名 AppStore 版本的 App ,以及上传到 AppStore 这两项操作。可以看到,在整个 CI 流程中没有任何手工操作的参与,都是由 GitHub 流程自动触发的。

那没有任何人手参与,CI 管道到底在哪里执行呢?答案能在下面 CI 的系统架构图中找到。

CI 架构通常由 CI中心服务 和 CI构建中介 (Build Agent)所组成。当 GitHub 发生变更时会通知 CI 中心服务,中心服务会把构建任务(Build Job)调度和分发给可用的 CI 构建中介。当接收到任务时,构建中介会从 GitHub 上下载代码并按照 CI 管道的配置来执行构建任务。对,CI 构建中介就是具体的执行者

目前流行的 CI 构建中介主要分成三大类。

  • 全手工维护 CI。该类 CI 从物理主机、操作系统、Ruby 环境以及 Xcode 版本都由开发团队维护。这类型的 CI 维护成本比较高,例如,需要管理物理主机和网络联通性,需要维护操作系统的安全更新等,但同时也给了我们很大管控性和灵活性。

  • 云端虚拟机 CI。该类 CI 是通过租用云 Mac 虚拟机来进行搭建,目前比较流行的 Mac 虚拟机服务提供商有亚马逊的 AWS 和 MacStadium。有了这些云服务提供商,我们就不用再进行安全补丁等常规维护了,只需选择特定的 Mac OS 版本来启动虚拟机,然后在虚拟机上执行脚本来搭建 Ruby 和 Xcode 环境即可,就如 Moments App 项目里执行 setup.sh 脚本就能完成项目搭建。

  • 全服务 CI。该类 CI 不仅为我们提供了虚拟机,而且还提供了 Ruby 和 Xcode 等环境,我们只需要提供一个 CI 管道配置文件就能完成这个 App 的自动构建。

没有一类 CI 是完美的,它们都有各自的优缺点。这三类 CI 从上往下看,维护成本越来越低,但从长远来看,运行成本却越来越高。从便利性来看,下面的类型会比上面的更加易用,但同时也牺牲了灵活性。

我建议你根据团队的自身情况来选择。假如你所在的团队没有专门的人员来维护 CI,可以从全服务 CI 开始。随着项目和团队的发展,慢慢地升级为云端虚拟机 CI 和全手工维护 CI。作为一个只有一个开发者的开源项目,Moments App 也选择了全服务 CI。

下面我们就以 Moments App 为例子,看看如何配置一个全服务 CI。

配置 Travis CI

Moments App 选择了 Travis CI 作为全服务 CI,原因有如下四个。

  • Travis CI 使用了"代码即配置"的方式来配置 CI 管道,这是最重要的一个原因。我们可以把 CI 管道的配置信息都写在一个 YAML 文件里面,并保存在 GitHub 上。这样能方便我们把 CI 配置共享到多个项目,而且通过 Git 历史记录来不断对比和优化 CI 配置。除此之外,YAML 文件的配置方式已成为 CI 配置的标准,当需要升级为云端虚拟机 CI 和全手工维护 CI 时,我们可以重用 Travis CI 的 YAML 文件。相比之下,有些 CI 需要在网页上进行手工配置,而且无法看到修改历史,这使得我们无法通过代码把配置信息共享到其他项目中去。

  • Travis CI 免费给开源项目使用。

  • Travis CI 整合了 GitHub 和 GitLab 等代码管理平台,只需要一次授权就能整合 CI 服务。

  • Travis CI 支持多个不同版本的 Mac OS 和 Xcode,我们可以根据项目的要求来灵活选择不同的版本。例如通过 Travis CI,我们可以方便地测试 Xcode Beta 版的构建情况。

连接 Travis CI 与 GitHub

要搭建 Travis CI,首先需要使用 GitHub 账户登录到 Travis CI,然后给 Travis CI 授权,如下图所示:

然后在 Travis CI 的配置页面上点击 Activate 按钮来激活 Travis CI 与 GitHub 的连接。

就这样,Travis CI 与 GitHub 的连接完成了。

假如你需要管理该连接,可以到 GitHub 的 Settings -> Applications -> Installed GitHub Apps 下点击 Configure 按钮来打开下面的管理页面。

配置 .travis.yml

完成 Travis CI 与 GitHub 的连接以后,下一步是通过 .travis.yml 文件来配置 CI 管道。.yml 也叫作 YAML 文件,全称是 YAML Ain't Markup Language (YAML 并不是标记语言)。相比其他标记语言,YAML 具有更好的可读性,非常适合来做配置文件。下面我们一起看看 Moments App 的 .travis.yml 文件吧。

yaml
language: swift
osx_image: xcode12.2
env:
  global:
    - CI_BUILD_NUMBER=${TRAVIS_BUILD_NUMBER}
before_install:
  - bundle install
  - bundle exec pod install

在 .travis.yml 文件的开头,我们定义了项目所使用的语言以及 Xcode 的版本号。接着定义全局的环境变量CI_BUILD_NUMBER,该变量的值来自TRAVIS_BUILD_NUMBERTRAVIS_BUILD_NUMBER的值由 Travis CI 系统所提供,它能帮助我们生成一个自增的 Build Number(构建数值)。

CI_BUILD_NUMBER会在 increment_build_number.sh 脚本中使用,如下代码所示:

java
VERSION_XCCONFIG="Moments/Moments/Configurations/BaseTarget.xcconfig"
SED_CMD="s/\\(PRODUCT_VERSION_SUFFIX=\\).*/\\1${CI_BUILD_NUMBER}/" # Make sure setting this environment variable before call script.
sed -e ${SED_CMD} -i.bak ${VERSION_XCCONFIG} 
rm -f ${VERSION_XCCONFIG}.bak

这个 Shell 会更新 BaseTarget.xcconfig 文件里面的PRODUCT_VERSION的值。在下面 CI 管道的配置里,你将会看到如何使用该值。

最后是定义before_install步骤,该步骤会在每一个构建任务执行前先运行。在 Moments App 的 CI 里,我们在该步骤里安装了 Bundler 和 CocoaPods 的各个依赖项。

准备工作做完以后,我们看看如何配置 CI 管道的构建任务。

yaml
jobs:
  include:
    - stage: "Build"
      name: "Build internal app"
      script:
        - set -o pipefail
        - echo "machine github.com login $GITHUB_API_TOKEN" >> ~/.netrc
        - bundle exec fastlane download_profiles
        - bundle exec fastlane archive_internal

所有的 CI 管道都配置在jobs下面,首先看一下用于构建的Build任务。为了让 CI 可以访问存放在 GitHub 私有 Repo 里面的私钥、证书和 Provisioning Profile 文件,我们要为 Travis CI 配置GITHUB_API_TOKEN。在《24 | 解决打包痛点:如何统一管理 Certificates 和 Profiles?》 里,我们讲过如何获取 GitHub Access Token,假如你记不清楚了,可以回去复习一下。当拿到 GitHub Access Token 以后,就可以在 Travis CI 上的项目 Settings 页面里面进行添加,如下图所示:

同时,我们还需要把 local.keys 文件的其他环境变量一同加上。

配置好GITHUB_API_TOKEN环境变量后,我们就可以调用 fastlane 的download_profilesarchive_internal来完成 Build 的步骤了。

下面看看执行测试的Test任务。

yaml
- stage: "Test"
  name: "Test app"
  script:
    - set -o pipefail
    - bundle exec fastlane tests

执行测试的操作非常简单,只需要执行 fastlane 的tests即可。

接着再看看打包和部署 Internal App 的任务,具体代码如下:

yaml
- stage: "Archive, sign and deploy internal app"
  name: "Archive Internal app"
  if: branch = main
  script:
    - set -o pipefail
    - echo "machine github.com login $GITHUB_API_TOKEN" >> ~/.netrc
    - bundle exec fastlane download_profiles
    - ./scripts/increment_build_number.sh
    - bundle exec fastlane archive_internal
    - bundle exec fastlane deploy_internal

为了访问 GitHub 私有 Repo,我们也需要把GITHUB_API_TOKEN配置到 .netrc 文件里面,然后就可以下载私钥、证书和 Provisioning Profile 等文件了。接着是执行increment_build_number.sh来更新PRODUCT_VERSION的值。因为 Info.plist 文件引用了更新过的PRODUCT_VERSION,每次打包时,App 都具备不同的 Build Number。最后是执行 fastlane 的archive_internaldeploy_internal来完成打包和上传任务。

最后一个是打包和上传 Production 版本的 App 到 App Store,具体配置代码如下:

yaml
- stage: "Archive, sign and deploy production app"
  name: "Archive Production app"
  if: branch = release
  script:
    - set -o pipefail
    - echo "machine github.com login $GITHUB_API_TOKEN" >> ~/.netrc
    - bundle exec fastlane download_profiles
    - ./scripts/increment_build_number.sh
    - bundle exec fastlane archive_appstore
    - bundle exec fastlane deploy_appstore

这个在步骤上与打包和部署 Internal App 的任务基本一致,不同的地方是,我们通过条件判断语句if: branch = release来构建发布分支而不是主分支,并且最后调用的是 fastlane 的archive_appstoredeploy_appstore来打包和发布到 App Store。

到这里,Moments App 的 CI 就配置完毕了,其执行效果如下:

总结

在这一讲,我们讲述了 CI 的系统架构和构建流程,并且还以 Moments App 为例子讲述了如何使用 Travis CI 来搭建一套完整的 CI。在此基础上,你可以根据项目的具体需求,不断地完善 CI 管道,并推动项目的自动化与工程化建设。CI 是每个工程化团队所必备的,我建议每个团队都建立起自己的 CI 并不断优化。CI 不仅能解放所有手工操作,还能减少错误的发生,更重要的是 CI 能推动一个团队工程师文化的发展。

思考题

你可能已经注意到,在 .travis.yml 文件里面,我们都是调用 fastlane 里面的 Lane 来完成具体的构建任务,这样做有什么好处呢?

可以把你的答案写到留言区哦。从下一讲开始,我们就进入本模块的下半部分------App 上架后的优化,我会先介绍如何使用 Firebase 的统计分析服务,记得按时来听课。

源码地址

.travis.yml 文件地址:https://github.com/lagoueduCol/iOS-linyongjian/blob/main/.travis.yml