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