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.
The best practices in this document are based on the following goals:
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") }
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.
is_*
variables to check the toolchainWhen asserting or writing a conditional on the current toolchain, use one of the is_*
variables defined in BUILDCONFIG.gn if one meets your needs:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="build/config/BUILDCONFIG.gn" region_tag="toolchain_is_variables" adjust_indentation="auto" exclude_regexp="^$" %}
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.
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] (#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.
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.
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]) # ... }
:anything
label to get output directoriesWhen 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")
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
.