[譯] 構建、測試、分發!運用 Fastlane 與 Jenkins,完整的 iOS 持續交付指南

talisk發表於2019-03-02

iOS/macOS 真的很有趣。 你可以在很多領域獲得知識!你可能會了解 Bezier 或 3D 變換等圖形技術。你也需要了解如何使用資料庫、設計高效的架構。此外,你應該掌握嵌入式系統的記憶體管理方式(特別是那些處於 MRC 時代的人)。所有這些使得 iOS/macOS 的開發如此多元化並且具有挑戰性。

在這篇文章裡,我們將要學習你可能想了解的另一些知識:持續交付(CD)。持續交付是一種軟體方法,可幫助你隨時可靠地釋出產品。持續交付(CD)通常帶有術語持續整合(CI)。CI 也是一種軟體工程技術。這意味著系統始終將開發者的工作不斷地合併到主線。CI 和 CD 不僅對一個大團隊有用,而且對單人團隊也有用。如果你是單人團隊的唯一開發人員,CD 對你來說可能意味著更多,因為每個應用程式開發人員都無法避免交付。因此,本文將重點介紹如何為你的應用程式構建 CD 系統。幸運的是,所有這些技術也可以用於構建 CI 系統。

想象一下我們正在開發一款名為 Brewer 的 iOS 應用,我們的工作流大概像下圖這樣:

[譯] 構建、測試、分發!運用 Fastlane 與 Jenkins,完整的 iOS 持續交付指南

首先,我們開發。然後 QA 組的同事幫我們手工測試應用。QA 人員批准構建測試後,我們放出(提交到 AppStore 待稽核)我們的應用。在不同的階段中,我們有不同的環境。在開發期,我們建立的應用在一個測試環境中,以備平時測試。當 QA 組正在測試時,我們準備一個生產環境的應用,這可能是每週專門提供給 QA 測試用。最後,我們提交一個生產環境的應用。這樣,最終構建可能沒有一個明確的時間表。

讓我們深入瞭解交付部分。你可能會發現我們在構建測試應用程式方面有很多重複的工作。這是 CD 系統可以幫助你的。具體來說,我們的 CD 系統需要:

  1. 在不同的環境中構建應用程式(測試環境/生產環境)。
  2. 根據我們選擇的環境給應用簽名。
  3. 匯出應用程式並將其傳送到分發平臺(例如 Crashlytics 和 TestFlight)。
  4. 根據特定的時間表,構建應用程式。

概要

以下是我們在本文中要做的事情:

  • 設定你的專案:如何設定你的專案以支援不同環境之間的切換。
  • 手動程式碼簽名:如何手動處理證書和配置檔案。
  • 獨立環境:如何使用 Bundler 隔離系統環境。
  • 使用 fastlane 構建:如何使用 fastlane 構建和匯出應用程式。
  • Jenkins 今晚將為你服務:Jenkins 如何幫助你安排任務。

開始之前,你可能需要看看這些:

如果你是個忙碌的帥哥或靚女,別擔心,我為你在公共倉庫裡準備了 Brewer 應用以及一些示例指令碼!

那麼,讓我們開始吧!

設定你的專案

我們通常在開發人員測試階段連線到開發伺服器或測試伺服器。我們還需要在將應用發給 QA 組測試,或 AppStore 時,連線到生產伺服器。通過編輯程式碼切換伺服器可能不是一個好主意。這裡我們使用 Xcode 中的構建配置和編譯器標誌。我們不會詳細介紹配置。如果你對該設定該興趣,可以看看這篇 Yuri Chukhlib 寫的不錯的文章:

在我們的 Brewer 專案中,我們有三個構建選項:

  • Staging
  • Production
  • Release

每個都對映到特定的 Bundle 標識:

[譯] 構建、測試、分發!運用 Fastlane 與 Jenkins,完整的 iOS 持續交付指南

我們通過設定標識,來讓程式碼知曉我們正在用哪個環境。

