Fastlane + GitLab + Bitrise on iOS

CI & CD on iOS

David Lin
10 min readAug 2, 2018

前言

在上架與發行 Beta 版本時,總會希望先跑過測試,跑過 QA,一切就緒再上架。但想想每次上架前都要跑完測試、archive、上傳到 testflight 或 crashlytics beta,這些流程大都要花上 20 分鐘甚至有些專案會超過一小時,每次要發佈新版本都要走這些重複的流程實在是很沒有必要。

於是我們可以採用 CI 來幫我處理這些重複且繁瑣的工作,大多數人使用的是 Jenkins, Travis CI 兩者,最近比較新的 CI 服務有 Circle.ci 跟 Bitrise,使用何者 CI 並沒有差異,只要能定時跑 CI 即可。

什麼是 CI

還是要科譜一下 CI 到底是什麼,簡單的說你可以告訴 CI,請他幫你在接收到新的 push 時,做出相對應的事情,又或者請 CI 幫你定時發佈新的測試版本給 QA team 做測試用(又稱為 daily build)。

其實簡單的 CI 可以用自己的電腦架設,並且用 github webhook 做 push 的監控,只要一有新的 push 則 clone 到某一個目錄並且執行一段 script。

Why Bitrise CI

Jenkin CI, Travis CI, Circle.ci, Bitrise.ci 只是整合性服務,幫你把一些複雜的東西解決掉,加入 github/gitlab 的整合,讓你在設定上可以比較迅速。而 Jenkin CI 的插件多,可以客製化的地方很多,也可以架設在自己的電腦上,又或者做到 master-slave 的設定。

不過在小團隊中還不需要用到 Jenkin 這麼複雜的功能,Travis CI, Circle.ci, Bitrise.ci 可能會是比較快速的解法,這裡我採用 Bitrise 來幫我做 CI 的事。

Bitrise ci 跟 GitLab 也有不錯的整合,GitLab 的 pipline 會去抓 Bitrise 上執行的結果來告訴我們這個 feature branch 有沒有問題,能不能 merge。也可以在每個 pr 發出後,先跑測試,接著沒有問題後自動 merge。

CI CD 流程

流程大致如下:

  1. Push 至 GitLab
  2. bitrise 從 GitLab clone 我們剛剛 push 的 code
  3. bitrise 執行 fastlane 定義好的 lane
  4. fastlane 可能會先用 match 來做 code sign
  5. 跑測試
  6. 打包 app
  7. 上傳至 appstore, testflight, crashlytics beta

當然,如果你需要每個 push, pr 採用不同的 lane,可以參考 Bitrise 的 trigger map,有的時候並不需要每個 push 都發佈新的測試版,發佈新的測試版可能只會需要在 merge into develop branch 時才 trigger。

而如果你需要同一個專案有不同的 configuration,比如有三個 scheme:dev, beta, appstore,可以參考以下文章作專案 configuration 區分(方便管理 staging/production 參數、伺服器網址不同等等問題)

Fastlane 建置

大家應該可以發現,最主要的主角還是 Fastlane,他幫我們把大多數的工作都自動化了,只是執行這些東做的地方不是我們的 mac,而是遠端的 bitrise ci server。所以我們要先把 Fastlane 建立好,才能將他放到 bitrise 上。

這邊假設大家已經對 Fastlane 有一定程度的熟悉度,如果還不熟悉,可以參考 Gettgin started with Fastlane for iOS,以下將帶大家走過 Fastfile 的建置。

使用 Bundler 管理 Ruby 版本

我們最怕的是在 bitrise 上,又或者其他 ci 之中,系統 ruby 作怪會影響到專案的運行(比如 cocoapods, fastlane 版本問題)。

首先在 terminal 中輸入

$ touch Gemfile

在專案中建立一個 Gemfile 方便我們管理我們需要安裝的 gem(cocoapods, fastlane 都是 gem),路徑為 project file 的 root。

