google gn构建系统的介绍
GN語言和操作
- GN語言和操作
- 內(nèi)容
- 介紹
- 使用內(nèi)置的幫助
- 設(shè)計(jì)理念
- 語言
- 字符串
- 清單
- 條件語句
- 循環(huán)
- 函數(shù)調(diào)用
- 作用域和執(zhí)行Scoping and execution
- 命名事物
- 文件和目錄名稱
- 構(gòu)建配置
- 目標(biāo)
- CONFIGS
- 公共配置
- 模板
- 其他特性
- Imports
- 路徑處理
- 模式
- 執(zhí)行腳本
- 與Blaze的區(qū)別和相似之處
介紹
本頁面描述了許多語言的細(xì)節(jié)和行為。
使用內(nèi)置的幫助!
GN有一個(gè)廣泛的內(nèi)置幫助系統(tǒng),為每個(gè)功能和內(nèi)置變量提供參考。這個(gè)頁面更高級(jí)。
gn help
你也可以看到2016年3月份的GNE幻燈片。演講者筆記包含完整的內(nèi)容。
設(shè)計(jì)理念
-
編寫構(gòu)建文件不應(yīng)該是一個(gè)創(chuàng)造性的努力。理想情況下,兩個(gè)人應(yīng)該產(chǎn)生相同的構(gòu)建文件來實(shí)現(xiàn)相同的需求。除非絕對(duì)需要,否則不應(yīng)有任何靈活性。做越多的事情越可能產(chǎn)生致命的錯(cuò)誤。
-
定義應(yīng)該比代碼更像代碼。我不想編寫或調(diào)試Prolog。但是我們團(tuán)隊(duì)的每個(gè)人都可以編寫和調(diào)試C ++和Python。
-
構(gòu)建語言應(yīng)該被視為構(gòu)建應(yīng)該如何工作。表達(dá)任意事物不一定容易甚至不可能。我們應(yīng)該改變?cè)创a和工具,使構(gòu)建變得更簡(jiǎn)單,而不是把所有事情都變得更復(fù)雜以符合外部要求(在合理的范圍內(nèi))。
-
在有意義的時(shí)候就像Blaze一樣(見下面的“與Blaze的區(qū)別和相似之處”)。
語言
GN使用非常簡(jiǎn)單的動(dòng)態(tài)類型語言。類型是:
- 布爾(
true,false)。 - 64位有符號(hào)整數(shù)。
- 字符串。
- 列表(任何其他類型)。
- 范圍(Scopes)(有點(diǎn)像字典,僅是內(nèi)置的東西(built-in stuff))。
有一些內(nèi)置變量的值取決于當(dāng)前的環(huán)境。了解gn help更多信息。
語言中故意有許多遺漏。例如沒有用戶定義的函數(shù)調(diào)用,(模板是最接近的)。按照上述設(shè)計(jì)理念,如果你需要這樣的東西,你可能做錯(cuò)了。
變量sources有一個(gè)特殊的規(guī)則:賦值給它時(shí),將應(yīng)用一個(gè)排除模式列表。這被設(shè)計(jì)成自動(dòng)過濾掉某些類型的文件。見gn help set_sources_assignment_filter和gn help label_pattern了解更多。
語言書呆子的完整語法可以在gn help grammar獲取到。
字符串
字符串用雙引號(hào)括起來,并使用反斜杠作為轉(zhuǎn)義字符。唯一支持的轉(zhuǎn)義序列是:
\"(用于直接引用)\$(字面上的美元符號(hào))\\(用于文字反斜杠)
任何其他反斜杠的使用都被視為文字反斜杠。所以,例如,\b在模式中使用不需要轉(zhuǎn)義,大多數(shù)Windows路徑"C:\foo\bar.h"也不需要。
使用$支持簡(jiǎn)單的變量替換,其中美元符號(hào)后的單詞被替換為變量的值。如果沒有非變量名字符來終止變量名稱,可以選擇{}包圍名稱。更復(fù)雜的表達(dá)式不被支持,僅支持變量名稱替換。
a = "mypath"
b = "$a/foo.cc" # b -> "mypath/foo.cc"
c = "foo${a}bar.cc" # c -> "foomypathbar.cc"
b = "$a/foo.cc" # b -> "mypath/foo.cc"
c = "foo${a}bar.cc" # c -> "foomypathbar.cc"
您可以使用 “$0xFF” 語法對(duì)8位字符進(jìn)行編碼,因此帶有換行符(十六進(jìn)制0A)的字符串會(huì)如下所示,"look$0x0Alike$0x0Athis"。
清單
沒有辦法得到一個(gè)列表的長(zhǎng)度。如果你發(fā)現(xiàn)自己想要做這種事情,那么你就是想在構(gòu)建中做太多的工作。
列表支持追加:
a = [ "first" ]
a += [ "second" ] # [ "first", "second" ]
a += [ "third", "fourth" ] # [ "first", "second", "third", "fourth" ]
b = a + [ "fifth" ] # [ "first", "second", "third", "fourth", "fifth" ]
a += [ "second" ] # [ "first", "second" ]
a += [ "third", "fourth" ] # [ "first", "second", "third", "fourth" ]
b = a + [ "fifth" ] # [ "first", "second", "third", "fourth", "fifth" ]
將列表追加到另一個(gè)列表,是追加第二個(gè)列表中的項(xiàng)目,而不是將列表追加為嵌套成員。
您可以從列表中刪除項(xiàng)目:
a = [ "first", "second", "third", "first" ]
b = a - [ "first" ] # [ "second", "third" ]
a -= [ "second" ] # [ "first", "third", "fourth" ]
b = a - [ "first" ] # [ "second", "third" ]
a -= [ "second" ] # [ "first", "third", "fourth" ]
列表中的 - 運(yùn)算符搜索匹配項(xiàng)并刪除所有匹配的項(xiàng)目。從另一個(gè)列表中減去一個(gè)列表將刪除第二個(gè)列表中的每個(gè)項(xiàng)目。
如果找不到匹配的項(xiàng)目,將會(huì)拋出錯(cuò)誤,因此您需要事先知道該項(xiàng)目在移除之前確實(shí)已經(jīng)存在。鑒于沒有辦法測(cè)試包含,主要的用例是建立一個(gè)文件或標(biāo)志的主列表,并基于各種條件刪除那些不適用于當(dāng)前版本的構(gòu)建。
從風(fēng)格上來說,最好只添加到列表,并讓每個(gè)源文件或依賴項(xiàng)只出現(xiàn)一次。這與Chrome團(tuán)隊(duì)用于GYP的建議相反(GYP傾向于列出所有文件,然后刪除條件中不需要的文件)。
列表支持從零開始的下標(biāo)以提取值:
a = [ "first", "second", "third" ]
b = a[1] # -> "second"
b = a[1] # -> "second"
[]運(yùn)算符是只讀的,不能用來改變列表。這個(gè)主要的用例是當(dāng)一個(gè)外部腳本返回幾個(gè)已知的值,并且你想提取它們。
在某些情況下,如果您要添加到列表中,則很容易覆蓋列表。為了幫助理解這種情況,將非空列表分配給包含現(xiàn)有非空列表的變量是錯(cuò)誤的。如果您想避開此限制,請(qǐng)首先將目標(biāo)變量分配給空列表。
a = [“one”]
a = [“two”]#錯(cuò)誤:用非空列表覆蓋非空列表。
a = []#OK
a = [“two”]#OK
a = [“two”]#錯(cuò)誤:用非空列表覆蓋非空列表。
a = []#OK
a = [“two”]#OK
請(qǐng)注意,構(gòu)建腳本的執(zhí)行沒有內(nèi)在知識(shí)的底層數(shù)據(jù)的意義。例如,這意味著它不知道sources是一個(gè)文件名列表。所以,如果你刪除一個(gè)項(xiàng)目,它必須匹配文字字符串,而不是指定一個(gè)不同的名稱,那將解析為相同的文件名稱。
條件語句
條件看起來像C:
if(is_linux ||(is_win && target_cpu ==“x86”)){sources -= [ "something.cc" ]} else if(...){...} else {...}
sources -= [ "something.cc" ]} else if(...){...} else {...}
如果只能在某些情況下聲明目標(biāo),則可以在大多數(shù)地方使用它們,甚至在整個(gè)目標(biāo)周圍使用它們。
循環(huán)
你可以使用foreach迭代一個(gè)列表。這是不鼓勵(lì)的。構(gòu)建應(yīng)該做的大部分事情通常都可以在不做這件事情的情況下表達(dá)出來,如果你覺得有必要的話,這可能表明你在元構(gòu)建中做了太多工作。
foreach(i,mylist){print(i) # Note: i is a copy of each element, not a reference to it.
}
print(i) # Note: i is a copy of each element, not a reference to it.
}
函數(shù)調(diào)用
簡(jiǎn)單的函數(shù)調(diào)用看起來像大多數(shù)其他語言
print("hello, world")
assert(is_win, "This should only be executed on Windows")
assert(is_win, "This should only be executed on Windows")
這些功能是內(nèi)置的,用戶不能定義新的功能。
一些函數(shù)在它們下面接受一個(gè)由{ }組成的代碼塊:
static_library(“mylibrary”){sources = [“a.cc”]
}
sources = [“a.cc”]
}
其中大多數(shù)用來定義目標(biāo)。用戶可以使用下面討論的模板機(jī)制來定義新的函數(shù)。
確切地說,這個(gè)表達(dá)式意味著該塊成為函數(shù)執(zhí)行的參數(shù)。大多數(shù)塊式函數(shù)都會(huì)執(zhí)行塊,并將結(jié)果范圍視為要讀取的變量字典。
作用域和執(zhí)行(Scoping and execution)
文件和函數(shù)調(diào)用后面跟著{ }塊引入新的作用域。作用域是嵌套的。當(dāng)您讀取一個(gè)變量時(shí),將會(huì)以相反的順序搜索包含的作用域,直到找到匹配的名稱。變量寫入總是進(jìn)入最內(nèi)層的作用域。
除了最內(nèi)層的作用域以外,沒有辦法修改任何封閉作用域。這意味著當(dāng)你定義一個(gè)目標(biāo)時(shí),例如,你在塊內(nèi)部做的任何事情都不會(huì)泄露到文件的其余部分。
if/ else/ foreach語句,即使他們使用{ },不會(huì)引入新的范圍,所以更改將持續(xù)在語句之外。
命名事物
文件和目錄名稱
文件和目錄名稱是字符串,并被解釋為相對(duì)于當(dāng)前構(gòu)建文件的目錄。有三種可能的形式:
相對(duì)名稱:
"foo.cc"
"src/foo.cc"
"../src/foo.cc"
"src/foo.cc"
"../src/foo.cc"
源代碼樹絕對(duì)名稱:
“//net/foo.cc”
“//base/test/foo.cc”
“//base/test/foo.cc”
系統(tǒng)絕對(duì)名稱(罕見,通常用于包含目錄):
"/usr/local/include/"
"/C:/Program Files/Windows Kits/Include"
"/C:/Program Files/Windows Kits/Include"
構(gòu)建配置
目標(biāo)
目標(biāo)是構(gòu)建圖中的一個(gè)節(jié)點(diǎn)。它通常代表將要生成的某種類型的可執(zhí)行文件或庫文件。目標(biāo)取決于其他目標(biāo)。內(nèi)置的目標(biāo)類型(請(qǐng)參閱gn help <targettype>以獲取更多幫助)是:
action:運(yùn)行一個(gè)腳本來生成一個(gè)文件。action_foreach:為每個(gè)源文件運(yùn)行一次腳本。bundle_data:聲明數(shù)據(jù)加入到Mac / iOS包。create_bundle:創(chuàng)建一個(gè)Mac / iOS包。executable:生成一個(gè)可執(zhí)行文件。group:引用一個(gè)或多個(gè)其他目標(biāo)的虛擬依賴關(guān)系節(jié)點(diǎn)。shared_library:.dll或.so。loadable_module:.dll或.so只能在運(yùn)行時(shí)加載。source_set:一個(gè)輕量級(jí)的虛擬靜態(tài)庫(通常比真正的靜態(tài)庫更可取,因?yàn)樗臉?gòu)建速度會(huì)更快)。static_library:.lib或.a文件(通常你會(huì)想要一個(gè)source_set)。
您可以使用模板來擴(kuò)展它制作自定義目標(biāo)類型(請(qǐng)參見下文)。在Chrome中,一些更常用的模板是:
component:源集或共享庫,取決于構(gòu)建類型。test:測(cè)試可執(zhí)行文件 在移動(dòng)設(shè)備上,這將為測(cè)試創(chuàng)建適當(dāng)?shù)谋緳C(jī)應(yīng)用程序類型。app:可執(zhí)行文件或Mac / iOS應(yīng)用程序。android_apk:制作一個(gè)APK。有很多其他的Android模版,看//build/config/android/rules.gni。
CONFIGS
配置文件是命名對(duì)象,用于指定標(biāo)志集,包含目錄和定義。他們可以被應(yīng)用到一個(gè)目標(biāo),并推到相關(guān)的目標(biāo)。
要定義一個(gè)配置:
config("myconfig") {includes = [ "src/include" ]defines = [ "ENABLE_DOOM_MELON" ]
}
includes = [ "src/include" ]defines = [ "ENABLE_DOOM_MELON" ]
}
要將配置應(yīng)用于目標(biāo):
executable("doom_melon") {configs = [ ":myconfig" ]
}
configs = [ ":myconfig" ]
}
構(gòu)建配置文件通常指定設(shè)置默認(rèn)配置列表的目標(biāo)默認(rèn)值。目標(biāo)可以根據(jù)需要添加或刪除。所以在實(shí)踐中你通常會(huì)使用configs += ":myconfig"追加到默認(rèn)列表。
請(qǐng)參閱gn help config有關(guān)如何聲明和應(yīng)用配置的更多信息。
公共配置
目標(biāo)可以將設(shè)置應(yīng)用于依賴它的其他目標(biāo)。最常見的例子是一個(gè)第三方目標(biāo),它需要一些定義或包含目錄頭才能正確編譯。您希望這些設(shè)置既適用于第三方庫本身的編譯,也適用于使用該庫的所有目標(biāo)。
要做到這一點(diǎn),你寫一個(gè)你想要應(yīng)用的設(shè)置的配置:
config("my_external_library_config") {includes = "."defines = [ "DISABLE_JANK" ]
}
includes = "."defines = [ "DISABLE_JANK" ]
}
然后這個(gè)配置作為“公共”配置被添加到目標(biāo)。它既適用于目標(biāo),也適用于直接依賴目標(biāo)的目標(biāo)。
shared_library("my_external_library") {...# Targets that depend on this get this config applied.public_configs = [ ":my_external_library_config" ]
}
...# Targets that depend on this get this config applied.public_configs = [ ":my_external_library_config" ]
}
依賴目標(biāo)又可以通過將目標(biāo)作為“公共”依賴項(xiàng)添加到另一個(gè)級(jí)別,從而將依賴關(guān)系樹轉(zhuǎn)發(fā)到另一個(gè)級(jí)別。
static_library("intermediate_library") {...# Targets that depend on this one also get the configs from "my external library".public_deps = [ ":my_external_library" ]
}
...# Targets that depend on this one also get the configs from "my external library".public_deps = [ ":my_external_library" ]
}
通過把它設(shè)置成all_dependent_config一個(gè)目標(biāo)可以轉(zhuǎn)發(fā)一個(gè)配置給所有的依賴者,直到達(dá)到一個(gè)鏈接邊界為止。這是強(qiáng)烈不鼓勵(lì)的,因?yàn)樗鼘⒈缺匾臉?gòu)建配置超出更多的標(biāo)志和定義。使用public_deps來控制哪些標(biāo)志適用于哪里來代替它。
在Chrome中,更喜歡build/buildflag_header.gni用于定義的構(gòu)建標(biāo)題頭文件系統(tǒng),以防止大多數(shù)編譯器定義的錯(cuò)誤。
模板
模板是GN重用代碼的主要方式。通常情況下,模板會(huì)擴(kuò)展到一個(gè)或多個(gè)其他目標(biāo)類型。
# Declares a script that compiles IDL files to source, and then compiles those
#source files.
template("idl") {#Always base helper targets on target_name so they're unique。Target name#will be the string passed as the name when the template is invoked.idl_target_name =“$ {target_name} _generate”action_foreach(idl_target_name){...}#Your template should always define a target with the name target_name.#When other targets depend on your template invocation, this will be the#destination of that dependency.source_set(target_name){...deps = [ ":$idl_target_name" ] # Require the sources to be compiled.}
}
#source files.
template("idl") {#Always base helper targets on target_name so they're unique。Target name#will be the string passed as the name when the template is invoked.idl_target_name =“$ {target_name} _generate”action_foreach(idl_target_name){...}#Your template should always define a target with the name target_name.#When other targets depend on your template invocation, this will be the#destination of that dependency.source_set(target_name){...deps = [ ":$idl_target_name" ] # Require the sources to be compiled.}
}
通常,您的模板定義將放入.gni文件中,用戶將導(dǎo)入該文件以查看模板定義:
import("//tools/idl_compiler.gni")idl("my_interfaces") {sources = [ "a.idl", "b.idl" ]
}
idl("my_interfaces") {sources = [ "a.idl", "b.idl" ]
}
當(dāng)時(shí)聲明一個(gè)模板會(huì)在范圍內(nèi)的變量周圍創(chuàng)建一個(gè)閉包。當(dāng)模板被調(diào)用時(shí),魔術(shù)變量invoker被用來從調(diào)用范圍中讀取變量。模板通常會(huì)將感興趣的值復(fù)制到自己的范圍中:
template("idl") {source_set(target_name){sources = invoker.sources}
}
source_set(target_name){sources = invoker.sources}
}
模板執(zhí)行時(shí)的當(dāng)前目錄將是調(diào)用的構(gòu)建文件的目錄,而不是模板源文件。這是因?yàn)閺哪0逭{(diào)用者傳入的文件是正確的(這通常是模板中大多數(shù)文件處理的原因)。但是,如果模板本身有文件(可能會(huì)生成一個(gè)運(yùn)行腳本的動(dòng)作),則需要使用絕對(duì)路徑(“//foo/…”)來引用這些文件,以說明當(dāng)前目錄在調(diào)用時(shí)將不可預(yù)知。查看gn help template更多信息和更完整的例子。
其他特性
Imports
您可以使用import函數(shù)將.gni文件導(dǎo)入到當(dāng)前作用域。這不是 C++意義上的包含。導(dǎo)入的文件是獨(dú)立執(zhí)行的,生成的作用域被復(fù)制到當(dāng)前文件中(C ++在include指令出現(xiàn)的當(dāng)前上下文中執(zhí)行包含的文件)。這樣可以緩存導(dǎo)入的結(jié)果,還可以防止包含多個(gè)包含文件在內(nèi)的一些更“創(chuàng)造性”的用途。
通常情況下,一個(gè).gni會(huì)定義構(gòu)建參數(shù)和模板。了解gn help import更多信息。
您的.gni文件可以定義不導(dǎo)出到文件臨時(shí)變量,通過使用名稱中的前面的下劃線來包含它,就像_this。
路徑處理
通常情況下,您需要?jiǎng)?chuàng)建一個(gè)文件名或相對(duì)于不同目錄的文件名列表。運(yùn)行腳本時(shí),這種情況尤為常見,這些腳本是以構(gòu)建輸出目錄作為當(dāng)前目錄執(zhí)行的,而構(gòu)建文件通常是指與其包含的目錄相關(guān)的文件。
您可以使用rebase_path轉(zhuǎn)換目錄。查看gn help rebase_path更多的幫助和例子。將相對(duì)于當(dāng)前目錄的文件名轉(zhuǎn)換為相對(duì)于根目錄的典型用法是:new_paths = rebase_path("myfile.c", root_build_dir)
模式
模式用于為自定義目標(biāo)類型的給定輸入集生成輸出文件名,并自動(dòng)從sources變量中移除文件(請(qǐng)參閱參考資料gn help set_sources_assignment_filter)。
他們就像簡(jiǎn)單的正則表達(dá)式。了解gn help label_pattern更多信息。
執(zhí)行腳本
有兩種方法來執(zhí)行腳本。GN中的所有外部腳本都是Python。第一種方法是作為構(gòu)建步驟。這樣的腳本將需要一些輸入,并生成一些輸出作為構(gòu)建的一部分。調(diào)用腳本的目標(biāo)是使用“action”目標(biāo)類型聲明的(請(qǐng)參閱參考資料gn help action)。
執(zhí)行腳本的第二種方法是在構(gòu)建文件執(zhí)行期間同步。這在某些情況下是必要的,以確定要編譯的文件集合,或獲取構(gòu)建文件可能依賴的某些系統(tǒng)配置。構(gòu)建文件可以讀取腳本的標(biāo)準(zhǔn)輸出(stdout)并以不同的方式對(duì)其執(zhí)行操作。
同步腳本的執(zhí)行由exec_script函數(shù)完成(詳見gn help exec_script參考資料)。因?yàn)橥綀?zhí)行一個(gè)腳本需要暫停當(dāng)前的構(gòu)建文件執(zhí)行,直到Python進(jìn)程完成執(zhí)行,依靠外部腳本是慢的,應(yīng)該盡量減少。
為了防止濫用,允許調(diào)用的文件exec_script可以在頂層.gn文件中列入白名單。Chrome做到這一點(diǎn)需要額外的代碼審查這樣的補(bǔ)充。看gn help dotfile。
您可以同步讀取和寫入在同步運(yùn)行腳本時(shí)不鼓勵(lì)但偶爾需要的文件。典型的用例是傳遞一個(gè)比當(dāng)前平臺(tái)的命令行限制長(zhǎng)的文件名列表。請(qǐng)參閱gn help read_file以及gn help write_file如何讀取和寫入文件。如果可能,應(yīng)該避免這些功能。
超過命令行長(zhǎng)度限制的操作可以使用響應(yīng)文件繞過此限制,而不同步寫入文件。看gn help response_file_contents。
與Blaze的區(qū)別和相似之處
Blaze是Google的內(nèi)部構(gòu)建系統(tǒng),現(xiàn)在已經(jīng)作為Bazel公開發(fā)布。它啟發(fā)了一些其他系統(tǒng),如Pants和Buck。
在Google的同類環(huán)境中,對(duì)條件的需求非常低,并且可以通過少量的手段(abi_deps)來獲得。Chrome使用各地的條件,需要添加這些是文件看起來不同的主要原因。
GN還增加了“配置”的概念來管理一些棘手的依賴和配置問題,同樣不會(huì)出現(xiàn)在服務(wù)器上。Blaze有一個(gè)“配置”的概念,就像一個(gè)GN工具鏈,但內(nèi)置在工具本身。GN工具鏈的工作方式是試圖以一種簡(jiǎn)潔的方式將這個(gè)概念分離到構(gòu)建文件中的結(jié)果。
GN保留了一些GYP概念,比如“全部依賴”設(shè)置,這些設(shè)置在Blaze中有些不同。這部分是為了使現(xiàn)有的GYP代碼更容易轉(zhuǎn)換,GYP結(jié)構(gòu)通常會(huì)提供更細(xì)粒度的控制(根據(jù)具體情況而定,好或壞)。
GN也使用GYP名稱,比如“sources”而不是“srcs”,因?yàn)榭s寫似乎是不必要的,盡管它使用了Blaze的“deps”,因?yàn)椤癲ependencies”很難打字。Chromium還在一個(gè)目標(biāo)中編譯多種語言,因此指定目標(biāo)名稱前綴的語言類型被刪除(例如,從cc_library)。
總結(jié)
以上是生活随笔為你收集整理的google gn构建系统的介绍的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux内存管理和原理分析
- 下一篇: liunx查看python的site-p