GN语言和操作

GN语法

设计理念

  • 编写构建文件不应该是一个创造性的努力。理想情况下,两个人应该产生相同的构建文件来实现相同的需求。除非绝对需要,否则不应有任何灵活性。做越多的事情越可能产生致命的错误。

  • 定义应该比代码更像代码。我不想编写或调试Prolog。但是我们团队的每个人都可以编写和调试C ++和Python。

  • 构建语言应该被视为构建应该如何工作。表达任意事物不一定容易甚至不可能。我们应该改变源代码和工具,使构建变得更简单,而不是把所有事情都变得更复杂以符合外部要求(在合理的范围内)。

  • 在有意义的时候就像Blaze一样(见下面的“与Blaze的区别和相似之处”)

  • gn是动态类型语言

gn 类型

1. 布尔(true,false)

2. 64位有符号整数

3. 字符串

  • 字符串用双引号括起来,并使用反斜杠作为转义字符。唯一支持的转义序列是:

    • \" (用于直接应用)

    • \$(字面上的美元符号)

    • \\(用于文字反斜杠)

  • 任何其他反斜杠的使用都被视为文字反斜杠。所以,例如,\b在模式中使用不需要转义,大多数Windows路径"C:\foo\bar.h"也不需要。

  • 使用$支持简单的变量替换,其中美元符号后的单词被替换为变量的值。如果没有非变量名字符来终止变量名称,可以选择{}包围名称。更复杂的表达式不被支持,仅支持变量名称替换。

a = "mypath"
b = "$a/foo.cc"  # b -> "mypath/foo.cc"
c = "foo${a}bar.cc"  # c -> "foomypathbar.cc"

4. 列表(任何其他类型)

  • 列表支持追加(将列表追加到另一个列表,是追加第二个列表中的项目,而不是将列表追加为嵌套成员。):

a = [ "first" ]
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" ]
  • 列表支持从零开始的下标以提取值:

a = [ "first", "second", "third" ]
b = a[1]  # -> "second"
  • []运算符是只读的,不能用来改变列表。这个主要的用例是当一个外部脚本返回几个已知的值,并且你想提取它们。在某些情况下,如果您要添加到列表中,则很容易覆盖列表。为了帮助理解这种情况,将非空列表分配给包含现有非空列表的变量是错误的。如果您想避开此限制,请首先将目标变量分配给空列表。

a = [“one”]
a = [“two”]#错误:用非空列表覆盖非空列表。
a = []#OK
a = [“two”]#OK

5. 条件语句

  • 条件判断看起像C语法:

  if(is_linux ||(is_win && target_cpu ==“x86”)){
    sources -= [ "something.cc" ]
  } else if(...){
    ...
  } else {
    ...
  }

6. 循环

  • 你可以使用foreach迭代一个列表。这是不鼓励的。构建应该做的大部分事情通常都可以在不做这件事情的情况下表达出来,如果你觉得有必要的话,这可能表明你在元构建中做了太多工作。

foreach(i,mylist){
  print(i)  # Note: i is a copy of each element, not a reference to it.
}

7. 函数调用

  • 简单的函数调用看起来像大多数其他语言,这些功能是内置的,用户不能定义新的功能。

print("hello, world")
assert(is_win, "This should only be executed on Windows")
  • 一些函数在它们下面接受一个由{ }组成的代码块:

static_library(“mylibrary”){
  sources = [“a.cc”]
}

8. 作用域和执行

  • 文件和函数调用后面跟着{ }块引入新的作用域。作用域是嵌套的。当您读取一个变量时,将会以相反的顺序搜索包含的作用域,直到找到匹配的名称。变量写入总是进入最内层的作用域。

  • 除了最内层的作用域以外,没有办法修改任何封闭作用域。这意味着当你定义一个目标时,例如,你在块内部做的任何事情都不会泄露到文件的其余部分。

  • if/ else/ foreach语句,即使他们使用{ },不会引入新的范围,所以更改将持续在语句之外。

gn 命名事物

  • 文件和目录名称:文件和目录名称是字符串,并被解释为相对于当前构建文件的目录。有三种可能的形式:

1. 相对名称:

"foo.cc"
"src/foo.cc"
"../src/foo.cc"

2. 源代码树绝对名称:

“//net/foo.cc”
“//base/test/foo.cc”

3. 系统绝对名称(罕见,通常用于包含目录):

"/usr/local/include/"
"/C:/Program Files/Windows Kits/Include"

gn 构建配置

1. 目标

  • 目标是构建图中的一个节点。它通常代表将要生成的某种类型的可执行文件或库文件。目标取决于其他目标。内置的目标类型(请参阅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)
