Xcode構建過程的后臺工作(一)構建過程
Xcode構建過程的后臺工作(三)swift構建
Xcode構建過程的后臺工作(四)鏈接
構建過程:Clang構建
什么是Clang
Clang是蘋果官方編譯器,用于所有的C語言,比如C,C++,OC,OC++,大部分框架都在用的語言。編譯器一次編輯所有輸入文件,生成僅一個輸出文件,之后被連接器使用。如果要從OS訪問API,或從自己的代碼訪問實現文件,就需要一個叫做頭文件的東西。頭文件是一種承諾,承諾在其他地方存在這個實現文件,它們通常可以匹配。如果你只是更新實現文件,而忘記頭文件,你就食言了。通常這個問題不會在編譯過程中出現,因為編譯器相信你的承諾。問題出在鏈接過程中。編譯器通常包含不止一個頭文件,而且所有編譯器都是這樣被調用。
以demo PetWall為例。它是混合語言app,app本身用swift編寫。它使用了一個用Objective-C編寫的框架。 它有一個美學檔案支持庫,使用C++編寫。 隨著時間的推移,我們的應用程序越大,所以我們開始重組它,以便更容易查找文件。 例如,我們將所有與cat相關的文件移動到子文件夾中。我們不必更改任何實現文件,它仍然有效。所以它讓你想知道Clang 如何找到你的頭文件?讓我們看一個簡單的例子。
這是我們在代碼中使用的實現文件之一,在這個文件里包含一個名為cat.h的頭文件。 我們怎樣才能弄清楚Clang的作用?一是查看構建日志,看看Xcode構建系統編譯這個文件時做了什么。復制粘貼以下調用代碼:
$ clane <list of arguments> -c Cat.mm -o Cat.o -v
將其放入終端并輸入-v,-v是詳細符號。然后Clang會返回很多信息。 但是我們只需要關注一個重要的搜索路徑。我說搜索路徑,可能大家想到的是指向源代碼的搜索路徑。但不是這樣的。相反,你會看到headermaps(頭文件映射)。headermaps由Xcode構建系統創建,說明頭文件的位置。
讓我們來看看最重要的兩個headermaps文件。 前兩個條目只是 將框架名稱附加到標題中。 這兩個頭文件原來是公共文件。
我建議你不應該依賴這個功能,原因是我們將其放在那里是為了保持現有項目的 正常運行,但是之后使用Clang模塊可能會遇到問題,因此我們建議您在將公共或私有文件從自己的框架導入時自己標出框架名稱。 第三行是項目頭文件,這個例子中并不需要。 headermaps是為了鏈接回源代碼。如您所見,公共和私有 頭文件執行相同的操作,總是回歸源代碼。 這樣做是為了讓Clang可以為源目錄中的文件生成有用的報錯和警告消息, 而不是從構建目錄中復制過來的其他內容。由于許多人不知道頭文件映射的存在,因此會遇到某些問題。最常見的是忘了將頭文件添加到項目中。 它位于源目錄中, 但它不在項目中。 因此,請一定要保證將頭文件添加到項目中。 另一個問題是,如果頭文件具有相同的名稱,則它們可能會相互影響。 因此,頭文件務必使用唯一名稱。這也適用于系統頭文件。 如果 項目中里的本地頭文件與系統頭文件名字一樣,它將覆蓋系統頭文件,因此應該避免這種情況。
如何找到系統頭文件?
舉PetWall的 另一個例子。 在這個例子中,我們引入了SDK中的Foundation.h頭文件 。當我們尋找自己的頭文件時,我們可以做同樣的事情 。 但現在我們正在尋找系統頭文件。 headermap只適用于您自己的頭文件。所以我們可以忽略它們。 現在關注導入路徑。默認的SDK中有兩個目錄。第一個是用戶的,第二個是系統庫框架。先來看看第一個。
這是一個常規的包含目錄。我們只要輸入搜索關鍵詞,這里是Foundation/Foundation.h。頭文件找不到,因為它不在那里。 不過沒關系。 我們來試試下一個,系統庫框架。
這是一個框架目錄,所以著Clang的做法有些不同。首先,它要確定框架的定義,并檢查框架是否存在。
之后,從頭文件目錄中查找頭文件。
這里找到了,很好。 但是如果找不到頭文件會發生什么? 例如輸入不存在的虛假頭文件。顯然無法在 headers目錄中找到它。但接下來它還會查看私有頭文件目錄。 Apple的SDK中不會帶有任何私有頭文件。但是您的項目和框架可能有公共和私有頭文件。所以也會檢查。
因為它是一個虛假的頭文件,所以那里也沒有。 現在有趣的是,會中止搜索。 我們不會繼續搜索其他目錄。原因是我們已經找到框架。找到框架后,一般框架目錄中能找到頭文件。如果沒有找到,搜索就放棄了。 如果您對實現文件的樣子很感興趣,那么當所有頭文件都被導入和預處理之后,您可以要求Xcode為您的實現文件創建預處理文件。
這將創建一個非常大的輸出文件。有多大呢?舉個簡單的例子。Foundation.h是一個非常基礎的頭文件,是我們系統的基本頭文件。它是您很可能直接或間接地為其他頭文件導入此頭文件。這意味著每次調用編譯器,都要查找這個頭文件 。一天之內Clang要為一個include語句查找并處理800多個頭文件。也就是要解析和驗證超過9兆字節的源代碼。每次編譯器調用都會發生這種情況。這是大量的冗余工作。怎么改善? 這里有一個功能稱為預編譯頭文件。這是改善這種情況的一種方法。但我們有更好的東西。幾年前,我們推出了Clang模塊。Clang模塊允許我們為每個框架只查找和解析一次頭文件 ,然后將該信息存儲在硬盤上,以便緩存和重復使用。這應該會改善您的構建時間。 為此,Clang模塊必須具備特定的屬性。其中最重要的一點是上下文無關(context-free)。什么是上下文無關?這里有兩個代碼片段:
在這兩種情況下,我們都導入了 PetKit模塊。但我們事先有兩個不同的宏定義。 如果您使用傳統方法導入頭文件,則意味著文本也會被導入。預處理器將遵循此定義并將其應用于頭文件。但是如果你這樣做,那意味著每個案例的模塊都不同,不能重用。因此,如果您想使用模塊,則不能這樣做。模塊會忽略所有文本信息,這樣就能被所有實現文件中重用。另一個要求是模塊必須是獨立的(self-contained)。也就是說要明確所有依賴關系。這有個好處,就是只要你導入一個模塊,它就會起作用。不用考慮還要添加其他頭文件才能運行。 那么Clang如何知道要不要構建一個模塊呢?讓我們看一下一個簡單例子,NSString.h。
首先Clang要在框架中找到這個頭文件。我們已經知道如何做到這一點。這是Foundation.framework目錄。接下來,Clang編譯器會查找模塊目錄和模塊映射,它與頭文件目錄相關。
什么是模塊映射?模塊映射描述了某組頭文件夾轉換到模塊中。模塊圖實際上非常簡單。
這是Foundation的整個模塊映射。它顯然描述 了模塊的名稱,即 Foundation。然后它還指定了哪個 標頭是該模塊的一部分。
您會注意到這里只有一個頭文件,只有 Foundation.h。但這是一個特殊的頭文件。
這是umbrella頭文件 ,用特殊關鍵字umbrella標記。這意味著Clang還要 查找這個特定的頭文件,以確定NSString.h 是否是模塊的一部分。
就在這里,NSString.h是Foundation模塊的一部分。現在Clang可以將文本導入升級為模塊導入,為此我們要創建Foundation模塊。那么我們如何構建Foundation模塊呢? 首先,我們要創建一個單獨的Clang位置。Clang位置包含Foundation模塊中的所有頭文件。我們不會從原始編譯器調用中轉移任何現有上下文。因此,它是上下文無關的。我們實際轉移的是您傳遞給Clang的命令行實參,隨后繼續傳遞。在構建Foundation模塊時,框架本身會導入其他框架。 這意味著我們也必須構建這些模塊。我們不能停頓,因為它可能還包括其他框架。但我們已經可以看到他的好處了。某些導入可能是相同的。所以能重用那個模塊。
所有模塊都要序列化到模塊緩存區。
正如我所提到的,命令行實參會在創建該模塊時向后傳遞。這意味著這些參數會影響模塊的內容。因此,我們必須對這些參數進行哈希處理,并將為這些特定編譯器調用而創建的模塊存儲在與該哈希匹配的目錄中。如果更改不同限制文件的編譯器參數,例如寫入enable cat,這是不同的哈希,要求Clang重建所有模塊到與該哈希匹配的目錄中。
因此,為了更多地重用模塊緩存,如果可能的話應該嘗試保持參數一致性。
查找自己的頭文件
我們如何為自己的頭文件構建模塊?回到原來的cat示例,這次我們打開模塊。 如果我們要用頭文件映射,則headermap會映射到源目錄。
看一下這個源文件,現在遇到了問題。這里沒有模塊目錄。它看起來根本不是框架 ,Clang在這種情況下不知所措。所以我們引入了一個新概念來解決這個問題,它被稱為Clang的虛擬文件系統。它會創建一個虛擬的抽象框架,方便Clang構建模塊。但抽象框架只能映射到目錄文件。這樣Clang就能夠在源代碼中報錯。這就是在使用框架時構建模塊的方式。
如您所知,在開始時我提到 如果不確定框架名稱,可能會出現問題。那么讓我舉個例子看看是什么問題。
這是一個非常簡單的代碼示例,只有兩個輸入。
第一個導入PetKit模塊。 第二次導入,我們都知道這也是PetKit模塊的一部分,但Clang可能不知道,因為你沒有指定框架名稱。在這種情況下,您可能會 遇到重復定義的報錯。這種情況常見于導入兩次相同頭文件。Clang在幕后非常努力地解決了這樣的最常見問題。但它無法解決所有問題。這只是一個簡單的例子。我們來做一點調整吧。
修改一下上下文。模塊導入完全不受此影響,因為我說過的上下文可以忽略。cat導入仍然是頭文件的文本導入,它會遵循此更改。這時可能就不是重復定義,而是矛盾定義,無法解決,Clang解決不了。記住我的建議,在導入公共或私有頭文件時始終明確框架名稱。