iOS/macOS 真的很有趣。 你可以在很多領域獲得知識!你可能會了解 Bezier 或 3D 變換等圖形技術。你也需要了解如何使用資料庫、設計高效的架構。此外,你應該掌握嵌入式系統的記憶體管理方式(特別是那些處於 MRC 時代的人)。所有這些使得 iOS/macOS 的開發如此多元化並且具有挑戰性。
在這篇文章裡,我們將要學習你可能想了解的另一些知識:持續交付(CD)。持續交付是一種軟體方法,可幫助你隨時可靠地釋出產品。持續交付(CD)通常帶有術語持續整合(CI)。CI 也是一種軟體工程技術。這意味著系統始終將開發者的工作不斷地合併到主線。CI 和 CD 不僅對一個大團隊有用,而且對單人團隊也有用。如果你是單人團隊的唯一開發人員,CD 對你來說可能意味著更多,因為每個應用程式開發人員都無法避免交付。因此,本文將重點介紹如何為你的應用程式構建 CD 系統。幸運的是,所有這些技術也可以用於構建 CI 系統。
想象一下我們正在開發一款名為 Brewer 的 iOS 應用,我們的工作流大概像下圖這樣:
首先,我們開發。然後 QA 組的同事幫我們手工測試應用。QA 人員批准構建測試後,我們放出(提交到 AppStore 待稽核)我們的應用。在不同的階段中,我們有不同的環境。在開發期,我們建立的應用在一個測試環境中,以備平時測試。當 QA 組正在測試時,我們準備一個生產環境的應用,這可能是每週專門提供給 QA 測試用。最後,我們提交一個生產環境的應用。這樣,最終構建可能沒有一個明確的時間表。
讓我們深入瞭解交付部分。你可能會發現我們在構建測試應用程式方面有很多重複的工作。這是 CD 系統可以幫助你的。具體來說,我們的 CD 系統需要:
- 在不同的環境中構建應用程式(測試環境/生產環境)。
- 根據我們選擇的環境給應用簽名。
- 匯出應用程式並將其傳送到分發平臺(例如 Crashlytics 和 TestFlight)。
- 根據特定的時間表,構建應用程式。
概要
以下是我們在本文中要做的事情:
- 設定你的專案:如何設定你的專案以支援不同環境之間的切換。
- 手動程式碼簽名:如何手動處理證書和配置檔案。
- 獨立環境:如何使用 Bundler 隔離系統環境。
- 使用 fastlane 構建:如何使用 fastlane 構建和匯出應用程式。
- Jenkins 今晚將為你服務:Jenkins 如何幫助你安排任務。
開始之前,你可能需要看看這些:
- 什麼是 fastlane
- 什麼是 Jenkins
- 什麼是 Code signing
如果你是個忙碌的帥哥或靚女,別擔心,我為你在公共倉庫裡準備了 Brewer 應用以及一些示例指令碼!
那麼,讓我們開始吧!
設定你的專案
我們通常在開發人員測試階段連線到開發伺服器或測試伺服器。我們還需要在將應用發給 QA 組測試,或 AppStore 時,連線到生產伺服器。通過編輯程式碼切換伺服器可能不是一個好主意。這裡我們使用 Xcode 中的構建配置和編譯器標誌。我們不會詳細介紹配置。如果你對該設定該興趣,可以看看這篇 Yuri Chukhlib 寫的不錯的文章:
在我們的 Brewer 專案中,我們有三個構建選項:
- Staging
- Production
- Release
每個都對映到特定的 Bundle 標識:
我們通過設定標識,來讓程式碼知曉我們正在用哪個環境。
因此我們可以像這樣來寫:
#if PROD
print(“We brew beer in the Production”)
#elseif STG
print(“We brew beer in the Staging”)
#endif
複製程式碼
現在我們能夠不用修改程式碼,通過構建選項來切換測試環境與生產環境了!?
手動程式碼簽名
這是每個 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.staging 與 works.sth.brewer.production。
提醒下,我們需要 p12 格式的證書檔案,因為我們希望其可用在不同的機器上,只有 .p12 格式包含了證書的私鑰。看這篇文章來了解如何將 .cer(DEM 格式)檔案轉換為 .p12(P12 格式)格式檔案。
現在目錄下有我們的證書籤名檔案:
這些檔案由 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:developerRelease 和 qaRelease。這兩個任務都做了同樣的事:用指定配置來構建打包,並將匯出的 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 是你想要程式碼簽名的方式,我們在這裡放置了 manual。provisioningProfiles 也是一個字典。這是應用程式 ID 和相應的配置檔案之間的對映。最後我們完成了 fastlane 設定!現在你可以直接執行:
bundle exec fastlane qaRelease
複製程式碼
或是這樣:
bundle exec fastlane developerRelease
複製程式碼
來用合適的配置釋出測試構建!
Jenkins 今晚將為你服務
Jenkins是一個自動化伺服器,可幫助你執行 CI 與 CD 任務。它執行一個 Web GUI 介面,並且很容易定製,所以它對於敏捷團隊來說是一個很好的選擇。Jenkins 在我們專案中的規則如圖所示:
Jenkins 獲取專案的最新程式碼並定期為你執行任務。在執行指令碼的部分,我們可以看到 Jenkins 實際上執行了我們在前幾節中所做的任務。但現在我們不需要自己做,Jenkins 無縫地為你完成了這些!
從每晚構建作業開始,讓我們開始建立一個 Jenkins 任務。首先,我們建立一個『自定義專案』,並進入它的『配置』頁面。我們需要配置的第一件事是原始碼管理(SCM)部分:
Repository URL 是專案原始碼的地址。如果你的倉庫是私有的,你需要新增 Credentials 以獲得倉庫讀取許可權。你可以在 Branches to build 中設定目標分支,通常它是你的預設分支。
然後,接下來我們可以看到 Builder Trigger 部分。在本節中,我們可以決定是什麼觸發了構建作業。根據我們的工作流,我們希望它每週週末晚上開始。
然後我們檢查 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 建立 CD 系統。我們瞭解如何手動管理程式碼簽名。我們自動為我們建立了一條執行任務。我們還探討了如何在不更改程式碼的情況下切換配置。最後,我們建立了一個每天晚上構建應用程式的 CD 系統。
儘管許多 iOS 與 macOS 應用程式是由單人團隊建立的,但自動化交付流程仍然是一項高效的改進。通過自動化流程,我們可以降低配置錯誤的風險,避免被過期的程式碼簽名所阻塞,並減少構建上傳的等待時間。
本文中介紹的工作流程可能與你的工作流程不完全相同,但掌握每個團隊自己的工作流程和步伐非常重要。所以你必須建立自己的 CD 系統來滿足你的團隊的需要。通過將這些技術用作構建模組,你一定能夠構建自己定製的、更好的 CD 系統!
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。