日誌最後只剩 xcodebuild exit code 65,但真正的錯誤通常早已出現在前面。
最快的修法不是立即清空快取,而是先保存完整輸出與 .xcresult,再按建構、測試、Archive、exportArchive 所處階段,依序檢查 Scheme、依賴、執行目標、簽名與遠端會話。只有在相同提交於不同環境呈現不同結果時,才應把問題轉向遠端建構環境。
這篇文章適合以下讀者:
- 在 Xcode 介面建構成功,但透過 SSH 或 CI 執行
xcodebuild失敗的獨立開發者。 - 使用模擬器進行自動化測試,遇到裝置不可用、啟動逾時或測試失敗的小型團隊。
- 需要在遠端 Mac 上執行 Archive、簽名與無人值守打包的發布維護者。
[ SECTION_01 ] 先保全失敗現場
xcodebuild exit code 65 不是一種固定故障,也不是可以直接套用的修復命令。它只表示這次 xcodebuild 工作沒有成功完成;根因必須回到錯誤發生的階段尋找。
一個經過脫敏的失敗片段可能如下:
Test session results, code coverage, and logs:
/private/.../Build/TestResults.xcresult
Testing failed:
The app could not be launched because the target device was unavailable.
** TEST FAILED **
Command PhaseScriptExecution failed with a nonzero exit code
xcodebuild: error: Could not build workspace ...
Process exited with code 65
最後一行的資訊量最低。應保留以下資料:
- 完整標準輸出與錯誤輸出,而不是只截取最後十行。
- 實際執行的命令、工作目錄與提交識別。
DEVELOPER_DIR或目前使用的 Xcode 活動開發者目錄。- 建構動作,例如
build、test、archive或-exportArchive。 .xcresult結果包及其保存位置。
Apple 的執行測試與解讀結果文件說明了測試結果與日誌的查看方式。CI 應把結果包視為失敗產物一併上傳,否則重試後原始證據可能被覆蓋。
先看任務停在哪裡:
| 任務階段 | 主要觀察點 | 優先排查方向 | 不應直接下的結論 |
|---|---|---|---|
| Build | 編譯、資源處理、連結、Run Script | 原始碼、依賴、腳本、Configuration | 不是看到 65 就判定簽名 |
| Test | 測試產品是否完成、測試是否啟動 | Destination、模擬器服務、啟動設定 | 不是所有測試失敗都屬於編譯 |
| Archive | .xcarchive 是否產生 |
Scheme、Archive 設定、簽名資產 | Archive 失敗不等於匯出失敗 |
| exportArchive | Archive 已存在但無法輸出 | Distribution 身份、Profile、匯出選項 | 不應先刪除 Keychain 內容 |
[ SECTION_02 ] 專案入口與 Scheme 對照
圖形介面開啟的是 workspace,CI 卻可能仍然呼叫 project。當專案加入套件管理或多個 target 後,這個差異足以令本機與遠端結果不同。
應先確認命令實際指向的入口:
xcodebuild -workspace RedactedApp.xcworkspace \
-scheme RedactedScheme \
-configuration Release \
-destination 'platform=iOS Simulator,id=REDACTED' \
build
上例中的專案名稱、Scheme 與裝置識別字串都只是脫敏佔位符。實際排查時,不要把含有帳號、路徑或識別資訊的完整日誌直接貼到公開討論區。
Scheme 還有一個常被忽略的邊界:本機可用,不代表它已共享並進入版本控制。請在遠端的乾淨檢出目錄確認:
- 使用
.xcworkspace還是.xcodeproj。 - Scheme 是否存在於該提交,是否已設定為共享。
Debug、Release或自訂 Configuration 是否真的存在。- 遠端使用的 SDK、Destination 與互動式 Xcode 會話是否一致。
- Build Configuration 檔案是否依賴本機才有的絕對路徑或環境變數。
Apple 的自訂專案 Build Scheme 說明可用來核對 Scheme 的動作與設定;加入 Build Configuration 檔案的文件則適合檢查不同會話讀到的設定來源。
最快的對照方式,是以同一份脫敏命令分別在互動式終端與自動化會話執行,並比較 showBuildSettings 輸出。若設定已不同,先修正入口與環境;不要把清理 DerivedData 當成第一答案。
[ SECTION_03 ] 依賴、腳本與編譯鏈路
exit code 65 也可能包住完全不同的失敗。日誌中若出現套件解析、原始碼編譯、資源處理、連結或 Run Script 錯誤,修法不能混用。
依賴解析
乾淨檢出時,鎖定檔必須與成功建構的提交一致。私有套件倉庫還可能需要 SSH 金鑰、憑證或已登入的工具狀態。互動式終端能讀到的認證,不代表背景 CI 使用者也能讀到。
檢查:
- 鎖定檔是否已提交且未被 CI 改寫。
- 私有倉庫認證是否由同一使用者提供。
- 工作目錄是否正確,腳本是否以預期相對路徑執行。
PATH、腳本解譯器與必要環境變數是否一致。
編譯、連結與 Run Script
若第一個有效錯誤是 Swift 或 Objective-C 編譯錯誤,應回到原始碼與編譯設定。若是 undefined symbols 或 framework 找不到,應檢查連結設定、架構與 SDK。若錯誤來自 Run Script,則要確認腳本的執行權限、解譯器、輸入檔與輸出檔宣告。
修復後,請用相同提交做乾淨檢出再驗證。只在曾被手動修改過的工作目錄重試,無法證明修復已進入版本控制。若需要比較兩次建構,可把建構日誌與 xcresult 分析方法納入團隊記錄流程,避免只依賴終端機最後一行。
提醒: 清除 DerivedData 可能掩蓋快取污染,但不會修復錯誤的 Scheme、缺少的私有依賴、失效的腳本權限或不存在的測試目標。清理前先保存結果包,並寫明回退條件。
[ SECTION_04 ] 模擬器與測試啟動邊界
Test 任務要拆成兩個問題:測試產品是否成功建出,以及測試執行時能否啟動目標。兩者都可能最後呈現 xcodebuild exit code 65,但證據完全不同。
可使用分段方式縮小範圍:
xcodebuild -workspace RedactedApp.xcworkspace \
-scheme RedactedScheme \
-destination 'platform=iOS Simulator,id=REDACTED' \
build-for-testing
xcodebuild -workspace RedactedApp.xcworkspace \
-scheme RedactedScheme \
-destination 'platform=iOS Simulator,id=REDACTED' \
test-without-building
第一段失敗,優先看編譯、測試 target 與依賴;第一段成功、第二段失敗,才看模擬器啟動、測試服務與 Destination。
遠端 Mac 上尤其要核對:
- 目標系統是否已安裝,且與專案要求相容。
- 裝置識別字串是否仍有效,不要依賴本機曾建立的模擬器。
- SSH 背景工作是否有測試所需的圖形會話與服務。
- 測試環境變數是否由 CI 明確傳入,而不是依賴 GUI 使用者設定。
Apple 的測試環境變數參考可用於逐項比對;測試動作也能參照分階段執行測試的工作流程文件。不要以「模擬器可以列出」作為測試必然能啟動的證明。
[ SECTION_05 ] Archive 與簽名權限
發布流程必須分開記錄四個狀態:編譯完成、Archive 產生、代碼簽名完成、匯出成功。Archive 已產生但 exportArchive 失敗,排查方向就不再是原始碼編譯。
遠端無人值守環境應確認:
- Keychain 內有帶私密金鑰的 Distribution 簽名身份。
- Provisioning Profile 與 Bundle ID、團隊及簽名用途相符。
- SSH 或背景工作可在非互動狀態讀取所需憑據。
- Archive 與匯出選項使用的是同一個提交和預期 Configuration。
- Keychain 解鎖、權限調整與憑證存放方式已寫入運維紀錄。
Apple 的建立 Distribution-signed 程式碼文件與團隊簽名憑證同步說明可用來核實簽名身份;Provisioning Profile 的結構則應對照TN3125 技術說明。
任何刪除憑證、替換 Profile 或放寬 Keychain 權限的操作,都應先記下影響範圍、備份位置與回退方案。若只是 SSH 使用者沒有權限,刪除整套簽名資產反而會擴大故障。
[ SECTION_06 ] 可重複建構驗收清單
完成初步修復後,請依下列清單驗收。每項都應留下可供下一次比較的產物:
- [ ] 使用固定提交,而不是曾被手動修改的工作目錄。
- [ ] 記錄 workspace/project、Scheme、Configuration、SDK 與 Destination。
- [ ] 保存完整標準輸出、錯誤輸出及
.xcresult。 - [ ] 分別驗證 Build、Test、Archive 與
exportArchive的結果。 - [ ] 以乾淨檢出重跑,確認依賴鎖定檔與腳本都來自該提交。
- [ ] 在互動式終端與 SSH/CI 會話比較活動開發者目錄、工作目錄和環境變數。
- [ ] 測試任務另行確認模擬器目標、啟動服務與測試結果。
- [ ] 簽名任務確認私密金鑰、Profile、Keychain 權限及匯出設定。
- [ ] 重新連線或重啟主機後再執行一次,確認問題不是暫時會話狀態。
- [ ] 若只有特定主機或使用者失敗,將修復範圍限定在遠端環境。
判斷方向可以簡化為三條:
- 相同提交在所有環境都失敗:修專案、依賴、腳本或簽名設定。
- 只有特定使用者、SSH 會話或主機失敗:修建構環境與權限。
- 只有原主機反覆污染或無法重現:重建隔離環境,再以同一提交對照。
對需要長期執行的團隊,可先閱讀遠端 Mac 無人值守建構環境驗收指南,把上述結果轉成固定的主機交接紀錄。
[ SECTION_07 ] 當前方案與遠端 Mac 的取捨
如果目前方案是把 CI 直接掛在開發者日常使用的 Mac 上,常見缺點是工作目錄會被手動操作污染、GUI 與 SSH 讀到的設定不同,而且主機重啟或使用者登出後,模擬器、Keychain 與測試服務可能失去一致狀態。臨時借用本機設備也難以保留完整 .xcresult 和可重複的 Archive 流程。
在專案級排查完成後,使用同一提交於隔離的遠端 Mac 執行一次 Build、Test 或 Archive,能更清楚分辨是程式碼問題還是主機問題。若需要按週、按月使用獨立的 macOS 建構環境,NOVAKVM 的遠端 Mac 方案可作為對照測試;但長期固定重負載、必須接觸實體周邊或需要完全掌控硬體的團隊,購買自有 Mac 仍可能更合適。
常見問題
為什麼在 Xcode 介面可以建構,改用 xcodebuild 卻失敗?
兩者可能沒有使用相同的 workspace、Scheme、Configuration、SDK、Destination 或環境變數。圖形介面還可能沿用已開啟的使用者狀態,而 SSH 或 CI 使用另一個工作目錄與使用者。應先記錄實際命令、DEVELOPER_DIR、工作目錄及 xcresult,再在兩種會話中對照第一個有效錯誤。
exit code 65 到底代表簽名問題,還是編譯錯誤?
單看 65 無法判定。它只是 xcodebuild 未成功完成工作的退出狀態;真正原因可能出現在依賴解析、原始碼編譯、連結、測試啟動、Archive 或簽名匯出階段。請以日誌中最早出現的有效錯誤、建構產物與 xcresult 內容分層判斷,不要直接刪除憑證或清空所有快取。
遠端 Mac 執行 xcodebuild 找不到 Scheme 時,應該先檢查什麼?
先確認命令指向正確的 .xcworkspace 或 .xcodeproj,並用專案實際共享的 Scheme 名稱執行。Scheme 若只存在於某位使用者的本機設定,乾淨檢出或 CI 會找不到。可在遠端工作目錄列出可用 Scheme,再核對 Configuration、SDK 與 Destination 是否與互動式會話一致。
CI 要怎樣保存 exit code 65 的完整日誌和 xcresult?
將標準輸出與標準錯誤寫入持久化檔案,並在命令結束後保留 .xcresult 結果包;即使建構失敗,也要讓 CI 收集這兩類產物。結果包不可只保留測試成功時的版本。記錄命令、提交識別、活動開發者目錄與工作目錄,但要遮蔽帳號、路徑、Bundle ID、Team ID 和憑證名稱。