Xcode 10 新構建系統的一個小問題
Xcode 10 新構建系統的一個小問題
swift 釋出於 2018年10月09日
新 iPhone 和 Xcode 10 釋出已經有一段時間了。 相信大家也都把自己的開發環境切換到了 Xcode 10。 而這次 Xcode 10 同時帶來了一個全新的構建系統。 在使用中我們可能會遇到一些新問題。 比如新構建系統的 依賴迴圈檢測 。
如果我們的舊工程,之前對專案之間的依賴設定的不夠明確,就可能會在打包或者構建的時候遇到類似這樣的錯誤:
error: Multiple commands produce '/Library/Developer/Xcode/DerivedData/.../GracefulImagePicker.bundle' :
1 ) Target 'GracefulFeedback-GracefulImagePicker' has create directory command with output '/Users/marspro/Library/Developer/Xcode/DerivedData/.../GracefulImagePicker.bundle'
2 ) Target 'GracefulImagePicker-GracefulImagePicker' has create directory command with output'/Users/marspro/Library/Developer/Xcode/DerivedData/.../GracefulImagePicker.bundle'
上面是我開發中遇到的一個例項, 在使用 Xcode 9 的時候,這個工程編譯正常。 切換到 Xcode 10 之後,就遇到了這個錯誤。
經過一番查詢,瞭解到造成這個問題的原因就是 Xcode 10 的新構建系統對於包之間依賴以及名稱衝突的檢測更加嚴格了。 詳情在 Xcode 的官方文件中有介紹: ofollow,noindex" target="_blank">https://help.apple.com/xcode/mac/current/#/dev621201fb0 。
這裡面的大致意思給大家總結一下, 就是 Xcode 構建系統中有兩種指定包依賴的方式。
第一種方式是顯示指定,我們在 Target Dependencies 中指定的包,例如:
上圖指定 UIColor_Hex_Swift
這個包為依賴項, 這意味著什麼呢? 當我們當期專案開始編譯的時候, 構建系統首先會編譯 UIColor_Hex_Swift
這個專案, 然後才會編譯主工程。
另外一種方式可以稱為間接指定。 這種依賴關係相對更加隱蔽, 簡單來說就是隻要在主工程中引用的任何第三方庫,包括 Framwork,子專案等等,都會被構建系統認為主工程依賴於他們。 這樣在構建系統開始運作的時候,這些間接依賴依然會被首先編譯。
對於咱們上面說的包之間依賴的規則,新的構建系統預設是嚴格處理,保證不會造成 迴圈依賴 ,或者 名稱衝突 。
迴圈依賴是什麼呢, 假設我們有 A,B,C 三個工程, 用箭頭表示他們之間的依賴:
A -> B -> C -> A
按照上面的描述,A 依賴 B, B 依賴 C, 而 C 又反過來依賴了 A。 這就造成了 迴圈依賴 。 這樣構建系統就無法確認應該優先編譯哪個工程。
那麼如何解決這類問題呢, 目前有兩種解決方案。
退回舊的構建系統
因為舊版的構建系統對包之間這種驗證相對寬鬆,有可能我們舊工程中存在類似的問題,但也能正常構建通過,並沒有發生明顯的執行問題。這時候如果切換到新的構建系統上,就會造成專案馬上不可編譯。
如果你這時候有時間的壓力,沒有足夠的精力去修正之前專案中的這類問題的話,Xcode 10 允許開發者回退到舊的構建系統。具體操作方法是這樣, 開啟 File -> Workspace Settings... , 在隨後的視窗中選擇舊的構建系統 - Legacy Build System , 如下圖:
這樣修改後, 基本上你的舊專案就可以直接編譯通過了。 當然,如果時間允許的話,還是建議花些精力把自己專案中真正的依賴問題直接處理, 隨著時間的推移,可能以後不一定能那麼方便的直接切換回舊構建系統了。
修改專案中的依賴衝突問題
就像剛才我說的,如果精力允許的情況下,最好還是從根本上把自己專案中的依賴問題處理掉。 下面就和大家分享下自己專案中的處理方法。 我把剛開始的那段編譯錯誤再貼出來:
error: Multiple commands produce '/Library/Developer/Xcode/DerivedData/.../GracefulImagePicker.bundle' :
1 ) Target 'GracefulFeedback-GracefulImagePicker' has create directory command with output '/Users/marspro/Library/Developer/Xcode/DerivedData/.../GracefulImagePicker.bundle'
2 ) Target 'GracefulImagePicker-GracefulImagePicker' has create directory command with output'/Users/marspro/Library/Developer/Xcode/DerivedData/.../GracefulImagePicker.bundle'
從錯誤資訊不難看出, 這是一個名稱衝突,有兩個庫掛載了兩個重名的 bundle, GracefulImagePicker.bundle 。
這兩個庫在上面的資訊中也指出來了,是 GracefulFeedback
和 GracefulImagePicker
。 他們是我工程中用到的兩個自行開發的內部子模組。
這兩個庫都各自掛載了一個 bundle 資源包, 從這個錯誤中可以看出,新的構建系統不允許兩個庫掛載的 bundle 的名稱一樣。但在舊的構建系統中這裡是不會報錯的。
兩個庫的 bundle 掛載關係如下:
GracefulFeedback -> GracefulImagePicker.bundle GracefulImagePicker -> GracefulImagePicker.bundle
我的理解是,舊的構建系統應該是有一些機制自動處理了這種不同庫中,同名的 bundle 如何區分的問題,又或者是根本無視了這種衝突,直接覆蓋掉一個。 當然具體機制我沒有深入研究。不做過多展開。
而新的構建系統,檢測到這種名稱衝突後, 把它當做了一個編譯錯誤,讓開發者來自行處理。
本來之前對這兩個庫的定義是,資原始檔的名稱和庫的名稱是一樣的才對,並不應該出現名稱衝突,從報錯資訊上看,問題明顯發生在 GracefulFeedback 這個庫上面,它的資源 bundle 本該命名為 GracefulFeedback.bundle
, 但從編譯錯誤資訊中來看,它似乎命名成了 GracefulImagePicker.bundle
。
於是我回到 GracefulFeedback
這個工程中檢視, 果然發現了問題根源所在,我們的子模組都通過 CocoaPods
來實現,檢視它的 .podspec
檔案時發現這樣一段內容:
s.resource_bundles = {
'GracefulImagePicker' => [ 'GracefulFeedback / Assets /*.png']
}
果然,它的資源包被命名成了 GracefulImagePicker
, 而不是預期的 GracefulFeedback
。 這就導致了它的資源包和同樣另外一個模組的重名了。
新的構建系統,通過更嚴格的機制,幫助我發現了這個問題。 將 .podspec
中這段內容修改正確後,這個工程順利的在新構建系統中編譯通過了。 當然,哪種方式更好,也並不是絕對的。
不過我們得出了一個經驗,新的構建系統採用更加嚴格的驗證方式。 反過來推動我們投入更多的精力來把之前專案結構中不嚴謹的地方改正過來。
關於 CocoaPods
和 .podspec
的詳細介紹,我們之前的文章有過討論,大家也可以去了解一下。這裡就不過多討論了。
總結
Xcode 10 這次採用了全新的構建系統,使用更加嚴格的驗證機制。總體來說是一件好事,但是對於開發者來說,需要一個轉換的過程。 比如我第一次用 Xcode 10 編譯同樣的專案一直報錯的時候,心裡就感覺很疑惑,經過一番瞭解後,才知道其中的原因。 不過整個過程下來後,回顧起來也有一些收穫的。 在這裡和大家分享出來,也是希望能幫助大家節省時間,遇到和我類似的問題時候有一個參考。
對於新的構建系統的各種細節,如果大家有更多其他的瞭解,也歡迎一起交流。
如果你覺得這篇文章有幫助,還可以關注微信公眾號 swift-cafe,會有更多我的原創內容分享給你~本站文章均為原創內容,如需轉載請註明出處,謝謝。

關注微信公眾號
發現更多精彩
swift-cafe