概览
在 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
针对该许可名单所做的修改