歡迎來到真實世界 - Continuous Delivery:在你睡覺的時候,電腦們可是都在勤奮地工作喔

IMG_7479.jpg

在iOS開發的世界,有個非常有趣,但也非常痛苦的地方,就是iOS的開發者,其實需要的基本知識非常地多,Cocoa framework 本身就涵蓋了前端的UI邏輯,與資料庫等等的後端邏輯,既要注意頁面跟頁面之間狀態的處理,也要小心記憶體的運用,有時候還要學貝茲曲線跟 3D 轉場。雖然每一樣都不可能像各領域的專家一樣精通,但也算是攻城獅裡面武器相當多的種族了。今天,不才小弟要來分享,身為 iOS 工程師,你可能還可以多學的技術:Continous Delivery!身為一個攻城獅,你一定或多或少聽說過 Continous Intgration 跟 Continous Delivery (CI/CD),但是實際生活中,除非是跟一個團隊一起開發,不然應該很少有機會會碰到 CI/CD 的概念。

所謂的 CI,就是在開發的過程中,我們需要隨時隨地都確保我們的 code 主幹都處在可以一個發佈的狀態。也就是說,不能因為正在開發一個新功能,我們的主幹程式就無法運作或是無法打包新版本。而 CD,指的則是,我們希望在開發的任何一個階段,都要能夠自動化打包出版本,給需要的人使用。誰會是需要的人?在開發的過程中,工程團隊想要手動測試 app 時,就會需要一個 build 來測試,而在開發完畢後,UI測試人員也會需要一個 build 來做測試。最後,在 APP 要上線時,理所當然也會需要一個 build 來送到 iTunesConnect 上以供審核。

所以在系列作拖搞將近三個月後,今天在真實世界的我們,要來介紹一個非常實用的技巧,如何做出一個專供 Apple 生態系使用的 CD 系統,讓你可以每天都提早五分鐘下班(不是很吸引人)。

如果還沒看過系列作的前面幾集,歡迎參考小弟的鍵盤藍綠藻Neo – 來寫一些iOS、技術、與垃圾話,這個系列記載了教科書沒有記載的現實生活開發技術,沒有背景音樂也沒有跑馬燈。

Outline

我們會以下面的步驟來說明如何建立一個好用的 CD 系統,你隨時都可以直接跳到某個章節開始閱讀,也可以直接 End 看電影心得,別擔心,小弟已經開始思考只寫電影心得的可能性。

  • 在這個 CD 系統中,我們選擇 XX 而不選 XX 的理由
  • 任務簡介與基本的 Project 設定
  • 手動化你的 code signing
  • 用 Bundler 管理你的系統工具
  • 如何設定 fastlane
  • 如何設定 Jenkins

開始之前,不是一定,但最好先具備有:

  • fastlane 的基本知識
  • iOS/macOS code signing 的手動管理經驗
  • Jenkins 的基本知識

我們開始吧!

Why not XX

在這個世界上,有成千上萬的公司,運行著成千上萬的工作流程,我們不可能設計出一套系統,同時通用到所有人的工作流程上。所以身為一個系統的建置者,你的任務就是要設計出一個系統,讓它能夠完美地整合進去公司或你個人既有的工作流程中。在這篇文章中,因為篇幅有限(也是因為作者比較懶),我們不可能列出所有可能的設計方式,但是我們會讓你知道,我們每一個選擇的原因,這樣你在實做的時候,就能夠知道那些東西是適合你的,那些不是。

Why Fastlane & Jenkins

Fastlane 是一套熱門的CI軟體,它提供了許多好用的工具,還有直觀的腳本檔,讓你可以不用碰觸到很多系統的細節面,也能夠做到各種客制流程,而且它同時支援iOS/MacOS還有Android平台。大多數的設定你都可以透過 commandline 直接跟Xcode 互動做到,但相信我,你不會想要自己寫那些 script 的XD。目前有些線上的 CI 工具,像是 bitrise,讓你可以用點按的就能夠設定好 CI,但如果你有預算上的限制,並且希望任務也能夠在自己的電腦上運作,那 fastlane 就會是你的最佳選擇。

