結構化設定

結構化設定可讓 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
  • uint8int8
  • uint16int16
  • uint32int32
  • uint64int64
  • string (需要 max_size 屬性)
  • vector (需要 elementmax_count 屬性)

如要瞭解設定結構定義的完整語法,請參閱 CML 參考文件

元件具備設定結構定義後,您必須使用軟體組裝或 GN,為宣告的欄位定義值。

僅限 Google 員工:如果您的元件針對工程和非工程建構類型的設定不同,您必須先驗證結構化設定,再簽入。

定義設定值的方式有兩種:在 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
  }
}

荒漠油廠

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

荒漠油廠

use example_config::Config;

...

// Retrieve configuration
let config = Config::take_from_startup_handle();

將設定匯出至檢查工具

您可以將元件的設定匯出至 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);

荒漠油廠

// 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) Add a section covering use with subpackaged components.

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