软件组装

借助软件组装,开发者可以使用自定义的操作系统快速构建产品。具体来说,软件组装是一组工具,可根据输入(包括一组 Fuchsia 软件包、一个内核和配置文件)生成产品软件包 (PB)。

产品套装

产品软件包是一个包含规范明确的制品的目录,可运送到任何环境,用于刷写、更新或模拟 Fuchsia 目标。不建议开发者直接检查 Product Bundle 的内容,而是可以依赖 Fuchsia 提供的工具。例如,ffx 工具用于使用产品软件包刷写或模拟设备:

# 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>

产品包可能同时包含产品和恢复产品,它们是不同的可启动体验。main 通常用于真实产品体验,如果 main 出现故障且无法启动,则使用 recoveryrecovery 通常是一种轻量级体验,能够通过恢复出厂设置或无线 (OTA) 更新来解决 main 插槽中的问题。通常,最终用户可以在启动期间按住设备上的某个实体按钮,切换要启动的产品。

软件组装提供用于构建产品软件包的 build 工具,定义了输入(平台、产品配置、主板配置)的格式,并提供了用于构建这些输入的 build 规则。

包含主商品和恢复商品的产品套装

图 1. 产品套装可能同时包含主产品和恢复产品。

平台、产品配置和板级配置

组装的三个输入(平台产品配置主板配置)是制品目录。内部格式可能会发生变化,不应依赖于此。开发者需要使用提供的 Bazel 或 GN 构建规则来构建和使用这些输入。

平台由 Fuchsia 团队打造。它包含任何 Fuchsia 产品可能想要使用的每一位已编译的平台代码。Fuchsia 团队将平台发布到 https://chrome-infra-packages.appspot.com/p/fuchsia/assembly/platform

产品配置由开发者定义最终用户体验来生成。它可能包含标志,用于指明要包含的平台功能。例如,产品配置可以设置 platform.fonts.enabled=true,从而使程序集包含来自平台的相应字体支持。 如需了解所有可用标志,请参阅此参考文档。产品配置还可以包含用于构建用户体验的自定义代码。

主板配置由支持特定硬件目标的开发者生成。它包含在该硬件上启动所需的所有驱动程序。 此外,主板配置还可以声明平台可使用的硬件。例如,如果硬件具有实时时钟 (RTC),则板配置可以通过设置 provided_features=["fuchsia::real_time_clock"] 标志来指明这一点。程序集会读取此标志,并包含来自平台的必要代码,以使用此硬件。Fuchsia 团队维护着一小部分主板配置,并将其发布到 https://chrome-infra-packages.appspot.com/p/fuchsia/assembly/boards

环境

其他操作系统也支持自定义,但 Fuchsia 软件汇编具有在任何可想象的环境中运行并快速运行的独特能力。Fuchsia 产品可以在不到一分钟的时间内完成自定义和组装,这比其他操作系统快得多。

Fuchsia 软件组装目前在以下环境中受支持(虽然没有技术限制阻止 Fuchsia 在未来扩展支持):

  • Bazel
  • CLI(实验性)
  • GN(以 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 = [ ... ],
)

如需查看完整示例,请参阅 getting-started 代码库。

命令行界面

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

您可以运行 ffx product-bundle create 命令,使用已构建的平台、主板和产品制品生成新的产品软件包。

规模和审查

软件组装提供用于验证产品包质量的工具。

大小检查工具会告知用户商品包是否符合目标硬件的分区大小限制。您可以使用以下 Bazel 规则生成产品大小报告

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

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

审查工具可确保产品包符合一组安全标准。如果开发者提供必要的审查配置,scrutiny 会在构建产品软件包期间运行。请参阅以下审查配置示例:

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.
)

实现平台功能

本部分介绍了如何在平台中实现可通过产品配置或主板配置启用的新功能。

平台功能始终在 fuchsia.git 中实现,并且必须足够通用,以便在多个产品或主板上启用。如果该功能特定于某个产品或主板,请考虑将其放在产品或主板配置中。

实现平台功能通常涉及以下步骤:

  1. 在产品配置或主板配置中写入功能标志
  2. 准备一个可以读取标志的子系统
  3. 在子系统中编写并启用功能代码

显示平台功能实现方式的示意图