Jenkins 則是一個有網頁 UI 的自動化排程工具,讓你可以利用 GUI 設定工作內容,像是設定 project 參數等,也可以做到執行排程,像是每日定時任務等。也因為有大量 plugin 的關係,跟很多既有平台的整合也都不錯。其它的選擇也有像是雲端的 CI 系統如 BuddyBuild (恭喜加入 Apple )、CircleCI 等等可以使用,好處是設定更加簡單了,但相對地能客制的部份就比較少,並且因為機器不在我們手邊,遇到比較麻煩的問題也就只能求助 support。對 Jenkins 來說,你除了可以買一台 Mac mini 來架站之外,也可以架在自己的電腦上方便測試,所以這篇我們主要還是以 Jenkins 為主。

Why not match or sigh/cert

你大概已經知道(或者已經很熟悉),fastlane 提供了許多好用的工具,像是 match 或者是 cert/sigh,來幫我們管理 code signing,但我們這篇並不會使用者些工具,為甚麼呢?自動化的工具的確很方便,但對於大公司或是外包人員來說,首先,並不是所有開發者都有 developer portal 的權限的,很多開發者因為公司政策的關係,只能取得開發者的權限,要修改或是下載 certificate/provisioning profile 都是不行的。這時候 match/sigh/cert 就完全派不上用場了。除此之外,大家應該都有跟 Xcode 裡的 code signing 奮戰的經驗吧?我們希望在 code signing 這邊,越少黑盒子,流程越透明,未來遇到問題的時候,就越容易進入狀況並且找到解決方法。

Our task

假設我們現在正在開發一個 app 叫 Brewer,它每天晚上都會準時釀啤酒,你可以在github上面找到它:

GitHub - koromiko/Brewer: I brew beer everyday :)

我們的工作流程是:

  1. 每天一早,工程師要拿到一個最新的 build,來幫同事做簡易的手動測試 (Staging build)
  2. 每星期的一開始,也會產生一個 build,讓公司裡的 QA 人員做完整的測試 (Production build)

所以我們的CD系統,要能夠

  1. 針對不同的任務切換不同的 build configuration (staging/production)
  2. 針對不同任務使用不同的 code signing
  3. 把打包好的build送到發佈系統 (Crashlytics, testFlight, etc)
  4. 固定時間啟動任務

所以我們會這樣設計我們的 CD 系統:

pic1.001.jpeg

在圖的最上面,是我們原本的工作流程,就是一般常見的開發、測試、發佈流程。從開發到測試大概的時間是一周。而中間 Delivery 那一列,就是我們的 CD 策略,在開發階段,每一天都會自動釋出一個 nightly build 供開發者做測試,最後在一週的結尾,則會釋出一個版本供 QA 人員做測試。最底下的 Env,指的是我們希望釋出的版本,是運行在怎樣的環境。對開發者來說,我們會頃向用運行在 Staging 環境的版本做測試,這樣可以一直開假帳號、亂買東西也不用擔心弄壞 production。而對 QA 人員來說,他們就會希望能夠使用跟使用者一樣的環境來做測試,所以會在 Production 環境底下建置。最後,在 QA 過後就是 Release 階段,因為 Release 發生的頻率相對不高,並且很有可能沒有一個固定的週期,所以我們會將它設定成手動發佈。在這篇文章中我們只會介紹前兩種設定方法,一旦知道要怎樣設定之後,後面的設定應該都可以得心應手。

接著我們要來了解一下,怎樣透過 Xcode 的 build configuration 做到環境的切換,而不用更改code。詳細的內容可以參考這篇文章 Manage different environments in your Swift project with ease 的第 3, 4 點。簡單地來說,我們會透過 project 的 build configuration 來設定不同的 Flag,做到切換 Staging 版本跟 Production 的效果,我們的 build configuration 設定如下:

Screen Shot 2018-03-13 at 23.40.17

