雲端 Mac 的 iOS Mach-O 架構與最低系統版本驗收

雲端 Mac 的 iOS Mach-O 架構與最低系統版本驗收

一份在本機模擬器上正常執行的 iOS 專案,移至 HopVM 雲端 Mac 完成 Archive 後,仍可能直到安裝階段才暴露問題:某個預編譯 Framework 混入了 x86_64 切片、擴充功能連結到錯誤的平台,或巢狀動態函式庫悄悄提高了最低系統版本。與其等到交付後才排查,不如直接掃描匯出的 .app,把二進位檔的實際狀態轉化為流水線門禁。

為什麼要檢查最終匯出套件

Xcode 專案設定只能說明「準備如何建置」,最終套件才能說明「實際交付了什麼」。主程式、App Extension、Framework 與 .dylib 都可能擁有各自獨立的 Mach-O 標頭;相依套件管理工具下載的二進位套件,也未必會繼承主專案的設定。

門禁至少必須回答三個問題:

  1. 面向實體 iOS 裝置的二進位檔是否只包含允許的架構。
  2. 每個 Mach-O 的平台是否為 iOS,而不是模擬器或 macOS。
  3. 巢狀二進位檔的最低系統版本是否高於主 App 的宣告。

不要把「Archive 成功」視為交付驗收。連結器只會驗證目前目標能否組成建置產物,不會代替團隊判斷所有巢狀檔案是否符合發布基準。

應檢查透過 xcodebuild -exportArchive 產生的最終套件,而不是只掃描 DerivedData。匯出過程會重新組織 Framework、擴充功能與簽章內容,這才是最接近實際交付狀態的輸入。

固定輸入與驗收基準

先固定封存檔、匯出目錄與報告目錄,避免腳本掃描到前一次工作的殘留檔案。雲端 Mac 由多條流水線依序使用時,工作目錄的隔離尤其重要。

set -euo pipefail

ARCHIVE_PATH="$PWD/output/App.xcarchive"
EXPORT_PATH="$PWD/output/export"
REPORT_PATH="$PWD/output/macho-report.tsv"

rm -rf "$EXPORT_PATH"
mkdir -p "$EXPORT_PATH"

xcodebuild -exportArchive \
  -archivePath "$ARCHIVE_PATH" \
  -exportPath "$EXPORT_PATH" \
  -exportOptionsPlist "$PWD/ci/ExportOptions.plist"

APP_PATH="$(find "$EXPORT_PATH" -maxdepth 2 -type d -name '*.app' -print -quit)"
test -n "$APP_PATH"

產品基準應納入版本庫,而不是散落在 CI 設定介面中。例如,可使用 ci/macho-policy.env 儲存允許的架構與最低版本:

EXPECTED_ARCHS="arm64"
EXPECTED_PLATFORM="IOS"
DECLARED_MIN_IOS="17.0"

DECLARED_MIN_IOS 必須符合專案實際支援的範圍。此處版本僅為腳本範例,不能複製後直接當成產品決策。

遞迴列舉所有 Mach-O

不能只依副檔名搜尋,因為 Framework 的主二進位檔通常沒有副檔名。更穩妥的方式是逐一遍歷檔案,再由 file 判斷是否屬於 Mach-O。

: > "$REPORT_PATH"
failure=0

while IFS= read -r -d '' candidate; do
  if ! file -b "$candidate" | grep -q 'Mach-O'; then
    continue
  fi

  archs="$(lipo -archs "$candidate" 2>/dev/null || true)"
  build="$(vtool -show-build "$candidate" 2>/dev/null || true)"
  platform="$(awk '/platform/{print $2; exit}' <<<"$build")"
  minos="$(awk '/minos/{print $2; exit}' <<<"$build")"

  printf '%s	%s	%s	%s
' \
    "${candidate#"$APP_PATH"/}" "$archs" "$platform" "$minos" \
    >> "$REPORT_PATH"

  if [[ "$archs" != "$EXPECTED_ARCHS" ]]; then
    printf 'architecture mismatch: %s (%s)
' "$candidate" "$archs" >&2
    failure=1
  fi

  if [[ "$platform" != "$EXPECTED_PLATFORM" ]]; then
    printf 'platform mismatch: %s (%s)
