Software Assembly

Software Assembly enables developers to quickly build a product using a customized operating system. Concretely, Software Assembly is a set of tools that produce a Product Bundle (PB) from inputs including a set of Fuchsia packages, a kernel, and config files.

Product Bundle

A Product Bundle is a directory of well-specified artifacts that can be shipped to any environment and is used to flash, update, or emulate a Fuchsia target. It's not expected that developers inspect the contents of a Product Bundle directly, instead they can rely on the tools provided by Fuchsia. For example, the ffx tool is used to flash or emulate a device using a Product Bundle:

# Flash a hardware target with the product bundle.
$ ffx target flash --product-bundle <PATH/TO/PRODUCT_BUNDLE>

# Start a new emulator instance with the product bundle.
$ ffx emu start <PATH/TO/PRODUCT_BUNDLE>

A Product Bundle may contain both a main and a recovery product, which are distinct bootable experiences. Oftentimes main is used for the real product experience, and recovery is used if main is malfunctioning and cannot boot. recovery is generally a lightweight experience that is capable of addressing issues in the main slot using a factory reset or an Over-The-Air (OTA) update. Typically, an end-user can switch which product to boot into by holding a physical button down on a device during boot up.

Software Assembly offers build tools for constructing Product Bundles, defines the format of the inputs (platform, product config, board config), and provides build rules to construct those inputs.

A product bundle containing both a main product and a recovery product

Figure 1. A Product Bundle may contain both a main product and a recovery product.

Platform, product config, and board config

The three inputs to assembly (platform, product config, board config) are directories of artifacts. The internal format is subject to change and shouldn't be depended on. Developers need to use the provided Bazel or GN build rules to construct and use these inputs.

The platform is produced by the Fuchsia team. It contains every bit of compiled platform code that any Fuchsia product may want to use. The Fuchsia team releases the platform to https://chrome-infra-packages.appspot.com/p/fuchsia/assembly/platform.

The product config is produced by a developer defining the end-user experience. It may contain flags indicating which features of the platform to include. For example, the product config can set platform.fonts.enabled=true, resulting in assembly including the relevant fonts support from the platform. See this reference for all the available flags. The product config can additionally include custom code for building the user experience.

The board config is produced by a developer supporting a particular hardware target. It includes all the necessary drivers to boot on that hardware. Additionally, the board config can declare which hardware is available to be used by the platform. For example, if the hardware has a Real Time Clock (RTC), the board config can indicate that by setting the provided_features=["fuchsia::real_time_clock"] flag. Assembly reads this flag and includes the necessary code from the Platform for using this piece of hardware. The Fuchsia team maintains a small set of board configs and releases them to https://chrome-infra-packages.appspot.com/p/fuchsia/assembly/boards.

Environments

Customization is supported by other operating systems, but Fuchsia Software Assembly has the unique ability to run in any conceivable environment and do so quickly. A Fuchsia product can be customized and assembled in less than a minute, which is much faster than other operating systems.

Fuchsia Software Assembly is currently supported in the following environments (while no technical limitation prevents Fuchsia from extending support in the future):

  • Bazel
  • CLI (experimental)
  • GN (in fuchsia.git)

Bazel:

# A product bundle can contain both 'main' and 'recovery' products (systems/slots).
fuchsia_product_bundle(
    name = "my_product_bundle",
    main = ":main_product",
    recovery = "...",
)

# A product is a single bootable experience that is built by combining
# a platform, product, and board.
fuchsia_product(
    name = "main_product",
    platform = "//platform:x64",
    product = ":my_product",
    board = "//boards:x64",
)

# A product configuration defines the user experience by enabling
# platform features and including custom product code.
fuchsia_product_configuration(
    name = "my_product",
    product_config_json = {
        platform = {
            fonts = {
                enabled = True,
            },
        },
    },

    # The product code is included as packages.
    base_packages = [ ... ],
)

For a complete example, see the getting-started repository.

Command Line Interface (experimental):

