结构化配置允许 C++/Rust 组件直接在其清单中声明配置架构。使用结构化配置的优势包括:
- 配置中的错误是在构建和组装时检测到的。
- 您可以使用同一组件和不同的配置值创建多个软件包。
- 组件通过静态类型的库读取其配置。
- 组件框架仅启动配置有效的组件。
- 可在运行时使用
ffx
工具查看配置。 - 可在运行时设置值。
如需详细了解结构化配置的实现和行为,请参阅其参考文档。
如需在组件中使用结构化配置,您必须更新构建规则、声明架构、定义值并生成客户端库。
更新构建规则
如需在生成客户端库时防止出现循环依赖项,请定义一条编译组件清单的 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" },
},
}
结构化配置支持以下类型:
bool
uint8
、int8
uint16
、int16
uint32
、int32
uint64
、int64
string
(需要max_size
属性)vector
(需要element
和max_count
属性)
如需了解配置架构的完整语法,请参阅 CML 参考文档。
组件有了配置架构后,必须使用软件汇编或 GN 为声明的字段定义值。
仅限 Google 员工:如果您的组件针对 eng 和非 eng 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 文件必须与组件的配置架构完全匹配。具有部分或缺失配置的组件将无法解析,因此无法启动。
读取配置
组件会使用生成的库读取其已解析的配置值。使用以下构建模板生成库:
C++
executable("bin") {
# ...
deps = [
":example_config",
# ...
]
}
fuchsia_structured_config_cpp_elf_lib("example_config") {
cm_label = ":manifest"
}
Rust
rustc_binary("bin") {
edition = "2021"
# ...
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,以便将其显示在崩溃报告中。客户端库提供了将组件配置导出到检查树的函数:
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 进行测试
您可以使用 Realm Builder 动态替换组件的配置值,而不考虑配置字段的 mutability
。
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();