概览
在 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_host
和 is_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。