問題
最近把我們組負責的模塊改成了通過 Cocoapods 集成到主工程,竟然在運行單元測試的 Target 時出現了類似下面的錯誤:
Showing All Messages
Multiple commands produce '/Users/Alvin/Library/Developer/Xcode/DerivedData/XCFrameworkDemo-fjmdgsvymhuemlebgdzvccvivdtu/Build/Products/Debug-iphonesimulator/cocoapods-artifacts-Debug.txt': 1. That command depends on command in Target 'XCFrameworkDemo' (project 'XCFrameworkDemo'): script phase “[CP] Prepare Artifacts” 2. That command depends on command in Target 'XCFrameworkDemoTests' (project 'XCFrameworkDemo'): script phase “[CP] Prepare Artifacts”
經過一番調查后才發現這是 Cocoapods 在集成 XCFramework 時的一個 bug。
XCFramework
XCFramework 是蘋果在 WWDC2019 新推出的代碼框架格式,主要是為了解決代碼庫在不同平台、不同架構的分發問題。現在蘋果生態下大致可分為 4 個不同的 OS(iOS,macOS,watchOS,tvOS),各個 OS 又支持不同的架構:
以往為了支持多個平台,代碼庫的使用者需要配置不同的庫,有時還需要設置一些搜索路徑、編譯選項等等。但如果代碼庫支持 XCFramework 后,就只需要把新的 .xcframework 文件直接拖到 Xcode,Xcode 將會自動為我們處理好一切。
XCFramework 的本質是一個類似文件夾的容器,它把多個平台和架構的文件都按指定的格式放置在一起。典型的目錄結構類似下面這樣,本文以 BugfenderSDK 為例:
Info.plist 跟其它地方的用途一樣,記錄了這個 .xcframework 的屬性,只不過 CFBundlePackageType 這里是 XFWK,其余部分定義了這個 SDK 所支持的平台、架構和對應的庫文件路徑。
通過 Cocoapods 集成 XCFramework
作為蘋果生態最受歡迎的依賴管理工具,Cocoapods 從 1.9 Beta 版本開始支持 XCFramework。為了處理 .xcframework 文件, pod install 后會在 Target 的構建階段(Build Phases)新加入了名為 [CP] Prepare Artifacts 的步驟。
可以看到,在這個步驟中執行了一個腳本文件,並且設定了輸入輸出文件列表。.xcfilelist 文件是 WWDC18 推出的新特性,用於幫助 Xcode 更好地處理 Targets 之間的依賴,可用於加快編譯速度:
In WWDC’18 Session #408 (“Building Faster in Xcode”), they present a new feature of Xcode 10 where you can provide .xcfilelist files to specify the list of input and output files for the build phase, which will allow Xcode to better determine the dependency graph between targets, files having to be rebuilt, etc.
Note: Input & Output files for build phases were already present in previous versions of Xcode to determine when to run the build phases and optimise the build dependency resolution; what’s new in Xcode 10 is the ability to put the list of those input/output files in xcfilelist — that can then be generated/updated by external tools without having to modify the xcodeproj for that
如果輸入輸出文件列表里的文件都已經存在,那么 Xcode 就會跳過對應步驟的重建。換句話說,開發者可以自定義構建步驟,跳過一些不需要重復執行的操作,減少編譯時間。本文 Demo 里的輸入輸出文件列表內容如下。
Pods-XCFrameworkDemoTests-artifacts-Debug-input-files.xcfilelist:
${PODS_ROOT}/Target Support Files/Pods-XCFrameworkDemoTests/Pods-XCFrameworkDemoTests-artifacts.sh ${PODS_ROOT}/BugfenderSDK/BugfenderSDK.xcframework
Pods-XCFrameworkDemoTests-artifacts-Debug-output-files.xcfilelist:
${BUILT_PRODUCTS_DIR}/cocoapods-artifacts-${CONFIGURATION}.txt
而腳本所做的事可以概括為以下兩點:
- 根據編譯目標的平台和架構找到 .xcframework 里對應的庫,然后復制到 DerivedData 目錄
- 生成
cocoapods-artifacts-${CONFIGURATION}.txt文件,里面的內容是第一步復制后的庫路徑,用於最終產品的編譯鏈接。
本文的問題就出現在 XCFrameworkDemo 和 XCFrameworkDemoTests 兩個 Target 都依賴了 BugfenderSDK 這個帶 XCFramework 的第三方庫,導致在 [CP] Prepare Artifacts 這一步都去生成 cocoapods-artifacts-${CONFIGURATION}.txt 文件,造成沖突。
解決方法
解決本文的問題有三個方法:
- 升級到 1.10.0 或以上。從這個版本開始 Cocoapods 會重新移除
[CP] Prepare Artifacts步驟,徹底解決問題。不過這需要整個項目組的人員及工具鏈都進行升級,成員較少的團隊可以采用。 - 避免主工程 Target 和單元測試 Target 同時直接依賴帶有 XCFramework 的第三方庫。比如將所有依賴都打包到另一個子 Target。要做到這點有可能比較復雜,比如把我這邊的模塊改為集成到子 Target 就需要很大的工作量。
- 改為源碼編譯或者回歸到原來的 .framework 或 .a 集成方式。