針對不同的設定,我們需要設定不一樣的 AppID,這樣的好處是我們可以在測試裝置上同時安裝不同的版本也不用擔心搞混,就發佈系統來說,也比較好管理。

Screen Shot 2018-03-15 at 08.48.31

接著我們要來針對不同的 Configuration 做不同的 code signing。

讓 certificate 跟 provisioning profile 回歸你的控制

Screen Shot 2018-05-04 at 16.01.04.png

寫 iOS/macOS 的開發者,應該已經非常熟悉這個畫面了吧!沒錯,我們會第一時間把自動管理憑證關掉,不然你可能會進入自動更新的無間地獄。把自動化關掉之後,就要來準備好所有 code signing 所需要的檔案了,針對我們上面的環境設定,我們需要準備這些檔案:

  • Provisioning profile: 針對不同 AppID 的 Ad Hoc distribution profile
  • Certificate: Ad Hoc distribution certificate,並且匯出成 .p12

要注意的是,在 developer portal 下載的憑證檔是利用 DEM 加密的 .cer 檔,但 DEM 的檔案裡並沒有包括私鑰,也就是說如果你換電腦了,這張憑證就會因為找不到你的私鑰而失效。所以我們必需要把 DEM 輸出成包含私鑰的 P12 檔,輸出的方法可以參考這篇 stackoverfow

最後我們把上述的檔案都下載下來,存到另外開的 codesigning 資料夾:

Screen Shot 2018-03-14 at 08.29.55.png

這個資料夾不能跟你的 code 放在一起,它可以放在 jenkins 主機上,也可以做成一個 git repository 或者一個雲端共享資料夾,再利用 fastlane 將這些檔案下載下來使用,好處是不用每次移機時也要手動移動這些檔案。放遠端的作法基本上是安全的,如果你覺得放遠端安全性是有疑慮的,可以參考 match - fastlane docs

用 Bundler 管理你的系統工具

如果你是 iOS/macOS 開發者,你應該已經很熟悉用 homebrew 來安裝像是 Cocoapods 或是 Carthage 等等套件,homebrew 讓你可以輕易地管理你的系統程式,讓你自己電腦裡面的環境保持一致。但是我們希望我們的建置環境能夠越獨立越好,這樣才不會因為換了電腦建置就會出錯,或者是還要再重覆套件安裝一樣的動作。也就是說我們希望可以製作出一個獨立的環境,可以被完整地複製到不同電腦上,這時候我們就會需要 Bundler。Bundler 是一個 Ruby 的環境管理系統,它可以幫你設定出一個能夠被複製的虛擬環境,之後不管換到任何電腦,對在這個虛擬環境中的程式來說,執行的環境都是一模一樣的。

我們會透過 bundler 管理 fastlane 跟 Cocoapods 的安裝,所以請在 project folder 裡面新增一個 Gemfile 檔案,內容如下:

source "https://rubygems.org"

gem "fastlane"
gem "cocoapods"

Gemfile 是 Ruby 套件管理程式gem的設定檔,裡面描述了你這個project所需要用到的程式。Bundler會透過gem來安裝程式,並且幫你設定好虛擬環境。

接著,我們就可以來安裝這兩隻程式:

bundle install --path vendor/bundler

—path vendor/bundler 表示我們希望把程式安裝在當前的目錄底下,而不是裝到系統檔案夾如 /usr/local/binusr/bin裡面,這樣你的 project 就不會跟你的系統有任何掛勾了。

請記得將 vendor 這個資料夾加入 .gitignore 之中。

設定完 bundler 之後,未來我們想要在虛擬環境下執行程式的話,就需要下 bundler exec 的前綴,比方說,fastlane init 是初始化一個 fastlane 的 project,現在我們要改成 bundler exec fastlane init,表示我們要初始化一個 fastlane project,並且在剛剛設定的虛擬環境下執行這個指令。

來fastlane一下

fastlane docs 是一個好用的工具,幫助你自動化完成許多繁複的工作。就算你是一人團隊,你也可以透過 fastlane 自動化所有測試、發佈跟憑證管理等等工作。