[譯] 構建、測試、分發!運用 Fastlane 與 Jenkins,完整的 iOS 持續交付指南

因此我們可以像這樣來寫:

#if PROD
  print(“We brew beer in the Production”)
#elseif STG
  print(“We brew beer in the Staging”)
#endif
複製程式碼

現在我們能夠不用修改程式碼,通過構建選項來切換測試環境與生產環境了!?

手動程式碼簽名

[譯] 構建、測試、分發!運用 Fastlane 與 Jenkins,完整的 iOS 持續交付指南

這是每個 iOS / macOS 開發人員熟知的紅色按鈕。我們通過取消選中此框來啟動每個專案。但為什麼它如此臭名昭著?你可能知道它會下載證書和配置檔案,並將其嵌入到你的專案和系統中。如果有任何檔案遺漏,它會為你製作一個新檔案。對於單人的專案組來說,這裡不會有問題。但是如果你在一個大團隊中,你可能會無意中重新整理原始證書,然後由於證書無效而導致構建系統停止工作。對我們來說,這是一個隱藏了太多資訊的黑匣子。

所以在我們 Brewer 專案中,我們想手動做這件事,在我們的配置中有有三個應用 ID:

  • works.sth.brewer.staging
  • works.sth.brewer.production
  • works.sth.brewer

我們將在這篇文章裡關注前兩個配置,現在我們要準備:

  • Certificate:一個 Ad Hoc、App Store 分發證書,採用 .p12 格式。
  • Provisioning Profiles:兩個應用標識的 Ad Hoc 分發配置檔案,works.sth.brewer.stagingworks.sth.brewer.production

提醒下,我們需要 p12 格式的證書檔案,因為我們希望其可用在不同的機器上,只有 .p12 格式包含了證書的私鑰。看這篇文章來了解如何將 .cer(DEM 格式)檔案轉換為 .p12(P12 格式)格式檔案。

現在目錄下有我們的證書籤名檔案:

[譯] 構建、測試、分發!運用 Fastlane 與 Jenkins,完整的 iOS 持續交付指南

這些檔案由 CD 系統使用,所以請將該資料夾放在 CD 機器上。 請不要將這些檔案放到你的專案中,不要將它們提交到你的專案倉庫。 將程式碼簽名檔案託管在不同的私有倉庫中可以。你可能希望瞭解有關安全問題的討論,可以看看 match — fastlane docs

用 fastlane 構建 ?

fastlane 是讓開發和釋出工作流自動化的工具。比如,它可以通過一個指令碼構建應用、執行單元測試、向 Crashlytics 上傳二進位制。你不需要一步一步手動地做這些事。

在這個專案中,我們將要用 fastlane 完成兩項任務:

  • 構建、釋出測試環境的應用。
  • 構建、釋出生產環境的應用。

這兩種方法之間的區別僅僅在於配置。共同的任務包括:

  • 用證書和配置檔案簽名
  • 構建並匯出應用
  • 把應用上傳到 Crashlytics(或其它分發平臺)

明確了任務,我們現在可以開始編寫 fastlane 指令碼了。我們將使用 Swift 版 fastlane 在我們的專案中編寫我們的指令碼。Swift 版 fastlane 還在測試階段,執行一切良好,但除了:

  • 它還不支援外掛
  • 它不能捕獲異常

但是用 Swift 編寫指令碼使得開發人員更易於閱讀和維護。而且你可以輕鬆地將 Swift 指令碼轉換為 Ruby 指令碼。所以讓我們試試吧!

首先初始化我們的專案(還記得 Bundler 吧?):

bundler exec fastlane init swift
複製程式碼

然後,你可以在 fastlane/Fastfile.swift 中找到指令碼。在指令碼中,有一個 fastfile 類。這是我們的主要程式。在本類中用 Lane 為字尾命名的每一個方法都是一個 lane。我們可以將預定義的動作新增到 lane,並使用命令執行 lane:

