本页面描述了许多语言的细节和行为。
GN有一个广泛的内置帮助系统,为每个功能和内置变量提供参考。这个页面更高级。
gn help
你也可以看到2016年3月份的GNE幻灯片。演讲者笔记包含完整的内容。
编写构建文件不应该是一个创造性的努力。理想情况下,两个人应该产生相同的构建文件来实现相同的需求。除非绝对需要,否则不应有任何灵活性。做越多的事情越可能产生致命的错误。
定义应该比代码更像代码。我不想编写或调试Prolog。但是我们团队的每个人都可以编写和调试C ++和Python。
构建语言应该被视为构建应该如何工作。表达任意事物不一定容易甚至不可能。我们应该改变源代码和工具,使构建变得更简单,而不是把所有事情都变得更复杂以符合外部要求(在合理的范围内)。
在有意义的时候就像Blaze一样(见下面的“与Blaze的区别和相似之处”)。
GN使用非常简单的动态类型语言。类型是:
true
,false
)。有一些内置变量的值取决于当前的环境。了解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”]
}
其中大多数用来定义目标。用户可以使用下面讨论的模板机制来定义新的函数。
确切地说,这个表达式意味着该块成为函数执行的参数。大多数块式函数都会执行块,并将结果范围视为要读取的变量字典。
文件和函数调用后面跟着{ }
块引入新的作用域。作用域是嵌套的。当您读取一个变量时,将会以相反的顺序搜索包含的作用域,直到找到匹配的名称。变量写入总是进入最内层的作用域。
除了最内层的作用域以外,没有办法修改任何封闭作用域。这意味着当你定义一个目标时,例如,你在块内部做的任何事情都不会泄露到文件的其余部分。
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
以获取更多帮助)是:
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
。配置文件是命名对象,用于指定标志集,包含目录和定义。他们可以被应用到一个目标,并推到相关的目标。
要定义一个配置:
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
更多信息和更完整的例子。
您可以使用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是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
)。