概览
在 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_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_*
变量检查工具链
对当前工具链进行断言或编写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
。