Structured Configuration

Structured configuration allows C++/Rust components to declare configuration schemas directly in their manifest. Benefits of using structured configuration include:

  • Errors in configuration are detected at build and assembly time.
  • Multiple packages can be created using the same component and different configuration values.
  • Components read their configuration with statically-typed libraries.
  • Component Framework only starts components with valid configuration.
  • Configuration can be viewed at runtime with ffx tooling.
  • Values can be set at runtime.

For more details on the implementation and behavior of structured configuration, see its reference.

To use structured configuration in your component, you must update build rules, declare a schema, define values, and generate a client library.

Update build rules

To prevent cyclic dependencies when generating client libraries, define a fuchsia_component_manifest rule that compiles the component manifest. Pass this compiled manifest GN label into the fuchsia_component rule.

fuchsia_component_manifest("manifest") {
  component_name = "config_example"
  manifest = "meta/config_example.cml"
}

fuchsia_component("component") {
  cm_label = ":manifest"
  deps = [ ":bin" ]
}

Declare configuration schema

You must declare a configuration schema in a component's manifest:

{
    ...
    config: {
        greeting: {
            type: "string",
            max_size: 512,
        },
        delay_ms: { type: "uint64" },
    },

}

Structured config supports the following types:

  • bool
  • uint8, int8
  • uint16, int16
  • uint32, int32
  • uint64, int64
  • string (requires max_size property)
  • vector (requires element and max_count properties)

See the CML reference doc for the complete syntax for a config schema.

Once your component has a configuration schema, you must define values for the declared fields, either using Software Assembly or GN.

Googlers only: if your component is configured differently for eng and non-eng build types, you must add verification of your structured configuration before you check it in.

There are two ways to define config values: in a JSON5 file or inline in GN.

Define configuration values using Software Assembly

If your component's configuration varies between products, see the documentation for Assembling Structured Configuration. For components whose configuration only varies between e.g. tests and production, see the next section.

Define & package configuration values using GN

The fuchsia_structured_config_values GN template validates the defined values against the config schema and compiles them into a .cvf file that must be packaged with your component.

There are two ways to define config values in GN: in a JSON5 file or inline.

JSON5 file

You can write a component's configuration values in a JSON5 file. Because JSON5 is a strict superset of JSON, existing JSON configuration files can also be reused for structured config.

Each key in the JSON object must correspond to a config key in the schema and the value must be of a compatible JSON type:

{
    // Print "Hello, World!" by default.
    greeting: "World",

    // Sleep for only 100ms by default.
    delay_ms: 100,
}

Provide the path to the JSON5 file in a fuchsia_structured_config_values rule.

fuchsia_structured_config_values("values_from_json_file") {
  cm_label = ":manifest"
  values_source = "../config_example_default_values.json5"
}

Inline values

The fuchsia_structured_config_values template also supports defining configuration values inline:

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
  }
}

By using declare_args, you can change configuration values on the command line at build time:

C++

$ fx set core.qemu-x64 \
  --with //examples/components/config \
  --args='config_example_cpp_greeting="C++ CLI Override"'

Rust

$ fx set core.qemu-x64 \
  --with //examples/components/config \
  --args='config_example_rust_greeting="Rust CLI Override"'

Package the component and values

To package a component and a set of values together, add the fuchsia_component and fuchsia_structured_config_values rules as dependencies of a fuchsia_package.

C++

fuchsia_package("cpp_config_example") {
  deps = [
    ":component",
    ":values_from_gn",
  ]
}

Rust

fuchsia_package("rust_config_example") {
  deps = [
    ":component",
    ":values_from_gn",
  ]
}

The build system verifies your component's configuration schema and value file. A component with a faulty configuration (for example: field mismatch, bad constraints, missing value file) will fail to build.

Checking the configuration

Component manager validates a component's configuration when the component is resolved.

Use ffx component show to print out a components configuration key-value pairs. The component does not have to be running for this to work.

$ ffx component show config_example
                Moniker: /core/ffx-laboratory:config_example
        Component State: Resolved
                      ...
          Configuration: greeting -> "World"
                      ...

A CVF file must match the component's configuration schema exactly. Components with partial or missing configurations will not resolve and therefore not start.

Reading the configuration

Components read their resolved configuration values with a generated library. Generate a library using the following build templates:

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"
}

Use the following functions from the library to read configuration values:

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();

Export configuration to Inspect

You can export a component's configuration to Inspect so that it is available in crash reports. The client libraries have functions to export a component's configuration to an Inspect tree:

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));

Use ffx inspect show to print out the component's exported configuration:

$ ffx inspect show core/ffx-laboratory\*config_example
core/ffx-laboratory\:config_example:
  ...
  payload:
    root:
      config:
        greeting = World

Runtime values from outside the component

By default, components will only receive configuration values from a .cvf file returned by their resolver, usually from their package. In order to have those values replaced by another component, they must have a mutability property which opts them in to particular sources of mutation:

    config: {
        greeting: {
            // ...
            mutability: [ /* ... */ ],
        },
    },

Providing values from parent components

Parent components can provide values to children launched in a collection when the child has opted in to receiving them.

First, the child must add a mutability property to the appropriate configuration field:

{
    ...
    config: {
        greeting: {
            type: "string",
            max_size: 512,
            mutability: [ "parent" ],
        },
    },

}

When launching the child with the Realm protocol, pass config values as overrides:

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:?}"))?;

See the full example for details.

Providing values with ffx component

When creating a component with ffx component create or running it with ffx component run, you can override configuration values as if you're acting as the parent component.

If you run the above configuration example without any overrides you can see it print the default configuration to its logs:

$ 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)

Passing --config allows you to override that greeting:

$ 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)`

Each configuration field is specified as KEY=VALUE where KEY must be a field in the component's config schema which has mutability: [ "parent" ] and VALUE is a string that can be parsed as JSON matching the type of the field.

Testing with Realm Builder

You can use Realm Builder to dynamically replace the configuration values of a component regardless of the configuration field's mutability.

TODO(https://fxbug.dev/42053123) Add a section covering use with subpackaged components.

C++

realm_builder.SetConfigValue(child_name, "greeting", "Fuchsia");

Rust

builder.set_config_value(&config_component, "greeting", "Fuchsia".into()).await.unwrap();

Realm Builder validates the replaced value against the component's configuration schema.

When setting values dynamically, Realm Builder requires users to choose whether or not to load packaged configuration values for the launched component.

To load a component's packaged values when providing a subset of values in code:

C++

realm_builder.InitMutableConfigFromPackage(child_name);

Rust

builder.init_mutable_config_from_package(&config_component).await.unwrap();

To set all of a component's values in code without using packaged values:

C++

realm_builder.InitMutableConfigToEmpty(child_name);

Rust

builder.init_mutable_config_to_empty(&config_component).await.unwrap();