Codelab:实现平台功能

此 Codelab 将引导您完成在 Fuchsia 平台中实现新功能的过程,该功能可根据产品或主板配置有条件地包含和配置。

前提条件

本 Codelab 假定您熟悉以下内容:

学习内容

  • 如何确定某项功能是否应属于平台。
  • 如何在 Assembly 配置架构中定义功能标志。
  • 如何创建和注册 Assembly 子系统。
  • 如何使用子系统 API 来包含软件包、配置和内核实参。
  • 如何在产品或主板中启用新平台功能。

功能放置指南

平台功能始终fuchsia.git 中实现,并且必须足够通用,以便在多个产品或主板上启用。如果某项功能仅适用于单个产品或主板,则不应将其放在平台中。

Fuchsia 平台是所有产品的核心共享基础。向平台添加特定于产品或主板的功能会增加平台的大小和复杂性,从而给所有其他产品带来长期的维护负担。保持平台通用性至关重要。

请按照以下准则决定将功能放置在何处:

  • 平台功能:对多种产品或主板有用的功能。它应该是通用且可配置的,例如一种新的可选网络服务,不同的产品可以选择包含和配置该服务。

  • 产品功能:特定于某个产品的功能。这通常是最终用户可见的功能,例如构建界面的 Fuchsia 软件包,或单个产品的独特字体集。

  • 板级功能:特定于某个板级硬件的功能,例如特定硬件组件的驱动程序,或该板级独有的配置值(例如 GPIO 引脚号)。

Codelab 步骤

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

  1. 在 config_schema 中声明功能标志
  2. 定义新的子系统
  3. 实现子系统逻辑
  4. 在产品/主板中启用该功能

图表:展示平台功能的实现方式

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

1. 在 config_schema 中声明功能标志

平台功能通常会根据产品或板级配置中设置的标志有条件地启用。第一步是定义这些标志的架构。

所有平台功能标志都在 //src/lib/assembly/config_schema 中的 Rust 结构体内声明。每个子系统通常在此目录中都有自己的文件(例如 fonts_config.rsnetwork_config.rs)。

示例:如需查看示例,请按照以下步骤操作:

  1. 如需定义一个标志来启用假设的“Tandem”联网功能,请创建一个新文件 //src/lib/assembly/config_schema/src/platform_settings/tandem_config.rs

    use serde::{Deserialize, Serialize};
    
    /// Configuration for the Tandem networking feature.
    #[derive(Debug, Deserialize, Serialize, PartialEq)]
    #[serde(default, deny_unknown_fields)]
    pub struct TandemConfig {
        /// Enables the core Tandem service.
        pub enabled: bool,
    
        /// Specifies the maximum number of concurrent connections.
        pub max_connections: u32,
    }
    
    // Choose reasonable default values.
    impl Default for TandemConfig {
        fn default() -> Self {
            Self {
                enabled: false,
                max_connections: 10,
            }
        }
    }
    

    默认值的最佳实践

    • 始终在 PlatformSettings 内的结构字段上使用 #[serde(default)](如下一步所示)。
    • 为配置结构体(在本例中为 TandemConfig)实现 impl Default,以定义每个字段的默认值。
    • 避免将字段级 #[serde(default = "...")] 与结构体上的 #[derive(Default)] 结合使用,因为这可能会导致行为不一致。
  2. 将此新配置结构体添加到 //src/lib/assembly/config_schema/src/platform_settings.rs 中的主 PlatformSettings

    // ... other imports
    mod tandem_config;
    
    // ... other fields
    pub struct PlatformSettings {
        // ... other fields
        #[serde(default)]
        pub tandem: tandem_config::TandemConfig,
    }
    
  3. 在产品配置中,您现在可以启用此新功能。例如:

    fuchsia_product_configuration(
        name = "my_product",
        product_config_json = {
            platform = {
                tandem = {
                    enabled = True,
                    max_connections = 20,
                },
            },
        },
    )
    

    常见标志模式

    已启用相应功能的国家/地区... 当产品配置集... 或者当板级配置设置...
    所有价格为 eng的商品 platform.build_type = eng
    特定商品 platform.tandem.enabled = True
    支持的板卡上的产品 provided_features = [ "tandem_hw" ]

2. 定义新的子系统

