在iOS專案中依賴Flutter Module-④報錯Multiple commands produce/[CP] Embed Pods Frameworks

JK_溪楓發表於2021-08-24

上一篇# 在iOS專案中依賴Flutter Module-③本地podspec中轉依賴遠端Flutter編譯產物實現了遠端依賴Flutter編譯產物,但是應用到專案後也遇到了一個編譯錯誤,編譯時報錯Multiple commands produce '*.framework',具體的報錯資訊如下。

Multiple commands produce '/Users/user/Library/Developer/Xcode/DerivedData/MyProj-flazyqyatfvrvsgcoofvwrizuvot/Build/Products/Debug-iphoneos/MyProj.app/Frameworks/FMDB.framework':
1) That command depends on command in Target 'MyProj' (project 'MyProj'): script phase “[CP] Embed Pods Frameworks”
2) That command depends on command in Target 'MyProj' (project 'MyProj'): script phase “[CP] Embed Pods Frameworks”


Multiple commands produce '/Users/user/Library/Developer/Xcode/DerivedData/MyProj-flazyqyatfvrvsgcoofvwrizuvot/Build/Products/Debug-iphoneos/MyProj.app/Frameworks/MMKV.framework':
1) That command depends on command in Target 'MyProj' (project 'MyProj'): script phase “[CP] Embed Pods Frameworks”
2) That command depends on command in Target 'MyProj' (project 'MyProj'): script phase “[CP] Embed Pods Frameworks”


Multiple commands produce '/Users/user/Library/Developer/Xcode/DerivedData/MyProj-flazyqyatfvrvsgcoofvwrizuvot/Build/Products/Debug-iphoneos/MyProj.app/Frameworks/MMKVCore.framework':
1) That command depends on command in Target 'MyProj' (project 'MyProj'): script phase “[CP] Embed Pods Frameworks”
2) That command depends on command in Target 'MyProj' (project 'MyProj'): script phase “[CP] Embed Pods Frameworks”


Multiple commands produce '/Users/user/Library/Developer/Xcode/DerivedData/MyProj-flazyqyatfvrvsgcoofvwrizuvot/Build/Products/Debug-iphoneos/MyProj.app/Frameworks/Sentry.framework':
1) That command depends on command in Target 'MyProj' (project 'MyProj'): script phase “[CP] Embed Pods Frameworks”
2) That command depends on command in Target 'MyProj' (project 'MyProj'): script phase “[CP] Embed Pods Frameworks”
複製程式碼

這個編譯錯誤是Xcode10使用新構建系統之後才出現的,新構建系統為了提高可靠性和構建效能會檢查重複構建/重複輸出檔案,檢測到重複構建產物可能會丟擲相關錯誤,Multiple commands produce '*.framework'是其中之一。

重複構建產物是新版構建系統新增功能之一,更全面的介紹可以看官方的介紹文件 # Build System Release Notes for Xcode 10

首先檢查構建系統設定,用新版Xcode開啟專案,點選螢幕左上角的File -> WorkSpace Settings,即可看到構建系統的配置項,如下圖所示。

Xcode10以後預設使用New Build System,即新版構建系統,而舊版構建系統Legacy Build Sysytem已經標記為Deprecated,但是目前還能用。

截圖2021-08-17 下午11.05.28.png

我用的是Xcode12.5,預設使用New Build System,排查這幾個報錯的framework後發現存在重複匯入/依賴的情況,Flutter側的iOS外掛層依賴了MMKVFMDBSentry,另外在iOS側也依賴了這幾個庫。編譯Flutter Module階段,外掛層依賴的第三方庫會被編譯成相關的.xcframework,最後通過本地podspec中轉依賴將.xcframework嵌入到iOS專案中,iOS側通過CocoaPods依賴的第三方庫在編譯階段也會被編譯成.xcframework嵌入到專案中。編譯前存放在不同的路徑下,但是編譯時會加工匯出到同一個路徑下,如此就出現重複構建的問題。

# Build System Release Notes for Xcode 10 中雖然沒有提到Multiple commands produce ...,但是提到了duplicate output file錯誤,要求確保某個Target的產物目錄都在同一個編譯階段處理,不能在多個編譯階段重複生產/輸出檔案。

Targets which have multiple asset catalogs that aren’t in the same build phase may produce an error regarding a “duplicate output file”. (39810274)

Workaround: Ensure that all asset catalogs are processed by the same build phase in the target.
複製程式碼