接著我們需要透過 bundler 幫我們管理 ruby 版本以及 gem 版本。這邊可以指定 ruby 版本,但目前我先不設定,大家可以採用自己習慣的 ruby 版本。在 Gemfile 中我們需要以下幾行來安裝 cocoapods 以及 fastlane。

source "https://rubygems.org"gem "fastlane"
gem "cocoapods"

輸入後存檔並且在 terminal 輸入

$ bundle install --path bundler

透過 bundler 安裝我們指定的 ruby & gem 到指定的 path bunlder/ 中(路徑可以依照自己喜好更改),裝好之後的專案就不會受到系統 ruby 的影響了,所有的 ruby & gem 都會從 bundler 裡面拿。

Match

Code sign 部分我選用 match 來幫忙,match 主要是將你的 certificates & Provisioning files 放到某 private repo 中,以 remote clone 的方式拉進來。

使用方式很簡單,可以直接參考官方的 setup,大約三行就結束了:

那比較會有問題的是,如果這個 cert 會有很多人用到怎麼辦?而 match 產生的 cert 跟 p12 是分開的檔案,直接安裝 cert 會發現 private key missing,因為 private key 被分開存在 p12 之中,這個問題如何解決呢?

可以參考 Export Distribution Certificate and Private Key as Single .p12 File,將 cert 跟 p12 合好後,丟給需要的人安裝在他們的電腦中。安裝完成後,可以到 Xcode 中下載 match 產生的 provision file,並且在專案中 target -> General 中選擇 provision file 的地方設定好(這邊不建議給 xcode 自動選擇,比較偏好手動選)

Fastfile

不多廢話,直接看 code 在一一解釋

大家可以看到前面定義了一堆專案的名稱、provision檔案名稱等等,這是為了後面方便 build app 用的參數。

  • ProjectSetting 定義了一些共用的值,比如 xcworkspace 的名稱等等
  • Configuration 是打包一個 app 所必須用到的資訊
  • ENV 環境變數都可以用 export 的方式放在 .bashrc, .zshrc 之中,或者之後可以放在 bitrise 的 ENV Vars 中(沒有在 fastfile 中定義的 env var 需要自己額外定義)。

接著,fastlane 中我們定義的 lane 皆為繞在 match_using & package 這兩個 method 中。一開始我們先取得需要用到的 cert & provision,供之後 build app 後 code sign 用,這時候需要用到 match_using。而 package 則會依據我們傳入的 config 打包出我們需要的 app。

包好後看是要傳到 beta, testflight, appstore 皆可。這邊我還做了一件事是將 build info 傳至 slack,如此一來,每當 ci 完成一個 task 後,我們都能知道一些資訊。

接著先在自己的電腦跑跑看,看能不能正確上傳到testflight。

Bitrise

Bitrise 設定上也是頗無腦,一直按下一步下一步即可,但比較需要注意的是,如果你的 match 是在 private repo(理當也要在 private repo 中),那 Bitrise 會產生一個 public ssh key,請將他加入到你的 gitlab 帳號中~大致上就這樣,專案就建立好了。

如果順利的話大概就會看到:

Bitrise Env Vars

  • FASTLANE_OUTPUT_DIRECTORY 是用來指定 fastlane build output 的路徑
  • BUILD_BY 用來明示是誰 trigger 了 fastlane,這邊我會輸入 Bitrise,這樣在 Slack 中才能知道是誰 trigger 了 fastlane
  • MATCH_PASSWORD 是 match 在 init 時,請你輸入的 passphase,如果忘記了的話請到當初產生 match 的電腦中的 keychain 搜尋 match 即可找密碼。

總結

從當兵前搞到現在終於把 CI 架起來,也算是喜事一件(?

會想要架設 CI 也是想盡量減少分心在其他雜事的時間,專心寫 code,而 CI 的輔助可以在 merge 或者 release 前跑測試確保所有的 code 都正常運作阿!

如果還想看更進階的可以看 Cookpad 的工作流程,影片&簡報在此:

--

--