ffx product create --platform 28.20250718.3.1 \
                   --product-config <PATH/TO/MY_PRODUCT_CONFIG> \
                   --board-config cipd://fuchsia/assembly/boards/x64@28.20250718.3.1 \
                   --out my_product_bundle

The ffx product create command can be run to produce a new product bundle using already built platform, board, and product artifacts. This command replaces the entire contents of the directory specified in the --out flag, rather than updating the existing contents.

Size and scrutiny

Software Assembly provides tools for verifying the quality of a Product Bundle.

The size check tool informs the user whether the Product Bundle fits within the partition size constraints of the target hardware. A product size report can be generated using the following Bazel rules:

fuchsia_product_size_check(
    name = "main_product_size_report",
    product_image = ":main_product",
)

fuchsia_product(
    name = "main_product",
    ...
)

The scrutiny tool ensures that the Product Bundle meets a set of security standards. If a developer provides the necessary scrutiny configs, scrutiny runs during the construction of a Product Bundle. See the following scrutiny configuration example:

fuchsia_product_bundle(
    name = "my_product_bundle",
    main = ":main_product",
    main_scrutiny_config = ":main_scrutiny_config",
)

fuchsia_scrutiny_config(
    name = "main_scrutiny_config",
    base_packages = [ ... ],    # Allowlist of base packages to expect.
    kernel_cmdline = [ ... ],   # Allowlist of kernel arguments to expect.
    pre_signing_policy = "...", # File containing the policies to check before signing.
)

Implementing a platform feature

This section explains how to implement a new feature in the platform that can be enabled by either a product config or a board config.

A platform feature is always implemented in fuchsia.git and must be generic enough that it can be enabled on multiple products or boards. If the feature is specific to a product or board, consider putting it inside the product or board config instead.

Implementing a platform feature often involves the following steps:

  1. Write your feature flag in the product config or board config.
  2. Prepare a subsystem that can read the flag.
  3. Write and enable your feature code inside the subsystem.

Diagram showing how a platform feature is implemented

Figure 2. Implementing a platform feature and enabling it in a product config.

1. Write your feature flag (if necessary)

Platform features are not often added to all products by default, but are typically added when the product or board config has a specific flag set. The first step to writing a platform feature is to determine when your feature needs to be enabled, and therefore what product or board flags are necessary to turn on the feature.

For example, if you want to allow the product config to enable a feature called foo.bar, the product config could be written like the following:

fuchsia_product_configuration(
    name = "my_product",
    product_config_json = {
        platform = {
            foo = {
                bar = True,
            },
        },
    },
)

If you have many configuration options and need to organize them in a way that is not easy to fit into the product config, you can consider a separate config file. A config file can be any format, but most teams choose json. The config file can be passed into the product config as a single file like the following:

fuchsia_product_configuration(
    name = "my_product",
    product_config_json = {
        platform = {
            foo = {
                bar_config = "LABEL(//path/to/config:file.json)",
            },
        },
    },
)

Here are some examples of common situations:

Include feature in When product config flag is And board config flag is
All eng products by default platform.build_type = eng
Products that ask for it platform.<SUBSYSTEM>.<FEATURE> = true
Products that use a board that supports the hardware provided_features = [ "<HARDWARE>" ]

Assembly platform feature flags are declared in //src/lib/assembly/config_schema. (Here is the fonts config that was mentioned previously.)

2. Prepare a subsystem

An assembly subsystem is a group of similar platform features (for example, connectivity, diagnostics, and fonts are all separate subsystems). The job of a subsystem is to read product and board config flags and decide when and how to include platform features.

Below is a simple subsystem. This assumes that you have defined a new feature flag inside FooConfig and are making it available in the define_configuration function. Your subsystem needs to read those flags and call assembly APIs to include your feature.

use crate::subsystems::prelude::*;
use assembly_config_schema::platform_config::foo_config::FooConfig;

