Fastlane + Jenkins 搭建简单的 iOS 持续集成平台

背景

对于一个产品,肯定有打包测试的需求。如果每次打包,都需要通过原始的 Xcode + Archive 方式(或者脚本打包)的话特别麻烦,需要打包的人有 Mac 系统和 Xcode,还需要拉代码,打包的时候还需要 Build 号加 1,成本非常高。构建一个这样的持续平台,每个人只要能连上 Jenkins,都能打包,非常方便。

为什么要自己写一篇呢?就是因为在网上找不到一篇可以完整把平台搭起来的文章,自己搭的过程中,也遇到了不少问题,所以就在这里整理一下。

说明:本文不并对 Fastlane 和 Jenkins 进行介绍了,主要是搭建步骤的记录。

搭建环境

  • Xcode 10.2.1
  • MacOS 10.14.5
  • JDK 1.8
  • cocoapods 1.7.3
  • ruby 2.6.3
  • Homebrew 2.1.6
  • Fastlane 2.126.2
  • Jenkins 2.183

Fastlane 部分

安装 Fastlane

如果 ruby 版本满足要求(这里就不介绍 ruby 安装了,网上资料很多),可以直接在命令行执行以下命令安装 Fastlane

#安装fastlane
sudo gem install -n /usr/local/bin fastlane

初始化项目

使用 Fastlane 初始化你要集成的项目

cd 项目目录
bundle exec fastlane init

之后会让选 打包的目的,有 4 种,可以参见下面的代码

  1. Automate screenshots
  2. ‍✈️ Automate beta distribution to TestFlight
  3. Automate App Store distribution
  4. Manual setup - manually setup your project to automate your tasks
xxx:xxxxx xxx$ bundle exec fastlane init
[✔]  
[✔] Looking for iOS and Android projects in current directory...
[13:33:35]: Created new folder './fastlane'.
[13:33:35]: Detected an iOS/macOS project in the current directory: 'G100.xcworkspace'
[13:33:35]: -----------------------------
[13:33:35]: --- Welcome to fastlane  ---
[13:33:35]: -----------------------------
[13:33:35]: fastlane can help you with all kinds of automation for your mobile app
[13:33:35]: We recommend automating one task first, and then gradually automating more over time
[13:33:35]: What would you like to use fastlane for?
1.   Automate screenshots
2. ‍✈️  Automate beta distribution to TestFlight
3.   Automate App Store distribution
4.   Manual setup - manually setup your project to automate your tasks
?

这里我选的 2 ,因为我们现在主要还是用 TestFlight 包来测试。
选完后,会需要输入 AppleID 和 密码等信息,在问了你的 Apple ID,Team 的问题之后,Fastlane 会自动检测当前目录下项目的 App Name 和 App Identifier。会将信息自动保存中 Fastlane 目录下的 AppFile 文件中

这里要注意,这里的 AppleID 必须是苹果开发者账号,普通账号是不行的。
我还遇到了一个bug,我账号本身是苹果开发者账号(但是已经过期了),同时也在另一个开发者账号 Team 下,在输入我的账号过程中,虽然中途是出现了选择 Team,但是选完之后,还是报错,说我的账号不在真正的开发者账号的 Team 下。所以没办法,换了一个账号,不知道有没有好的解决方案?

在项目初始化结束后,会生成一个 fastlane文件夹,类似下面这样

fastlane
├── Appfile
├── Fastfile

其中:
Appfile 主要存储 App 相关的一些信息,比如AppleID,bundleId等。
Fastfile 就是我们要写打包代码的地方了

开始写打包脚本了

我们先来看看 Fastfile 里面长什么样

default_platform(:ios)

