在雲端 Mac 上構建與分發 XCFramework:二進位快取加速多倉庫整合

CI/CD 實踐 ·約 7 分鐘閱讀

在雲端 Mac 上構建與分發 XCFramework:二進位快取加速多倉庫整合

上週三個倉庫同時提交,CI 裡同一個內部音訊處理框架被重新編譯了三次,每次都要跑將近八分鐘的 archive。等到第三次建置失敗、提示簽章憑證過期,值班同事在群組裡問了一句:「這東西不是昨天才編過嗎,為什麼每個倉庫都要自己再來一遍?」這句話戳中了不少團隊的痛點——共享二進位框架本該只編一次,卻因為沒有快取機制,變成每條流水線各跑各的重複勞動。

在 HireVPS 的獨享雲端 Mac 上,我們把這套流程重新搭了一遍:統一編譯多切片(multi-slice)XCFramework,簽章、算校驗碼,再落到一個輕量的本機二進位快取裡,多個倉庫直接拉取產物,不用各自重複建置。以下把具體做法整理出來。

為什麼要自己快取 XCFramework

Swift Package Manager 支援 binaryTarget 直接引用預編譯的 XCFramework,這本身不是新知識,但真正讓它發揮作用的前提是「產物可信、可追溯、能被多方共享」。如果每個倉庫的 CI 各自跑一次 archive,你得到的其實是好幾份「理論上一樣但實際沒驗證過一致」的二進位檔,一旦某次編譯環境有細微差異(例如 Xcode 小版本更新),排查起來會很麻煩。集中編譯、統一分發,才能保證所有下游拿到的是完全同一份產物。

經驗之談:凡是被 3 個以上倉庫引用的內部框架,都值得單獨拿出來做二進位快取;引用少於 3 個的,重複建置的成本可能還不如維護快取高。

雲端 Mac 的資源優勢

這套流程對機器有兩個硬性要求:一是需要獨享的 macOS 圖形與命令列環境來跑完整的 xcodebuild archive,二是編譯多個架構切片時磁碟 I/O 壓力不小,共享資源的機器很容易互相拖慢。在 HireVPS 按天租用一台 M4 或 M4 Pro 機型,選好節點後就能拿到獨立 SSH 憑據,root 權限直接可用,不需要跟別人搶 CPU 時間片,連續跑好幾個小時的多架構編譯也不會被鄰居任務打斷。具體開哪個機型、哪個節點目前可選,在控制台確認當前可選配置即可。

建置多切片 XCFramework

一個能同時給真機和模擬器用的 XCFramework,至少要包含兩個切片:ios-arm64(真機)和 ios-arm64-simulator(Apple Silicon 模擬器)。如果團隊裡還有 Intel Mac 跑模擬器,記得加上 ios-x86_64-simulator,否則那台機器一跑測試就會報架構不匹配。

分別歸檔兩個切片

xcodebuild archive \
  -scheme AudioCore \
  -destination "generic/platform=iOS" \
  -archivePath build/AudioCore-iOS.xcarchive \
  SKIP_INSTALL=NO \
  BUILD_LIBRARY_FOR_DISTRIBUTION=YES

xcodebuild archive \
  -scheme AudioCore \
  -destination "generic/platform=iOS Simulator" \
  -archivePath build/AudioCore-iOSSim.xcarchive \
  SKIP_INSTALL=NO \
  BUILD_LIBRARY_FOR_DISTRIBUTION=YES

合併成 XCFramework

xcodebuild -create-xcframework \
  -framework build/AudioCore-iOS.xcarchive/Products/Library/Frameworks/AudioCore.framework \
  -framework build/AudioCore-iOSSim.xcarchive/Products/Library/Frameworks/AudioCore.framework \
  -output build/AudioCore.xcframework

編譯完成後打個 zip,這份檔案就是要分發的最終產物,之後不要再手動改動它。

簽章與校驗碼

內部框架也建議簽章,方便下游驗證來源:

codesign --sign "Apple Distribution: Your Team" \
  --timestamp \
  build/AudioCore.xcframework

zip -r AudioCore-1.4.0.xcframework.zip build/AudioCore.xcframework

簽完打包後,用 SwiftPM 內建的指令算一次校驗碼,這個值要寫進下游倉庫的 Package.swift:

swift package compute-checksum AudioCore-1.4.0.xcframework.zip

校驗碼驗證失敗最常見的原因是打包後又改了 zip 內容,或是換了壓縮工具導致檔案內部順序不同。原則很簡單:算完校驗碼之後,這個 zip 檔案就凍結了,不再動它。

搭建本機二進位快取

不需要多複雜的服務,一個能提供靜態檔案下載的目錄結構就夠用:

層級 內容 範例路徑
框架名 每個內部框架一個目錄 /cache/AudioCore/
版本號 每個版本一個子目錄 /cache/AudioCore/1.4.0/
產物 zip 包 + 校驗碼文字 AudioCore-1.4.0.xcframework.zip, checksum.txt

下游倉庫的 Package.swift 裡這樣引用:

.binaryTarget(
    name: "AudioCore",
    url: "https://cache.internal.example/AudioCore/1.4.0/AudioCore-1.4.0.xcframework.zip",
    checksum: "上一步算出的校驗碼"
)

儲存規劃上,中型團隊保留 3-4 個核心框架、各最近 10 個版本就夠用,單份壓縮包通常在 20-80MB,總量控制在幾個 GB 內,超過 90 天沒被拉取的舊版本定期清理即可。

接入多倉庫 CI 流水線

各下游倉庫的 CI 不再執行 xcodebuild archive,只需要 swift package resolve 拉取指定版本,時間從幾分鐘的編譯直接降到幾秒的下載。發新版本時,在快取目錄裡新建版本號目錄、更新對應倉庫的 checksum 引用即可,不需要動快取伺服器的程式碼。

踩坑提醒:切勿讓多個倉庫同時引用「latest」這種可變標籤,校驗碼機制要求 URL 和內容一一對應,一旦允許覆蓋同一版本號下的檔案,SwiftPM 的完整性驗證就失去意義了。

檢查清單

常見問題

XCFramework 要包含哪些切片才能同時支援真機與模擬器?

至少要有 ios-arm64(真機)與 ios-arm64-simulator(Apple Silicon 模擬器)兩個切片;若團隊裡還有 Intel Mac 跑模擬器,需再加上 ios-x86_64-simulator,否則那台機器跑測試會直接出現架構不符的錯誤。

binaryTarget 的校驗碼驗證失敗通常是什麼原因?

最常見是打包後又手動修改了 zip 內容,或用了不同壓縮工具導致檔案順序改變;正確做法是用 swift package compute-checksum 直接對最終要分發的 zip 檔算一次,算完後不要再更動這個檔案。

本機二進位快取伺服器要規劃多大的儲存空間?

以中型團隊為例,3-4 個核心框架各保留最近 10 個版本,單份壓縮後通常在 20-80MB 之間,總量控制在幾個 GB 內即可,建議在雲端 Mac 的 SSD 上獨立分割區存放,並定期清理超過 90 天未被拉取的版本。

HireVPS

今天就試試獨享雲端 Mac mini

按日起租,2 分鐘取得 SSH/VNC 憑證,配置隨時可升級。

立即租用 Mac mini