借助结构化配置,C++/Rust 组件可以直接在其清单中声明配置架构。使用结构化配置的优势包括:
- 在构建和汇编时检测配置中的错误。
- 可以使用同一组件和不同的配置值创建多个软件包。
- 组件使用静态类型库读取其配置。
- 组件框架仅启动具有有效配置的组件。
- 可以使用
ffx工具在运行时查看配置。 - 可以在运行时设置值。
如需详细了解结构化配置的实现和行为,请参阅其 参考文档。
如需在组件中使用结构化配置,您必须更新 build 规则、声明架构、定义值并生成客户端库。
更新 build 规则
为防止在生成客户端库时出现循环依赖项,请定义一个用于编译组件清单的 fuchsia_component_manifest 规则。将此已编译的清单 GN 标签传递到 fuchsia_component 规则中。
fuchsia_component_manifest("manifest") {
component_name = "config_example"
manifest = "meta/config_example.cml"
}
fuchsia_component("component") {
cm_label = ":manifest"
deps = [ ":bin" ]
}
声明配置架构
您必须在组件的清单中声明配置架构:
{
...
config: {
greeting: {
type: "string",
max_size: 512,
},
delay_ms: { type: "uint64" },
},
}
结构化配置支持以下类型:
booluint8、int8uint16,int16uint32,int32uint64、int64string(需要max_size属性)vector(需要element和max_count属性)
如需查看配置架构的完整语法,请参阅 CML 参考文档。
组件具有配置架构后,您必须使用软件汇编或 GN 为声明的字段定义值。
仅限 Google 员工:如果您的组件针对工程 build 类型和非工程 build 类型配置不同,则必须 先验证结构化配置,然后才能将其签入。
您可以通过两种方式定义配置值:在 JSON5 文件中或在 GN 中内联。
使用软件汇编定义配置值
如果组件的配置因产品而异,请参阅文档 了解汇编结构化配置。对于配置仅在测试和生产等之间有所不同的组件,请参阅下一部分。
使用 GN 定义和打包配置值
fuchsia_structured_config_values GN 模板会根据配置架构验证定义的值,并将其编译到必须与组件打包在一起的 .cvf 文件中。
您可以通过两种方式在 GN 中定义配置值:在 JSON5 文件中或内联。
JSON5 文件
您可以在 JSON5 文件中编写组件的配置值。由于 JSON5 是 JSON 的严格超集,因此现有的 JSON 配置文件也可以重复用于结构化配置。
JSON 对象中的每个键都必须与架构中的配置键相对应,并且值必须是兼容的 JSON 类型:
{
// Print "Hello, World!" by default.
greeting: "World",
// Sleep for only 100ms by default.
delay_ms: 100,
}
在 fuchsia_structured_config_values 规则中提供 JSON5 文件的路径。
fuchsia_structured_config_values("values_from_json_file") {
cm_label = ":manifest"
values_source = "../config_example_default_values.json5"
}
内联值
fuchsia_structured_config_values 模板还支持内联定义配置值:
C++
declare_args() {
# Set this in args.gn to override the greeting emitted by this example.
config_example_cpp_greeting = "World"
}
fuchsia_structured_config_values("values_from_gn") {
cm_label = ":manifest"
values = {
greeting = config_example_cpp_greeting
delay_ms = 100
}
}
Rust
declare_args() {
# Set this in args.gn to override the greeting emitted by this example.
config_example_rust_greeting = "World"
}
fuchsia_structured_config_values("values_from_gn") {
cm_label = ":manifest"
values = {
greeting = config_example_rust_greeting
delay_ms = 100
}
}
通过使用 declare_args,您可以在构建时通过命令行更改配置值:
C++
$ fx set core.x64 \
--with //examples/components/config \
--args='config_example_cpp_greeting="C++ CLI Override"'
Rust
$ fx set core.x64 \
--with //examples/components/config \
--args='config_example_rust_greeting="Rust CLI Override"'
打包组件和值
如需将组件和一组值打包在一起,请将 fuchsia_component 和 fuchsia_structured_config_values 规则添加为 fuchsia_package 的依赖项。
C++
fuchsia_package("cpp_config_example") {
deps = [
":component",
":values_from_gn",
]
}
Rust
fuchsia_package("rust_config_example") {
deps = [
":component",
":values_from_gn",
]
}
构建系统会验证组件的配置架构和值文件。配置有缺陷的组件(例如:字段不匹配、约束条件错误、缺少值文件)将无法构建。
检查配置
组件管理器会在解析组件时验证组件的配置。
使用 ffx component show 输出组件的配置键值对。组件不必运行即可执行此操作。
$ ffx component show config_example
Moniker: /core/ffx-laboratory:config_example
Component State: Resolved
...
Configuration: greeting -> "World"
...
CVF 文件必须与组件的配置架构完全匹配。配置不完整或缺失的组件将无法解析,因此无法启动。
读取配置
组件使用生成的库读取其解析的配置值。使用以下 build 模板生成库:
C++
executable("bin") {
# ...
deps = [
":example_config",
# ...
]
}
fuchsia_structured_config_cpp_elf_lib("example_config") {
cm_label = ":manifest"
}
Rust
rustc_binary("bin") {
edition = "2024"
# ...
deps = [
":example_config",
# ...
]
}
fuchsia_structured_config_rust_lib("example_config") {
cm_label = ":manifest"
}
使用库中的以下函数读取配置值:
C++
// Import the header as if it's located in the same directory as BUILD.gn:
#include "examples/components/config/cpp/example_config.h"
...
// Retrieve configuration
auto c = example_config::Config::TakeFromStartupHandle();
Rust
use example_config::Config;
...
// Retrieve configuration
let config = Config::take_from_startup_handle();
将配置导出到 Inspect
您可以将组件的配置导出到 Inspect,以便在崩溃报告中使用。客户端库具有将组件的配置导出到 Inspect 树的函数:
C++
// Record configuration to inspect
inspect::ComponentInspector inspector(loop.dispatcher(), inspect::PublishOptions{});
inspect::Node config_node = inspector.root().CreateChild("config");
c.RecordInspect(&config_node);
Rust
// Record configuration to inspect
let inspector = fuchsia_inspect::component::inspector();
inspector.root().record_child("config", |config_node| config.record_inspect(config_node));
使用 ffx inspect show 输出组件的导出配置:
$ ffx inspect show core/ffx-laboratory\*config_example
core/ffx-laboratory\:config_example:
...
payload:
root:
config:
greeting = World
来自组件外部的运行时值
默认情况下,组件只会从其解析器返回的
.cvf文件(通常来自其软件包)接收配置值。如需让其他组件替换这些值,它们必须具有 mutability 属性,该属性会选择让它们加入特定的突变来源:
config: {
greeting: {
// ...
mutability: [ /* ... */ ],
},
},
提供来自父组件的值
当子组件选择接收值时,父组件可以向集合中启动的子组件提供值。
首先,子组件必须将 mutability 属性添加到相应的配置字段:
{
...
config: {
greeting: {
type: "string",
max_size: 512,
mutability: [ "parent" ],
},
},
}
使用 Realm 协议启动子组件时,将配置值作为替换项传递:
C++
fuchsia_component_decl::Child child_decl;
child_decl.name(child_name);
child_decl.url(kChildUrl);
child_decl.startup(fuchsia_component_decl::StartupMode::kLazy);
fuchsia_component_decl::ConfigOverride greeting_override;
greeting_override.key("greeting");
greeting_override.value(fuchsia_component_decl::ConfigValue::WithSingle(
fuchsia_component_decl::ConfigSingleValue::WithString(expected_greeting)));
child_decl.config_overrides({{greeting_override}});
fuchsia_component_decl::CollectionRef collection;
collection.name(kCollectionName);
fuchsia_component::CreateChildArgs child_args;
realm->CreateChild({collection, child_decl, std::move(child_args)})
.ThenExactlyOnce([this](fidl::Result<fuchsia_component::Realm::CreateChild>& result) {
if (!result.is_ok()) {
FX_LOGS(ERROR) << "CreateChild failed: " << result.error_value();
ZX_PANIC("%s", result.error_value().FormatDescription().c_str());
}
QuitLoop();
});
RunLoop();
Rust
let child_decl = Child {
name: Some(child_name.to_string()),
url: Some(String::from(CHILD_URL)),
startup: Some(StartupMode::Lazy),
config_overrides: Some(vec![ConfigOverride {
key: Some("greeting".to_string()),
value: Some(ConfigValue::Single(ConfigSingleValue::String(
expected_greeting.to_string(),
))),
..ConfigOverride::default()
}]),
..Child::default()
};
let collection_ref = CollectionRef { name: COLLECTION_NAME.to_string() };
realm
.create_child(&collection_ref, &child_decl, CreateChildArgs::default())
.await
.context("sending create child message")?
.map_err(|e| anyhow::format_err!("creating child: {e:?}"))?;
如需了解详情,请参阅完整示例。
使用 ffx component 提供值
使用 ffx component create 创建组件或使用 ffx component run 运行组件时,您可以替换配置值,就像您充当父组件一样。
如果您在不进行任何替换的情况下运行上述配置示例,您可以看到它将默认配置输出到其日志中:
$ ffx component run /core/ffx-laboratory:hello-default-value fuchsia-pkg://fuchsia.com/rust_config_from_parent_example#meta/config_example.cm --follow-logs
...
[02655.273631][ffx-laboratory:hello-default-value] INFO: Hello, World! (from Rust)
传递 --config 可让您替换该问候语:
$ ffx component run /core/ffx-laboratory:hello-from-parent fuchsia-pkg://fuchsia.com/rust_config_from_parent_example#meta/config_example.cm --config 'greeting="parent component"' --follow-logs
...
[02622.752978][ffx-laboratory:hello-from-parent] INFO: Hello, parent component! (from Rust)`
每个配置字段都指定为 KEY=VALUE,其中 KEY 必须是组件的配置架构中具有 mutability: [ "parent" ] 的字段,而 VALUE 是可以解析为与字段类型匹配的 JSON 的字符串。
使用 Realm Builder 进行测试
无论配置字段的 mutability 如何,您都可以使用 Realm Builder 动态替换
组件的配置值。
TODO(https://fxbug.dev/42053123) 添加一个部分,介绍如何与子软件包组件搭配使用。
C++
realm_builder.SetConfigValue(child_name, "greeting", "Fuchsia");
Rust
builder.set_config_value(&config_component, "greeting", "Fuchsia".into()).await.unwrap();
Realm Builder 会根据组件的配置架构验证替换的值。
动态设置值时,Realm Builder 要求用户选择是否为启动的组件加载打包的配置值。
如需在代码中提供部分值时加载组件的打包值,请执行以下操作:
C++
realm_builder.InitMutableConfigFromPackage(child_name);
Rust
builder.init_mutable_config_from_package(&config_component).await.unwrap();
如需在代码中设置组件的所有值而不使用打包值,请执行以下操作:
C++
realm_builder.InitMutableConfigToEmpty(child_name);
Rust
builder.init_mutable_config_to_empty(&config_component).await.unwrap();