借助结构化配置,C++/Rust 组件可以直接在 其清单中的内容。使用结构化配置的好处包括:
- 配置中的错误是在构建和组装时检测出来的。
- 您可以使用相同的组件和不同的配置值创建多个软件包。
- 组件使用静态类型的库读取其配置。
- 组件框架只会启动配置有效的组件。
- 可在运行时使用
ffx
工具查看配置。 - 这些值可在运行时设置。
有关结构化配置的实现和行为的详情,请参见 reference。
要在组件中使用结构化配置,您必须更新构建规则、声明架构、 定义值并生成客户端库。
更新构建规则
为防止在生成客户端库时出现循环依赖项,请定义
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 参考文档 架构。
组件有了配置架构后,就必须为 (使用 Software Assembly 或 GN)。
仅限 Google 员工:如果 eng 和非 eng 的组件配置不同 构建类型,就必须 请先为您的结构化配置添加验证,然后再签入。
定义配置值的方法有两种:在 JSON5 文件中定义,或在 GN 中内嵌。
使用 Software Assembly 定义配置值
如果不同产品的组件配置不尽相同,请参阅相关文档 组装结构化配置。对于 不同的配置只会请参阅 部分。
定义和使用 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
}
}
储油站
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"'
储油站
$ 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",
]
}
储油站
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"
}
储油站
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();
储油站
use example_config::Config;
...
// Retrieve configuration
let config = Config::take_from_startup_handle();
将配置导出到“检查”
您可以将组件的配置导出到“检查”,以便 崩溃报告客户端库具有将组件配置导出到 检查树:
C++
// Record configuration to inspect
inspect::ComponentInspector inspector(loop.dispatcher(), inspect::PublishOptions{});
inspect::Node config_node = inspector.root().CreateChild("config");
c.RecordInspect(&config_node);
储油站
// 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();
储油站
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");
储油站
builder.set_config_value(&config_component, "greeting", "Fuchsia".into()).await.unwrap();
Realm Builder 会根据组件的配置架构验证替换后的值。
动态设置值时,Realm Builder 会要求用户选择是否 为已启动的组件加载打包的配置值。
如需在代码中提供值的子集时加载组件的打包值,请执行以下操作:
C++
realm_builder.InitMutableConfigFromPackage(child_name);
储油站
builder.init_mutable_config_from_package(&config_component).await.unwrap();
要在代码中设置组件的所有值而不使用打包值,请执行以下操作:
C++
realm_builder.InitMutableConfigToEmpty(child_name);
储油站
builder.init_mutable_config_to_empty(&config_component).await.unwrap();