iOS 持续集成 --Travis CI + Fir.im 自动编译发布

1,548 阅读5分钟

写在前面

持续集成一直是现在非常热门的话题,通过持续集成可以自动编译项目,并且配合单元测试可以实现持续集成+自动化测试。我在结合CI的基础上,通过fir-cli的发布命令,完成了持续集成+自动部署。这样能够很好的减少测试、发包等一系列重复的工作,极大的提高了效率。

持续集成的优点:

  • “快速失败”,在对产品没有风险的情况下进行测试,并快速响应;
  • 最大限度地减少风险,降低修复错误代码的成本;
  • 将重复性的手工流程自动化,让工程师更加专注于代码;
  • 保持频繁部署,快速生成可部署的软件;
  • 提高项目的能见度,方便团队成员了解项目的进度和成熟度;
  • 增强开发人员对软件产品的信心,帮助建立更好的工程师文化。

这边推荐一篇文章给大家,谈谈持续集成,持续交付,持续部署之间的区别

Travis CI

如果你的项目是托管在 Github 上,那么 Travis CI 是做持续集成非常好的选择。在 Ruby 的世界中,Travis CI 已久负盛名,从 2013 年 4 月起,Travis 也开始支持 iOS 和 Mac 平台。

本文主要来一步步讲解在项目中集成 Travis CI,不仅包括项目的编译和单元测试的运行,还包括将应用部署到 Fir.im 的测试设备上。

与Github集成

首先需要到 Travis CI 官网上,使用自己的 Github 进行登录。登录后新建与 Github Repo 的关联,如下图所示: new repo

选择需要管理的 Repo,把前面的按钮置为绿色就关联 OK,这样 Travis CI 这边的配置就完成了: connect repo

然后去 Github 关联的 Repo 中,找到 Settings - Webhooks&Services 中添加 Webhook 即可,不需要填信息,直接 test 就能通过,如下图所示: Github Webhook

按照上述步骤,就成功的将 Travis CI 和 Github 关联起来了。

配置Travis CI

Travis CI 通过配置文件 .travis.yml 来工作,文件存放在项目的根目录下,接下里我们就来配置这个文件。

基础配置

虽然我们是 Swift 项目,但是在 Travis CI 支持的语言库里面仅支持 Objective-C,不过 iOS 项目不管哪种语言使用这个配置都可以进行部署。

Travis 编译器运行在虚拟机环境下。该编译器已经利用 Ruby,Homebrew,CocoaPods,xctool 和一些默认的编译脚本进行过预配置。上述的配置项已经足够编译你的项目了。 预装的编译脚本会分析你的 Xcode 项目,并对每个 target 进行编译。如果所有文件都没有编译错误,并且测试也没有被打断,那么项目就编译成功了。现在可以将相关改动 Push 到 GitHub 中看看能否成功编译。

接下来,我们配置其他的 iOS 项目的基础配置。

编译命令

接下来我们针对项目来使用自定义编译命令。iOS 项目编译使用 xcodebuild,Travis 提供了 xcodebuild 和 Xctool。xcodebuild 是Apple提供的命令,而 Xctool 是来自 Facebook 的命令行工具。推荐使用 Xctool,本篇教程也是使用 Xctool 来进行编译命令的编写。

Xctool 是来自 Facebook 的命令行工具,它可以简化程序的编译和测试。它的彩色输出信息比 xcodebuild 更加简洁直观。同时还添加了对逻辑测试,应用测试的支持。

language: objective-c
script:
- xctool -workspace $APP_NAME.xcworkspace -scheme $APP_NAME -sdk iphoneos  -configuration Release OBJROOT=$PWD/build SYMROOT=$PWD/build ONLY_ACTIVE_ARCH=NO CODE_SIGN_IDENTITY="$DEVELOPER_NAME"

我这边给的配置命令,是直接最后能够发布到发包平台编译命令,通过 Travis 能自动将应用部署到我们的所有测试设备上。要使这个命令编译通过,我们需要进行如下的配置。

程序签名

为了在 Travis 中能给程序签名,我们需要准备好所有必须的证书和配置文件。就像每个 iOS 开发人员知道的那样,这可能是最困难、最容易出错的一步。

iPhone 发布证书 + 私钥

我们可以在自己的开发环境将证书和私钥给导出来。如果还没有发布证书的话,先创建一个。登录苹果开发者账号,按照步骤,创建一个新的生产环境证书 (Certificates > Production > Add > App Store and Ad Hoc)。然后下载并安装证书。之后,可以在钥匙串中找到它。打开 Mac 中的 钥匙串 应用程序: Certificates

右键单击证书,选择 Export… 将证书导出至 scripts/certs/dist.cer。然后导出私钥并保存至 scripts/certs/dist.p12。记得输入私钥的密码。 备注:scripts 文件夹在项目根目录下。