platform :ios do
  # 切到 develop 分支
  sh 'git checkout develop'
  # 代码
  git_pull
  # build 号加1
  increment_build_number_in_plist(
    target: "xxx"
  )
  # 下面是拿到新的版本号,提交代码
  build_number = get_build_number_from_plist(target: "xxx")
  git_commit(path:".", message:"Bump build to #{build_number}")
  sh 'git push origin develop'
  
  # pod install
  cocoapods(repo_update: false)

  # 打包上传到 TestFlight
  lane :tf do
    desc "Push a new beta build to TestFlight"
    build_app(
      workspace: "xxx.xcworkspace", 
      scheme: "xxx"
    )
    # 上传到 TestFlight,这里是上传完成就结束了,不等到到苹果那边处理完
    upload_to_testflight(skip_waiting_for_build_processing: true)
  end

  # 打测到包到第三方平台,这里是走的蒲公英
  lane :adhoc do
    desc "Push a new beta build to pgyer"
    # 这里是打的 Debug 版本
    build_app(
      workspace: "xxx.xcworkspace", 
      configuration: "Debug", 
      scheme: "xxx", 
      export_method: "development"
    )
    # 上传版本到蒲公英平台
    pgyer(
      api_key: "xxxxx", 
      user_key: "xxxxx"
    )
  end
end

从 Fastfile 中可以看到,里面有两个 lane,每个 lane 都可以独立执行,就拿上面的代码来说。
如果要上传到 TestFlight,执行bundle exec fastlane rf 就可以了
如果要上传到蒲公英平台,执行bundle exec fastlane adhoc 就可以了

注意

  • 以上代码,用到了 Fastlane 的插件,一个是 Build 号加 1(Fastlane 本身 Build 号加 1 的功能针对多个 Target(如果项目有 Test 的话,就是多 Target 了)有些问题),一个是上传蒲公英平台
  • 修改版本号插件 fastlane-plugin-versioning
  • 蒲公英插件 pgyer
  • 插件安装方法:先切换到工程目录,执行bundle exec fastlane add_plugin pgyer即可

针对于上传 TestFlight 双重认证问题
如果 AppleID 开启了双重认证,那自动上传 TestFlight 就会有问题了,因为执行脚本过程中,会出现如下提示,要求手动确认并 输入 6 位 code,怎么办呢?

Login to App Store Connect (xxx@email.com)
Two-factor Authentication (6 digits code) is enabled for account 'xxx@email.com'
More information about Two-factor Authentication: https://support.apple.com/en-us/HT204915