' "$candidate" "$platform" >&2
    failure=1
  fi
done < <(find "$APP_PATH" -type f -print0)

exit "$failure"

報告採用定位字元分隔,既可作為建置附件保留,也方便後續轉換成表格。失敗訊息必須包含檔案路徑與實際值;若只輸出「架構檢查失敗」,排障人員就不得不重新執行完整工作。

不要在門禁中直接修改套件

發現 x86_64 後直接執行 lipo -remove 看似省事,卻會變更已簽章的內容,也會掩蓋相依套件生產流程中的問題。正確做法是確認該檔案來自原始碼編譯、二進位相依套件,還是複製腳本,接著修正上游並重新 Archive。

比對最低系統版本

先讀取主 App 的產品宣告:

PLIST="$APP_PATH/Info.plist"
APP_MIN_IOS="$(/usr/libexec/PlistBuddy \
  -c 'Print :MinimumOSVersion' "$PLIST")"

printf 'declared minimum iOS: %s
' "$APP_MIN_IOS"

接著從每個 Mach-O 的 LC_BUILD_VERSION 讀取 minos。判斷時應使用語意化版本比較,不能採用一般字串比較,否則 17.10 可能會被錯誤地排在 17.9 前面。

檢查對象 讀取位置 失敗條件
主 App 宣告 Info.plistMinimumOSVersion 與版本庫基準不一致
主程式 LC_BUILD_VERSIONminos 高於產品宣告
Framework 與動態函式庫 各自的 LC_BUILD_VERSION 高於產品宣告
App Extension 擴充功能 Info.plist 與 Mach-O 宣告值或實際值超出基準

可使用系統內建的版本排序功能完成比較:

version_gt() {
  [[ "$1" != "$2" ]] &&
    [[ "$(printf '%s
%s
' "$1" "$2" | sort -V | tail -n 1)" == "$1" ]]
}

if version_gt "$minos" "$APP_MIN_IOS"; then
  printf 'minimum iOS mismatch: %s requires %s
' \
    "$candidate" "$minos" >&2
  failure=1
fi

如果 vtool 沒有傳回平台或版本,不應靜默略過。應先使用 otool -l 儲存完整的載入命令,再確認它究竟是舊格式二進位檔、異常檔案,還是腳本誤判。

接入 CI 並處理常見誤報

將檢查安排在 Archive 與匯出之後、上傳之前,並且一律上傳 macho-report.tsv。即使工作失敗,也應透過 CI 的失敗後附件機制保留報告。

常見誤報主要分為三類:

因此,腳本應依產品類型分別維護策略,不要讓同一套規則同時涵蓋 iOS、模擬器測試套件與 macOS 工具。若流水線同時產生多種建置產物,應針對每個匯出目錄分別執行,並在報告第一欄寫入產物類型。

最後再執行一次簽章結構驗證:

codesign --verify --deep --strict --verbose=2 "$APP_PATH"

這不能取代 Mach-O 檢查,但能發現腳本誤改二進位檔、巢狀簽章失效等後續問題。完整流程應依序為匯出、掃描、版本比對、簽章驗證與保存報告,完成後才允許交付。如此一來,每次失敗都能定位到具體檔案、具體欄位與具體修正入口,而不是等到安裝失敗後才猜測原因。

常見問題

為什麼不能只檢查主 App 的架構?

Framework、App Extension 與動態程式庫各自包含 Mach-O。任一巢狀檔案若帶入模擬器切片、錯誤平台或過高的最低系統版本,都可能造成安裝或啟動失敗。

在 iOS 交付檔看到 x86_64 時該怎麼辦?

先找出檔案來源,不要直接刪除切片後交付。應修正依賴建置或封存流程,再重新匯出只包含實機平台與架構的套件。

最低系統版本要讀 Info.plist 還是 Mach-O?

兩者都要讀。Info.plist 代表產品宣告,LC_BUILD_VERSION 代表實際編譯結果;所有巢狀二進位檔的 minos 都不應高於產品宣告。

獨享物理節點

用一台可依專案啟停的雲端 Mac 重現工作流程

從三種 Apple Silicon 配置中選擇合適的資源,按日、週、月或季租用。設備為獨享物理機,並非虛擬機器;實際可用狀態以控制台即時回傳的資訊為準。

選擇配置並下單