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

IMG_7479.jpg

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

所謂的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都是不行的。這時候matchsighcert就完全派不上用場了。除此之外,大家應該都有跟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表示我們希望把程式安裝在當前的目錄底下,而不是裝到系統檔案夾如/usrlocalbin或usrbin裡面,這樣你的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

 

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

bundle exec fastlane developerRelease
# or
bundle exec fastlane qaRelease 

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

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

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

 

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

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

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的相關檔案的位置跟密碼,而這些資訊一樣是透過系統變數給予的。

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

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

 

這個參數的型態是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變數,像是LCALL跟LANG主要是要確保fastlane能運行在正確的locale底下,而KEYCHAINNAME、KEYCHAINPASSWORD與CERTIFICATEPASSWORD就是我們在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的某個版本,或者呼叫某個在usrlocal/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

歡迎來到真實世界 – Continuous Delivery:在你睡覺的時候,電腦們可是都在勤奮地工作喔 有 “ 34 則迴響 ”

  1. 大大你好~
    最近看您的文章開始使用 Jenkins 時有遇到一些問題想要請教一下。我照個您的設定一路做到架設 fastlane, Jenkins,然後到 Jenkins 這關我卡關了,一開始使用 fastlane 出現錯誤,之後我嘗試使用
    “`
    xcodebuild clean archive -workspace -scheme
    “`
    來 build 我的專案,但我還是遇到幾個問題:
    1.
    Code Signing Error: No profile for team ‘xxxxxxxxxx’ matching ‘xxxxxx provisioning file’ found: Xcode couldn’t find any provisioning profiles matching ‘xxxxxxxxxx/xxxxxx provisioning file’. Install the profile (by dragging and dropping it onto Xcode’s dock item) or select a different one in the General tab of the target editor.

    遇到問題一後,我跟著這個解法:http://code-dojo.blogspot.tw/…/fix-ios-code-signing-issue-w…
    把 MobileDevice/ProvisioningFile 手動丟進 Jenkins 相對應的資料夾中,但結果出現新的問題 2
    2.
    Command /bin/sh failed with exit code 1

    ** ARCHIVE FAILED **

    The following build commands failed:
    PhaseScriptExecution [CP]\ Embed\ Pods\ Frameworks /Users/Shared/Jenkins/Library/Developer/Xcode/DerivedData/xxxx-iOS-fiefytviwdzcwhcpzmgqylqrxhnl/Build/Intermediates.noindex/ArchiveIntermediates/xxxx-iOS-AppStore/IntermediateBuildFilesPath/xxxx-iOS.build/App\ Store-iphoneos/xxxx-iOS.build/Script-D4A72D515718C2A87C201C2D.sh
    (1 failure)

    感覺已經到最後一關了,我猜是 provisioning file 的問題,不知道是路徑的問題還是其他問題,想請問大大您有遇過這樣的問題嗎?

    1. hi 這兩個問題都還蠻常遇到的,第二個問題我猜可能是certificate的問題,看看能不能印出xcodebuild的log,看有沒有相關的錯誤訊息,經驗上第二個錯誤訊息通常都是誤植。可以檢查keychain裡面有沒有對應的certificate,並且把keychain unlock,如果還是不行,可以試著清掉derived data再試看看

      1. 我有使用 delete keychain -> create keychain 方式來管理我的 cert & provision,不確定使用個方式還需不需要 unlock keychain?還有如何檢查 keychain 中有沒有相對應的 certificate 呢?

        清理過 derived data 也試過了,一樣是不行

        第二個步驟我使用 xcodebuild,但似乎沒有什麼 log,唯一像是 log 的在此:
        https://gist. github.com/yoxisem544/4daf64779b11153b3e89697c11870849

        謝謝大大熱心回答 > <
        感覺這個弄好就OK了

      2. 看log最後一行是停在

        Code Signing /Users/Shared/Jenkins/Library/Developer/Xcode/DerivedData/2140-iOS-fiefytviwdzcwhcpzmgqylqrxhnl/Build/Intermediates.noindex/ArchiveIntermediates/2140-iOS-Beta/InstallationBuildProductsLocation/Applications//2140 Beta.app/Frameworks/Alamofire.framework with Identity iPhone Distribution: BITWAN INC. (ZJQZ436DQA)

        表示它找不到iPhone Distribution: BITWAN INC. (ZJQZ436DQA)這張certificate,檢查的方法就是看keychain裡有沒有同樣teamID、同樣是distribution的certificate。並且確定keychain是有被unlocked的(圖示是解開的鎖頭)

        我記得舊版的fastlane createKeychain是不會自動unlock的,所以也可以檢查一下fastlane版本

        還有一種狀況是certifcate有設定passphrase(p12),在透過createKeychain匯入時如果匯入失敗,是不會出現exception,fastlane會照常執行,這個可能也要注意一下

        以上希望有幫助!

      3. 我剛剛使用 security find-certificate 發現 keychain-db 中有 ZJQZ436DQA 這張 certificate,也確定 fastlant 是在新版的狀態,這樣的 create_keychain 應該會自動解鎖吧?我看源碼有 unlock_keychain.rb 這包東西。(這邊就當成是 unlock 了)

        大大所說的certifcate有設定passphrase匯入失敗有什麼方式可以除錯嗎?
        我在 createKeychain 後 importCertificate 再 updateProjectProvisioning,跟您在 github 上的 code 流程應該是一樣的,所以也許是在這個環節出了問題?

        在此先感謝大大熱心回答 > <

    1. 看起來fastFile應該是正確的
      在importCertificate時,fastlane會有成功或失敗的log,可以從Jenkins的console查看(搜尋importCertificate應該就會看到)。
      請問一下你有試過用Xcode archieve同樣的專案嗎?
      另外在command line執行xcodebuild時可以試著指定configuration,這樣可以確保它會抓指定的confguration裡的rpovisioning profile

      整理一下我自己的抓錯流程通常是:
      1. 先在xcode跑archieve,確定certificate有正確被匯入keychain
      2. 在commandline跑xcodebuild(帶上configuraiton, workspace等等參數),確保fastlane在跑build_app時不會出錯
      以上都是先手動匯入正確的certificate進keychain

      上面都完成後,再跑fastlane,這樣應該可以確定問題出在那邊

      ps 不好意思我修改你上面的留言,不然都要滾很久XD

      1. 我在 jenkins 搜尋 importCertificate 是正常的,並無成功失敗log,跟local端一樣。

        手動 import cert 到 keychain 還沒嘗試過,找個時間再來試試QQ
        謝謝大大回答!

  2. 大大您好,看到你的教學文,深受啟發,但是考量到,實際使用,我在實作步驟上,做到了手動管理certificates,再來是個別安裝好,Fastlane and Jenkins過程中困難重重,不段找尋其他網路資料來補足遇到的issues。

    上述步驟都順利過關後,現在卡在…fastlane大大你的file是用swift寫的,可是我想要用plugin所以,只好採用ruby。但我不太會將你的swift語法,直接轉成ruby。請問我該如何按照你的swift邏輯,轉換成ruby版本呢?

    還是我只能去閱讀fastlane的docs,慢慢學會ruby…

    1. hihi
      雖然Swift跟Ruby語法不太一樣,但是fastlane的action在命名上都是有互相對應的,比方說gym, cocoapods等等兩邊名稱都是一樣的,頂多在Swift會把像是build_app改成buildApp
      不過參數的部份要注意,像build_ios_app(scheme: “MyApp", workspace: “MyApp.xcworkspace")在ruby裡面參數順序是沒有關係的,不過在Swift,就需要照原本的順序。因為Swift的fastlane底層其實就是呼叫Ruby,所以所有的對應都可以在https://github.com/fastlane/fastlane/blob/master/fastlane/swift/Fastlane.swift 原始碼裡面找到。
      但如果你說的是configuration那邊用protocol的技巧,就真的需要一點ruby語法的基礎,可以參考ruby的"module"的語法,用module去做出像protocol一樣的功能出來。
      希望有幫助囉!

      1. 謝謝大大,迅速的回覆!看來勢必還是要去學一下ruby的基礎語法囉~ Orz 真希望fastlane swift 趕快支援plugin

    2. 這是我的fastfile配置,有參考NEO大的部分配置,跟我的做結合,是ruby寫的,也跟NEO大說的一樣,其實語法基本上是互通的,ruby也是個不錯的語言值得一學

      1. 林大大 您好!
        超感謝你提供ruby版本,我自己也去讀了一遍ruby語法教學,大至少都看得懂了!

        想請問一下,是否有一個地方怪怪的
        module ProjectSetting
        Target = “Your-App"
        ProjectName = “Your-App.xcodeproj"
        WorkspaceName = “Your-App.xcworkspace"
        SDK = “iphoneos"
        end

        但是在build_app的方法呼叫參數裡卻有一個ProjectSetting::Workspace的呼叫,但是module ProjectSetting 裡面的成員是WorkspaceName,請問這樣是一個bug嗎?:

        build_app(
        workspace: ProjectSetting::Workspace,
        scheme: config.scheme,
        clean: true,
        output_directory: @output_directory,
        output_name: “#{config.productName}.ipa",
        configuration: config.buildConfiguration,
        silent: true,
        export_method: config.exportMethod,
        export_options: {
        signingStyle: “automatic", # or manual
        compileBitcode: false,
        uploadBitcode: false,
        provisioningProfiles: {
        config.appIdentifier => config.provisioningProfile
        }
        },
        sdk: ProjectSetting::SDK
        )

  3. 作者大大,雖然有林大大提供的超棒ruby fastfile範例,但是他是使用match套件,我想說還是暫時忠於你的文章脈絡,想自己處理codesign。可是您的swift fastfile代碼是屬於散落在文章穿插。能否提供一個gist有完整的swift fastfile嗎?因為像是ProjectSetting似乎範例代碼有少幾個變數,在你其他代碼使用它時,我並沒有看到這個變數,例如
    enum ProjectSetting {
    static let codeSigningPath = environmentVariable(get: “CODESIGNING_PATH")
    static let certificatePassword = environmentVariable(get: “CERTIFICATE_PASSWORD")
    }

    這邊卻呼叫ProjectSetting.project,但是enum ProjectSetting並沒有project的靜態變數。
    updateProjectProvisioning(
    xcodeproj: ProjectSetting.project,
    profile: “\(ProjectSetting.codeSigningPath)/\(config.provisioningProfile).mobileprovision",
    targetFilter: “^\(ProjectSetting.target)$",
    buildConfiguration: config.buildConfiguration
    )

    所以整體閱讀起來有點霧煞煞,再麻煩是否能提供完整的fastfile了,萬分感謝你!

    1. 1. 我貼的code其實是gist網址,code最下面有連結可以點過去哦
      2. 少了幾個變數是因為neo大用的是產生keychain,然後把cert存在裡面做codesign。我是直接使用match,所以不會有以上的key。我跟neo大部分差別在codesign不一樣,其他87%是差不多的唷。
      3. codesign建議使用match,先讓fastlane能跑之後,我們再來處理maunal code sign的部分。因為 codesign 真的很坑
      4. neo大的這一整個 fastfile 牽扯到太多東西,我當初消化內化也花了三四天的時間,雖然local machine可以正確執行,但擺到 jenkins 就掛掉,後來改用 match 就頭好壯壯,超詭異。建議你先讓fastlane能在你的電腦上跑起來,再來看有什麼問題需要調整~沒有跑起來永遠不知道問題在哪XD

      1. 了解! 的確能跑比較重要,不然拖好幾天哩。我是主要想說作者大要求下載的provisionProfile and cert都搞定了,那就來試看看手動,原來林大你還有遇到jenkins掛掉這關啊…那真的很煩。先按照你的思路立馬試看看!

      2. 我後來直接用bitriseCI最快最省事,Jenkins有緣再說XDDD
        然後ENV是環境變數,一般是指定在bash,但向我使用zsh就應該指定在zshrc之中。

        而 FASTLANE_PASSWORD是fastlane用來登入你的 apple id 的密碼(也真的就是你apple id的密碼,用以登入itunesconnect,如果有開二段驗證,請去apple id網站申請app password專用密碼)
        密碼還是別commit到github比較好XD

        因為放到jenkins需要把環境切得很乾淨,所以neo大大才會把ruby版本獨立出來用bundler裝,我之前是卡在keychain產生成功了,問題出在不知道cert&provisioning file有沒有存到keychian中,又或者是讀取的時候讀不到無法codesign,總之問題很妙,後來也有嘗試使用docker來架虛擬機,後來也是放棄。(直接買台mac mini來跑JenkinsCI更方便~畢竟你同時寫code時JenkinsCI在跑,你的電腦CPU還是會吃100%)

      3. 想請教一下林大,你在開頭有使用ENV[“FASTLANE_PASSWORD"] = “"
        但是這個ENV不是應該設定在~/.bash_profile裡面,再到fastfile獲取該值,避免密碼跟著被commit到git server嗎?

      4. 謝謝林大,對於ENV的解釋喔!所以你後來有徹底實現fastlane + jenkins成功運行後,才決定用bitriseCI嗎? 還是jenkins遇到不解之謎,只好試看看bitriseCI?我真的也是慢慢卡慢慢學,哈哈

      5. 林大大…不好意思一直打擾。但想問一下,你有遇到match成功跑完appstore, adhoc, development的cert and pro之後,我build run在實體機,卻一直失敗,Could not write to the device. 請問你有類似的經驗嗎?
        為何可以在虛擬器跑,卻無法跑在實體機上?

      6. 1. 我jenkins, travis嘗試一輪後,只有bitrise能正常使用,後來我也所幸先直接使用bitrise並且專注開發上,等以後有遇到jenkins高人,再請教也不遲
        2. 我猜應該是你codesign沒弄好,以致無法在裝置上執行(但我不知道你是怎麼樣放到手機上執行的),嘗試google看看?或者到fastlane repo 找 issue看看

      7. 林大大,昨天熬夜 睡到剛剛,哈哈。我是之前自己去apple developer 網站自己申請cert and pro然後去Xcode target general 選取provisionProfile。然後可以正常跑在實體機上。但換成match之後,雖然安裝成功,可是反而只能在虛擬機跑,無法在實體機上跑。

        我只好再查查google囉~ 原來你的情況是這樣才換bitrise阿!了解囉~謝謝你的分享。我繼續努力,有結果在po上來回報

  4. 完整的fastfile可以在 https://github.com/koromiko/Brewer/blob/master/fastlane/Fastfile.swift 這邊找到。感謝林大大的分享,必需要同意用match真的省事很多XD 我是因為公司政策沒辦法直接從apple developer protal下載certificate跟provisioning profile,所以才全部都用手動,不然真的可以用match就好,我自己的side project就是用match處理。
    而且我還有遇過直接在Jenkins machine上下fastlane指令跑會過,但用Jenkins跑就是不會過的問題…

    1. 感謝Neo大大!!!提供完整版fastfile,早上慢慢啃它!我也是擔心公司政策,因為服務的是金融機構,可能會管很嚴,所以想說手動解,不過現在自己練習先跑得動match再說,今天搞一整天他的原理,總算順了點!

      1. Yes!!!! 我終於都搞定在本機上 fastlane + match + jenkins 這坑真的多到我快吐了。如果有任何本文讀者有興趣,歡迎一起討論喔,我收集很多issue solutions

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Google photo

您的留言將使用 Google 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s