组装子系统是一个 Rust 模块,负责处理一组相关平台功能的配置。它会读取 config_schema 中定义的标志,并使用 Assembly 构建器 API 来包含和配置功能代码。

位置:子系统位于 //src/lib/assembly/platform_configuration/src/subsystems 中。

如需查看示例,请按以下步骤操作:

  1. 为“Tandem”功能创建新的文件 //src/lib/assembly/platform_configuration/src/subsystems/tandem.rs

    use crate::subsystems::prelude::*;
    use assembly_config_schema::platform_config::tandem_config::TandemConfig;
    
    pub(crate) struct TandemSubsystem;
    impl DefineSubsystemConfiguration<TandemConfig> for TandemSubsystem {
        fn define_configuration(
            context: &ConfigurationContext<'_>,
            tandem_config: &TandemConfig,
            builder: &mut dyn ConfigurationBuilder,
        ) -> anyhow::Result<()> {
            if tandem_config.enabled {
                // Actions to include the feature will go here
                // See Step 3 for details
                println!("Tandem feature enabled with max_connections: {}", tandem_config.max_connections);
            }
            Ok(())
        }
    }
    

    说明

    • 结构体 TandemSubsystem 实现了 DefineSubsystemConfiguration 特征,并使用我们之前定义的 TandemConfig 结构体进行类型化。
    • define_configuration 函数会接收 ConfigurationContext、我们的特定 TandemConfigConfigurationBuilder
    • 在此函数内,我们检查 enabled 标志。如果为 true,我们将使用 builder 将功能组件添加到系统映像。
  2. 将模块添加到 //src/lib/assembly/platform_configuration/src/subsystems.rs

    // ... other mods
    mod tandem;
    
  3. 在同一文件的主 Subsystems::define_configuration 函数中调用其 define_configuration 函数,并传递平台设置的相关部分:

    // In Subsystems::define_configuration
    tandem::TandemSubsystem::define_configuration(
        context,
        &platform.tandem,
        builder,
    )?;
    

3. 实现子系统逻辑

在子系统的 define_configuration 函数中,您将使用 ConfigurationBuilder 方法根据功能标志向产品添加功能。

正在更新 tandem.rs,例如

use crate::subsystems::prelude::*;
use assembly_config_schema::platform_config::tandem_config::TandemConfig;
use assembly_platform_configuration::{
    ConfigurationBuilder,
    KernelArg,
    ConfigValueType,
    Config,
    PackageSetDestination,
    PackageDestination,
    FileEntry,
    BuildType
};

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

        if tandem_config.enabled {
            // 1. Add the feature's code at build time using an Assembly Input Bundle (AIB)
            builder.platform_bundle("tandem_core");

            // 2. Set a runtime configuration value
            builder.set_config_capability(
                "fuchsia.tandem.MaxConnections",
                Config::new(ConfigValueType::Int32, tandem_config.max_connections.into()),
            )?;

            // 3. Conditionally add a runtime kernel argument based on build type
            if context.build_type == &BuildType::Eng {
                builder.kernel_arg(KernelArg::TandemEngDebug);
            }

            // 4. Include a domain config package for more complex runtime configuration
            builder.add_domain_config(PackageSetDestination::Blob(PackageDestination::TandemConfigPkg))
                  .directory("config/data")
                  .entry(FileEntry {
                      source: "//path/to/tandem/configs:default.json".into(),
                      destination: "settings.json".into(),
                  })?;
        }

        Ok(())
    }
}

构建时与运行时启用

  • 构建时:仅当相应功能处于启用状态时,工件才会包含在映像中。这有助于节省空间、提高安全性、启用静态分析,并提高不需要该功能的其他产品的性能。
  • 运行时:工件始终包含在内,但其行为在运行时受控制(例如,通过配置值或内核实参)。

构建时间

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

