本文旨在介绍 GN 的术语和思维方式。此值应为 您有足够的背景知识,以便在 GN 中获取方位以及如何在 Fuchsia 中使用方位。 GN(和 Fuchsia 版本)比下文要讨论的更复杂,但是 一般开发者不需要更深入地了解大部分内容。
GN 文档页面QuickStart和语言部分详细介绍了
了解 GN 的相关背景,请参阅参考文档,其中提供了完整的语言文档。使用
gn help
命令,以交互方式为各个
主题。Ninja 也有自己的文档。
运行 jiri update
后的 Fuchsia 检出中,命令
fx gn
和 fx ninja
提供对预构建二进制文件的访问权限。
两阶段操作:gn
和 ninja
与 make
不同,gn
只是故事的一半。名称如下:GN stands
即可生成 Ninja。这些客户之间
工具将构建过程分成两个步骤:
gn gen
会采用所有配置选择并做出所有决策。 它实际上所做的就是在 build 目录中生成.ninja
文件。 此步骤只有在更改配置或 对 build 目录执行完全 nuke 操作。一般来说,您只需要 更新会在 GN 文件发生更改时,而在增量构建中则会发生 。ninja
会运行命令来编译和链接等。它会处理增量 构建和并行处理。这是每次更改时都要执行的步骤 源文件,例如运行make
。GN 会自动向 在出现相关情况时再次运行gn gen
,以重新生成 Ninja 文件。BUILD.gn
文件(或一些其他相关文件)已更改,因此对于大部分 之后,所有更改都会由ninja
完成。
与 GNU make
相比,Ninja 非常简单。它只是将
命令及其输入文件由机器写入,
人类。不过,它融入了一些有用的东西
要在 make
中完成以下操作:
- 当命令行发生变化时,重新构建每个文件。命令行只会 在 GN 再次运行时真的会发生变化。在那之后,Ninja 非常精明 增量构建 - 针对已更改的文件执行命令, 重新运行未更改的命令
- 处理编译器生成的依赖项文件。忍者知道 makefile
是编译器在
.d
文件中发出并在发生以下情况时直接使用它们的子集: 。 - 默认情况下使用
-j$(getconf _NPROCESSORS_ONLN)
运行。您可以将-j1
传递给 进行序列化或-j1024
,但在 它就会执行您通常需要的并行处理 - 防止并行作业出现交错的
stdout
/stderr
输出。忍者 会缓冲输出,这样错误消息就不会因从 多个进程 - 支持详细/详细命令输出。默认情况下,Ninja 会
为其运行的每个命令显示
Kbuild
样式的消息,以文字进度表的形式表示 样式。-v 开关类似于Kbuild
中的 V=1,用于显示每个实际命令。
GN 是作为 Chromium 项目的一部分开发的,旨在取代旧版 build 系统。Fuchsia 从他们那里继承了它,现在在整个树中使用它作为 主要构建系统
构建目录和 args.gn
Ninja 始终在 build 目录中运行。Ninja 运行的所有命令都通过
build 目录的根目录。常见情况是 ninja -C build-dir
。
GN 和 Ninja 都不在意您使用的构建目录。常见做法
使用源目录的子目录,而且由于文件路径是
通常以相对于 build 目录的位置为基础,指定
如果您将 build 代码放入编译器,编译器中就会包含大量 ../
目录;但应该可以正常使用长期以来
Chromium(早于 GN 本身),以在源代码中使用 out/_something_
目录,而 Fuchsia 继承了该默认值。但没什么在乎
您选择的目录名称,但 out
子目录位于顶层
适用于 Fuchsia 的 .gitignore
文件。
基本命令是 gn gen build-dir
。这会根据需要创建 build-dir/
。
并使用 Ninja 文件进行当前配置。如果
build-dir/args.gn
存在,则 gn gen
将读取该文件以设置 GN build
参数(见下文)。args.gn
是一个采用 GN 语法的文件,它可以赋值
用于替换任何硬编码默认值的 GN 构建参数。这意味着
重复 gn gen build-dir
会保留您上次执行的操作。
您还可以将 --args=...
添加到 gn gen 中,或使用 gn args
命令
配置构建参数gn args
命令提供了一种
在 args.gn
文件中设置 $EDITOR,然后在退出编辑器时命令
将使用新参数重新运行 gn gen
。您还可以只修改
args.gn
,并且下一次 Ninja 运行会重新生成 build 文件。
您还可以使用调用 gn gen
的 fx set
命令设置参数。对于
将 foobar
设置为“true
(通过 fx set
):
$ fx set <your configuration> --args 'foobar=true'
如需了解详情,请参阅 GN Build 参数。
GN 语法和格式
GN 语法不区分大小写。x=1 y=2
等同于:
x = 1
y = 2
不过,GN 代码有一种真正的缩进和格式设置样式。通过
gn format
命令将语法上有效的 GN 代码重新格式化为规范化格式
样式。支持 Emacs 和 Vim 的编辑器语法。规范格式
将由 Tricium 强制执行,并且将会进行批量重新格式化。如果您
例如格式设置、在上游 GN 中提交 bug 或更改
我们会大规模重新调整各项内容的格式,使其符合新的单一事实。
源路径和 GN 标签
对于文件和
来指代由 GN 定义的实体。路径可以是相对路径,也就是说
到包含路径字符串的 BUILD.gn
文件的目录。
它们也可以是“来源-绝对”,也就是相对于来源的根
树。在 GN 中,来源绝对路径以 //
开头。
当源路径最终在命令中使用时,它们将转换为 适用于操作系统的绝对路径或相对 build 路径 目录(运行命令的位置)。
预定义变量用于在来源路径上下文中查找 build 目录:
$root_build_dir
是 build 目录本身$root_out_dir
是当前工具链的子目录(见下文) <ph type="x-smartling-placeholder">- </ph>
- 在这里,所有“顶级”目标。在许多 GN build 中 可执行文件和库都位于此处
$target_out_dir
是$root_out_dir
的子目录,用于保存由 当前BUILD.gn
文件中的目标。这就是目标文件所在的位置。$target_gen_dir
是建议用来放置生成的代码的对应位置$root_gen_dir
是存放在此区域之外所需的生成的代码的位置, 子目录
GN 标签是我们用来表示 BUILD.gn
文件中定义的内容的方式。它们分别是
并且始终显示在 GN 字符串中。完整语法
GN 标签为 "dir:name"
,其中 dir
部分是指定
特定的 BUILD.gn
文件。name
是指在该文件中定义的目标
和target_type("name") { ... }
。作为一种简写形式,您可以定义
与其目录同名。没有 :
的 "//path/to/dir"
标签
部分是 "//path/to/dir:dir"
的简写形式。这是最常见的情况。
依赖关系图和 BUILD.gn
文件
GN 中的所有内容均植根于依赖关系图中。存在一个根
BUILD.gn
文件。系统甚至连其他 BUILD.gn
文件被读取的唯一方式是,
依赖于该目录中的某个标签。
没有通配符。每个目标都必须命名为某个
要构建的其他目标您可以在ninja
上为各个目标
来显式地构建这些组件否则,它们必须位于图表中
从 //:default
目标(在根 BUILD.gn
文件中名为 default
)移除。
有一个名为 group()
的通用元目标类型与
它只是构建依赖项的一种方式
生成出色的图表default
等顶级目标通常是组。您可以
为某件硬件的所有驱动程序创建一组
例如用例中的二进制文件。
当某些代码在运行时使用某些内容(一个数据文件、另一个可执行文件、
etc.)但在构建时并未将其用作直接输入,
使用它的目标的 data_deps
列表。这也足以获得
将内容放入 BOOTFS 图片中的指定位置。
目标还可以带有 testonly = true
标签,以表明目标
包含测试。GN 可防止非 testonly
的目标依赖于
让您可以在一定程度上控制测试二进制文件的位置
结果。
映像文件的构建是通过一个或多个 zbi()
目标驱动的。这将
通过构建和使用 ZBI 托管工具来制作 ZBI目标可以
这种图像在其依赖关系图中存在,因此您可以将其提供给
以及所需的任何驱动程序或可执行文件,
图片。
请注意,获取 Ninja 文件中定义的目标的粒度为
BUILD.gn
文件,尽管来自默认或任何其他目标的依赖关系图
都以单个目标为粒度因此,在集群内
默认图表中的 BUILD.gn
文件使该文件中的所有目标(以及
工具链(见下文)可用作 Ninja 命令行上的目标,甚至
尽管它们不是默认构建的
更多高级概念
GN 表达式语言和 GN 范围
GN 是一种简单的动态类型的命令式语言,其唯一目的在于 最后是制定声明式的 Ninja 规则。一切都以 作用域,这既是语言的词法绑定结构, 数据类型。
GN 值可以是以下任何一种类型:
- 布尔值,
true
或false
- 使用普通十进制语法有符号的整数;不常使用
- 字符串,始终采用“双引号”(以下有关“
$
”扩展的说明) - 作用域(在大括号中):
{ ... }
;请参见下文。 - 使用方括号中的值列表:
[ 1, true, "foo", { x=1 y=2 } ]
为 这个列表包含四个元素
值是动态类型的,不存在隐式类型强制转换, 但从不会进行这样的类型检查。不同类型的值一律不 但它们并不相同
字符串字面量可以扩展简单的 $var
或 ${var}
表达式,
或双引号。这是一个立即扩展:当 var 是一个字符串时,x${var}y
与 x +
var + y
相同。这样,任何值都可以
格式整齐的字符串。
由字母数字和下划线组成的标识符可通过
赋值运算符。使用 =
执行命令式分配,并通过 +=
进行修改
这实际上是 GN 语言的所有功能(还有一些特殊方法可将
附带效应,例如 print()
,用于调试;和 write_file()
,已使用
)。
每个文件在内部表示为一个范围,不存在全局范围。
共享的“全局”可以在 .gni
文件中定义,并在其
已使用 (import("//path/to/something.gni")
)。每个 `.gni 文件都会处理一次
每个工具链(有关工具链的信息,请参阅下文))以及生成的
范围会复制到导入文件范围。
目标声明会引入一个子范围:
foo = true
executable("target") {
foo = 12
}
# Outside the target, foo == true
如果定义了一个变量,但从未定义过,那么 GN 诊断错误时会非常严格
某个范围内的使用情况目标中的范围就像关键字参数
目标的列表,并检查参数名称是否拼写正确
正确。目标定义代码还可以使用 assert()
来诊断
错误。
值也可以是范围。在使用时,它就像结构体:
value.member
。但是,作用域始终是执行
生成其一组名称和值:
foo = {
x = global_tuning + 42
if (some_global && other_thing == "foobar") {
y = 2
}
}
这始终定义了 foo.x
,但有时仅定义了 foo.y
。
GN 工具链
GN 有一个名为“工具链”的概念。这一切都将在 开发者不需要直接处理这些场景, 来了解其机制
这就是封装编译器和默认编译开关的内容。时间是 也是将同一内容以不同的方式编译两次的真正方式 方法。在 Fuchsia 中,将有几个工具链:
- 主机
- Vanilla userland(使用默认
-fPIE
编译) - Userland 中的共享库(使用
-fPIC
编译) userboot
- 内核
- ARM64 的内核物理地址模式(使用
-mstrict-align
编译) - 适用于 x86 的 Multiboot(使用
-m32
编译) - 适用于 Gigaboot 的 UEFI
- 工具链也用于“变体” 架构即我们允许有选择性地 为部分用户空间启用 ASan 或类似工具。
每个工具链都由 GN 标签进行标识。目标标签的完整语法
实际为 //path/to/dir:name(//path/to/toolchain/label)
。通常,
工具链,扩展为 label($current_toolchain)
,
也就是说,标签引用通常位于同一工具链内。
所有 GN 文件都会在每个工具链中单独进行实例化。每个工具链
可以以不同的方式设置全局变量,因此 GN 代码可以使用 if
(is_kernel)
或 if (current_toolchain == some_toolchain)
等测试来行为
在不同情境下会有怎样的不同。这样,GN 代码便会与源代码保持一致
但它仍然可以针对不同内容
内核和用户等