用於 iOS 開發的 CI 系統:Intel 到 ARM 的轉型
已發表: 2024-02-14在不斷發展的技術領域,公司必須適應變革之風,以保持相關性和競爭力。 從 Intel x86_64 架構到iOS ARM 架構的轉變是席捲科技界的轉變之一, Apple 突破性的 Apple M1 晶片就是例證。 在這種背景下, iOS 的 CI 系統已成為企業應對此轉變的重要考慮因素,確保軟體開發和測試流程保持高效並符合最新的技術標準。
蘋果大約三年前發布了 M1 晶片,從那時起,該公司就明確表示將採用 ARM 架構,並最終放棄對基於英特爾的軟體的支援。 為了保持架構之間的兼容性,Apple 推出了新版本的Rosetta,這是其專有的二進位翻譯框架,在2006 年從PowerPC 到Intel 的重大架構轉型期間,該框架已被證明是可靠的。轉型仍在進行中,我們已經看到Xcode 在 14.3 版本中失去了對 Rosetta 的支援。
在 Miquido,我們幾年前就意識到需要從 Intel 遷移到 ARM。 我們於 2021 年中開始準備工作。作為一家同時擁有多個客戶端、應用程式和專案的軟體公司,我們面臨著一些必須克服的挑戰。 如果您的公司面臨類似情況,本文可以作為您的操作指南。 所描述的情況和解決方案是從iOS 開發的角度描述的- 但您也可能會找到適合其他技術的見解。我們過渡策略的關鍵組成部分涉及確保我們的iOS CI 系統針對新架構進行全面優化,強調了以下內容的重要性: iOS 的 CI 系統在如此重大的變化中保持高效的工作流程和高品質的輸出。
問題:Intel 到 iOS ARM 架構的遷移
遷移過程分為兩個主要分支。
1. 用新的 M1 Macbook 取代現有的基於 Intel 的開發人員計算機
這個過程應該是相對簡單的。 我們制定了一項政策,在兩年內逐步更換所有開發人員的英特爾 Macbook。 目前,我們 95% 的員工都在使用基於 ARM 的 Macbook。
然而,在這個過程中,我們遇到了一些意想不到的挑戰。 2021 年中期,M1 Mac 的短缺減緩了我們的更換進程。 到 2021 年底,我們只能更換近 200 台等待的 Macbook 中的少數幾台。 我們估計大約需要兩年時間才能將公司所有的 Intel Mac 完全替換為 M1 Macbook,包括非 iOS 工程師。
幸運的是,蘋果發布了新的 M1 Pro 和 M2 晶片。 因此,我們將重點從用 M1 Mac 替換英特爾晶片轉向用 M1 Pro 和 M2 晶片替換它們。
軟體尚未準備好進行切換導致開發人員感到沮喪
第一批收到新款 M1 Macbook 的工程師經歷了一段困難時期,因為大多數軟體還沒有準備好切換到蘋果新的 iOS ARM 架構。 Rubygems 和 Cocoapods 等第三方工具(依賴許多其他 Rubygems 的依賴管理工具)受到最大的影響。 其中一些工具當時並未針對 iOS ARM 架構進行編譯,因此大多數軟體必須使用 Rosetta 運行,從而導致效能問題和挫折感。
然而,軟體創建者努力解決大多數出現的問題。 突破性的時刻隨著 Xcode 14.3 的發布而到來,它不再支援 Rosetta。 這向所有軟體開發人員發出了一個明確的信號,即蘋果正在推動英特爾向 iOS ARM 架構遷移。 這迫使大多數先前依賴 Rosetta 的第 3 方軟體開發人員將其軟體遷移到 ARM。 如今,Miquido 每天使用的 99% 的第三方軟體都在沒有 Rosetta 的情況下運作。
2. 更換iOS版Miquido的CI系統
事實證明,更換 Miquido 的持續整合 iOS 系統比僅僅更換機器要複雜得多。首先,請看一下我們當時的基礎設施:
我們有一個 Gitlab 雲端實例和 9 台基於 Intel 的 Mac Mini 連接到它。 這些機器充當作業運行器,Gitlab 負責編排。 每當 CI 作業排隊時,Gitlab 都會將其指派給滿足 gitlab-ci.yml 檔案中指定的專案要求的第一個可用執行程式。 Gitlab 將建立一個包含所有建置命令、變數、路徑等的作業腳本。然後該腳本被移到執行器上並在該機器上執行。
雖然這個設定看起來很強大,但由於對英特爾處理器的支援不佳,我們面臨虛擬化問題。 因此,我們決定不使用 Docker 等虛擬化,而是在實體機本身上執行作業。 我們試圖建立一個基於 Docker 的高效可靠的解決方案,但缺乏 GPU 加速等虛擬化限制導致作業的執行時間是在實體機器上的兩倍。 這導致了更多的開銷並迅速填滿隊列。
由於 macOS SLA,我們只能同時設定兩個虛擬機器。 因此,我們決定擴展實體運行器池,並將它們設定為直接在其作業系統上執行 Gitlab 作業。 然而,這種方法也有一些缺點。
建置過程和運行者管理中的挑戰
- 沒有隔離建置目錄沙箱之外的建置。
運行器在實體機上執行每個構建,這意味著構建不會與構建目錄沙箱隔離。 這有其優點和缺點。 一方面,我們可以使用系統快取來加速構建,因為大多數專案都使用同一組第三方依賴項。
另一方面,快取變得難以維護,因為一個專案的剩餘內容可能會影響所有其他項目。 這對於系統範圍的快取尤其重要,因為 Flutter 和 React Native 開發都使用相同的運行器。 React Native 尤其需要透過 NPM 快取許多依賴項。
- 潛在的系統工具混亂。
儘管這兩個作業都不是使用 sudo 權限執行的,但它們仍然可以存取某些系統或使用者工具,例如 Ruby。 這構成了破壞其中一些工具的潛在威脅,特別是因為 macOS 將 Ruby 用於其一些舊版軟體,包括一些舊版 Xcode 功能。 Ruby 的系統版本不是您想要搞亂的東西。
然而,引入 rbenv 又增加了處理的複雜性。 需要注意的是,Rubygems 是按 Ruby 版本安裝的,其中一些 gem 需要特定版本的 Ruby。 我們使用的幾乎所有第三方工具都依賴 Ruby,其中 Cocoapods 和 Fastlane 是主要參與者。
- 管理簽名身分。
當涉及到執行器上的系統鑰匙圈時,管理來自不同客戶端開發帳戶的多個簽名身分可能是一個挑戰。 簽名身分是高度敏感的數據,因為它使我們能夠對應用程式進行協同設計,使其容易受到潛在威脅。
為了確保安全,身份應該跨項目沙箱化並受到保護。 然而,考慮到 macOS 在鑰匙圈實現中引入的額外複雜性,這個過程可能會成為一場噩夢。
- 多專案環境中的挑戰。
並非所有專案都是使用相同的工具建立的,尤其是 Xcode。 一些項目,尤其是那些處於支援階段的項目,是使用開發項目時使用的最新版本的 Xcode 進行維護的。 這意味著如果這些項目需要任何工作,CI 必須有能力建構它。 因此,跑者必須同時支援多個版本的 Xcode,這有效地縮小了可用於特定作業的跑者的數量。
5.需要額外的努力。
跨運行器進行的任何更改(例如軟體安裝)必須同時在所有運行器上執行。 儘管我們為此提供了自動化工具,但仍需要額外的精力來維護自動化腳本。
滿足不同客戶需求的客製化基礎架構解決方案
Miquido 是一家與具有不同需求的多個客戶合作的軟體公司。 我們客製化我們的服務以滿足每個客戶的特定要求。 我們經常為小型企業或新創公司託管程式碼庫和必要的基礎設施,因為他們可能缺乏維護它的資源或知識。
企業客戶通常有自己的基礎設施來託管他們的專案。 然而,有些企業沒有能力這樣做,或者產業法規有義務使用其基礎設施。 他們也不願意使用任何第三方 SaaS 服務,例如 Xcode Cloud 或 Codemagic。 相反,他們想要一個適合其現有架構的解決方案。
為了適應這些客戶,我們經常在我們的基礎設施上託管項目,或在他們的基礎設施上設定相同的持續整合 iOS 配置。 但是,在處理敏感資訊和文件(例如簽名身分)時,我們會格外小心。
利用 Fastlane 進行高效率的建置管理
Fastlane 是一個方便的工具。 它由稱為操作的各種模組組成,有助於簡化流程並將其在不同客戶端之間分開。 其中一項操作稱為匹配,有助於維護開發和生產簽名身分以及設定檔。 它還在作業系統層級工作,在建置時將這些身分分離在單獨的鑰匙圈中,並在建置後執行清理,這非常有用,因為我們在實體電腦上執行所有建置。
我們最初出於特定原因轉向 Fastlane,但發現它具有可能對我們有用的附加功能。
- 建置上傳到 Testflight
過去,AppStoreConnect API 並未向開發者公開提供。 這意味著將建置上傳到 Testflight 的唯一方法是透過 Xcode 或使用 Fastlane。 Fastlane 是一個工具,它基本上刪除了 ASC API 並將其轉變為稱為Pilot的操作。 然而,這種方法經常在下次 Xcode 更新時失效。 如果開發人員想要使用命令列將其建置上傳到 Testflight, Fastlane 是最佳選擇。
- Xcode 版本之間輕鬆切換
一台機器上有多個 Xcode 實例,因此有必要選擇用於建置的 Xcode。 不幸的是,蘋果讓在 Xcode 版本之間切換變得不方便——你必須使用「xcode-select」來執行此操作,這也需要 sudo 權限。 Fastlane 也涵蓋了這一點。
- 為開發人員提供的附加實用程序
Fastlane 提供了許多其他有用的實用程序,包括版本控制以及將建置結果提交至 Webhooks 的功能。
快車道的缺點
讓 Fastlane 適應我們的專案是合理且可靠的,所以我們朝這個方向前進。 我們成功地使用了它好幾年了。 但這些年來,我們也發現了一些問題:
- Fastlane 需要 Ruby 知識。
Fastlane 是一個用 Ruby 寫的工具,需要很好的 Ruby 知識才能有效地使用它。 當 Fastlane 配置或工具本身有錯誤時,使用irb或pry來調試它們可能非常具有挑戰性。
- 對眾多寶石的依賴。
Fastlane 本身就依賴約 70 個寶石。 為了降低破壞系統 Ruby 的風險,該專案正在使用本地捆綁器 gem。 取得所有這些寶石會耗費大量時間。
- 系統 Ruby 和 rubygems 問題。
因此,前面提到的系統 Ruby 和 rubygems 的所有問題也適用於此。
- Flutter 專案的冗餘。
Flutter 專案也被迫使用fastlane match只是為了保持與 iOS 專案的兼容性並保護跑步者的鑰匙圈。 這是完全不必要的,因為 Flutter 內建了自己的建置系統,而前面提到的開銷只是為了管理簽名身分和設定檔而引入的。
大多數問題都已解決,但我們需要一個更強大、更可靠的解決方案。
想法:為 iOS 採用新的、更強大的持續整合工具
好消息是,蘋果已經完全控制了其晶片架構,並為 macOS 開發了新的虛擬化框架。 該框架允許用戶創建、配置和運行 Linux 或 macOS 虛擬機,這些虛擬機可以快速啟動並具有類似本機的性能——我的意思確實是類似本機的。
這看起來很有希望,並且可能成為我們新的 iOS 持續整合工具的基石。 然而,這只是完整解決方案的一部分。 有了虛擬機器管理工具,我們還需要一些可以與我們的 Gitlab 運行程式協調使用該框架的東西。
有了這個,我們關於虛擬化效能不佳的大多數問題都將變得過時。 它還可以讓我們自動解決我們打算用 Fastlane 解決的大部分問題。
開發按需簽名身分管理的客製化解決方案
我們還有最後一個問題要解決──簽名身分管理。 我們不想為此使用 Fastlane,因為它似乎超出了我們的需求。 相反,我們正在尋找一種更適合我們要求的解決方案。 我們的需求很簡單:身份管理過程必須按需完成,專門用於建置時間,鑰匙圈上沒有任何預先安裝的身份,並且與其運行的任何機器相容。
當 Apple 發布允許用戶和 ASC 之間進行通訊的「altool」時,分發問題和缺乏穩定的 AppstoreConnect API 就變得過時了。
因此,我們有了一個想法,並且必須找到一種方法將這三個面向連接在一起:
- 尋找利用 Apple 虛擬化框架的方法。
- 使其與 Gitlab 運行程式一起工作。
- 尋找跨多個專案和運行者進行簽名身分管理的解決方案。
解決方案:我們的方法概覽(包括工具)
我們開始尋找解決方案來解決前面提到的所有問題。
- 利用Apple 的虛擬化框架。
對於第一個障礙,我們很快就找到了解決方案:我們遇到了 Cirrus Labs 的 tar 工具。 從一開始,我們就知道這將是我們的選擇。
使用 Cirrus Lab 提供的 tart 工具最顯著的優點是:
- 從原始 .ipsw 映像建立虛擬機器的可能性。
- 使用預先打包模板(安裝了一些實用工具,如brew或Xcode)建立虛擬機器的可能性,可在Cirrus Labs GitHub頁面上找到。
- Tart 工具利用加殼器來支援動態影像建構。
- Tart 工具支援 Linux 和 MacOS 鏡像。
- 該工具利用 APFS 檔案系統的一項出色功能,無需實際為其保留磁碟空間即可複製檔案。 這樣,您就不需要分配原始影像大小 3 倍的磁碟空間。 您只需要足夠的磁碟空間來容納原始映像,而複製僅佔用與原始映像不同的空間。 這非常有用,特別是因為 macOS 影像往往非常大。
例如,安裝了 Xcode 和其他實用程式的可運行 macOS Ventura 映像至少需要 60GB 的磁碟空間。 在正常情況下,一個映像及其兩個克隆將佔用高達 180GB 的磁碟空間,這是一個很大的數量。 這只是開始,因為您可能想要擁有多個原始映像或在單一虛擬機器上安裝多個 Xcode 版本,這會進一步增加大小。
- 該工具允許對原始虛擬機器和克隆虛擬機器進行 IP 位址管理,並允許透過 SSH 存取虛擬機器。
- 能夠在主機和虛擬機器之間交叉安裝目錄。
- 該工具用戶友好,並且具有非常簡單的 CLI。
在利用它進行虛擬機器管理方面,該工具幾乎沒有任何缺點。 幾乎沒有什麼,除了一件事:雖然很有前途,但用於動態構建圖像的打包插件過於耗時,所以我們決定不使用它。
我們嘗試了餡餅,效果非常好。 它的性能與本機類似,管理也很容易。
在成功整合塔並取得令人印象深刻的成果後,我們接下來專注於解決其他挑戰。
- 尋找一種將 tart 與 Gitlab runner 結合起來的方法。
在解決了第一個問題後,我們面臨的問題是如何將tart與Gitlab runner結合。
讓我們先描述一下 Gitlab 運行者實際上做了什麼:
我們需要在圖中添加一個額外的難題,其中涉及將任務從運行器主機分配到虛擬機器。 GitLab 作業是一個 shell 腳本,包含關鍵變數、路徑條目和指令。
我們的目標是將這個腳本傳輸到虛擬機器並運行它。
然而,事實證明這項任務比我們最初想像的更具挑戰性。
跑者
標準的 Gitlab 運行器執行器(例如 Docker 或 SSH)設定起來很簡單,幾乎不需要任何設定。 然而,我們需要對配置有更大的控制,這促使我們探索 GitLab 提供的自訂執行器。
自訂執行器是非標準配置的絕佳選擇,因為每個執行器步驟(準備、執行、清理)都以 shell 腳本的形式描述。 唯一缺少的是一個命令列工具,它可以執行我們需要的任務並在運行器配置腳本中執行。
目前,有一些可用的工具可以做到這一點,例如 CirrusLabs Gitlab tart 執行器。 這個工具正是我們當時所尋找的。 然而,它還不存在,經過研究,我們沒有找到任何工具可以幫助我們完成任務。
編寫自己的解決方案
由於找不到完美的解決方案,我們自己編寫了一個。 畢竟我們是工程師! 這個想法看起來很可靠,而且我們擁有所有必要的工具,所以我們繼續進行開發。
我們選擇使用 Swift 和 Apple 提供的幾個開源程式庫:用於處理命令列執行的 Swift Argument Parser 和用於處理與虛擬機器的 SSH 連接的 Swift NIO。 我們開始開發,幾天后,我們獲得了工具的第一個工作原型,該工具最終演變成 MQVMRunner。
從高層次來看,該工具的工作原理如下:
- (準備步驟)
- 讀取 gitlab-ci.yml 中提供的變數(圖像名稱和其他變數)。
- 選擇請求的VM基礎
- 克隆請求的 VM 基礎。
- 設定一個交叉掛載的目錄並將Gitlab作業腳本複製到其中,並為其設定必要的權限。
- 運行克隆並檢查 SSH 連線。
- 如果需要,請設定任何所需的依賴項(例如 Xcode 版本)。
- (執行步驟)
- 執行 Gitlab 作業,透過 SSH 從準備好的 VM 複製上的交叉安裝目錄執行腳本。
- (清理步驟)
- 刪除克隆影像。
發展中的挑戰
在開發過程中,我們遇到了一些問題,導致開發並沒有像我們希望的那樣順利。
- IP 位址管理。
管理 IP 位址是一項必須小心處理的重要任務。 在原型中,SSH 處理是使用直接和硬編碼的 SSH shell 命令來實現的。 但是,在非互動式 shell 的情況下,建議使用金鑰驗證。 此外,建議將主機新增至known_hosts 檔案以避免中斷。 儘管如此,由於虛擬機器 IP 位址的動態管理,特定 IP 的條目可能會重複,從而導致錯誤。 因此,我們需要為特定作業動態指派known_hosts以防止此類問題。
- 純粹的 Swift 解決方案。
考慮到這一點,以及 Swift 程式碼中的硬編碼 shell 命令並不優雅,我們認為使用專用的 Swift 程式庫會更好,並決定使用 Swift NIO。 我們解決了一些問題,但同時引入了一些新問題,例如,有時放置在 stdout 上的日誌會在 SSH 通道由於命令完成執行而終止後被傳輸,而且,正如我們所基於的在進一步的工作中輸出,執行隨機失敗。
- Xcode 版本選擇。
由於耗時,Packer 外掛程式不是動態映像建置的選項,因此我們決定使用預先安裝多個 Xcode 版本的單一 VM 基礎。 我們必須找到一種方法,讓開發人員在 gitlab-ci.yml 中指定他們需要的 Xcode 版本 - 並且我們已經提出了可在任何專案中使用的自訂變數。 然後,MQVMRunner 將在複製的 VM 上執行「xcode-select」以設定對應的 Xcode 版本。
還有很多很多
簡化 iOS 工作流程與 Mac Studios 的專案遷移和持續集成
我們已在兩個新的 Mac Studio 上進行了設置,並開始遷移專案。 我們希望使開發人員的遷移過程盡可能透明。 我們無法使其完全無縫,但最終,我們達到了他們只需在 gitlab-ci.yml 中做幾件事的地步:
- 跑者的標籤:使用Mac Studios而不是Intels。
- 鏡像名稱:可選參數,引入是為了將來我們需要多個基礎虛擬機器時的兼容性。 目前,它始終預設為我們擁有的單一基礎虛擬機器。
- Xcode的版本:可選參數; 如果未提供,將使用可用的最新版本。
該工具得到了非常好的初步回饋,因此我們決定將其開源。 我們新增了一個安裝腳本來設定 Gitlab Custom Runner 以及所有必要的操作和變數。 使用我們的工具,您可以在幾分鐘內設定自己的 GitLab 運行程序 - 您唯一需要的是執行作業的起始和基礎 VM。
最終的iOS持續整合結構如下:
3.高效率身分識別管理解決方案
我們一直在努力尋找有效的解決方案來管理客戶的簽名身分。 這尤其具有挑戰性,因為簽名身分是高度機密的數據,不應在不安全的地方儲存超過必要的時間。
此外,我們希望僅在建置時載入這些身份,而不需要任何跨專案解決方案。 這意味著該身分不應在應用程式(或建置)沙箱之外存取。 我們已經透過過渡到虛擬機器解決了後一個問題。 然而,我們仍然需要找到一種僅在建置時將簽名身分儲存和載入到虛擬機器中的方法。
快車道比賽的問題
當時,我們仍在使用 Fastlane 匹配,它將加密的身份和配置存儲在單獨的存儲庫中,在構建過程中將它們加載到單獨的鑰匙串實例中,並在構建後刪除該實例。
這種方法看似方便,但存在一些問題:
- 需要整個 Fastlane 設定才能運作。
Fastlane 是 rubyem,第一章中列出的所有問題都適用於此。
- 在建置時檢出儲存庫。
我們將身分保存在一個單獨的儲存庫中,該儲存庫是在建置過程而不是設定過程中檢出的。 這意味著我們必須建立對身份存儲庫的單獨訪問,不僅針對 Gitlab,還針對特定的運行程序,類似於我們處理私有第三方依賴項的方式。
- 在比賽之外很難管理。
如果您使用 Match 來管理身份或配置,則幾乎不需要手動幹預。 手動編輯、解密和加密設定檔以便匹配稍後仍可使用它們是乏味且耗時的。 使用 Fastlane 執行此程序通常會導致完全清除應用程式配置設定並建立新的設定。
- 調試起來有點困難。
如果出現任何程式碼簽署問題,您可能會發現很難確定剛安裝的身份和組態匹配,因為您需要先對它們進行解碼。
- 安全問題。
使用提供的憑證來配對存取的開發人員帳戶,以代表他們進行更改。 儘管 Fastlane 是開源的,但一些客戶出於安全考慮而拒絕了它。
- 最後但並非最不重要的一點是,擺脫Match將消除我們徹底擺脫 Fastlane 的最大障礙。
我們最初的要求如下:
- 加載需要從安全的地方(最好是非明文形式)對身份進行簽名,並將其放入鑰匙串中。
- Xcode 應該可以存取該身分。
- 優選地,身分密碼、鑰匙圈名稱和鑰匙圈密碼變數應該可設定以用於調試目的。
Match 擁有我們需要的一切,但僅僅為了使用 Match 來實現 Fastlane 似乎有點大材小用,尤其是對於具有自己的構建系統的跨平台解決方案。 我們想要類似 Match 的東西,但沒有沉重的 Ruby 負擔,它可以承載。
創建自己的解決方案
所以我們想--讓我們自己寫吧! 我們使用 MQVMRunner 做到了這一點,所以我們也可以在這裡做到這一點。 我們也選擇了 Swift 來這樣做,主要是因為我們可以使用 Apple Security 框架免費獲得許多必要的 API。
當然,事情也沒有想像中那麼順利。
- 安全框架到位。
最簡單的策略是像 Fastlane 一樣呼叫 bash 命令。 然而,有了可用的安全框架,我們認為用於開發會更優雅。
- 缺乏經驗。
我們對 macOS 的安全框架經驗不是很豐富,而且事實證明它與我們在 iOS 上習慣的安全框架有很大不同。 在許多情況下,這對我們來說適得其反,因為我們沒有意識到 macOS 的限制,或者假設它與 iOS 上的工作方式相同——大多數假設都是錯誤的。
- 糟糕的文檔。
委婉地說,Apple 安全框架的文件很簡陋。 這是一個非常古老的 API,可以追溯到 OSX 的第一個版本,有時,我們的印像是它從那時起就沒有更新過。 大部分程式碼沒有記錄,但我們透過閱讀原始程式碼來預測它是如何運作的。 對我們來說幸運的是,它是開源的。
- 棄用而不更換。
該框架的很大一部分已被棄用; 蘋果正在嘗試擺脫典型的「macOS 風格」鑰匙串(透過密碼存取多個鑰匙圈),並實施「iOS 風格」鑰匙圈(單一鑰匙串,透過 iCloud 同步)。 因此,他們早在 2014 年就在 macOS Yosemite 中棄用了它,但在過去的九年裡沒有提出任何替代方案 - 因此,目前我們可用的唯一 API 已被棄用,因為還沒有新的 API。
我們假設簽章身分可以作為 base64 編碼字串儲存在每個專案的 Gitlab 變數中。 它是安全的、基於每個項目的,如果設定為屏蔽變量,它可以作為非明文在建置日誌中讀取和顯示。
所以,我們有了身份數據。 我們只需要將它放入鑰匙圈中。 使用安全 API 經過幾次嘗試並仔細閱讀安全框架文件後,我們準備了一個原型,後來成為 MQSwiftSign。
學習 macOS 安全系統,但過程很艱難
我們必須深入了解 macOS 鑰匙圈的運作方式才能開發我們的工具。 這涉及研究鑰匙串如何管理項目、它們的存取和權限以及鑰匙串資料的結構。 例如,我們發現鑰匙圈是作業系統忽略 ACL 設定的唯一 macOS 檔案。 此外,我們還了解到特定鑰匙串項目上的 ACL 是保存在鑰匙圈檔案中的純文字 plist。 一路走來,我們遇到了一些挑戰,但我們也學到了很多。
我們遇到的一項重大挑戰是提示。 我們的工具主要設計為在 CI iOS 系統上運行,這意味著它必須是非互動式的。 我們無法要求使用者在 CI 上確認密碼。
然而,macOS 安全系統設計精良,使得在沒有使用者明確許可的情況下無法編輯或讀取機密訊息,包括簽名身分。 若要在沒有確認的情況下存取資源,存取程序必須包含在資源的存取控制清單中。 這是一個嚴格的要求,任何程序都不能破壞,即使是系統自備的蘋果程序。 如果任何程式需要讀取或編輯鑰匙圈條目,使用者必須提供鑰匙串密碼來解鎖它,並且可以選擇將其新增至條目的 ACL。
克服使用者權限挑戰
因此,我們必須找到一種方法,讓 Xcode 能夠存取由我們的鑰匙圈設定的身份,而無需使用密碼提示請求使用者許可。 為此,我們可以更改項目的存取控制列表,但這也需要用戶許可 – 當然,確實如此。 否則,就會破壞 ACL 的全部意義。 我們一直在嘗試繞過該保護措施 - 我們試圖達到與“security set-key-partition-list”命令相同的效果。
深入研究框架文件後,我們沒有找到任何允許編輯 ACL 而不提示使用者提供密碼的 API。 我們發現最接近的是`SecKeychainItemSetAccess`,它每次都會觸發UI提示。 然後我們再次深入研究,但這次是最好的文檔,即原始程式碼本身。 蘋果是如何實現的呢?
事實證明,正如預期的那樣,他們使用的是私有 API。 名為「SecKeychainItemSetAccessWithPassword」的方法基本上與「SecKeychainItemSetAccess」執行相同的操作,但不是提示使用者輸入密碼,而是將密碼作為函數的參數提供。 當然,作為私人 API,它沒有在文檔中列出,但 Apple 缺乏此類 API 的文檔,就好像他們無法想到創建供個人或企業使用的應用程式一樣。 由於該工具僅供內部使用,因此我們毫不猶豫地使用了私有 API。 唯一需要做的就是將 C 方法橋接到 Swift 中。
因此,原型的最終工作流程如下:
- 建立臨時解鎖鑰匙圈並關閉自動鎖。
- 從環境變數(由 Gitlab 傳遞)取得並解碼 Base64 編碼的簽名身分資料。
- 將身分匯入到已建立的鑰匙圈中。
- 為導入的身份設定適當的存取選項,以便 Xcode 和其他工具可以讀取它以進行協同設計。
進一步升級
原型運作良好,因此我們確定了一些想要添加到該工具中的附加功能。 我們的目標是最終取代 fastlane; 我們已經實現了“match”操作。 然而,fastlane 仍然提供了兩個我們還沒有的有價值的功能——設定檔安裝和 export.plist 創建。
設定檔安裝
設定檔安裝非常簡單 - 它分解為提取設定檔 UUID 並將檔案複製到“~/Library/MobileDevice/Provisioning Profiles/”,並以 UUID 作為檔案名稱 - 這足以讓 Xcode 正確地看到它。 在我們的工具中添加一個簡單的插件來循環提供的目錄並對它在其中找到的每個 .mobileprovision 檔案執行此操作並不是火箭科學。
匯出.plist創建
然而,export.plist 的創建稍微複雜一些。 為了產生正確的IPA 文件, Xcode 要求使用者提供一個plist 文件,其中包含從各種來源收集的特定資訊 - 專案文件、授權plist、工作區設定等。Xcode 只能透過分發精靈收集這些資料而不能通過的原因我不知道如何透過 CLI 進行操作。 然而,我們使用 Swift API 來收集它們,只有專案/工作空間引用和少量關於如何建立 Xcode 專案文件的知識。
結果比預期的要好,因此我們決定將其作為另一個插件添加到我們的工具中。 我們也將其作為開源專案發布給更廣泛的受眾。 目前,MQSwiftSign 是一款多用途工具,可成功地取代建置和分發 iOS 應用程式所需的基本 fastlane 操作,我們在 Miquido 的每個專案中都使用它。
最後的想法:成功
從 Intel 切換到iOS ARM 架構是一項具有挑戰性的任務。 由於缺乏文檔,我們面臨許多障礙並花費了大量時間來開發工具。 然而,我們最終建立了一個強大的系統:
- 管理兩名跑步者而不是九名;
- 運行完全在我們控制之下的軟體,沒有大量 rubygems 形式的開銷——我們能夠在構建配置中擺脫 fastlane 或任何第三方軟體;
- 對我們通常不關注的事物的大量知識和理解 - 例如 macOS 系統安全性和安全框架本身、實際的 Xcode 專案結構等等。
我很樂意鼓勵您 – 如果您在為 iOS 建立設定 GitLab 運行程式時遇到困難,請嘗試我們的 MQVMRunner。 如果您需要使用單一工具建立和分發應用程式的協助,並且不想依賴 rubygems,請嘗試 MQSwiftSign。 對我有用,也可能對你有用!