還提到It is an error for any individual file in the build to be produced by more than one build command.,如果在多個構建命令中生產同一個檔案也會出錯。而前面展開的錯誤資訊,則屬於在Target的某個編譯階段重複構建某個.framework,而且都發生在[CP] Embed Pods Frameworks指令碼階段。在Xcode中展開[CP] Embed Pods Frameworks即可看到相關的指令碼和輸入輸出列表檔案,具體包括以下檔案:

  • Pods-MyProj-frameworks.sh
  • Pods-MyProj-frameworks-Debug-input-files.xcfilelist
  • Pods-MyProj-frameworks-Debug-output-files.xcfilelist
  • Pods-MyProj-frameworks-Release-input-files.xcfilelist
  • Pods-MyProj-frameworks-Release-output-files.xcfilelist

image.png

檢視Pods-MyProj-frameworks.sh原始碼後發現,這個指令碼會逐個呼叫install_framework函式把Pods中依賴的第三方庫都重新生成簽名的framework,呼叫install_framework時會傳入庫檔案的匯入路徑。可以看到出現了重複的第三方庫,但是路徑不一樣。${BUILT_PRODUCTS_DIR}裡面是CococPods正常依賴的第三方庫,${PODS_XCFRAMEWORKS_BUILD_DIR}裡面是依賴的Flutter編譯產物(xcframework)。

  ...
  install_framework "${BUILT_PRODUCTS_DIR}/FMDB/FMDB.framework"
  install_framework "${BUILT_PRODUCTS_DIR}/MMKV/MMKV.framework"
  install_framework "${BUILT_PRODUCTS_DIR}/MMKVCore/MMKVCore.framework"
  ...
  install_framework "${PODS_XCFRAMEWORKS_BUILD_DIR}/Flutter/Flutter.framework"
  install_framework "${PODS_XCFRAMEWORKS_BUILD_DIR}/FMDB/FMDB.framework"
  install_framework "${PODS_XCFRAMEWORKS_BUILD_DIR}/MMKV/MMKV.framework"
  install_framework "${PODS_XCFRAMEWORKS_BUILD_DIR}/MMKVCore/MMKVCore.framework"
  install_framework "${PODS_XCFRAMEWORKS_BUILD_DIR}/mmkv_flutter/mmkv_flutter.framework"
複製程式碼

而在install_framework函式內部,會計算產物的匯出路徑,將構建的framework匯出到這個路徑。而且構建產物都放在${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}目錄下,具體路徑由framework名稱決定。如果匯入路徑中的framework名稱重複,則會出現重複構建framework的情況。

local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
binary="${destination}/${basename}.framework/${basename}"
複製程式碼

再開啟Pods-MyProj-frameworks-Debug-input-files.xcfilelistPods-MyProj-frameworks-Debug-output-files.xcfilelist檔案,這2個檔案分別列舉了所有framework的輸入路徑和輸出路徑,並且跟Pods-MyProj-frameworks.sh裡面用到的輸入輸出路徑一致。開啟Pods-MyProj-frameworks-Debug-input-files.xcfilelist檔案能看到FMDB.framework / MMKV.framework / ...這些庫有不同的輸入路徑,而在Pods-MyProj-frameworks-Debug-output-files.xcfilelist檔案中這些庫則出現瞭如下的重複輸出路徑,並且跟install_framework函式內部計算的匯出路徑一致。通過對比,編譯成功的情況下是不會出現重複項的,如果有重複的構建匯出路徑,則會出現Multiple commands produce '*.framework'編譯錯誤。

...
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FMDB.framework
...
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MMKV.framework
...
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FMDB.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MMKV.framework
複製程式碼

所以問題出在script phase “[CP] Embed Pods Frameworks”編譯階段,新構建系統檢測到了重複構建行為,而解決方案我目前找到2個,建議選擇方案2。

  1. 選擇舊的構建系統,具體的操作路徑: Xcode -> File -> WorkSpace Settings -> Build System -> Legacy Build Sysyte,可行,但不建議,因為這個構建系統已經被標記Deprecated
  2. 選擇新的構建系統,但是要從源頭解決重複依賴的問題,所以我從FlutterModuleSDK刪除了這些可能重複依賴的xcframework,在iOS專案編譯時只使用iOS側依賴的第三方庫即可,保留一個輸入源即可。可以看構建指令碼 flutter_build_script.sh,把需要刪除的xcframework加到黑名單,編譯Flutter之後會刪除掉黑名單裡的xcframework,這樣編譯iOS專案的時候就不會重複構建framework。

相關文章