If you're running this in a non-interactive session (e.g. server or CI)
check out [https://github.com/fastlane/fastlane/tree/master/spaceship#2-step-verification](https://github.com/fastlane/fastlane/tree/master/spaceship#2-step-verification)

(Input `sms` to escape this prompt and select a trusted phone number to send the code as a text message)

(You can also set the environment variable `SPACESHIP_2FA_SMS_DEFAULT_PHONE_NUMBER` to automate this)
(Read more at: [https://github.com/fastlane/fastlane/blob/master/spaceship/docs/Authentication.md#auto-select-sms-via-spaceship-2fa-sms-default-phone-number](https://github.com/fastlane/fastlane/blob/master/spaceship/docs/Authentication.md#auto-select-sms-via-spaceship-2fa-sms-default-phone-number))

Please enter the 6 digit code:

Fastlane提供的两步验证解决方案:

1.访问 https://appleid.apple.com/account/manage
2.生成一个 APP-SPECIFIC PASSWORDS,保留生成的特殊密码

生成的特殊密码

3.使用环境变量提供这个密码给fastlane:FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD
4.执行bundle exec fastlane spaceauth -u user@email.com,生成 session cookie。(过程中会提示输入输入 6 位 code,之后就会生成 session 了)
5.通过环境变量 FASTLANE_SESSION 提供 session cookies。

环境变量设置方法:
可以在 Fastfile 中设置,可以在 before_all 里面做这个事情,这个代码会在我们的 lane 之前执行,代码如下

before_all do
    ENV["FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD"] = "生成的特殊密码"
    ENV["FASTLANE_SESSION"] = '生成的session cookie'
  end

最终 Fastfile 是这样的

default_platform(:ios)

platform :ios do
  before_all do
    ENV["FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD"] = "生成的特殊密码"
    ENV["FASTLANE_SESSION"] = '生成的session cookie'
  end
  
  # 切到 develop 分支
  sh 'git checkout develop'
  # 代码
  git_pull
  # build 号加1
  increment_build_number_in_plist(
    target: "xxx"
  )
  # 下面是拿到新的版本号,提交代码
  build_number = get_build_number_from_plist(target: "xxx")
  git_commit(path:".", message:"Bump build to #{build_number}")
  sh 'git push origin develop'
  
  # pod install
  cocoapods(repo_update: false)

  # 打包上传到 TestFlight
  lane :tf do
    desc "Push a new beta build to TestFlight"
    build_app(
      workspace: "xxx.xcworkspace", 
      scheme: "xxx"
    )![生成的特殊密码.png](https://upload-images.jianshu.io/upload_images/809937-0e3004d8ab15f6de.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

    # 上传到 TestFlight,这里是上传完成就结束了,不等到到苹果那边处理完
    upload_to_testflight(skip_waiting_for_build_processing: true)
  end

  # 打测到包到第三方平台,这里是走的蒲公英
  lane :adhoc do
    desc "Push a new beta build to pgyer"
    # 这里是打的 Debug 版本
    build_app(
      workspace: "xxx.xcworkspace", 
      configuration: "Debug", 
      scheme: "xxx", 
      export_method: "development"
    )
    # 上传版本到蒲公英平台
    pgyer(
      api_key: "xxxxx", 
      user_key: "xxxxx"
    )
  end
end

Jenkins 部分

安装 Jenkins

首先要安装 homebrew,怎么安装这里就不说明了
直接命令安装 Jenkins

brew install jenkins

配置 jenkins 管理员账号和密码

打开浏览器,进入 http://localhost:8080/ 如果没有效果,看下shell 中Jenkins 的日志,日志还是较为详细的
根据网站提示,会让创建账号,进行简单的配置,根据提示来就行了,还是比较简单的
配置完毕在jenkins工作目录 /Users/XXXX/.jenkins 下 config.xml 文件会记录登录账户的信息

安装 Jenkins 插件

在配置过程中,也会让选,是安装推荐的插件,还是自己来安装插件,我是先安装了推荐的插件,然后再加了一些自己需要的插件

点击 Manage Jenkins -> Manage Plugins, 之后可以看到已经安装过的插件,也可以搜索自己需要的插件进行安装,还是很方便的

我这边 Cocoapods 和 Build,都是走的 Fastlane,所以这里可以不用安装 Cocoapods 和 Xcode 的插件。
我这里安装的插件有(其他的应该大部分都是默认安装的):
Git plugin
GitLab Plugin
JDK Tool Plugin
Environment Injector Plugin
Localization: Chinese (Simplified)

Jenkins 首页长这个样子:


Jenkins 首页

创建 Job

见图


创建 Job
Job 修改
添加 Git 信息

添加执行脚本


添加执行脚本
添加执行脚本

创建好后,就可以构建版本了


构建版本

以上图就是整个 Jenkins 创建流程了,但是执行过程中会遇到一些问题

Jenkins 遇到的问题

  1. 构建过程中,会出现无法切换分支问题(比如执行sh 'git checkout develop'),因为 Jeknins 是会把版本切到最新的 commit, 而不是 branch, 当前显示的分支名会是 HEAD。这种情况下,fastlane 执行一下命令会出错
  sh 'git checkout develop':
  git_pull
  increment_build_number_in_plist(target: "xxx")
  build_number = get_build_number_from_plist(target: "xxx")
  git_commit(path:".", message:"Bump build to #{build_number}")
  sh 'git push origin develop'

解决方案: 直接 checkout 远端分支来修改处理,代码如下

  sh 'git checkout origin:develop'
  git_pull
  increment_build_number_in_plist(target: "xxx")
  build_number = get_build_number_from_plist(target: "xxx")
  git_commit(path:".", message:"Bump build to #{build_number}")
  sh 'git push origin HEAD:develop'
  1. 局域网无法访问 Jenkins, 配置还好后会发现,局域网其他电脑不能通过该机器的 IP 来打开 Jenkins, 如 http://192.168.31.129:8080/
    解决方案:
    在 Jenkins 首页,点 Manage Jenkins,选择 Configure System
    修改 Jenkins 地址

外网访问 Jenkins

有的时候,可能希望通过外网直接访问 Jenkins (这样不太安全,不建议)
这里可以使用 ngrok (这里只是用的是免费版本), 安装部署很方便,下载安装后,直接执行

./ngrok http 8080

之后会生成外网地址,直接用这个地方访问 Jenkins 就可以了,如下


ngrok

推荐阅读更多精彩内容