bundle exec fastlane <lane name>.
複製程式碼

讓我們填充一些程式碼:

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
    }
}
複製程式碼

我們為任務建立兩個 lane:developerReleaseqaRelease。這兩個任務都做了同樣的事:用指定配置來構建打包,並將匯出的 ipa 上傳到 Crashlytics。

兩個 lane 都有一個 package 方法。package() 方法的宣告看起來是這樣:

func package(config: Configuration) {
}
複製程式碼

引數時一個遵循 Configuration 協議的物件。Configuration 的定義如下:

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 }
}
複製程式碼

然後我們建立兩個兩個遵循該協議的結構體:

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"
}
複製程式碼

使用該協議,我們能夠確保每個配置都具有所需的設定。每當我們有新的配置時,我們不需要編寫 package 的詳細資訊。

那麼,package(config:) 看起來如何?說愛你他需要從檔案系統中匯入證書。記住我們的程式碼簽名資料夾,我們用 importCertificate action 來實現我們的目標。

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

keychainName是你的鑰匙串的名稱,預設名稱是『登入』。keychainPassword 是你鑰匙串的密碼,fastlane 使用它來解鎖你的鑰匙串。由於我們將 Fastfile.swift 提交到倉庫以確保交付程式碼在每臺計算機中都是一致的,因此在 Fastfile.swift 中將密碼寫為字串文字可不是一個好主意。因此,我們使用環境變數來替換字串文字。在系統中,我們用這個方式來儲存環境變數:

export KEYCHAIN_NAME=”KEYCHAIN_NAME”;
export KEYCHAIN_PASSWORD=”YOUR_PASSWORD”;
複製程式碼

在 Fastfile 中,我們用 environmentVariable(get:) 獲得環境變數的值。通過使用環境變數,我們可以避免程式碼中出現密碼,來顯著提高安全性。

回到 importCertificate()certificatePath 是你的 .p12 證書檔案的路徑。我們建立一個名為 ProjectSetting 的列舉來標識共享的專案設定。這裡我們也用環境變數來傳遞密碼。

enum ProjectSetting {
    static let codeSigningPath = environmentVariable(get: "CODESIGNING_PATH")
    static let certificatePassword = environmentVariable(get: "CERTIFICATE_PASSWORD")
}
複製程式碼

匯入證書後,我們將設定配置檔案。我們用 updateProjectProvisioning

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

此操作獲取配置檔案,匯入配置檔案並在指定的配置中修改你的專案設定。配置檔案引數是提供配置檔案的路徑。目標過濾器使用正規表示式符號來查詢我們要修改的目標。請注意,updateProjectProvisioning 不會修改你的專案檔案,因此如果你想在本地計算機上執行它,請小心。CD 任務無關緊要,因為 CD 系統不會對程式碼庫進行任何更改。

好的,我們完成了程式碼簽名部分!以下部分將非常簡單明瞭,請耐心等待!

讓我們現在來構建應用:

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 幫助你構建並匯出專案。它底層是呼叫 xcodebuild 的。除了 exportOptions,每個引數都很直觀。讓我們看看它長啥樣:

exportOptions: [
    "signingStyle": "manual",
    "provisioningProfiles": [config.appIdentifier: config.provisioningProfile] ]
複製程式碼

不像其他引數,它是一個 dictionary。signingStyle 是你想要程式碼簽名的方式,我們在這裡放置了 manualprovisioningProfiles 也是一個字典。這是應用程式 ID 和相應的配置檔案之間的對映。最後我們完成了 fastlane 設定!現在你可以直接執行:

bundle exec fastlane qaRelease
複製程式碼

或是這樣:

bundle exec fastlane developerRelease
複製程式碼

來用合適的配置釋出測試構建!

Jenkins 今晚將為你服務

Jenkins是一個自動化伺服器,可幫助你執行 CI 與 CD 任務。它執行一個 Web GUI 介面,並且很容易定製,所以它對於敏捷團隊來說是一個很好的選擇。Jenkins 在我們專案中的規則如圖所示:

