[GN+Ninja學習 0x05] GN編寫規(guī)范
OpenHarmony使用gn+ninja來維護開源項目的構建。之前沒有接觸過gn+ninja,是時候系統(tǒng)性的來學習下了。邊學邊記錄下學習過程,希望對同樣需要學習gn+ninja的朋友有所幫助。
這一篇,我們來學習下GN的編寫規(guī)范,風格指南,或者最佳實踐。也可以閱讀官方的英文原版內容??docs/standalone.md??
1、Naming and ordering within the file文件中的命名和排序
(1)Location of build files構建文件的位置
在靠近代碼之處創(chuàng)建更多的構建文件比在頂層創(chuàng)建更少的構建文件更有意義;這與我們對GYP所做的形成鮮明對比。這使得構建文件、源文件更容易找到,需要檢視的內容更是,因為更改集中于特定的子目錄。-- 注:所以使用gn,會看到較多的BUILD.gn。根目錄下有BUILD.gn,每個子模塊有BUILD.gn,通過依賴關系來調用。
(2)Targets
- 大多數 BUILD.gn文件應具有與目錄同名的目標。此目標應為第一個目標。
- 其他目標應該按照某種邏輯順序排列,通常更重要的目標將排在第一位,單元測試目標將緊跟相應的功能模塊的目標。如果沒有明確的順序,請考慮按字母順序排列。
- 測試支持庫應為名為“test_support”的靜態(tài)庫,例如,“//ui/compositor:test_support”。測試支持庫應包含非測試支持版本的庫作為公共依賴 public deps,以便測試程序只需要依賴于test_support目標(而不是兩者)。
命名建議
- 目標和配置應使用小寫字母和下劃線分隔單詞來命名,除非有充分的理由不這樣做。
- 源代碼集source_set、組group和靜態(tài)庫static_library不需要名稱全局唯一。更喜歡為此類目標提供簡短、非冗余的名稱,而不必擔心全局唯一性。例如,編寫依賴項"http://mojo/public/bindings"看起來比編寫依賴項"http://mojo/public/bindings:mojo_bindings"要好得多。
- 共享庫shared_library(以及擴展的組件components–注:Chrome項目里有這樣的components 目標)必須具有全局唯一的輸出名稱。為此類目標提供上面的簡短非唯一名稱,然后為該目標提供全局唯一名稱output_name。
- 應為可執(zhí)行文件和測試指定一個全局唯一的名稱。從技術上講,只有輸出名稱必須是唯一的,但由于只有輸出名稱出現在 shell 和bots中,因此如果名稱與可執(zhí)行文件出現的其他位置匹配一致,則迷惑性要小得多。
(3)Configs配置
- 與單個target目標關聯的config配置應與target目標同名,并用?
?_config?
?跟在它后面。如 target名稱為foo,則對應的config名稱為foo_config。 - config配置應緊挨著使用它的相應target目標之前出現。
(4)Example
示例src/foo/BUILD.gn如下:
2、Ordering within a target目標內的排序
在一個target目標內,推薦使用如下的排序,輸出在前,依賴在后,參與構建的內容在中間。
- output_name / visibility / testonly
- sources
- cflags, include_dirs, defines, configs,這幾個可以根據情況排序
- public_deps
- deps
(1)Conditions
僅影響一個變量的簡單條件(例如,添加單個源或為一個特定操作系統(tǒng)添加標志)可以位于它們影響的變量之下。影響范圍較多的更復雜的條件應該在底部。
編寫條件時,應最大程度地減少條件塊的數量。
3、Formatting and indenting 格式化和縮進
GN 包含一個內置的代碼格式化程序,用于定義格式設置樣式。一些額外的說明:
- 變量為小寫帶下劃線分割單詞lower_case_with_underscores
- 注釋應該是完整的句子,末尾有句點。
- 編譯器選項標志等應始終注釋它們的作用以及需要標志的原因。
(1)Sources源文件
優(yōu)先僅列出一次源文件??梢杂袟l件地包含源文件,而不是將它們全部列在頂部,然后在它們不適用時有條件地排除它們。條件包含通常更清晰,因為文件僅列出一次,并且在閱讀時更容易推理。
(2)Deps依賴
- 依賴應按字母順序排列。
- 當前文件中的 Deps 應首先寫入,并且不能使用文件名限定(僅需要:foo )。
- 其他 deps 應始終使用完全限定的路徑名,除非出于某種原因需要相對路徑名。
(3)Import導入
使用完全限定的路徑進行導入:
4、Usage用法
這一節(jié)介紹下如何選擇使用不同的target目標類型,主要是source_set、shared_library、loadable_module,以及Components(這是Chrome項目定義的模板,可以忽略)。
(1)Source sets versus static libraries source_set與static_library的對比
在大多數情況下,源代碼集和靜態(tài)庫可以互換使用。如果您不確定要使用什么,則source_set幾乎永遠不會出錯,并且不太可能引起問題,但是在大型項目中使用正確類型的target目標類型可能很重要,因此您應該了解以下權衡。
靜態(tài)庫static_library遵循不同的鏈接規(guī)則。當鏈接靜態(tài)庫時,只有包含未解析符號的對象文件才會被引入構建中。source_set則會鏈接每個對象文件,都添加到最終二進制文件中。
- 如果最終將代碼鏈接到組件component、共享庫或可加載模塊中,通常需要使用source_set。這是因為沒有從共享庫中引用的符號的對象文件將根本不鏈接到最終庫中。即使該對象文件具有標記為導出的符號,該符號的目標依賴于該共享庫的需求,也會發(fā)生此遺漏。這將在鏈接后續(xù)目標時導致未定義的符號。
- 單元測試(以及具有副作用的靜態(tài)初始值設定項的任何其他內容)必須使用源代碼集source_set。gtest TEST 宏創(chuàng)建注冊測試的靜態(tài)初始值設定項。但是,由于沒有代碼引用對象文件中的符號,因此將測試鏈接到靜態(tài)庫,然后鏈接到測試可執(zhí)行文件意味著測試將被剝離。
- 在某些平臺上,靜態(tài)庫可能涉及復制組成它的對象文件中的所有數據。這會占用更多的磁盤空間,對于具有非常大對象文件的配置中的某些非常大的庫,可能會導致超出靜態(tài)庫大小的內部限制。源集沒有此限制。某些目標根據生成配置在源集和靜態(tài)庫之間切換,以避免此問題。一些平臺(或工具鏈)可能支持一種稱為“薄檔案”的東西,它沒有這個問題;但你不能依賴它作為便攜式解決方案。
- source_set可以沒有源文件,而靜態(tài)庫如果沒有源文件,則會給出特定于平臺的奇怪錯誤。如果目標只有頭文件(用于包括檢查目的)或在某些平臺上有條件地沒有源文件,請使用source_set。
- 在特定鏈接不需要大量符號的情況下(在鏈接測試二進制文件時尤其如此),將該代碼放在靜態(tài)庫static_library中可以顯著提高鏈接性能。這是因為鏈接不需要的對象文件從一開始就不會被考慮在內,而不是強迫鏈接器在以后的傳遞中去除未使用的代碼,因為沒有代碼引用它。
(2)Components versus shared libraries versus source sets 組件、共享庫和source_set的比對
組件是Components Chrome 模板(而不是內置的 GN 概念),不再贅述。
就像源代碼集與靜態(tài)庫權衡一樣,對于何時應該使用組件,沒有硬性規(guī)定。使用組件可以顯著加快鏈接速度,從而顯著加快增量構建速度,但它們要求您考慮需要從目標導出哪些符號。
(3)Loadable modules versus shared libraries 可加載模塊和共享庫的比對
共享庫shared_library會被列在依賴它的target目標的鏈接行上,并在應用程序啟動和符號自動解析時由操作系統(tǒng)自動加載??杉虞d模塊loadable_module不會直接鏈接,應用程序必須手動加載它。
在 Windows 和 Linux 上,共享庫和可加載模塊會產生相同類型的文件(分別是.dll和 .so)。唯一的區(qū)別是它們如何鏈接到依賴它的目標上。在這些平臺上,可加載模塊的??deps?
?依賴與共享不會進行鏈接的??data_deps?
?依賴是相同的。
在Mac上,這些target目標具有不同的格式:共享庫將生成.dylib文件,可加載模塊將生成.so文件。
將可加載模塊loadable_module用于插件等等。對于類似插件的庫,最好同時為目標類型使用可加載模塊(即使對于無關緊要的平臺),并為依賴于它的目標使用data_deps,以便從兩個地方都清楚地知道庫將如何鏈接和加載。
5、Build arguments構建參數
(1)Scope作用域
構建參數的作用域應限定在一個行為單元,例如啟用功能。通常,將在導入的文件中聲明一個參數,以將其與可以使用它的構建子集共享。
Chrome項目相關的一些風格指導可以自行查看原文,基本上不需要了解。
(2)Type類型
參數支持所有GN語言的類型,字符串、列表、條件、循環(huán)、作用域等。
在絕大多數情況下,布爾值類型boolean是首選類型,因為大多數參數都啟用或禁用功能或包含。
String字符串類型通常用于文件路徑。字符串也用于枚舉,盡管有時也使用整形integer。
(3)Naming conventions命名約定
雖然圍繞參數命名沒有硬性規(guī)定,但有許多共同的約定。如果要查看當前構建目錄的參數名稱和默認值的參數列表,請使用gn args out/Debug --list --short。
- use_foo- 指示要包含的依賴項或主要代碼路徑(例如use_open_ssl,use_ozone,use_cups)
- enable_foo- 表示要啟用的功能或工具(例如enable_google_now,enable_nacl,enable_remoting,enable_pdf)
- disable_foo - 不建議使用,請使用enable_foo-,然后改變其默認值
- is_foo- 通常是全局狀態(tài)描述符(例如is_chrome_branded,is_desktop_linux); 非全局變量的不推薦使用
- foo_use_bar- 前綴可用于指示參數的限制的作用域(例如,rtc_use_h264,v8_use_snapshot)
(4)Variables變量命名約定
在.gni文件中的頂級局部變量前面加上下劃線前綴。此前綴會導致變量無法被導入到其他構建文件。
6、小結
本篇,我們學習了GN的編寫規(guī)范,風格指南,包含命名、排序,格式化和縮進,用法,格式參數等的推薦用法。