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