[譯] 構建、測試、分發!運用 Fastlane 與 Jenkins,完整的 iOS 持續交付指南

Jenkins 獲取專案的最新程式碼並定期為你執行任務。在執行指令碼的部分,我們可以看到 Jenkins 實際上執行了我們在前幾節中所做的任務。但現在我們不需要自己做,Jenkins 無縫地為你完成了這些!

從每晚構建作業開始,讓我們開始建立一個 Jenkins 任務。首先,我們建立一個『自定義專案』,並進入它的『配置』頁面。我們需要配置的第一件事是原始碼管理(SCM)部分:

[譯] 構建、測試、分發!運用 Fastlane 與 Jenkins,完整的 iOS 持續交付指南

Repository URL 是專案原始碼的地址。如果你的倉庫是私有的,你需要新增 Credentials 以獲得倉庫讀取許可權。你可以在 Branches to build 中設定目標分支,通常它是你的預設分支。

然後,接下來我們可以看到 Builder Trigger 部分。在本節中,我們可以決定是什麼觸發了構建作業。根據我們的工作流,我們希望它每週週末晚上開始。

[譯] 構建、測試、分發!運用 Fastlane 與 Jenkins,完整的 iOS 持續交付指南

然後我們檢查 Poll SCM,這意味著 Jenkins 會定期輪詢指定的倉庫。日程安排文字區域要寫上以下內容:

H 0 * * 0–4
複製程式碼

這是什麼意思呢?讓我們先看看官方說明:

這個欄位遵循 cron 的語法(有細微差別)。具體而言,每行包含由 TAB 或空格分隔的 5 個欄位: MINUTE HOUR DOM MONTH DOW MINUTE 分鐘小時內的分鐘數(0-59) HOUR 小時一天中的小時(0-23) DOM 每月的一天(1-31) MONTH(1-12) DOW 星期幾(0-7),其中0和7是星期日。

它由五部分構成

  • 分鐘
  • 小時
  • 日期
  • 月份

該欄位可以是數字。 我們也可以用『*』來表示『所有』數字。 我們用『H』表示一個 hash,自動選擇『某個』數字。

所以我們會這樣寫:

H 0 * * 0–4
複製程式碼

意思是:任務將在週日到週四,每晚零點到一點執行。

最後,但是最重要的,來檢查下 Build 部分的內容,這是我們希望 Jenkins 執行的東西:

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
複製程式碼

前 6 行是設定我們之前描述的環境變數。第 7 行安裝依賴項,包括 fastlane。然後最後一行執行一個名為『developerRelease』的 lane。總之,這個任務每個工作日晚上都會建立並上傳一個 developerRelease。這是我們第一次每晚構建!?

你可以通過單擊 Jenkins 專案頁面的側面選單中的內部版本號來檢查構建狀態:

[譯] 構建、測試、分發!運用 Fastlane 與 Jenkins,完整的 iOS 持續交付指南

綜述

我們一起學會了如何用 fastlane 和 Jenkins 建立 CD 系統。我們瞭解如何手動管理程式碼簽名。我們自動為我們建立了一條執行任務。我們還探討了如何在不更改程式碼的情況下切換配置。最後,我們建立了一個每天晚上構建應用程式的 CD 系統。

儘管許多 iOS 與 macOS 應用程式是由單人團隊建立的,但自動化交付流程仍然是一項高效的改進。通過自動化流程,我們可以降低配置錯誤的風險,避免被過期的程式碼簽名所阻塞,並減少構建上傳的等待時間。

本文中介紹的工作流程可能與你的工作流程不完全相同,但掌握每個團隊自己的工作流程和步伐非常重要。所以你必須建立自己的 CD 系統來滿足你的團隊的需要。通過將這些技術用作構建模組,你一定能夠構建自己定製的、更好的 CD 系統!


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章