结构化配置

借助结构化配置,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
  • uint8int8
  • uint16int16
  • uint32int32
  • uint64int64
  • string(需要 max_size 属性)
  • vector(需要 elementmax_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_componentfuchsia_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();