# Declares a new AIB with the name "tandem_core".
assembly_input_bundle("tandem_core") {
  # 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/my/tandem:pkg" ]

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

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

builder.platform_bundle("tandem_core");

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

运行时

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

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

// Add a config capability named `fuchsia.tandem.MaxConnections` to the config package.
builder.set_config_capability(
    "fuchsia.tandem.MaxConnections",
    Config::new(ConfigValueType::Int32, tandem_config.max_connections.into()),
)?;

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

在组件中使用平台定义的配置功能

当组件需要使用平台(通过子系统中的 builder.set_config_capability)定义和提供的配置功能时,组件的 CML 文件必须包含 use 声明。即使值由父 realm 提供,此声明也必须指定配置值的 type

示例组件 CML (my_component.cml)

{
    use: [
        {
            config: "fuchsia.tandem.MaxConnections", // The capability name
            from: "parent",
            key: "max_conn", // The key used in this component's structured config
            type: "int32",     // The type MUST be specified here
        },
    ],
    // ... other parts of the manifest
    config: {
        max_conn: { type: "int32" },
    },
}

组件代码

您还必须更新组件的源代码,以使其在其结构化配置中预期此键。这通常涉及更新反序列化配置值的结构,该结构通常由 ffx component config get 命令或类似工具生成。定义一个用于反序列化配置的结构:

// Example in the component's config.rs (e.g., src/config.rs)
use serde::Deserialize;

// This struct should match the keys and types in the CML 'config' block.
#[derive(Debug, Deserialize)]
pub struct TandemComponentConfig {
    pub max_conn: i32,
    // ... other config fields
}

然后,在组件的初始化代码中,检索配置:

let config = fuchsia_component::config::Config::take_from_startup_handle();
let tandem_config: TandemComponentConfig = config.get();

要点

  • type(例如,"bool""int32""string")必须包含在平台提供的配置的 use stanza 中。
  • 如果类型为 "string",您还必须添加 max_size
  • use stanza 中的 key 将功能映射到组件自身 config 架构中的字段名称,从而映射到用于在组件代码中加载配置的结构体中的字段。
  • 确保组件的代码(例如,Rust、C++) 已更新,可处理新的配置键。

网域配置:对于复杂的配置、项目列表或需要自定义类型的配置,网域配置比配置功能更合适。虽然通常可以将复杂的配置“扁平化”为一组简单的键值对以实现配置功能,但这可能会变得难以管理。

例如,假设某个组件需要一个网络端点列表,其中每个端点都有网址、端口和协议。使用配置功能时,您可能需要将其扁平化为一系列键。例如:

// This approach is NOT recommended for lists or complex types.
"endpoint.0.url": "host1.example.com",
"endpoint.0.port": 443,
"endpoint.1.url": "host2.example.com",
"endpoint.1.port": 8080,

这会变得难以管理,尤其是在端点数量可变的情况下。在这种情况下,网域配置是一种更简洁的解决方案。您可以在软件包中提供单个 JSON 文件,供组件在运行时解析:

// A domain config file (e.g., tandem_config.json)
{
  "endpoints": [
    {
      "url": "host1.example.com",
      "port": 443,
      "protocol": "HTTPS"
    },
    {
      "url": "host2.example.com",
      "port": 8080,
      "protocol": "HTTP"
    }
  ]
}

网域配置是 Fuchsia 软件包,可提供一个配置文件,供您的组件在运行时读取和解析,例如:

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

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

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

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

builder.kernel_arg(KernelArg::TandemEngDebug);

4. 在产品/主板中启用该功能

实现子系统后,您可以在产品或主板配置文件中设置第 1 步中定义的标志,以启用该功能。

在产品配置中启用

如需为特定产品启用“Tandem”功能,请修改其 fuchsia_product_configuration 目标(通常在 BUILD.bazel 文件中):

fuchsia_product_configuration(
    name = "my_product",
    product_config_json = {
        platform = {
            # ... other platform settings
            tandem = {
                enabled = True,
                max_connections = 20,
            },
        },
    },
    # ... other attributes
)

根据主板功能启用

板级功能是指开发板声明其支持特定硬件的方式。平台子系统可以读取主板 context.board_config.provided_features,以有条件地启用或停用功能。

如果您的子系统逻辑会检查主板功能,请确保主板配置(例如 //boards/my_board/BUILD.bazel)中包含该值。例如:

fuchsia_board_configuration(
    name = "my_board",
    provided_features = [
        "tandem_hw",  # This will be seen by context.board_config.provided_features
    ],
    # ...
)

修改配置后,重新构建产品软件包将包含 Tandem 功能及其在子系统中定义的配置。