使用 GN 工具链的最佳实践

概览

在 GN 中,工具链提供了一种以多种方式构建目标的方法。为了了解和调试 GN 代码,您需要知道自己使用的是哪种工具链。由于 GN 代码可基于 current_toolchain 设定条件,因此在工具链 A 中执行某项操作的目标可能会在工具链 B 中执行完全不同的操作,而且该目标在工具链 C 中可能根本不存在。

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

请参阅 GN 工具链和 Fuchsia Build 以详细了解工具链的工作原理;或运行 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_* 变量检查工具链

对当前工具链进行断言或编写conditional时,请使用 BUILDCONFIG.gn 中定义的 is_* 变量之一(如果它符合您的需求):

is_android = 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_component_build = false
is_official_build = 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