我們打算利用 fastlane,加入兩個任務,一個是 staging 環境的發佈,一個是 production 環境的發佈。這兩種設定都包含以下固定的工作:

  • 從檔案匯入 certificate 與 provisioning profile
  • build app 並且上傳到 Crashlytics

兩種設定差別只在於 build configuration 的不同而已,所以我們的 fastlane 要能依據我們所選取的任務,找到對應的 certificate 跟 provisioning profile,用它們來建置 app 並發佈。

在這篇文章中,將以 Swift 設定檔來當範例。選 Swift 來撰寫設定檔,目前 fastlane Swift 是基於 Ruby 的 wrapping,並不是真的原生 Swift,所以 fastlane plugin 是不被支援的,有需要用 plugin 的這點可能要考慮一下。另外,Swift 也無法 catch 任何Ruby 發出來的例外,如果有需要完整的例外處理,就還是請用 Ruby 原生的 fastlane 吧。

安裝方法請參考 Setup - fastlane docs

安裝完後,我們要先初始化我們的 fastlane project:

bundler exec fastlane init swift

接著,你就可以在fastlane/Fastfile.swift找到你的 fastlane 設定檔。在設定檔裡面,我們可以找到 Fastfile 這個 class,這個 class 裡面所定義的 method,只要是以 Lane 結尾的 method,都會被認定為是一個 "lane",可以透過 fastlane 來執行。所以我們先新增兩個lane,一個叫做 developerRelease,另外一個叫做 qaRelase

class Fastfile: LaneFile {
    func developerReleaseLane() {
        desc("Create a developer release")
	package(config: Staging())
	crashlytics
    }

    func qaReleaseLane() {
        desc("Create a qa release")
        package(config: Production())
        crashlytics
    }
}

未來我們想要打包release的時候,都可以執行下面這樣的指令:

bundle exec fastlane developerRelease
# or
bundle exec fastlane qaRelease 

想要打包給 developer 就執行上面的指令,想要打包給 QA 人員,就執行下面的指令,這樣是不是非常地直觀阿!(是)。我們可以看到在這兩個 lane 裡面都會呼叫一個 method:package,並且跟據不同的 lane 給予不同的 config 參數,這個 package 的接口如下:

func package(config: Configuration) {
}

它的參數,其實是一個叫 Configuration 的 protocol:

protocol Configuration {
    /// file name of the certificate 
    var certificate: String { get } 
    
    /// file name of the provisioning profile
    var provisioningProfile: String { get } 
    
    /// configuration name in xcode project
    var buildConfiguration: String { get }
    
    /// the app id for this configuration
    var appIdentifier: String { get }
    
    /// export methods, such as "ad-doc" or "appstore"
    var exportMethod: String { get }
}

所有的 configuration 都要 conform 這個 protocol,才能被傳入 package 裡面做打包。在這個範例裡面,我們設定了兩種 configuration:

struct Staging: Configuration { 
	var certificate = "ios_distribution"
	var provisioningProfile = "Brewer_Staging"
	var buildConfiguration = "Staging"
	var appIdentifier = "works.sth.brewer.staging"
	var exportMethod = "ad-hoc"
}

struct Production: Configuration { 
	var certificate = "ios_distribution"
	var provisioningProfile = "Brewer_Production"
	var buildConfiguration = "Production"
	var appIdentifier = "works.sth.brewer.production"
	var exportMethod = "ad-hoc"
}

跟據實際的狀況,在這兩個struct裡面實作 protocol,這樣可以確保我們的 package 有一致的接口,同時又能符合各種不同的狀況。

接著,我們要開始來實作 package 了。還記得 package 的任務嗎?首先,它需要從檔案引入 certificate 跟 provisioning profile。關於 certificate,我們會使用 importCertificate 這個action,來讀取系統中的 .p12 檔:

importCertificate(
keychainName: environmentVariable(get: "KEYCHAIN_NAME"),
keychainPassword: environmentVariable(get: "KEYCHAIN_PASSWORD"),
certificatePath: "\(ProjectSetting.codeSigningPath)/\(config.certificate).p12",
certificatePassword: ProjectSetting.certificatePassword
)

KeychainName 這個參數就填入你 keyChain 的名稱,keyChainPassword 通常就是你系統的密碼。

因為 fastlane 的設定檔,通常都會跟著原始檔一起被 commit 到 git 裡,把密碼放在 code 裡面,通常不是一個好主意,所以我們會用系統變數來取代固定的字串。在系統上,我們會存入系統變數:

export KEYCHAIN_NAME="KEYCHAIN_NAME";
export KEYCHAIN_PASSWORD="YOUR_PASSWORD";

而在 fastlane 裡面,就利用 environmentVariable(get:) 來讀取系統變數。這樣我們就可以避免把機密字串 commit 到 git 上,大大增加系統的安全性。 接著我們透過 certificatePath 指定 certificate .p12 檔的位置還有這個檔案的密碼。在這裡, ProjectSetting 是一個 enum,存放著 project 相關的變數,我們定義了 codesigningPath 與 certificatePassword,代表所有 code signing 的相關檔案的位置跟密碼,而這些資訊一樣是透過系統變數給予的。

enum ProjectSetting {
	static let codeSigningPath = environmentVariable(get: "CODESIGNING_PATH")
	static let certificatePassword = environmentVariable(get: "CERTIFICATE_PASSWORD")
}

以上 certificate 的引入就設定完畢了!接下來是 provisioning profile 的設定,我們會利用 updateProjectProvisioning,跟據不同的設定檔,來更新 project 的 provisioning profile:

updateProjectProvisioning(
xcodeproj: ProjectSetting.project,
profile: "\(ProjectSetting.codeSigningPath)/\(config.provisioningProfile).mobileprovision",
targetFilter: "^\(ProjectSetting.target)$",
buildConfiguration: config.buildConfiguration
)

config 就是我們一開始代入的 package 的參數,是一個 conforms Configuration protocol 的 struct 或 class。在 profile 這個參數中,我們利用 codeSigningPath 跟`config.provisioningProfile` 來指定 provisioning profile 的位置,我們同時也在 buildConfiguration 這個參數裡指定我們要修改的 build configuration,這樣 updateProjectProvisioning 就會把指定的 provisioning profile 寫入指定的 configuration 裡面了。

注意:這個 action 會直接修改你的 .xcodeproj,對 Jenkis 來說,所有修改都是不會被 commit 的,所以在 CD 系統中這些修改都是沒有問題的,但如果你希望在你的 working directory 裡面執行 fastlane,就要特別留心 .xcodeproj 的修改問題

設定完了 code signing 之後,我們就可以開始來 build 跟 export 了:

buildApp(
workspace: ProjectSetting.workspace,
scheme: ProjectSetting.scheme,
clean: true,
outputDirectory: "./",
outputName: "\(ProjectSetting.productName).ipa",
configuration: config.buildConfiguration,
silent: true,
exportMethod: config.exportMethod,
exportOptions: [
"signingStyle": "manual",
"provisioningProfiles": [config.appIdentifier: config.provisioningProfile] ],
sdk: ProjectSetting.sdk
)

buildApp 這個 action,能夠幫你 build 你的 project,並且輸出 ipa,供上傳或送審。大部份的參數都非常直觀,像 configuration 這裡,可以看出來我們使用 config 物件來指定我們要使用的 build configuration。另外,在 exportOptions 這個參數:


exportOptions: [
    "signingStyle": "manual",
    "provisioningProfiles": [
    	config.appIdentifier: config.provisioningProfile
	] 
]

這個參數的型態是 dictionary,`signingStyle` 指的是你希望使用的 code signing 的方法,這邊必需要設定成 "manual"。再來是 provisioningProfiles 這個參數指定 app id 跟 provisioning profile 的 mapping,也就是那一個 app id 該使用那一個provisioning profile 去 sign,它也是一個 dictionary,key 是 app id,而 value 就是 provisioning profile 的檔案名稱。在這邊我們使用 config.appIdentifier: config.provisioningProfile,來讓這個 mapping 可以由 config 物件決定。

