This codelab walks through the process of defining and building a new product bundle in Fuchsia using the Bazel build system. A product bundle is the distributable artifact that contains all the images and metadata needed to flash, update, or emulate a Fuchsia product.
As part of this process, you will also define a product configuration to specify the software features, settings, and packages that make up the product.
A product configuration in Fuchsia defines the user experience for a specific product. It is distinct from a board configuration, which specifies hardware-specific details. A product configuration typically includes:
Product configurations are defined using the fuchsia_product_configuration rule in BUILD.bazel files.
Let's explore how to define a product configuration and use it to build a system image.
A product configuration is primarily a JSON object (represented as a dictionary in Starlark) passed to the fuchsia_product_configuration rule.
# //products/my_product/BUILD.bazel load( "@rules_fuchsia//fuchsia:assembly.bzl", "fuchsia_product_configuration", ) fuchsia_product_configuration( name = "product_config", product_config_json = { "platform": { "build_type": "eng", "feature_set_level": "standard", }, "product": {}, }, )
The platform key in the configuration dictionary controls various aspects of the Fuchsia system. Common fields include:
build_type: Controls the security and optimization level.eng: Engineering build. This build is best for debugging as it has assertions enabled and optimizations disabled.userdebug: This build is optimized like the user build, but with some debug features and tools enabled.user: Production build. This build is fully optimized and secure.feature_set_level: Defines the baseline set of features included in the system.embeddable: Minimal subset of bootstrap. This is optimized for memory constrained environments and does not self-update.bootstrap: Bootable, serial-only. Only the /bootstrap realm. No netstack or storage drivers. Primarily used for board-level bringup.utility: Smallest configuration with the /core realm. Best for utility-type systems like recovery.standard: A “full Fuchsia” configuration. Includes netstack and self-update capabilities. This is the default.storage: Configures the filesystem (e.g., Fxfs) and storage layout.For a full list of platform settings, see the PlatformSettings documentation.
For example, a Fuchsia product configuration with platform settings may look like the following:
fuchsia_product_configuration( name = "product_config", product_config_json = { {{"<strong>"}} "platform": { "build_type": "eng", "feature_set_level": "standard", "storage": { "filesystems": { "volume": "fxfs", }, }, }, "product": {}, {{"</strong>"}} }, )
The product key is used for higher-level product settings.
One of the most important settings is the session. A session is the top-level component that defines the product's user experience (e.g., the graphical shell or main application).
To use a session, you must specify its URL and ensure the package containing the component is included in the base packages.
Base packages are the set of packages that are included in the system image. They are available immediately at boot, are immutable (read-only), and are updated as part of the system OTA.
For example, a Fuchsia product configuration with product settings may look like the following:
Example:
fuchsia_product_configuration( name = "product_config", product_config_json = { "platform": { "build_type": "eng", "feature_set_level": "standard", }, {{"<strong>"}} "product": { "session": { "url": "fuchsia-pkg://fuchsia.com/my_session#meta/my_session.cm", }, }, }, base_packages = [ "//src/my_session:my_session_package", ], {{"</strong>"}} )
For a full list of product settings, see the ProductSettings documentation.
Once you have a fuchsia_product_configuration, you can use the fuchsia_product rule to assemble the final system image. This rule combines your product configuration with a specific board configuration.
load( "@rules_fuchsia//fuchsia:assembly.bzl", "fuchsia_product", ) fuchsia_product( name = "image.x64", board_config = "//boards:x64", platform_artifacts = "//build/bazel/assembly/assembly_input_bundles:platform_eng", {{"<strong>"}} product_config = ":product_config", {{"</strong>"}} )
board_config: Points to the board configuration target.platform_artifacts: Points to the bundle of platform artifacts (AIBs) to use.To run your product on a device or emulator, you need to package it into a Product Bundle using the fuchsia_product_bundle rule.
load( "@rules_fuchsia//fuchsia:assembly.bzl", "fuchsia_product_bundle", ) fuchsia_product_bundle( name = "my_product.x64", product_bundle_name = "my_product.x64", {{"<strong>"}} main = ":image.x64", {{"</strong>"}} )
This target produces the artifacts needed for ffx product-bundle commands.
For example:
Run in emulator:
ffx emu start my_product.x64
Flash to device:
ffx target flash -b my_product.x64
To build your product bundle, it must be registered with the GN build system. This involves the following steps:
Fuchsia's build system uses GN, but your product bundle is defined in Bazel. You need to use the bazel_product_bundle GN template to create a bridge.
Note: The bazel_inputs_from_gn list ensures that necessary board artifacts (like the drivers and bootloader) are available to Bazel. The allow_eng_platform_bundle_use flag is required because this example uses an eng build type in the product configuration.
Create a BUILD.gn file in your product directory (e.g., //products/my_product/BUILD.gn):
# //products/my_product/BUILD.gn import("//build/bazel/assembly/bazel_product_bundle.gni") bazel_product_bundle("my_product.x64") { testonly = true product_bundle_name = "my_product.x64" bazel_product_bundle_target = ":my_product.x64" bazel_product_image_target = ":image.x64" bazel_inputs_from_gn = [ "//boards/x64:x64.bazel_input", "//build/images/flash:esp.bazel_input", ] allow_eng_platform_bundle_use = true }
You may need to add your new product to the bazel_action_allowlist in //build/bazel/BUILD.gn to avoid visibility errors:
# //build/bazel/BUILD.gn group("bazel_action_allowlist") { visibility = [ # ... "//products/my_product:*", ] # ... }
You may also need to add it to the non_hermetic_deps visibility list in //build/BUILD.gn:
# //build/BUILD.gn group("non_hermetic_deps") { visibility = [ # ... "//products/my_product:*", ] # ... }
This GN target (:my_product.x64) now represents your Bazel product bundle in the GN build graph.
Finally, add this new GN target to the global list of product bundles in //products/BUILD.gn to make it discoverable by fx:
# //products/BUILD.gn group("product_bundles") { testonly = true deps = [ # ... other products "//products/my_product:my_product.x64", ] }
Fuchsia supports multi-product builds, meaning you can have multiple products available in the same build directory.
Configure your build environment using fx set. You should use a product configuration that enables the Bazel build system, such as fuchsia.x64:
fx set fuchsia.x64
To work with your specific product, set it as the “main” product bundle. This configures fx tools to target your product by default:
fx set-main-pb my_product.x64
To build your currently selected main product (and its dependencies):
fx build
If you want to explicitly build a specific product bundle regardless of the main setting:
fx build my_product.x64
You can switch the active “main” product bundle at any time without re-running fx set by running fx set-main-pb again:
fx set-main-pb minimal.x64
In a multi-product build environment, all products share the same GN arguments (defined by fx set). Therefore, you cannot use fx set ... --args to configure product-specific features. Instead, all product configuration must be done via the fuchsia_product_configuration Bazel rule as demonstrated in this codelab.
This codelab covered the basics of defining and building a new product bundle.
To continue learning, you can:
//products.