pub(crate) struct FooSubsystem;
impl DefineSubsystemConfiguration<FooConfig> for FooSubsystem {
    fn define_configuration(
        context: &ConfigurationContext<'_>,
        foo_config: &FooConfig,
        builder: &mut dyn ConfigurationBuilder,
    ) -> anyhow::Result<()> {

        // Read the flag and enable the feature code.
        // See the below sections for more APIs.
        if foo_config.bar {
            builder.platform_bundle("foo");
        }

        Ok(())
    }
}

After declaring a new subsystem, you can call its define_configuration in subsystems.rs and pass it your config.

See the next section to learn about the available APIs to enable your feature inside the subsystem.

3. Enable your feature code

Determine if your code needs to be enabled at build-time or runtime. Enabling your feature at build-time means that assembly will fully exclude your code from the product when the product does not need it. But it also means that in order to turn on your feature, the build rules must be updated. Enabling your feature at runtime makes it easier to turn on and off without updating the build rules, but results in adding unnecessary code to products that never want the feature.

Whenever possible, build-time enablement is preferred in order to save space, tighten security, enable static analysis, and increase performance for other products that do not need the feature.

Build time

Assembly organizes build-time features using Assembly Input Bundles (AIBs). A feature owner can insert many types of artifacts into a single AIB, and Assembly can be instructed when and how to add that AIB to a product. All AIBs are defined in //bundles/assembly/BUILD.gn. Here is an example:

# Declares a new AIB with the name "foo".
assembly_input_bundle("foo") {
  # Include this package into the "base package set".
  # See RFC-0212 for an explanation on package sets.
  # The provided targets must be fuchsia_package().
  base_packages = [ "//path/to/code:my_package" ]

  # Include this file into BootFS.
  # The provided targets must be bootfs_files_for_assembly().
  bootfs_files_labels = [ "//path/to/code:my_bootfs_file" ]
}

To include the AIB, use the following method in your subsystem:

builder.platform_bundle("foo");

If you add a new AIB, don't forget to add it to the appropriate list in //bundles/assembly/platform_aibs.gni, or you will get an error at build-time indicating that the AIB cannot be found.

Runtime

Assembly supports multiple types of runtime configuration. These types are listed in order of preference.

Config capabilities: A Fuchsia component can read the value of config capabilities at runtime, while Assembly sets the default value for those capabilities at build time, for example:

// Add a config capability named `fuchsia.foo.bar` to the config package.
builder.set_config_capability(
    "fuchsia.foo.bar",
    Config::new(ConfigValueType::String { max_size: 512 }, "my_string".into()),
)?;

Assembly will add all default config capabilities to a config package in BootFS, therefore the capability will need to be routed from the /root component realm to your component.

Domain configs: For complex configurations or those requiring custom types, domain configs are preferable to config capabilities. Domain configs are Fuchsia packages that provide a config file for your component to be read and parsed at runtime, for example:

// Create a new domain config in BlobFS with a file at "my_directory/foo_config.json".
builder.add_domain_config(PackageSetDestination::Blob(PackageDestination::FooConfigPkg))
      .directory("my_directory")
      .entry(FileEntry {
          source: config_src,
          destination: "foo_config.json".into(),
      })?;

Your component must launch the domain config package as a child and use the directory, for example:

{
    children: [
        {
            name: "my-config",
            url: "fuchsia-pkg://fuchsia.com/foo-config#meta/foo-config.cm",
        },
    ],
    use: [
       {
            directory: "my_directory",
            from: "#foo-config",
            path: "/my_directory",
        },
    ],
}

Kernel argument: A kernel argument is only used for enabling kernel features. Assembly constructs a command line to pass to the kernel at runtime, for example:

builder.kernel_arg(KernelArg::MyArgument);

Appendix: Developer overrides

Developers oftentimes want to locally test something on an existing product by adding new code or flipping a feature flag. Modifying the product or board configs is undesirable because it pollutes the git-tree (fuchsia.git). Assembly supports a method of locally modifying an existing product without polluting the git-tree using developer overrides.