图 2. 实现平台功能并在产品配置中启用该功能。

1. 编写功能标志(如有必要)

平台功能通常不会默认添加到所有产品中,但当产品或主板配置设置了特定标志时,通常会添加这些功能。编写平台功能的第一步是确定何时需要启用该功能,以及需要哪些产品或主板标志才能启用该功能。

例如,如果您想允许产品配置启用名为 foo.bar 的功能,则可以按如下方式编写产品配置:

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

如果您有许多配置选项,并且需要以不易于纳入产品配置的方式来整理这些选项,可以考虑使用单独的配置文件。配置文件可以是任何格式,但大多数团队会选择 json。配置文件可以作为单个文件传递到产品配置中,如下所示:

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

以下是一些常见情况示例:

包含的功能 当商品配置标志为 并且板级配置标志为
默认情况下,所有 eng 商品 platform.build_type = eng
要求提供此信息的商品 platform.<SUBSYSTEM>.<FEATURE> = true
使用支持相应硬件的开发板的产品 provided_features = [ "<HARDWARE>" ]

程序集平台功能标志在 //src/lib/assembly/config_schema 中声明。(此处是之前提到的字体配置。)

2. 准备子系统

程序集子系统是一组类似的平台功能(例如,connectivity, diagnosticsfonts 都是单独的子系统)。子系统的任务是读取产品和主板配置标志,并决定何时以及如何包含平台功能。

下面是一个简单的子系统。此示例假设您已在 FooConfig 内定义了一个新的功能标志,并使其在 define_configuration 函数中可用。您的子系统需要读取这些标志,并调用程序集 API 来包含您的功能。

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(())
    }
}

声明新子系统后,您可以在 subsystems.rs 中调用其 define_configuration 并将配置传递给它。

如需了解可用于在子系统内启用功能的 API,请参阅下一部分。

3. 启用功能代码

确定您的代码需要在构建时还是运行时启用。 在 build 时启用该功能意味着,当产品不需要您的代码时,程序集将完全从产品中排除您的代码。但这同时也意味着,为了启用该功能,必须更新 build 规则。在运行时启用功能可以更轻松地开启和关闭功能,而无需更新 build 规则,但会导致向不需要该功能的产品添加不必要的代码。

在可能的情况下,最好在 build 时启用该功能,以便节省空间、提高安全性、启用静态分析,并提高不需要该功能的其他产品的性能。

构建时间

Assembly 使用 Assembly Input Bundle (AIB) 来整理 build-time 功能。功能所有者可以将多种类型的制品插入单个 AIB 中,并可以指示 Assembly 何时以及如何将该 AIB 添加到产品中。所有 AIB 均在 //bundles/assembly/BUILD.gn 中定义。示例如下:

# 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" ]
}

如需添加 AIB,请在子系统中使用以下方法:

builder.platform_bundle("foo");

如果您添加了新的 AIB,请务必将其添加到 //bundles/assembly/platform_aibs.gni 中的相应列表,否则您会在 build 时收到错误,表明找不到该 AIB。

运行时

程序集支持多种类型的运行时配置。这些类型按优先顺序列出。

配置功能:Fuchsia 组件可以在运行时读取配置功能的值,而 Assembly 会在 build 时为这些功能设置默认值,例如:

// 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()),
)?;

程序集会将所有默认配置功能添加到 BootFS 中的配置软件包,因此需要将功能从 /root 组件 realm 路由到您的组件。

网域配置:对于复杂配置或需要自定义类型的配置,网域配置比配置功能更可取。网域配置是 Fuchsia 软件包,可提供一个配置文件,供您的组件在运行时读取和解析,例如:

// 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(),
      })?;

您的组件必须以子进程身份启动网域配置软件包,并 use 相应目录,例如:

{
    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",
        },
    ],
}

内核实参:内核实参仅用于启用内核功能。汇编会在运行时构建要传递给内核的命令行,例如:

builder.kernel_arg(KernelArg::MyArgument);

附录:开发者替换

开发者有时会想通过添加新代码或翻转功能标志来在现有产品上本地测试某些内容。修改产品或主板配置是不理想的,因为这会污染 git 树 (fuchsia.git)。Assembly 支持一种在本地修改现有产品的方法,该方法使用开发者替换项,不会污染 git 树。