由于 Travis 需要知道私钥密码,因此我们要把这个密码存储在某个地方。当然,我们不希望以明文的形式存储。我们可以用 Travis 的安全环境变量。打开终端,并定位到包含 .travis.yml 文件所在目录。首先用下面的命令安装 Travis gem:

之后,用下面的命令添加密钥密码:

travis encrypt "KEY_PASSWORD={password}" --add

上面的命令会安装一个叫做 KEY_PASSWORD 的加密环境变量到 .travis.yml 配置文件中。这样就可以在被 Travis CI 执行的脚本中使用这个变量。

iOS 配置文件 (发布)

如果还没有用于发布的配置文件,那么也创建一个新的。根据开发者账号类型,可以选择创建 Ad Hoc 配置文件 (Provisioning Profiles > Distribution > Add > Ad Hoc)。然后将其下载保存至 scripts/profile/ 目录。

由于 Travis 需要访问这个配置文件,所以我们需要将这个文件的名字存储为一个全局环境变量。并将其添加至 .travis.yml 文件的全局环境变量 section 中。例如,如果配置文件的名字是 YFSwiftFramework_Ad_Hoc.mobileprovision,那么按照如下进行添加:

env:
  global:
  - APP_NAME="YFSwiftFramework"
  - 'DEVELOPER_NAME="iPhone Distribution: {your_name}"'
  - PROFILE_NAME="YFSwiftFramework_Ad_Hoc"
  

上面还声明了三个环境变量。

  • APP_NAME 通常为项目默认 target 的名字。
  • DEVELOPER_NAME 是 Xcode 中,默认 target 里面 Build Settings 的 Code Signing Identity > Release 对应的名字。
  • PROFILE_NAME 是配置文件的名字。

加密证书和配置文件

如果你的 GitHub 仓库是公开的,你可能希望对证书和配置文件 (里面包含了敏感数据) 进行加密。如果你用的是私有仓库或者不想加密的证书和配置文件的,可以跳至下一节。

首先,我们需要一个密码来对所有的文件进行加密。本文用密码 “123” 来进行举例。在Terminal中输入下面三个命令:

openssl aes-256-cbc -k "123" -in scripts/profile/YFSwiftFramework_Ad_Hoc.mobileprovision -out scripts/profile/YFSwiftFramework_Ad_Hoc.mobileprovision.enc -a
openssl aes-256-cbc -k "123" -in scripts/certs/dist.cer -out scripts/certs/dist.cer.enc -a
openssl aes-256-cbc -k "123" -in scripts/certs/dist.p12 -out scripts/certs/dist.p12.enc -a

通过上面的命令,可以创建出以 .enc 结尾的加密文件。之后可以把原始文件忽略或者移除掉。至少不要把原始文件提交到 GitHub 中,否则原始文件会显示在 GitHub 中。

接下来,我们告诉Travis如何对我们加密的文件就进行解密。解密过程,需要用到密码。具体使用方法跟之前创建的 KEY_PASSWORD 变量一样,输入下面命令:

travis encrypt "ENCRYPTION_SECRET=123" --add

最后,我们需要告诉 Travis 哪些文件需要进行解密。将下面的命令添加到 .travis.yml 文件中的 before-script 部分:

before_script:
- openssl aes-256-cbc -k "$ENCRYPTION_SECRET" -in scripts/profile/$PROFILE_NAME.mobileprovision.enc
  -d -a -out scripts/profile/$PROFILE_NAME.mobileprovision
  - openssl aes-256-cbc -k "$ENCRYPTION_SECRET" -in scripts/certs/dist.cer.enc -d -a
  -out scripts/certs/dist.cer
  - openssl aes-256-cbc -k "$ENCRYPTION_SECRET" -in scripts/certs/dist.p12.enc -d -a
  -out scripts/certs/dist.p12
  

before-script是编译脚本执行之前,会执行的操作。这样Travis就回在我们执行编译脚本之前将我们的文件解密还原成原来的文件了。

添加脚本

现在我们需要确保证书都导入至 Travis CI 的钥匙串中。为此,我们需要在 scripts 文件夹中添加一个名为 add-key.sh 的文件:

#!/bin/sh
security create-keychain -p travis ios-build.keychain
security default-keychain -s ios-build.keychain
security unlock-keychain -p travis ios-build.keychain
security set-keychain-settings -t 3600 -l ~/Library/Keychains/ios-build.keychain
security import ./scripts/certs/dist.cer -k ~/Library/Keychains/ios-build.keychain -T /usr/bin/codesign
security import ./scripts/certs/dist.p12 -k ~/Library/Keychains/ios-build.keychain -P $KEY_PASSWORD -T /usr/bin/codesign
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp ./scripts/profile/$PROFILE_NAME.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/
security find-identity -p codesigning ~/Library/Keychains/ios-build.keychain
security list-keychain

通过上面的命令创建了一个名为 ios-build 的临时钥匙串,里面包含了所有证书。注意,这里我们使用了 $KEY_PASSWORD 来导入私钥。最后一步是将配置文件拷贝至 Library 文件夹。