以上我們就完成了從 code signing 到 build 跟 export 的設定了!

現在你可以透過

bundle exec fastlane qaRelease

或是

bundle exec fastlane developerRelease

來輸出針對不同環境建置的 app 了!

以上是利用 fastlane 打包的部份,但別忘了,我們還有定時打包的功能,這個任務,就要交給 Jenkins 來做。

The housekeeper - Jenkins

Jenkins 是一個 CI/CD 的管理系統,幫助你自動化與定期化執行各種不同的任務。它有著憨厚(?)的介面,讓你不用寫一堆 script 就能夠設定好自動化任務。如果你是一個大團隊,建議另外弄一台 mac mini 當做 Jenkins 的主機,所有發佈的任務都在那台主機上運行,這樣可以確保每次出去的版本都不會因為環境的不同而出現不能預期的問題。如果你是一人團隊,那你就把 Jenkins 裝在自己的電腦,或者直接使用 fastlane 做打包就好,沒有 24 小時運行的主機的話,設定定期任務是沒有太大意義的(為了在 build code 時能順順地看 Netflix,我也好想要一台 mac mini)。

Jenkins 在 mac 上安裝非常容易,可以直接透過 dmg 檔安裝,Jenkins installation and setup。另外,我們會需要 git 的 plugin,讓 Jenkins 能夠支援 git 的操作。dmg 安裝會設定一個 mac 的 user: jenkins,它不能登入也不能操作 commandline,也不佔太多空間,純粹就是讓 Jenkins server 有獨立的權限控管。

安裝完之後,我們先來看看,Jenkins 在我們的 CD中,扮演怎樣的角色:

img2.jpeg

從圖上可以看得出來,Jenkins 扮演著管家的角色,時間一到,它就會負責去 repository 抓最新的原始碼,抓下來之後,開始執行指定的任務,任務內容包括:

  1. 設定 environment variables
  2. 透過 bundler 安裝 dependency
  3. 執行 fastlane 的任務

了解了 Jenkins 的工作之後,我們就可以開始來新增我們的定期任務。先從 nightly build 開始,我們先新增一個 Jenkins freestyle project,並點擊 Configure 進到設定頁面。設定頁面裡有幾個重點我們需要注意:

第一個是 Source Code Management(SCM),在這邊我們會設定我們 project 的repository URL,還有指定要 fetch的branch,如下圖:

Screen Shot 2018-03-17 at 23.15.31.png

Repository URL 就是設定我們的 github 或其它系統的 repository url,Credentials 則是可以設定你在 repository 的帳號跟密碼,這樣 Jenkins 才能通過授權下載原始碼(如果是 private repository)。Branches to build 可以設定你的目標 branch,以定期任務來說,通常都會設定成你的 default branch。

再往下,我們可以看到 Builder Trigger 區塊,這個區塊是要設定這個任務的觸發點,也就是要設定自動化啟動任務的條件。如果這邊都沒有設定任何 trigger,那任務就是手動觸發。我們的 trigger 設定如下:

Screen Shot 2018-03-17 at 23.28.02.png

我們啟動了一個 Poll SCM,它的意思是,讓 Jenkins 定期向我們的 source code repository 查看是否有更新資料,如果有的話,就啟動我們現在這個 task。設定是一串空白隔開的火星文:

H 0 * * 0-4

這是甚麼意思?讓我們先看一下,關於 Poll SCM 的說明:

This field follows the syntax of cron (with minor differences). Specifically, each line consists of 5 fields separated by TAB or whitespace:
MINUTE HOUR DOM MONTH DOW
MINUTE  Minutes within the hour (0–59)
HOUR    The hour of the day (0–23)
DOM The day of the month (1–31)
MONTH   The month (1–12)
DOW The day of the week (0–7) where 0 and 7 are Sunday.

從說明可以看得出來,這一串字串總共有五個部份,由左到右依序是:

  • 星期幾

