概览
在 GN 中,模板提供了一种添加到 GN 的内置目标类型的方法。基本上
模板是 GN 构建可重用函数的主要方式。模板定义 go
在可导入目标 .gn
文件的 .gni
(GN import) 文件中。
本文档详细介绍了创建 GN 模板的最佳做法,以及每个 包括一个例子。这些最佳做法是 Fuchsia 构建系统政策中列出的做法。
运行 fx gn help template
以获取更多信息和更完整的示例,并查看
GN 语言和操作
。
模板
在 .gni
中定义模板,在 BUILD.gn
中定义目标
从技术上讲,可以导入 .gni
和 BUILD.gn
文件。最佳
实际的做法是在 .gni
文件中定义模板,以及
目标位于 .gn
文件中。这可以让用户清楚地了解什么是模板。用户
希望导入模板以便他们使用它们,而不想导入目标。
文档模板和参数
记录您的模板和参数,包括:
- 模板用途和所引入概念的一般说明。推荐一个实际使用示例。
- 应记录所有参数。对于常见且简单转发的参数(例如
deps
或visibility
),其含义与内置 GN 规则中的含义一致,因此无需额外信息即可列出。 - 如果模板生成
metadata,
,则应列出data_keys
。
要为您的模板添加文档,请在模板定义前插入注释块 来指定公共合同
declare_args() {
# The amount of bytes to allocate when creating a disk image.
disk_image_size_bytes = 1024
}
# Defines a disk image file.
#
# Disk image files are used to boot the bar virtual machine.
#
# Example:
# ```
# disk_image("my_image") {
# sources = [ "boot.img", "kernel.img" ]
# sdk = false
# }
# ```
#
# Parameters
#
# sources (required)
# List of source files to include in the image.
# Type: list(path)
#
# sdk (optional)
# This image is exported to the SDK.
# Type: bool
# Default: false
#
# data_deps
# deps
# public_deps
# testonly
# visibility
#
# Metadata
#
# files
# Filenames present in this image.
template("disk_image") {
...
}
使用单个操作模板封装工具
对于每种工具,都制定一个使用 action
封装工具的规范模板。
此模板的作用是针对该工具将 GN 参数转换为 args
;
就是这么简单。这会在该工具周围设置封装边界
例如将参数转换为参数。
请注意,在此示例中,我们在一个文件中定义了 executable()
,
另一个中的template()
,因为
模板和目标应分开
# //src/developer_tools/BUILD.gn
executable("copy_to_target_bin") {
...
}
# //src/developer_tools/cli.gni
template("copy_to_target") {
compiled_action(target_name) {
forward_variables_from(invoker, [
"data_deps",
"deps",
"public_deps",
"testonly",
"visibility"
])
assert(defined(invoker.sources), "Must specify sources")
assert(defined(invoker.destinations), "Must specify destinations")
tool = "//src/developer_tools:copy_to_target_bin"
args = [ "--sources" ]
foreach(source, sources) {
args += [ rebase_path(source, root_build_dir) ]
}
args += [ "--destinations" ]
foreach(destination, destinations) {
args += [ rebase_path(destination, root_build_dir) ]
}
}
}
考虑将模板设为不公开
名称以下划线开头的模板和变量(例如 template("_private")
)
会被视为不公开,其他对其执行import()
操作的文件将看不到这些内容,但可以
使用的 ID 相同。这对于内部帮助程序模板或
例如,您可以定义一些“局部全局变量”,
帮助程序对用户无用的情况。
template("coffee") {
# Take coffee parameters like roast and sugar
...
_beverage(target_name) {
# Express in beverage terms like ingredients and temperature
...
}
}
template("tea") {
# Take tea parameters like loose leaf and cream
...
_beverage(target_name) {
# Express in beverage terms like ingredients and temperature
...
}
}
# We don't want people directly defining new beverages.
# For instance they might add both sugar and salt to the ingredients list.
template("_beverage") {
...
}
有时您无法将模板设为不公开,因为模板实际上需要使用
但您仍想将其隐藏
直接使用。在这种情况下,您可以将强制执行切换为信号,
将模板放在某个路径(例如 //build/internal/
)下的文件中。
测试模板
编写使用您的模板进行构建的测试,或使用由您的 模板数量。
您不应依赖其他人的 build 和测试来测试您的模板。 自行测试可以提高模板的可维护性,因为它速度更快 以验证未来对模板所做的更改,从而更轻松地隔离故障。
# //src/drinks/coffee.gni
template("coffee") {
...
}
# //src/drinks/tests/BUILD.gni
import("//src/drinks/coffee.gni")
coffee("coffee_for_test") {
...
}
test("coffee_test") {
sources = [ "taste_coffee.cc" ]
data_deps = [ ":coffee_for_test" ]
...
}
参数
对必需参数进行断言
如果您的模板中有必需参数,assert
即可定义它们。
如果用户忘记指定必需参数,并且没有定义断言, 就无法明确说明所犯的错误。通过断言,您可以 提供有用的错误消息。
template("my_template") {
forward_variables_from(invoker, [ "sources", "testonly", "visibility" ])
assert(defined(sources),
"A `sources` argument was missing when calling my_template($target_name)")
}
template("my_other_template") {
forward_variables_from(invoker, [ "inputs", "testonly", "visibility" ])
assert(defined(inputs) && inputs != [],
"An `input` argument must be present and non-empty " +
"when calling my_template($target_name)")
}
始终转发 testonly
在目标上设置 testonly
可防止非测试目标使用它。
如果您的模板未将 testonly
转发到内部目标,则:
- 您的内部目标可能无法构建,因为您的用户可能会向您传递
testonly
依赖项。 - 如果用户发现他们的
testonly
工件最终出现在生产工件中,他们会感到惊讶。
以下示例展示了如何转发 testonly
:
template("my_template") {
action(target_name) {
forward_variables_from(invoker, [ "testonly", "deps" ])
...
}
}
my_template("my_target") {
visibility = [ ... ]
testonly = true
...
}
请注意,如果内部操作的父级范围定义了 testonly
,
那么forward_variables_from(invoker, "*")
就不会转发此邮件
避免破坏变量下面是一些可用于解决此问题的模式:
# Broken, doesn't forward `testonly`
template("my_template") {
testonly = ...
action(target_name) {
forward_variables_from(invoker, "*")
...
}
}
# Works
template("my_template") {
testonly = ...
action(target_name) {
forward_variables_from(invoker, "*")
testonly = testonly
...
}
}
# Works
template("my_template") {
testonly = ...
action(target_name) {
forward_variables_from(invoker, "*", [ "testonly" ])
forward_variables_from(invoker, [ "testonly" ])
...
}
}
唯一的例外是对 testonly = true
进行硬编码的模板,因为
它们绝不能用在生产目标中。例如:
template("a_test_template") {
testonly = true
...
}
将 visibility
转发到主要目标并隐藏内部目标
GN 用户希望能够在任何目标上设置 visibility
。
这条建议与“始终转发 testonly”类似,不过,
它只会应用于主目标(名为 target_name
的目标)。其他目标应
限制其 visibility
,以免用户依赖于您的内部目标
未包含在合同中的
template("my_template") {
action("${target_name}_helper") {
forward_variables_from(invoker, [ "testonly", "deps" ])
visibility = [ ":*" ]
...
}
action(target_name) {
forward_variables_from(invoker, [ "testonly", "visibility" ])
deps = [ ":${target_name}_helper" ]
...
}
}
如果转发 deps
,也会转发 public_deps
和 data_deps
所有接受 deps
的内置规则都采用 public_deps
和 data_deps
。
一些内置规则不区分依赖项类型(例如 action()
将 deps
和 public_deps
一视同仁)。但依赖于您生成的
目标(例如,依赖于您生成的 action()
的 executable()
)
对传递 deps
和 public_deps
的处理方式不同)。
template("my_template") {
action(target_name) {
forward_variables_from(invoker, [
"data_deps",
"deps",
"public_deps",
"testonly",
"Visibility"
])
...
}
}
目标名称
定义一个名为 target_name
的内部目标
您的模板应至少定义一个名为 target_name
的目标。
这样,您的用户可以通过一个名称调用您的模板,然后使用该模板。
名称。
# //build/image.gni
template("image") {
action(target_name) {
...
}
}
# //src/some/project/BUILD.gn
import("//build/image.gni")
image("my_image") {
...
}
group("images") {
deps = [ ":my_image", ... ]
}
target_name
是适合的输出名称,但会提供替换项
如果模板生成单个输出,则使用目标名称来选择 输出名称是良好的默认行为。但是,目标名称必须是唯一的 目录,因此您的用户将无法始终使用 同时兼顾目标和输出内容。
最好为用户提供替换项:
template("image") {
forward_variables_from(invoker, [ "output_name", ... ])
if (!defined(output_name)) {
output_name = target_name
}
...
}
为内部目标名称添加 $target_name
前缀
GN 标签必须是唯一的,否则您将收到生成时间错误。如果所有人 同一项目遵循相同的命名惯例,则冲突会更少 从而更轻松地将内部目标名称 与创建它们的目标相关联。
template("boot_image") {
generate_boot_manifest_action = "${target_name}_generate_boot_manifest"
action(generate_boot_manifest_action) {
...
}
image(target_name) {
...
deps += [ ":$generate_boot_manifest_action" ]
}
}
不根据目标标签推断输出名称
人们往往倾向于假设目标名称和输出名称之间的关系。 例如,以下示例将正常运行:
executable("bin") {
...
}
template("bin_runner") {
compiled_action(target_name) {
forward_variables_from(invoker, [ "testonly", "visibility" ])
assert(defined(invoker.bin), "Must specify bin")
deps = [ invoker.bin ]
tool = root_out_dir + "/" + get_label_info(invoker.foo, "name")
...
}
}
bin_runner("this_will_work") {
bin = ":bin"
}
但是,此示例会产生生成时间错误:
executable("bin") {
output_name = "my_binary"
...
}
template("bin_runner") {
compiled_action(target_name) {
forward_variables_from(invoker, [ "testonly", "visibility" ])
assert(defined(invoker.bin), "Must specify bin")
tool = root_out_dir + "/" + get_label_info(invoker.bin, "name")
...
}
}
# This will produce a gen-time error saying that a file ".../bin" is needed
# by ":this_will_fail" with no rule to generate it.
bin_runner("this_will_fail") {
bin = ":bin"
}
以下是解决此问题的一种方法:
executable("bin") {
output_name = "my_binary"
...
}
template("bin_runner") {
compiled_action(target_name) {
forward_variables_from(invoker, [ "testonly", "visibility" ])
assert(defined(invoker.bin), "Must specify bin")
tool = bin
...
}
}
bin_runner("this_will_work") {
bin = "$root_out_dir/my_binary"
}
GN 函数和生成
仅将 read_file()
用于源文件
read_file()
发生在生成期间,无法安全地用于从生成的
文件或构建输出它可用于读取源文件,例如
填充 build 依赖项的清单文件或 JSON 文件。
值得注意的是,read_file()
不能与 generated_file()
或 write_file()
一起使用。
首选generated_file()
(而非write_file()
)
一般来说,建议您使用 generated_file()
而不是 write_file()
。
generated_file()
提供了更多功能,并解决了一些难题
共 write_file()
个。例如,generated_file()
可以并行执行,
而 write_file()
是在生成时串行完成的。
两个命令的结构非常相似。例如,您可以将
write_file()
的这个实例:
write_file("my_file", "My file contents")
在 generated_file()
的这个实例中:
generated_file("my_file") {
outputs = [ "my_file" ]
contents = "My file contents"
}
首选 rebase_path()
中的相对路径
例如,始终在 rebase_path()
中指定 new_base
rebase_path("foo/bar.txt", root_build_dir)
。避免使用单参数形式,
为 rebase_path("foo/bar.txt")
。
GN 的 rebase_path()
有三个参数,后两个是可选参数。
其单参数形式返回一个绝对路径,
即将被弃用。避免在构建模板和目标中使用。
new_base
的值因具体情况而异,root_build_dir
是
因为它是执行构建脚本的位置。查看更多
有关 rebase_path()
的信息,请参阅
GN 参考文档。
项目或构建输出的路径时,相对路径可以保持不变 目录更改。与绝对路径相比,它有一些优势:
- 保护用户隐私,避免将可能敏感的信息从 构建输出中的路径
- 提高了内容寻址缓存的效率。
- 使聊天机器人之间的互动成为可能,例如,一个聊天机器人执行 关注另一个聊天机器人。
另请参阅:
rebase_path(x)
是否返回被认为有害的绝对路径?
模式和反模式
目标输出
使用 get_target_outputs()
提取单个元素时,GN 不会
可让您在分配前为列表下标。要解决此问题,
您可以使用下面这种不太有效的解决方法:
# Appending to a list is elegant
deps += get_target_outputs(":some_target")
# Extracting a single element to use in variable substitution - ugly but reliable
_outputs = get_target_outputs(":other_target")
output = _outputs[0]
message = "My favorite output is $output"
# This expression is invalid: `output = get_target_outputs(":other_target")[0]`
# GN won't let you subscript an rvalue.
此外,get_target_outputs()
还存在一些令人痛苦的限制:
- 仅支持
copy()
、generated_file()
和action()
目标。 - 只能查询同一
BUILD.gn
文件中定义的目标。
因此,您经常会发现一个目标的输出路径被硬编码在另一个
BUILD.gn
文件。这会生成脆弱的协定。当合同违背时
来排查中断问题非常困难在进行在线搜索时,请尽量避免
,如果不能,可以添加大量内联文档。
检查类型是否为字符串
虽然 GN 不允许进行全面的类型检查,但您可以检查变量 是一个字符串,并且只是一个字符串,具体方法如下所示:
if (var == "$var") {
# Execute code conditional on `var` type being string
}
检查 var 是否为单例列表
同样,您也可以检查变量是否为单例列表,如下所示:
if (var == [var[0]]) {
# Execute code conditional on `var` type being a singleton list
}
但请注意,如果类型不是列表或为空,则会崩溃。
集合运算
GN 将列表和范围作为汇总数据类型(而非结合数据类型)提供 例如映射或集有时,会使用列表而不是集。通过 下方示例列出了 build 变体,并检查是否其中一个变体 是“profile”变体:
if (variants + [ "profile" ] - [ "profile" ] != variants) {
# Do something special for profile builds
...
}
这是一种反模式。相反,变体可以定义如下:
variants = {
profile = true
asan = false
...
}
if (variants.profile) {
# Do something special for profile builds
...
}
转发 "*"
forward_variables_from()
会将指定变量复制到当前
范围。除非您
指定 "*"
,在这种情况下,它只会直接复制变量
指定范围。它也永远不会破坏
即生成时间错误。
有时,您需要从调用程序复制所有内容, 需要从封装在一起的 范围。您会遇到以下模式:
forward_variables_from(invoker, "*", [ "visibility" ])
forward_variables_from(invoker, [ "visibility" ])
exec_script()
GN 的内置函数
exec_script
是增强 GN 能力的强大工具。与action()
类似,
exec_script()
可以调用外部工具。与action()
不同,exec_script()
可以与 build 生成同步调用该工具,这意味着
可以在 BUILD.gn
逻辑中使用该工具的输出。
由于这会在生成时间方面造成性能瓶颈(即 fx set
需要
因此请务必谨慎使用此功能。
如需了解详情,请参阅
这篇书面报道
。
已在 //.gn
中设置许可名单。如需了解变更内容,请咨询OWNERS
针对该许可名单所做的修改