创建好文件之后,确保给其授予了可执行的权限:在命令行输入:chmod a+x scripts/add-key.sh 即可。为了正常使用脚本,必须要这样处理一下。

指定证书签名编译

至此,已经导入了所有的证书和配置文件,我们可以开始给应用程序签名了。接下来我们就编译用于我们证书签名的应用程序。我建议在编译命令中使用 OBJROOT 和 SYMROOT 来指定输出目录。另外,为了创建 release 版本,还需要把 SDK 设置为 iphoneos,以及将 configuration 修改为 Release:

script:
- xctool -workspace $APP_NAME.xcworkspace -scheme $APP_NAME -sdk iphoneos  -configuration Release OBJROOT=$PWD/build SYMROOT=$PWD/build ONLY_ACTIVE_ARCH=NO CODE_SIGN_IDENTITY="$DEVELOPER_NAME"

打包IPA

我们需要在 scripts 文件夹中添加一个名为 sign-and-upload.sh 的文件,将应用程序的二进制文件打包成IPA文件:

#!/bin/sh
if [[ "$TRAVIS_PULL_REQUEST" != "false" ]]; then
  echo "This is a pull request. No deployment will be done."
  exit 0
  fi
if [[ "$TRAVIS_BRANCH" != "master" ]]; then
  echo "Testing on a branch other than master. No deployment will be done."
  exit 0
  fi
PROVISIONING_PROFILE="$HOME/Library/MobileDevice/Provisioning Profiles/$PROFILE_NAME.mobileprovision"
OUTPUTDIR="$PWD/build/Release-iphoneos"
xcrun -log -sdk iphoneos PackageApplication "$OUTPUTDIR/$APP_NAME.app" -o "$OUTPUTDIR/$APP_NAME.ipa"

第二行至第九行非常重要。我们并不希望在某个特性分支上创建新的 release。对 pull 请求也一样的。由于安全环境变量被禁用,所以 pull 请求也不会编译。

第十四行,才是真正的签名操作。这个命令会在 build/Release-iphoneos 目录生成 2 个文件:TravisExample.ipa 和 TravisExample.app.dsym。第一个文件包含了分发至手机上的应用程序。dsym 文件包含了二进制文件的调试信息。这个文件对于记录设备上的 crash 信息非常重要。之后当我们部署应用程序的时候,会用到这两个文件。

创建好文件之后,确保给其授予了可执行的权限:在命令行输入:chmod a+x scripts/sign-and-upload.sh 即可。为了正常使用脚本,必须要这样处理一下。

执行脚本

我们需要在 scripts 文件夹中添加一个名为 remove-key.sh 的文件,移除之前创建的临时钥匙串,并删除配置文件。虽然这不是必须的,不过这有助于进行本地测试。

#!/bin/sh
security delete-keychain ios-build.keychain
rm -f ~/Library/MobileDevice/Provisioning\ Profiles/$PROFILE_NAME.mobileprovision

最后一步,我们必须告诉 Travis 什么时候执行这三个脚本。在应用程序编译、签名和清除等之前,需要先添加私钥。在 .travis.yml 文件中添加如下内容:

before_script:
- "./scripts/add-key.sh"
script:
- xctool -workspace $APP_NAME.xcworkspace -scheme $APP_NAME -sdk iphoneos  -configuration Release OBJROOT=$PWD/build SYMROOT=$PWD/build ONLY_ACTIVE_ARCH=NO CODE_SIGN_IDENTITY="$DEVELOPER_NAME"
after_success:
- "./scripts/sign-and-upload.sh"
after_script:
- "./scripts/remove-key.sh"

部署IPA到Fir.im

创建一个蒲公英账号,并配置好应用程序。配置好应用程序后蒲公英会为每个应用程序创建一个用户Token,我们需要确保它们是加密的。执行以下命令:

1
travis encrypt "FIR_APP_TOKEN={Token}}" --add

编辑 scripts/sign-and-upload.sh 文件,增加以下脚本:

if [ ! -z "$FIR_APP_TOKEN" ]; then
  echo ""
  echo "***************************"
  echo "*   Uploading to Fir.im   *"
  echo "***************************"
  fir p $OUTPUTDIR/$APP_NAME.ipa \
  -T $FIR_APP_TOKEN
  fi

最后需要添加 Fir.im 上传命令的环境。在 .travis.yml 文件中添加如下内容:

before_install:
- brew update
- gem install fir-cli

到这里就实现了持续集成、持续交付,并且持续部署到Fir.im,是不是很简单呢!

示例项目

参考

版权声明

Ivan’s Blog by Ivan Ye is licensed under a Creative Commons BY-NC-ND 4.0 International License. 由叶帆创作并维护的叶帆的博客博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证

本文首发于Ivan’s Blog | 叶帆的博客博客( yeziahehe.com ),版权所有,侵权必究。

本文链接:yeziahehe.com/2016/08/07/…