Unity 專案在本機可以順利匯出 iOS 工程,移到雲端 Mac 後卻可能卡在資源匯入、原生相依套件或連結階段。最棘手的不是建置失敗本身,而是所有步驟都塞進同一個命令:日誌只留下「建置失敗」,無法判斷應該重新執行 Unity、清除快取,還是檢查 Xcode 工程。更穩妥的做法是將流水線拆成兩個獨立關卡:先產生可稽核的 Xcode 工程,再執行不含簽署動作的原生編譯驗收。
先定義輸入與產物邊界
一次可重現的建置至少要固定四類輸入:提交版本、Unity 編輯器版本、套件相依鎖定檔,以及場景清單。不要讓指令碼臨時讀取某位開發者電腦上的編輯器設定,也不要直接覆寫上一次匯出的 Xcode 工程後繼續編譯。
建議為每次任務準備獨立目錄:
| 路徑 | 用途 | 是否可快取 |
|---|---|---|
Source/ |
目前提交版本的 Unity 工程 | 否 |
Library/ |
資源匯入結果 | 有條件 |
Build/iOS/ |
本次匯出的 Xcode 工程 | 否 |
.build/DerivedData/ |
原生編譯中間檔案 | 有條件 |
Artifacts/ |
日誌、摘要與驗收結果 | 否 |
Library 的快取鍵應包含 Unity 版本、目標平台,以及 Packages/manifest.json、Packages/packages-lock.json 的摘要。只要其中任一項發生變化,就應重新匯入。DerivedData 也不應直接在不同 Xcode 版本或不同工程設定之間重複使用。
快取只能加速輸入已固定的工作,不能取代版本鎖定。命中邊界不明確的快取,通常比完整重建更難除錯。
使用批次模式匯出 Xcode 工程
將匯出進入點放在 Assets/Editor/IosExport.cs,並根據建置設定中已啟用的項目產生場景清單,避免在指令碼與編輯器介面中維護兩份清單。
using System;
using System.Linq;
using UnityEditor;
using UnityEditor.Build.Reporting;
public static class IosExport
{
public static void Run()
{
var scenes = EditorBuildSettings.scenes
.Where(scene => scene.enabled)
.Select(scene => scene.path)
.ToArray();
if (scenes.Length == 0)
throw new InvalidOperationException("No enabled scenes.");
var options = new BuildPlayerOptions
{
scenes = scenes,
locationPathName = "Build/iOS",
target = BuildTarget.iOS,
options = BuildOptions.CleanBuildCache
};
var report = BuildPipeline.BuildPlayer(options);
if (report.summary.result != BuildResult.Succeeded)
throw new InvalidOperationException("Unity export failed.");
}
}
執行時明確傳入專案目錄、日誌路徑與方法名稱:
"$UNITY_EDITOR" \
-batchmode \
-nographics \
-quit \
-projectPath "$PWD" \
-executeMethod IosExport.Run \
-logFile "$PWD/Artifacts/unity-export.log"
任務開始前應刪除 Build/iOS,但不要無條件刪除 Library。任務結束後,應同時檢查結束碼、日誌檔案,以及 Build/iOS/Unity-iPhone.xcodeproj 是否存在。只看到目錄存在並不代表匯出成功,失敗的任務也可能留下未完成的產物。
分離原生相依套件與編譯驗收
Unity 匯出成功只代表產生器已完成工作,無法證明 Objective-C、Swift、IL2CPP 產物與原生程式庫能夠一起編譯。應先執行專案規定的原生相依套件安裝步驟,再判斷要建置 workspace 還是 project。當相依套件工具產生 .xcworkspace 後,若仍繼續建置 .xcodeproj,通常會發生找不到模組或缺少連結程式庫的錯誤。
set -euo pipefail
cd Build/iOS
rm -rf ../../.build/DerivedData
if [ -d "Unity-iPhone.xcworkspace" ]; then
xcodebuild \
-workspace Unity-iPhone.xcworkspace \
-scheme Unity-iPhone \
-configuration Release \
-sdk iphoneos \
-derivedDataPath ../../.build/DerivedData \
CODE_SIGNING_ALLOWED=NO \
build
else
xcodebuild \
-project Unity-iPhone.xcodeproj \
-scheme Unity-iPhone \
-configuration Release \
-sdk iphoneos \
-derivedDataPath ../../.build/DerivedData \
CODE_SIGNING_ALLOWED=NO \
build
fi
這裡的目標是驗證編譯與連結,而不是產生可供散佈的安裝套件。簽署應安排在後續受控階段,避免一般提交驗證任務接觸不必要的敏感資料。
讓快取依規則失效,而不是靠猜測
Unity 專案常見的錯誤做法,是只用分支名稱作為快取鍵。兩個提交位於同一個分支,並不代表資源匯入結果相同。可以先產生輸入摘要:
{
printf '%s
' "$UNITY_VERSION"
printf '%s
' "$XCODE_VERSION"
shasum -a 256 Packages/manifest.json
shasum -a 256 Packages/packages-lock.json
find Assets -name '*.asmdef' -o -name '*.rsp' | sort | xargs shasum -a 256
} | shasum -a 256 | awk '{print $1}' > Artifacts/cache-key.txt
不要快取整個工作目錄
將整個工程目錄封裝成快取,會混入舊的匯出產物、暫存設定與已刪除檔案。應分別管理 Library 與 DerivedData,並在還原後繼續驗證輸入摘要。若出現無法解釋的編譯差異,應先保留失敗日誌,再使用空快取重新執行一次;如果空快取能夠成功,問題應歸類為快取邊界錯誤,而不是直接修改業務程式碼。
封存足以重現問題的最小證據集
建置通過後,不要只保留一個成功標記。至少應封存完整的 Unity 日誌、Xcode 輸出、提交雜湊、工具版本、快取鍵與匯出工程摘要。工程摘要可以記錄 scheme、設定與建置設定,但應排除憑證、權杖及本機私有路徑。
常見失敗可以依階段快速分流:
- Unity 日誌未進入
IosExport.Run:檢查方法名稱、指令碼編譯錯誤與編輯器版本。 - 匯出階段回報場景清單為空:檢查 Build Settings 中已啟用的場景,不要臨時硬編碼路徑。
- workspace 已存在,但指令碼仍建置 project:調整偵測時點,確保相依套件安裝已完成。
- IL2CPP 或原生程式庫連結失敗:核對目標平台、外掛程式架構與條件式匯入設定。
- 同一提交偶爾成功:停用快取後重新執行,並比較兩次的快取鍵與工具版本。
- 本機通過但遠端失敗:比較路徑大小寫、未提交檔案與環境變數,不要先歸因於機器效能。
最後,將「匯出成功」與「原生編譯成功」設為兩個獨立狀態。前者失敗時不必執行 Xcode;後者失敗時,也不必反覆匯入全部資源。如此一來,流水線更容易重試,日誌更精簡,責任邊界也更清楚。
常見問題
為什麼不讓 Unity 一次產生最終安裝檔?
拆開後能分辨 Unity 腳本、資源匯入、原生相依套件與 Xcode 編譯問題;先做無簽署編譯,也能在使用受控憑據前驗證程式碼與連結結果。
Library 快取可以跨 Unity 版本共用嗎?
不建議。快取鍵至少要包含 Unity 編輯器版本、相依套件鎖定檔與目標平台,任一條件不同就應重新產生 Library。
什麼情況要使用 xcworkspace?
原生相依套件安裝後若產生 workspace,就應建置 xcworkspace;沒有 workspace 時才使用 xcodeproj,判斷時點應放在相依套件準備完成之後。
把下一次 iOS 建置放到 MiniDebug M4 上。
固定提供 M4、16GB RAM 與 256GB SSD,可按日、週、月或季租用,並可從新加坡、日本東京、韓國首爾、香港、美国東部五個節點中選擇。實際可用狀態以控制台即時回傳為準。