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