此 Codelab 将引导您完成在 Fuchsia 平台中实现新功能的过程,该功能可根据产品或主板配置有条件地包含和配置。
前提条件
本 Codelab 假定您熟悉以下内容:
- Fuchsia 的源代码树和构建系统(GN 和 Bazel)。
- Rust 编程语言。
- Fuchsia 组件概念。
- 软件组装概念(产品配置、板级配置、平台)。
学习内容
- 如何确定某项功能是否应属于平台。
- 如何在 Assembly 配置架构中定义功能标志。
- 如何创建和注册 Assembly 子系统。
- 如何使用子系统 API 来包含软件包、配置和内核实参。
- 如何在产品或主板中启用新平台功能。
功能放置指南
平台功能始终在 fuchsia.git 中实现,并且必须足够通用,以便在多个产品或主板上启用。如果某项功能仅适用于单个产品或主板,则不应将其放在平台中。
Fuchsia 平台是所有产品的核心共享基础。向平台添加特定于产品或主板的功能会增加平台的大小和复杂性,从而给所有其他产品带来长期的维护负担。保持平台通用性至关重要。
请按照以下准则决定将功能放置在何处:
平台功能:对多种产品或主板有用的功能。它应该是通用且可配置的,例如一种新的可选网络服务,不同的产品可以选择包含和配置该服务。
产品功能:特定于某个产品的功能。这通常是最终用户可见的功能,例如构建界面的 Fuchsia 软件包,或单个产品的独特字体集。
板级功能:特定于某个板级硬件的功能,例如特定硬件组件的驱动程序,或该板级独有的配置值(例如 GPIO 引脚号)。
Codelab 步骤
实现平台功能涉及以下步骤:
图 1. 实现平台功能并在产品配置中启用该功能。
1. 在 config_schema 中声明功能标志
平台功能通常会根据产品或板级配置中设置的标志有条件地启用。第一步是定义这些标志的架构。
所有平台功能标志都在 //src/lib/assembly/config_schema 中的 Rust 结构体内声明。每个子系统通常在此目录中都有自己的文件(例如 fonts_config.rs 或 network_config.rs)。
示例:如需查看示例,请按照以下步骤操作:
如需定义一个标志来启用假设的“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)]结合使用,因为这可能会导致行为不一致。
- 始终在
将此新配置结构体添加到
//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, }在产品配置中,您现在可以启用此新功能。例如:
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 中。
如需查看示例,请按以下步骤操作:
为“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、我们的特定TandemConfig和ConfigurationBuilder。- 在此函数内,我们检查
enabled标志。如果为 true,我们将使用builder将功能组件添加到系统映像。
- 结构体
将模块添加到
//src/lib/assembly/platform_configuration/src/subsystems.rs:// ... other mods mod tandem;在同一文件的主
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")必须包含在平台提供的配置的usestanza 中。- 如果类型为
"string",您还必须添加max_size。 usestanza 中的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 功能及其在子系统中定义的配置。