[GN+Ninja學習 0x03] GN語法與操作學習
OpenHarmony使用gn+ninja來維護開源項目的構建。之前沒有接觸過gn+ninja,是時候系統性的來學習下了。邊學邊記錄下學習過程,希望對同樣需要學習gn+ninja的朋友有所幫助。
這一篇,我們來學習GN的語法和操作行為等,建議也可以閱讀原版文檔??GN Language and Operation??。
GN提供了擴展的內置幫助文檔系統,提供每一個函數功能和內置變量的詳細的參考引用。可以使用gn help來查看幫助,可以進一步使用gn help <command>、gn help <function>、gn help <variable>來查看具體的命令、函數、變量的使用幫助信息。
1、Design philosophy設計理念
- 編寫構建文件不應該是一項創造性的工作。理想情況下,兩個人應該在相同的要求下生成相同的構建文件。除非絕對需要,否則不應該有靈活性。盡可能多的事情應該是致命的錯誤。
- 構建定義應該讀起來更像代碼而不是規則。我不想編寫或調試Prolog。但是我們團隊中的每個人都可以編寫和調試C++和Python。
- 構建語言應該對構建應該如何工作持固執己見。表達武斷的東西不一定容易,甚至不可能。我們應該改變源代碼和工具使構建更簡單,而不是使一切都更復雜以符合外部要求(在合理范圍內)。
- 在有意義的時候,需要像Blaze一樣。
2、Language語言
GN使用機器簡單的,動態類型的語言。支持的類型有:
- Boolean (true, false). 布爾值。
- 64-bit signed integers. 64位有符號整數。
- Strings. 字符串。
- Lists (of any other types). 上述類型的列表。
- Scopes (sort of like a dictionary, only for built-in stuff). 作用域(類似字典)。
(1)Strings 字符串
字符串括在雙引號中,并使用反斜杠作為轉義字符。僅僅支持如下轉義序列是:
反斜杠的任何其他用法都被視為反斜杠。因此,例如,\b不需要轉義,大多數 Windows 路徑如 "C:\foo\bar.h")不需要轉義。
通過符號$支持簡單變量替換,其中美元符號$后面的單詞被替換為變量的值。如果沒有非變量名稱字符來終止變量名稱,則可以選擇${}將名稱括起來。不支持更復雜的表達式,僅支持變量名稱替換。
(2)Lists列表
除了把非空列表賦值給空列表(a == [])之外,沒有辦法獲得列表的長度。如果你發現自己想做這種事情,意味著在構建中做太多的工作?!?注:說的是,列表不提供獲取長度,也不應該獲取長度。
列表追加
列表支持追加,如下所示。將一個列表追加到另一個列表,會把每一個列表項追加為第二個列表中的項,而不是將該列表追加為嵌套成員。
列表刪除
還可以從列表中刪除項目,如下。列表中的減號運算符“-”搜索匹配項并刪除所有匹配項。從另一個列表中減去一個列表將刪除第二個列表中的每個項目。如果未找到匹配的項目,則會引發錯誤,因此您需要在刪除列表項之前,需要提前知道該列表項是否存在。
鑒于無法測試列表項的添加引入,可以這樣使用:設置一個文件或標志的主列表,然后根據各種條件刪除不適用于當前版本的文件或標志。— 注:這算是推薦做法,維護一個主列表,然后只做減法,排除不適合的列表項。這個和下文的GYP提供的建議一樣。這里讀起來有些奇怪。
在風格上,更喜歡只添加到列表中,讓每個源文件或依賴項出現一次。這與Chrome團隊過去為GYP提供的建議相反(GYP更愿意列出所有文件,然后基于條件刪除您不需要的文件)。
列表項獲取
列表支持從零開始的下標來提取值:
[] 運算符是只讀的,不能用于改變列表。其主要使用場景是當外部腳本返回多個已知值,并且您想要提取它們時。
在某些情況下,覆蓋一個列表比追加到一個列表更容易。為了幫助滿足這種情況,將非空列表賦值給值為非空列表的變量,會產生錯誤。如果要繞過此限制,請首先將目標變量賦值給一個空列表。如下:
(3)Conditionals條件
條件語句類似于 C語言,如下。可以在大多數情況下,使用條件語句。甚至可以把整個target目標放在條件里,如果這些target只在特定的條件下才需要聲明。
(4)Looping循環
您可以使用foreach循環訪問列表。這是不鼓勵的。構建應該做的大多數事情通常都可以在不這樣做的情況下來完成,如果你覺得有必要,這可能表明你在元構建中做了太多的工作。
(5)Function calls函數調用
簡單的函數調用看起來像大多數其他語言:
這些函數是內置的,用戶無法定義新的函數。一些函數采用以下代碼塊括起來:{ }。
大多數函數定義了目標target。用戶可以使用下面討論的template模板機制定義這樣的新功能。
準確地說,上面說的代碼塊{}作為函數參數來執行函數的。大多數塊樣式的函數執行代碼塊,并將生成的作用域做為供讀取的變量字典。
(6)Scoping and execution作用域與執行
文件和函數調用后面跟的{}塊引入新的作用域。作用域是嵌套的。讀取變量時,將按相反的順序搜索包含作用域,直到找到匹配的名稱。變量寫入始終轉到最內層的作用域。
除了最里面的作用域之外,無法修改任何封閉作用域。這意味著,例如,當您定義target目標時,您在塊內執行的任何操作都不會“泄漏”到文件的其余部分。
if/else/foreach語句,即使它們使用{}塊,也不會引入新的作用域,因此更改將保留在語句之外。
3、Naming things文件和目錄名稱
文件名和目錄名是字符串,被解釋為相對于當前構建文件的目錄。有三種可能的形式:
- 相對名稱:
- 源樹絕對名稱:
- 系統絕對名稱(罕見,通常用于包含目錄):
4、Build configuration構建配置
(1)Targets目標
一個目標target是構建圖中的一個節點。它通常表示將生成的某種可執行文件或庫文件。目標依賴于其他目標。內置目標類型如下所示??梢允褂妹頶n help <targettype>以獲取更多幫助??梢允褂媚0鍎摻ㄗ远x目標類型,來擴充內置的目標類型。
- action:運行腳本以生成文件。
- action_foreach:為每個源文件運行一次腳本。
- bundle_data:聲明數據以進入 Mac/iOS 捆綁包。
- create_bundle:創建蘋果/iOS 捆綁包。
- executable:生成可執行文件。
- group:引用一個或多個其他目標的虛擬依賴關系節點。
- shared_library:共享庫.dll或 .so。
- loadable_module:僅在運行時可加載.dll或 .so。
- source_set:輕量級虛擬靜態庫(通常比真正的靜態庫更可取,因為它的構建速度更快)。
- static_library:.lib 或 .a 文件(通??梢允褂靡粋€source_set替代)。
(2)Configs配置
Configs配置是命名對象,用于指定flags、include目錄和defines。它們可以應用于目標target并推送到依賴目標。
要定義配置,示例如下:
要將配置應用于目標,可以這樣做:
構建配置文件通常會為target目標指定包含默認配置的列表。目標可以根據需要向此列表中添加或刪除。因此,在實踐中,您通常會使用configs += ":myconfig"附加到默認值列表中。有關如何聲明和應用配置的詳細信息,請參閱gn help config。
(3)Public configs公共配置
一個target目標可以將配置項應用于依賴于它的其他target目標上。最常見的示例是第三方目標,它需要一些定義define或包含頭文件的include目錄,才能正確編譯。您希望這些配置項既應用于第三方庫本身的編譯,也應用于使用該庫的所有目標。
為此,您需要使用要應用的配置項編寫一個配置config:
然后,此配置將作為“公共”配置添加到目標中。它將既適用于目標,也適用于直接依賴于它的目標。注:使用的配置項是public_configs。
反過來,依賴目標可以通過將目標添加為“公共”依賴項,將其向上推進到依賴項樹的另一個級別。注:使用的配置項是public_deps。
(4)Templates模板
模板是 GN 重用代碼的主要方式。通常,模板會擴展一個或多個其他target目標類型。
通常,模板定義將放在一個.gni文件中,用戶將導入該文件以查看模板定義:
聲明模板會在此Scope作用域內的變量周圍創建一個閉包。調用模板時,魔術變量invoker用于讀取Scope作用域外的變量。模板通常會將其感興趣的值復制到自己的Scope作用域內:
執行模板時的當前工作目錄將是調用的構建文件的目錄,而不是模板源文件的目錄。因此,從模板調用程序傳入的文件將是正確的(這通常占模板中的大多數文件處理)。但是,如果模板本身具有文件(也許它會生成運行腳本的操作),則需要使用絕對路徑(“//foo/...”)來引用這些文件,以說明當前目錄在調用期間是不可預測的。有關詳細信息和更完整的示例,請參閱gn help template幫助。
5、Other features其他特性
(1)Imports導入
您可以使用該import函數將.gni文件導入到當前作用域中。這不是C意義上的包含。導入的文件將獨立執行,生成的作用域將復制到當前文件中(C當包含指令出現時,在當前上下文中執行包含的文件)。這允許緩存導入的結果,并且還阻止了一些更“創造性”的包含使用,如多次包含的文件。
通常,.gni文件將定義構建參數和模板。有關詳細信息,請參閱gn help import。
您的.gni文件可以通過在名稱中使用前置下劃線(如_this)來表明該臨時變量不會到導入文件使用。
(2)Path processing路徑處理
通常,您需要相對于其他目錄創建文件名或文件名列表。這在運行腳本時尤其常見,腳本是使用構建輸出目錄作為當前目錄執行的,而構建文件通常引用相對于其包含目錄的文件。
您可以使用rebase_path轉換目錄。有關更多幫助和示例,請參閱gn help rebase_path。將相對于當前目錄的文件名轉換為相對于根構建目錄的典型用法是:new_paths = rebase_path("myfile.c", root_build_dir)。
(3)Patterns模式
模式用于為自定義target目標類型的一組給定輸入生成輸出文件名,并自動從列表值中刪除文件(請參見gn help filter_include和gn help filter_exclude)。
它們就像簡單的正則表達式。有關詳細信息,請參閱gn help label_pattern。
(4)Executing scripts執行腳本
有兩種方法可以執行腳本。GN 中的所有外部腳本都是Python編寫的。第一種方法是作為構建步驟。這樣的腳本將接受一些輸入并生成一些作為構建部分的輸出。調用腳本的目標使用“action”目標類型進行聲明(請參見gn help action)。
執行腳本的第二種方法是在構建文件執行期間同步執行。在某些情況下,需要確定要編譯的文件集,或者獲取構建文件可能依賴的某些系統配置。構建文件可以讀取腳本的stdout標準輸出,并據此以不同的方式對其進行操作。
同步腳本執行由函數exec_script完成(有關詳細信息和示例,請參閱gn help exec_script)。由于同步執行腳本需要暫停當前構建文件的執行,直到Python進程完成執行,因此外部腳本的速度很慢,應將其最小化。
為防止濫用,允許調用exec_script的文件可以在頂級.gn文件中列入白名單。更多信息參考gn help dotfile。
您可以同步讀取和寫入文件,這是不鼓勵的,但在同步運行腳本時偶爾是必需的。典型的用例是傳遞一個長度超過當前平臺命令行限制的文件名列表。請參閱gn help read_file和gn help write_file了解如何讀取和寫入文件。如果可能的話,應避免使用這些功能。
超過命令行長度限制的操作可以使用響應文件來繞過此限制,而無需同步寫入文件。查閱gn help response_file_contents了解更多內容。
6、小結
本篇,我們學習了GN語言語法與腳本操作,支持的變量類型,命名、構建配置等等。