Fuchsia build system

The Fuchsia build system aims at building both boot images and updatable packages for various devices. The Fuchsia build system uses GN, a meta-build system that generates build files consumed by Ninja, which executes the actual build.

Concepts

If you are unfamiliar with Fuchsia's build system and GN, see Using GN build, which outlines the basic principles of the GN build system.

The following sections cover several concepts around Fuchsia's build system.

Boards and products

The contents of the generated Fuchsia images are controlled by a combination of a board and a product. Boards and products are build targets that define the packages and dependencies that are included in images. For more information on the structure and usage of these build configurations, see boards and products.

Build targets

Build targets are defined in BUILD.gn files that exist throughout the source tree. These files use a Python-like syntax to declare buildable objects. For example:

import("//build/some/template.gni")

my_template("foo") {
  name = "foo"
  extra_options = "//my/foo/options"
  deps = [
    "//some/random/framework",
    "//some/other/random/framework",
  ]
}

Available commands (invoked through the gn cli tool) and constructs built-in target declaration types are defined in the GN reference. There are also a handful of custom templates in .gni files in the //build project.

Fuchsia defines many custom templates to support defining and building Fuchsia specific artifacts.

Build optimization flags

When building Fuchsia using fx set, you can specify a build optimization flag to control the trade-off between compilation time, runtime performance, and debuggability. The build optimization flags are --debug, --balanced, and --release.

Choosing the right flag can significantly impact your development workflow, build times, and the performance characteristics of the resulting image.

Quick comparison

--debug --balanced --release
Primary focus Debug assertions, optimized for use with the debugger Compile speed and good runtime performance. Max runtime performance and smallest size
Compile time Faster (Incremental) Medium (2-4x faster than release for some targets) Slower
Runtime performance Slower Good (acceptable for most development) Faster
Binary size Larger Much smaller than debug, and slightly larger than release Smaller
Optimizations Minimal Some Full
Debug experience Full Between debug and release (slightly less debuggability than debug) Minimal
Recommended for Active coding and debugging Daily development and faster iterations Production, benchmarking, final validation

Set the compilation mode

Append the desired flag to your fx set command:

fx set PRODUCT.BOARD [--debug | --balanced | --release]

For example:

  • fx set core.x64 --debug
  • fx set core.x64 --balanced

Why --balanced?

The --balanced flag was introduced to address the significant compilation time overhead of --release builds, especially for large Rust and C++ targets. By selectively enabling optimizations and using faster alternatives like ThinLTO (instead of Full LTO) for C++ and more codegen units (i.e. threads at compile time) for Rust, --balanced offers a better developer experience for tasks requiring better-than-debug performance.

As Fuchsia evolves, --release builds may incorporate even more aggressive (and potentially slower to compile) optimizations like Rust Full LTO, PGO and higher optimization levels for performance-critical binaries. On the other hand, if you use --balanced this will allow you to do performance-aware development so that you can benefit from ongoing compile-time improvements while maintaining good runtime characteristics.

Full comparison of build optimization flags

This section does a full comparison between the build optimization flags:

--debug
  • Primary goal: Faster incremental compilation, full debuggability.
  • Optimizations: Minimal to none. Code is compiled to be as close to the source as possible.
  • Debug experience: Full debug symbols are included.
  • Runtime performance: Slower. Not suitable for performance testing or production.
  • Compile time (full rebuild): Generally faster than --release and --balanced for initial builds due to lack of optimization passes. Incremental builds are typically the fastest.
  • When should you use this:
    • Actively developing and debugging code.
    • You need to step through code with a debugger and inspect variables accurately.
    • Rapid iteration is more important than runtime performance.
--balanced
  • Primary goal: A balance between compilation speed and runtime performance.
  • Optimizations: A curated set of optimizations that provide good runtime performance without the excessive compile times of --release.
  • Debug experience: Between --debug and --release slightly more debuggability than release. Some optimizations might make precise debugging harder than --debug.
  • Runtime performance: Good. Slightly slower (potentially 10-20% in some areas) than a full --release build, but significantly faster than --debug. Performance is generally acceptable for most development and testing scenarios.
  • Compile time: Significantly faster than --release. For large Rust targets, this can be 2-4x faster.
    • For example: netstack3 compiles 4x faster (70s vs 280s).
    • For example: component_manager compiles 2.6x faster (70s vs 180s).
  • When should you use this:
    • This should be your default compilation mode when you need something that runs faster than --debug but want to avoid the long compile times of --release.
    • General development and iteration where --debug is too slow at runtime.
    • When you need to test features with reasonable performance without waiting for full release builds.
    • To benefit from ongoing compile-time improvements, as this mode is actively being optimized for speed.
--release
  • Primary goal: Maximum runtime performance and smallest binary size.
  • Optimizations: Full optimizations are enabled. This includes aggressive techniques like:
    • Link-Time Optimization (LTO), often Full LTO.
    • Profile-Guided Optimization (PGO) where applicable.
    • Higher compiler optimization levels (e.g., -O3).
  • Debug experience: Minimal. Debugging can be very challenging.
  • Runtime performance: Fastest. This is the mode for benchmarking and production deployments.
  • Compile time: Slowest, due to extensive optimization passes and LTO.
  • When should you use this:
    • Building for production or deployment.
    • Running performance benchmarks.
    • You need the absolute smallest binary size and highest runtime speed, and are willing to accept long build times.

Execute a build

The simplest way to execute a build is through the fx tool by using fx set to configure a build and then fx build as described in fx workflows.

Configure a build

Configure the primary build artifacts by choosing the board and product to build:

fx set

fx set core.x64

You can also set an optimization flag on this command. See Build optimization flags. For example:

fx set core.x64 --balanced

fx gn gen

fx gn gen $(fx get-build-dir) --args='import("//boards/x64.gni") import("//products/core.gni")'

For a list of all GN build arguments, run:

fx gn args $(fx get-build-dir) --list

This creates a build directory (usually out/default) that contains Ninja and Bazel files.

Generate a build

Once you have configured the build artifacts with fx set, you can then build Fuchsia:

fx build

fx build

This is what gets run under the hood by fx build.

Rebuilding

In order to rebuild the tree after modifying source code, you can just re-run fx build. This also applies if you modify BUILD.gn files as GN adds Ninja targets to update Ninja targets if build files are changed. The same holds true for other files used to configure the build.

Tips and tricks

These tips and tricks only apply for using the fx gn command.

Inspecting the content of a GN target

fx gn desc $(fx get-build-dir) //path/to/my:target

Finding references to a GN target

fx gn refs $(fx get-build-dir) //path/to/my:target

Referencing targets for the build host

Various host tools (some used in the build itself) need to be built along with the final image.

To reference a build target for the host toolchain from within a BUILD.gn file:

//path/to/target($host_toolchain)

Building only a specific target

If a target is defined in a GN build file as //foo/bar/blah:dash, that target (and its dependencies) can be built with:

fx build

fx build //foo/bar/blah:dash

fx build --host

fx build --host //foo/bar/blah:dash

Debugging build timing issues

When running a build, Ninja keeps logs that can be used to view the steps of the build process. To analyze the timing of a specific build iteration:

  1. Run a build as you normally would do. This generates the .nina_log file in your output directory.

  2. Use the fx ninjatrace2json tool to convert the Ninja log into a trace file. For example:

    fx ninjatrace2json <your_output_directory>/.ninja_log > trace.json
    
  3. Load the resulting trace.json file in a compatible trace viewer. For example, in Chrome, navigate to chrome://tracing and click "Load".

Alternatively, you can use fx report-last-build. This command gathers comprehensive build logs and timing information.