使用 GN 工具链的最佳实践

概览

在 GN 中,工具链提供了一种以多种方式构建目标的方法。如需了解和调试 GN 代码,您需要知道自己所处的工具链。由于 GN 代码可能取决于 current_toolchain,因此在 工具链 A 中执行某项操作的目标在工具链 B 中可能会执行完全不同的操作,并且 在工具链 C 中可能根本不存在。

本文档详细介绍了使用工具链解决 GN 代码(.gn.gni 文件)中常见 问题的最佳实践。这些最佳实践是对 Fuchsia 构建系统 政策中概述的最佳实践的补充。

如需详细了解工具链的工作原理,请参阅 GN 工具链和 Fuchsia 构建,或运行 fx gn help toolchain 以查看 GN 的内置文档。

目标

本文档中的最佳实践基于以下 目标:

  • 一致性 。最好采用一种方式来执行操作。
  • 清晰度 。使用断言清楚地传达意图。
  • 性能 。避免在构建中执行不必要的工作。

最佳实践

断言预期工具链

如果某个文件仅应在一种工具链或某些工具链中使用,请在顶部放置断言。

建议:在仅构建主机可执行文件的 BUILD.gn文件中断言is_host

assert(is_host)

# ...

建议:在仅在默认工具链中有意义的模板中断言 current_toolchain == default_toolchain

template("foo") {
  assert(current_toolchain == default_toolchain,
         "The foo template can only be used in the default toolchain")
}

将目标封装在条件语句中

如果您无法断言预期 工具链,因为某个文件需要使用多个 工具链,请将目标封装在条件块中,以避免不必要的 扩展。这样可以更轻松地了解在何处使用哪些目标,还有助于缩短 GN 生成时间。

建议:将目标封装在 is_hostis_fuchsia 检查中。

# example/BUILD.gn

executable("built_everywhere") {
  # ...
}

if (is_host) {
  executable("only_on_host") {
    # ...
  }
}

if (is_fuchsia) {
  executable("only_on_fuchsia") {
    # ...
  }
}

不建议:无条件地定义所有目标 。

# example/BUILD.gn

executable("built_everywhere") {
  # ...
}

executable("only_on_host") {
  # ...
}

executable("only_on_fuchsia") {
  # ...
}

这种方法会增加目标的数量,从而减慢 GN 和 ninja 的速度。 例如,当 GN 在默认工具链中看到对 example:only_on_fuchsia 的引用时,它会在默认工具链中评估 example/BUILD.gn 的所有内容,包括 only_on_host 目标。由于这会通过目标的依赖项以传递方式级联 ,因此在某些地方出错可能会导致 数万个不必要的目标

使用 is_* 变量检查工具链

断言 或编写当前工具链的 条件语句 时,如果其中一个 is_* 变量满足您的需求,请使用 BUILDCONFIG.gn 中定义的其中一个变量:

is_android = false
is_apple = false
is_chromeos = false
is_fuchsia = false
is_fuchsia_host = false
is_host = false
is_ios = false
is_linux = false
is_mac = false
is_win = false
is_uefi = false
is_component_build = false
is_official_build = false
is_elf = false
is_pecoff = false
is_dwarf = false

建议:使用 is_host 检查 主机工具链。

if (is_host) {
  # ...
}

不建议:使用 current_toolchain == host_toolchain 检查主机工具链。

if (current_toolchain == host_toolchain) {
  # ...
}

检查 current_toolchain == host_toolchain 通常是错误的,因为存在多个主机工具链,当 变体 涉及时。

仅当您有理由这样做时,才检查 current_toolchain 的值。 例如,一个有效的用例是检查 current_toolchain == default_toolchain 以定义与 工具链无关 的操作

最好减少工具链重定向次数,并尽早进行重定向

如需使用非默认工具链,您必须在某个时间点重定向到该工具链。 尽可能将这些重定向推送到构建图的更上层。这样可以减少重定向次数,并允许您 断言预期工具链

建议:在构建中尽早重定向到 host_toolchain 一次。

# example/BUILD.gn

group("tests") {
  testonly = true
  deps = [ "foo:tests($host_toolchain)" ]
}
# examples/foo/BUILD.gn

assert(is_host)

test("foo_unit_tests") {
  # ...
}

test("foo_integration_tests") {
  # ...
}

group("tests") {
  testonly = true
  deps = [
    ":foo_unit_tests",
    ":foo_integration_tests",
  ]
}

不建议:稍后在构建中多次重定向到 host_toolchain

# example/BUILD.gn

group("tests") {
  testonly = true
  deps = [ "foo:tests" ]
}
# examples/foo/BUILD.gn

if (is_host) {
  test("foo_unit_tests") {
    # ...
  }

  test("foo_integration_tests") {
    # ...
  }
}

group("tests") {
  testonly = true
  deps = [
    ":foo_unit_tests($host_toolchain)",
    ":foo_integration_tests($host_toolchain)",
  ]
}

这种方法会不必要地处理 examples/foo/BUILD.gn 两次,一次在默认工具链中,另一次在主机工具链中。

避免自动工具链转发

如果某个目标仅在特定工具链中有意义,只需断言预期工具链 即可

建议:断言预期的 工具链并定义目标一次。

assert(current_toolchain == desired_toolchain)

action(target_name) {
  # ...
}

不建议:使用 GN 组隐藏工具链 要求,该组会自动重定向所有其他工具链。

if (current_toolchain == desired_toolchain) {
  action(target_name) {
    # ...
  }
} else {
  group(target_name) {
    public_deps = [ ":$target_name($desired_toolchain)" ]
  }
}

虽然让目标在任何工具链中运行看起来很方便,但这种做法会使您更难了解实际情况。

将与工具链无关的操作放在默认工具链中

某些操作的行为与工具链无关,因此在多个工具链中重复这些操作是浪费资源。最常见的示例是代码生成:虽然我们可能会在多个工具链中构建生成的代码,但我们不应每次都重新生成代码。如需解决此问题,请确保仅在 default_toolchain 中定义操作。

建议:在 默认工具链中运行代码生成一次。

if (current_toolchain == default_toolchain) {
  action("codegen") {
    visibility = [ ":*" ]
    outputs = [ "$target_gen_dir/main.cc" ]
    # ...
  }
}

executable("program") {
  deps = [ ":codegen($default_toolchain)" ]
  sources = get_target_outputs(deps[0])
  # ...
}

不建议:在 每个工具链中重新执行代码生成。

action("codegen") {
  visibility = [ ":*" ]
  outputs = [ "$target_gen_dir/main.cc" ]
  # ...
}

executable("program") {
  deps = [ ":codegen" ]
  sources = get_target_outputs(deps[0])
  # ...
}

使用 :anything 标签获取输出目录

当您使用“target_gen_dir”或“target_out_dir”调用 get_label_info 时,只有标签的目录才重要,而不是其目标名称。如果没有有意义的特定目标,请使用名为“anything”的虚假目标。

建议:将虚假目标命名为“anything”。

codegen_dir = get_label_info(":anything($default_toolchain)", "target_gen_dir")

不建议:将虚假目标命名为“anything”以外的其他名称。

codegen_dir = get_label_info(":bogus($default_toolchain)", "target_gen_dir")

避免使用特定于语言的工具链

请勿为特定编程语言创建工具链。我们之前这样做过,但事实证明这是一个糟糕的主意。例如,我们曾经有 rust_toolchain,但后来将其移除。我们还计划 移除 fidl_toolchain