我們可以直接指定數字,或者0–59這樣的字串代表有效區間,使用*的話就表示所有可能的數值都會觸發,代表任意一個有效的數字,通常會被用在分的設定上,目地是要把系統上每一個 task 都盡量分散在一小時內運行。所以回到我們剛剛的 schdule setting:

H 0 * * 0-4

表示我們希望任務可以在週日到週四、一年中所有的月份、每天的 0 點不指定分執行,這就是 nightly build 的 schedule。

最後,我們往下可以看到 Build 的區塊,在這裡我們就可以設定當時間一到,要讓Jenkis執行的程式。內容如下:

export LC_ALL=en_US.UTF-8;
export LANG=en_US.UTF-8;

export CODESIGNING_PATH="/path/to/cert";
export CERTIFICATE_PASSWORD="xxx";
export KEYCHAIN_NAME="XXXXXXXX";
export KEYCHAIN_PASSWORD="xxxxxxxxxxxxxx"

bundle install --path vendor/bundler
bundle exec fastlane developerRelease

前面幾行都是在設定 environment 變數,像是 LC_ALL 跟 LANG 主要是要確保fastlane能運行在正確的locale底下,而 KEYCHAIN_NAME、KEYCHAIN_PASSWORD 與 CERTIFICATE_PASSWORD 就是我們在 fasten 設定教學時使用的環境變數。CERT_PATH 是你放置在 Jenkins 主機上的 code signing 目錄的位置。這裡我們會建議使用絕對路徑,經驗上,有時候設定相對路徑在某些 action 上面是無法運作的。 最後兩行則是安裝 dependency 跟真正執行 fastlane 來build你的project。這邊我們可以看得出來,我們的 nightly build task 會執行 developerRelease 這個 lane。

到這邊,我們終於把我們的 nightly build 建立起來了!你可以在 Jenkins 的 project 頁面點擊 build now 來看看你的任務是否有運行成功。

Screen Shot 2018-03-18 at 00.06.57.png

在底下的 Build history,你可以看到每一次 build 的時間與成功或失敗的記錄,點擊 build number 可以看到更詳細的狀態,也可以看到 console 的輸出。這些功能都可以讓你很方便地了解 build 的狀況還有如果發生問題的時候,能夠更快地找到問題並解決。

為甚麼任務一直失敗?

只要是跟系統相關的問題,通常狀況都非常多且複雜,雖然大多的錯誤都可以在 google 上找到解答,但是有時候你得到的錯誤訊息並不是有用的或是相關的。以下有幾點小技巧,讓你在遇到問題時可以更快獲得解答。

Unlock your KeyChain

不管你想把 certificate 存在那一個keychain裡,都要記得解鎖它噢。

Screen Shot 2018-03-18 at 00.12.21.png

想要了解更進階的 keychain 產生技巧,避免動到預設 login keychain,還有減少certificate 被從 keychain copy 出來的風險,可以參考小弟的 github project,裡面的 fastfile 是自動產生 keychain 並且在使用過後刪除 keychain 的範例。

先確保能在 Jenkins 主機能夠 build & export

fastlane 其實是把 Xcode commandline tool 打包成人類比較好理解的語言,所以通常如果不能 build,有很大的機會是因為 Xcode 本身就 build 不過,所以如果 Jenkins 的任務不過,請先不要急著在 Jenkins 的介面上 debug,先在 Jenkins 主機上,透過 commandline build 你的 project,這樣可以得到更多有用的資訊。

xcodebuild clean archive -workspace <your workspace file> -scheme <your scheme>

你可以加上 —verbose 參數,讓輸出的訊息更完整。

fastlane 透過 parse 你的 build setting 來決定要怎樣執成任務,所以如果你想確定 build 設定是不是正確,可以使用以下指令來檢查:

xcodebuild -showBuildSettings -workspace <your workspace file> -scheme <your scheme> -configuration <the configuration you want to check>

當你 achieve 完之後,也可以測試一下是否能夠 export 成功:

xcodebuild -exportArchive \
           -exportFormat ipa \
           -archivePath <ARCH PATH>" \
           -exportPath <IPA PATH> \
           -exportProvisioningProfile "<Provisioning Profile>"

CD 系統的設計原則

以 iOS/macOS 的 CD 系統來說,減少 bug 並且提早下班(或務實一點,準時下班),通常有幾個要點:

  • 減少系統相依性:盡量不要依賴某個系統的程式,像是需要 ruby 的某個版本,或者呼叫某個在 usr/local/bin 的程式。
  • 任務跟任務之間不能有相依性:每次的任務都是全新的開始,不能有下次任務用到上次任務產出的資料的情況發生,最好的做法就是執行完任務之後,就把中間產物刪除。
  • CD 系統要能夠被很快地複製:就算轉換到不同電腦上,應該也要能夠在裝完 Jenkins 後就直接被執行,像是把跟 project 無關的設定,如 path 跟 password,都移到 environment variables,這樣任務出錯時,我們就能夠把專注力放在我們的程式,而不是某個主機的設定上。

最後,你也可以參考 fastlane 的 troubleshooting,這裡面有許多有用的資訊:Troubleshooting - fastlane docs。 可以看到,大多數的問題,都是萬惡的 code signing 引起的,iOS 工程師通常會在一天的一早開始處理不能 build 的問題,並且在下班前五分鐘發現原來是用到了舊的 certificate,然後流著淚在公司加班。

Summary

雖然 iOS/macOS 工程師,有很大一部份都是一人團隊,所以所謂的 delivery 也就是 archive 後,直接點 Crashlytics 的 distribute 就送出了。但是就算是一人團隊,透過一個自動化系統,就能夠把 build 任務分攤到別的主機,減少 build 時切換環境發生的錯誤,還是非常值得投資的。更不用說在多人團隊,跟 workflow 結合的 delivery system,能夠帶來的好處就更多了。如果你不喜歡寫 script 或跟系統打交道,目前 Apple 生態系的 CD 系統其實也非常蓬勃,包括個人覺得相當好用的 bitrise、很多人都在使用的 CircleCI、還有被 Apple 買走的 Buddybuild(期待apple原生的CD環境),都是非常可以考慮的選項。這篇文章主要著眼在了解如何透過 fastlane 跟 Jenkins 建置簡單的 CD 系統,如果你都了解的話,一定可以設計出一套符合你目前團隊工作流程的 CD 系統的!

好了,前情提要真的太久了,本文開始。


Black Mirror

MV5BMjg1NTEyMDI3MV5BMl5BanBnXkFtZTgwNTg2MzgzMDI@._V1_SX1500_CR0,0,1500,999_AL_.jpg

身為一個反烏托邦腦粉,一定要推薦一下這部反烏托邦經典影集:Black Mirror。這是一部英國的影集,目前已經出到第四季,每一集之前完全沒有連貫,都是自成一格的獨立故事,也就是說你可以隨時從隨便一集開始看都沒關係。每一集都會描述一個科技或制度非常極端的世界,像是能夠讓人分不清真實或虛擬的 VR,或者一個能夠把一輩子看過的東西全部都錄下來的隱型眼鏡,透過這種極端的設定,來讓我們看到現在社會上某些方便的科技或法律可能帶來問題跟隱憂。每一集大概都一小時,製作精美的像電影一樣,劇情通常也是完全不會沉悶(除了反思的時候常會覺得人生無望XDDD),是評價跟閱覽數都相當高的影集。 Men Against Fire 是第三季的第五集,是第三季裡面個人覺得最好看的一集(第六集是最受歡迎的一集),雖然很想介紹內容,但是一介紹就爆雷了,所以請大家先去看影片,看完再回來敲碗(也就是拖稿)。最後提醒,大家可以透過Netflix觀看到這部影集喔,也請 Netflix 如果覺得你們的流量上升,那應該是因為小弟也有貢獻一、兩個導流,就給小弟一點業配謝謝!XD

Show Comments