Overview
In GN, toolchains provide a way to build targets in multiple ways. To understand
and debug GN code, you need to know what toolchain you're in. Because GN code
can be conditional on current_toolchain
, a target that does one thing in
toolchain A might do something completely different in toolchain B, and
it might not exist at all in toolchain C.
This document details the best practices for using toolchains to solve common
problems in GN code (.gn
and .gni
files). These best practices are in
addition to the best practices outlined in Fuchsia build system
policies.
See GN toolchains and the Fuchsia Build to learn more
about how toolchains work, or run fx gn help toolchain
to see GN's built-in
documentation.
Goals
The best practices in this document are based on the following goals:
- Consistency. Prefer to have one way of doing things.
- Clarity. Communicate intent clearly with assertions.
- Performance. Avoid unnecessary work in the build.
Best practices
Assert on the expected toolchain
If a file is only expected to be used in one toolchain or in certain toolchains, put an assertion at the top.
Recommended: Asserting is_host
in a
BUILD.gn
file that only builds host executables.
assert(is_host)
# ...
Recommended: Asserting current_toolchain ==
default_toolchain
in a template that only makes sense in the default toolchain.
template("foo") {
assert(current_toolchain == default_toolchain,
"The foo template can only be used in the default toolchain")
}
Wrap targets in conditionals
If you can't assert on the expected toolchain because a file needs to use more than one toolchain, wrap targets in conditional blocks to avoid unnecessary expansion. This makes it easier to understand what targets are used where, and it also helps reduce GN gen time.
Recommended: Wrapping targets in is_host
and is_fuchsia
checks.
# example/BUILD.gn
executable("built_everywhere") {
# ...
}
if (is_host) {
executable("only_on_host") {
# ...
}
}
if (is_fuchsia) {
executable("only_on_fuchsia") {
# ...
}
}
Not recommended: Defining all targets unconditionally.
# example/BUILD.gn
executable("built_everywhere") {
# ...
}
executable("only_on_host") {
# ...
}
executable("only_on_fuchsia") {
# ...
}
This approach increases the number of targets, slowing down both GN and ninja.
For example, when GN sees a reference to example:only_on_fuchsia
in the
default toolchain, it evaluates all of example/BUILD.gn in the default
toolchain, including the only_on_host
target. Since this cascades transitively
through the target's dependencies, getting this wrong in certain places can lead
to tens of thousands of unwanted targets.
Use is_*
variables to check the toolchain
When asserting or writing a
conditional on the current toolchain, use one
of the is_*
variables defined in
BUILDCONFIG.gn if one meets your needs:
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
Recommended: Using is_host
to check for
host toolchains.
if (is_host) {
# ...
}
Not recommended: Using current_toolchain ==
host_toolchain
to check for host toolchains.
if (current_toolchain == host_toolchain) {
# ...
}
Checking current_toolchain == host_toolchain
is usually wrong because there
are multiple host toolchains when variants are involved.
Only check the value of current_toolchain
if you have a reason for doing so.
For example, one valid use case is checking if current_toolchain ==
default_toolchain
to define a toolchain-agnostic
action.
Prefer fewer, earlier toolchain redirections
To get to a non-default toolchain, you have to redirect to it at some point. Push these redirections as far up the build graph as possible. This results in fewer redirections, and allows you to assert on the expected toolchain.
Recommended: Redirecting to host_toolchain
once earlier in the build.
# 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",
]
}
Not recommended: Redirecting to
host_toolchain
multiple times later in the build.
# 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)",
]
}
This approach needlessly processes examples/foo/BUILD.gn twice, once in the default toolchain and again in the host toolchain.
Avoid automatic toolchain forwarding
If a target only makes sense in a particular toolchain, simply assert on the expected toolchain.
Recommended: Asserting on the expected toolchain and defining the target once.
assert(current_toolchain == desired_toolchain)
action(target_name) {
# ...
}
Not recommended: Hiding the toolchain requirement with a GN group that automatically redirects all other toolchains.
if (current_toolchain == desired_toolchain) {
action(target_name) {
# ...
}
} else {
group(target_name) {
public_deps = [ ":$target_name($desired_toolchain)" ]
}
}
While it might seem convenient to make the target work in any toolchain, this practice makes it harder to understand what's really going on.
Put toolchain-agnostic actions in the default toolchain
Some actions behave the same no matter what the toolchain is, so it's wasteful
to repeat them in multiple toolchains. The most common example is code
generation: while we might build the resulting code in multiple toolchains, we
shouldn't have to generate the code again every time. To solve this, ensure the
action is only defined in default_toolchain
.
Recommended: Running code generation once in the 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])
# ...
}
Not recommended: Redoing code generation in every toolchain.
action("codegen") {
visibility = [ ":*" ]
outputs = [ "$target_gen_dir/main.cc" ]
# ...
}
executable("program") {
deps = [ ":codegen" ]
sources = get_target_outputs(deps[0])
# ...
}
Use the :anything
label to get output directories
When you call get_label_info
with "target_gen_dir" or "target_out_dir", only
the label's directory matters, not its target name. If there is no specific
target that makes sense, use a fake target called "anything".
Recommended: Naming the fake target "anything".
codegen_dir = get_label_info(":anything($default_toolchain)", "target_gen_dir")
Not recommended: Naming the fake target something other than "anything".
codegen_dir = get_label_info(":bogus($default_toolchain)", "target_gen_dir")
Avoid language-specific toolchains
Do not create a toolchain for a particular programming language. We did this
early on, and it turned out to be a bad idea. For example, we used to have
rust_toolchain
but later removed it. We are also planning to
remove the fidl_toolchain
.