GN root targets

The Fuchsia build uses GN's new root_patterns feature to considerably reduce the size of the GN and Ninja build graphs. This results in significantly faster gn gen time, and Ninja startup time.

However, this feature changes GN's default behavior when parsing BUILD.gn files in ways that can be surprising. This document explains how.

GN default behavior

For historical reasons, GN will instantiate every target defined in a BUILD.gn file when the latter is evaluated in the context of the default toolchain, even if nothing really depends on them.

As an example, consider the following three BUILD.gn files that each define two targets, with a few dependencies between them:

 _//BUILD.gn____    _//foo/BUILD.gn__    _//bar/BUILD.gn_
|               |  |                 |  |                |
|   A           |  |         D -------------> E          |
|               |  |                 |  |                |
|      B --------------> C           |  |         F      |
|_______________|  |_________________|  |________________|

When GN parses this build plan the following happens:

  • GN starts by loading //BUILD.gn, and evaluating it. Because it uses the default toolchain, it creates targets for all entries in that file, i.e. //:A and //:B.

  • GN follows dependencies of the targets it just created, since //:B depends on //foo:C, it first loads //foo/BUILD.gn and evaluates it.

  • Because it is still in the default toolchain, it instantiates all targets in that file too, and thus creates //foo:C and //foo:D, even though the latter is not a dependency of //:A or //:B

  • It follows dependencies again, and will load //bar/BUILD.gn, evaluate it, and create all targets defined here, hence //bar:E and //bar:F

This leads to the final build graph containing many more targets than needed, if one assumes that //BUILD.gn represents the root of the graph.

GN root_patterns

It is possible to change this default behavior by setting root_patterns in the .gn file, or using the --root-pattern=<pattern> command-line option (once or more).

These define a list of target label patterns, used to filter which of the non-dependency targets, present in BUILD.gn files evaluated in the default toolchain, to create.

For example, using gn gen --root-pattern=//:* with the same build plan will change GN's behavior in the following way:

  • GN starts by loading //BUILD.gn and evaluates it. Because it is in the default toolchain, any target in the file that is created, because they match the pattern (//:* really means "any target in //BUILD.gn"). It thus creates //:A and //:B as before.

  • GN follows dependencies, then loads //foo/BUILD.gn and evaluates it. It creates //foo:C because this is a direct dependency of one of the previous targets it created. However, it does not create //foo:D, as its label does not match the pattern //:*.

  • GN stops here, since it didn't create //foo:D, it has not reason to load //bar:BUILD.gn

Thus GN creates 3 targets, instead of 6 in the final build graph.

Practical results

In practice, using this feature reduces the size of our GN build graph considerably, speeding up the gn gen time as well. For example, using an fx set minimal.x64 configuration:

                        Default     --root-pattern=//:*    Reduction

Target count             183761              48375          -73%
Ninja files size (MiB)    571.7              180.2          -68%
`fx set` peak RAM (GiB)    5.02               2.89          -42%
`gn gen` time (s)          14.9               6.15          -58%
`fx set` time (s)          16.0               6.77          -57%

The //:root_targets target.

The //BUILD.gn file now defines a top-level target named root_targets that can be used to add dependencies to targets that absolutely must be in the build graph even though nothing else depends on them.

This is critical for a few cases:

  • Some special generated_file() targets whose output is used as an implicit input by other targets, but which cannot be depended on directly, such as //build/bazel:legacy_ninja_build_outputs

  • A few targets that hard-coded infra tools expect to always be built in the tree.

  • Some targets that are expected by some builder configurations that incorrectly didn't list them in their universe_package_labels, and just assumed their existence due to other required targets defined in the same BUILD.gn file.

Adding to this list should be minimized. It is always better to find a real transitive dependency from any of the other top-level //BUILD.gn targets if you really need something to always be defined in the final Ninja build manifest.