component源集或共享库,取决于构建类型
test测试可执行文件 在移动设备上,这将为测试创建适当的本机应用程序类型
app可执行文件或Mac / iOS应用程序
android_apk制作一个APK。有很多其他的Android模版,看//build/config/android/rules.gni

2.  配置

  • 配置文件是命名对象,用于指定标志集,包含目录和定义。他们可以被应用到一个目标,并推到相关的目标。

  • 要定义一个配置:

config("myconfig") {
 includes = [ "src/include" ]
 defines = [ "ENABLE_DOOM_MELON" ]
}
  • 要将配置应用于目标:

executable("doom_melon") {
  configs = [ ":myconfig" ]
}
  • 构建配置文件通常指定设置默认配置列表的目标默认值。目标可以根据需要添加或删除。所以在实践中你通常会使用configs += ":myconfig"追加到默认列表。

  • 请参阅gn help config有关如何声明和应用配置的更多信息。

3. 公共配置

  • 目标可以将设置应用于依赖它的其他目标。最常见的例子是一个第三方目标,它需要一些定义或包含目录头才能正确编译。您希望这些设置既适用于第三方库本身的编译,也适用于使用该库的所有目标。

  • 要做到这一点,你写一个你想要应用的设置的配置:

config("my_external_library_config") {
  includes = "."
  defines = [ "DISABLE_JANK" ]
}
  • 然后这个配置作为“公共”配置被添加到目标。它既适用于目标,也适用于直接依赖目标的目标。

shared_library("my_external_library") {
  ...
  # 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" ]
}
  • 通过把它设置成all_dependent_config一个目标可以转发一个配置给所有的依赖者,直到达到一个链接边界为止。这是强烈不鼓励的,因为它将比必要的构建配置超出更多的标志和定义。使用public_deps来控制哪些标志适用于哪里来代替它。

  • 在Chrome中,更喜欢build/buildflag_header.gni用于定义的构建标题头文件系统,以防止大多数编译器定义的错误。

4. 模板

  • 模板是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.
  }
}
  • 通常,您的模板定义将放入.gni文件中,用户将导入该文件以查看模板定义:

import("//tools/idl_compiler.gni")

idl("my_interfaces") {
  sources = [ "a.idl", "b.idl" ]
}
  • 当时声明一个模板会在范围内的变量周围创建一个闭包。当模板被调用时,魔术变量invoker被用来从调用范围中读取变量。模板通常会将感兴趣的值复制到自己的范围中:

template("idl") {
  source_set(target_name){
    sources = invoker.sources
  }
}
  • 模板执行时的当前目录将是调用的构建文件的目录,而不是模板源文件。这是因为从模板调用者传入的文件是正确的(这通常是模板中大多数文件处理的原因)。但是,如果模板本身有文件(可能会生成一个运行脚本的动作),则需要使用绝对路径(“//foo/…”)来引用这些文件,以说明当前目录在调用时将不可预知。查看gn help template更多信息和更完整的例子。

gn 其他特性

1. imports

  • 您可以使用import函数将.gni文件导入到当前作用域。这不是 C++意义上的包含。导入的文件是独立执行的,生成的作用域被复制到当前文件中(C ++在include指令出现的当前上下文中执行包含的文件)。这样可以缓存导入的结果,还可以防止包含多个包含文件在内的一些更“创造性”的用途。

  • 通常情况下,一个.gni会定义构建参数和模板。了解gn help import更多信息。

  • 您的.gni文件可以定义不导出到文件临时变量,通过使用名称中的前面的下划线来包含它,就像_this。

2. 路径处理

  • 通常情况下,您需要创建一个文件名或相对于不同目录的文件名列表。运行脚本时,这种情况尤为常见,这些脚本是以构建输出目录作为当前目录执行的,而构建文件通常是指与其包含的目录相关的文件。

  • 您可以使用rebase_path转换目录。查看gn help rebase_path更多的帮助和例子。将相对于当前目录的文件名转换为相对于根目录的典型用法是:new_paths = rebase_path("myfile.c", root_build_dir)

3. 模式

  • 模式用于为自定义目标类型的给定输入集生成输出文件名,并自动从sources变量中移除文件(请参阅参考资料gn help set_sources_assignment_filter)。

  • 他们就像简单的正则表达式。了解gn help label_pattern更多信息。

4. 执行脚本

  • 有两种方法来执行脚本。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。

gn与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)。

关注微信公众号『Rice嵌入式开发技术分享』,后台回复“微信”添加作者微信,备注”入群“,便可邀请进入技术交流群。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Rice嵌入式开发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值