程式碼研究室:實作平台功能

本程式碼研究室會逐步說明如何在 Fuchsia 平台中實作新功能,並根據產品或主機板設定有條件地納入及設定該功能。

必要條件

本程式碼研究室假設您熟悉下列項目:

課程內容

  • 如何判斷某項功能是否應納入平台。
  • 如何在組件設定結構定義中定義功能標記。
  • 如何建立及註冊 Assembly 子系統。
  • 如何使用子系統 API 納入套件、設定和核心引數。
  • 如何在產品或看板中啟用新平台功能。

功能擺放位置規範

平台功能一律fuchsia.git 中實作,且必須夠通用,才能在多個產品或開發板上啟用。如果某項功能僅適用於單一產品或開發板,則不屬於平台。

Fuchsia 平台是所有產品共用的核心基礎。在平台上新增產品專屬或開發板專屬功能,會增加平台大小和複雜度,並可能造成長期維護負擔。請務必保持平台通用。

請按照下列準則決定要將功能放在何處:

  • 平台功能:適用於多項產品或開發板的功能。這項服務應為可設定的通用服務,例如可供不同產品選擇納入及設定的新選用網路服務。

  • 產品功能:特定產品專屬的功能。這通常是使用者可見的功能,例如建構 UI 的 Fuchsia 套件,或是單一產品專用的字型集。

  • 開發板功能:特定開發板硬體專屬的功能,例如特定硬體元件的驅動程式庫,或是該開發板專屬的設定值 (例如 GPIO 針腳編號)。

程式碼研究室步驟

導入平台功能需要執行下列步驟:

  1. 在 config_schema 中宣告功能旗標
  2. 定義新的子系統
  3. 實作子系統邏輯
  4. 在產品/看板中啟用這項功能

圖表:顯示平台功能實作方式

圖 1. 實作平台功能,並在產品設定中啟用該功能。

1. 在 config_schema 中宣告功能旗標

平台功能通常會根據產品或主機板設定中設定的旗標,有條件地啟用。首先,您必須定義這些旗標的結構。

所有平台功能旗標都會在 //src/lib/assembly/config_schema 的 Rust 結構體中宣告。這個目錄中的每個子系統通常都有自己的檔案 (例如 fonts_config.rsnetwork_config.rs)。

範例:如需範例,請按照下列步驟操作:

  1. 如要定義標記來啟用假設的「Tandem」網路功能,請建立新檔案 //src/lib/assembly/config_schema/src/platform_settings/tandem_config.rs

    use serde::{Deserialize, Serialize};
    
    /// Configuration for the Tandem networking feature.
    #[derive(Debug, Deserialize, Serialize, PartialEq)]
    #[serde(default, deny_unknown_fields)]
    pub struct TandemConfig {
        /// Enables the core Tandem service.
        pub enabled: bool,
    
        /// Specifies the maximum number of concurrent connections.
        pub max_connections: u32,
    }
    
    // Choose reasonable default values.
    impl Default for TandemConfig {
        fn default() -> Self {
            Self {
                enabled: false,
                max_connections: 10,
            }
        }
    }
    

    預設值的最佳做法:

    • 請一律在 PlatformSettings 內的結構體欄位上使用 #[serde(default)] (如下一步所示)。
    • 為設定結構 (本例中為 TandemConfig) 導入 impl Default,為每個欄位定義預設值。
    • 請避免在結構體中同時使用欄位層級的 #[serde(default = "...")]#[derive(Default)],否則可能會導致行為不一致。
  2. 將這個新設定結構體新增至 //src/lib/assembly/config_schema/src/platform_settings.rs 的主要 PlatformSettings 中:

    // ... other imports
    mod tandem_config;
    
    // ... other fields
    pub struct PlatformSettings {
        // ... other fields
        #[serde(default)]
        pub tandem: tandem_config::TandemConfig,
    }
    
  3. 您現在可以在產品設定中啟用這項新功能。例如:

    fuchsia_product_configuration(
        name = "my_product",
        product_config_json = {
            platform = {
                tandem = {
                    enabled = True,
                    max_connections = 20,
                },
            },
        },
    )
    

    常見檢舉模式:

    已在以下服務啟用這項功能... 當產品設定集... 或者,當主機板設定...
    所有 eng的產品 platform.build_type = eng
    特定產品 platform.tandem.enabled = True
    支援的開發板上的產品 provided_features = [ "tandem_hw" ]

2. 定義新的子系統

組件子系統是 Rust 模組,負責處理相關平台功能群組的設定。這個外掛程式會讀取 config_schema 中定義的標記,並使用 Assembly Builder API 納入及設定功能程式碼。

位置:子系統位於 //src/lib/assembly/platform_configuration/src/subsystems

舉例來說,請按照下列步驟操作:

  1. 為「Tandem」功能建立新檔案: //src/lib/assembly/platform_configuration/src/subsystems/tandem.rs

    use crate::subsystems::prelude::*;
    use assembly_config_schema::platform_config::tandem_config::TandemConfig;
    
    pub(crate) struct TandemSubsystem;
    impl DefineSubsystemConfiguration<TandemConfig> for TandemSubsystem {
        fn define_configuration(
            context: &ConfigurationContext<'_>,
            tandem_config: &TandemConfig,
            builder: &mut dyn ConfigurationBuilder,
        ) -> anyhow::Result<()> {
            if tandem_config.enabled {
                // Actions to include the feature will go here
                // See Step 3 for details
                println!("Tandem feature enabled with max_connections: {}", tandem_config.max_connections);
            }
            Ok(())
        }
    }
    

    說明:

    • 結構體 TandemSubsystem 會實作 DefineSubsystemConfiguration 特徵,並以先前定義的 TandemConfig 結構體輸入型別。
    • define_configuration 函式會接收 ConfigurationContext、我們的特定 TandemConfigConfigurationBuilder
    • 在這個函式中,我們會檢查 enabled 標記。如果為 true,我們會使用 builder 將功能元件新增至系統映像檔。
  2. 將模組新增至 //src/lib/assembly/platform_configuration/src/subsystems.rs

    // ... other mods
    mod tandem;
    
  3. 在同一檔案的主要 Subsystems::define_configuration 函式中呼叫其 define_configuration 函式,並傳遞平台設定的相關部分:

    // In Subsystems::define_configuration
    tandem::TandemSubsystem::define_configuration(
        context,
        &platform.tandem,
        builder,
    )?;
    

3. 實作子系統邏輯

在子系統的 define_configuration 函式中,您會使用 ConfigurationBuilder 方法,根據功能標記將功能新增至產品。

更新 tandem.rs,例如:

use crate::subsystems::prelude::*;
use assembly_config_schema::platform_config::tandem_config::TandemConfig;
use assembly_platform_configuration::{
    ConfigurationBuilder,
    KernelArg,
    ConfigValueType,
    Config,
    PackageSetDestination,
    PackageDestination,
    FileEntry,
    BuildType
};

pub(crate) struct TandemSubsystem;
impl DefineSubsystemConfiguration<TandemConfig> for TandemSubsystem {
    fn define_configuration(
        context: &ConfigurationContext<'_>,
        tandem_config: &TandemConfig,
        builder: &mut dyn ConfigurationBuilder,
    ) -> anyhow::Result<()> {

        if tandem_config.enabled {
            // 1. Add the feature's code at build time using an Assembly Input Bundle (AIB)
            builder.platform_bundle("tandem_core");

            // 2. Set a runtime configuration value
            builder.set_config_capability(
                "fuchsia.tandem.MaxConnections",
                Config::new(ConfigValueType::Int32, tandem_config.max_connections.into()),
            )?;

            // 3. Conditionally add a runtime kernel argument based on build type
            if context.build_type == &BuildType::Eng {
                builder.kernel_arg(KernelArg::TandemEngDebug);
            }

            // 4. Include a domain config package for more complex runtime configuration
            builder.add_domain_config(PackageSetDestination::Blob(PackageDestination::TandemConfigPkg))
                  .directory("config/data")
                  .entry(FileEntry {
                      source: "//path/to/tandem/configs:default.json".into(),
                      destination: "settings.json".into(),
                  })?;
        }

        Ok(())
    }
}

建構時間與執行階段啟用:

  • 建構時間:只有在啟用這項功能時,構件才會納入映像檔。建議您使用這項功能,以節省空間、加強安全性、啟用靜態分析,並提升其他不需要這項功能的產品效能。
  • 執行階段:一律會納入 Artifact,但其行為會在執行階段受到控制 (例如透過設定值或核心引數)。

建構時間

組件會使用組件輸入套件 (AIB) 整理建構時間功能。功能擁有者可以在單一 AIB 中插入多種構件,並指示 Assembly 何時及如何將該 AIB 新增至產品。所有 AIB 皆定義於 //bundles/assembly/BUILD.gn。例如:

# Declares a new AIB with the name "tandem_core".
assembly_input_bundle("tandem_core") {
  # Include this package into the "base package set".
  # See RFC-0212 for an explanation on package sets.
  # The provided targets must be fuchsia_package().
  base_packages = [ "//path/to/my/tandem:pkg" ]

  # Include this file into BootFS.
  # The provided targets must be bootfs_files_for_assembly().
  bootfs_files_labels = [ "//path/to/my/tandem:bootfs" ]
}

如要加入 AIB,請在子系統中使用下列方法:

builder.platform_bundle("tandem_core");

如果您新增 AIB,請務必將其新增至 //bundles/assembly/platform_aibs.gni 中的適當清單,否則在建構時會發生錯誤,指出找不到 AIB。

執行階段

組件支援多種執行階段設定。這些類型會依偏好順序列出。

設定功能:Fuchsia 元件可在執行階段讀取設定功能的值,而組件會在建構時設定這些功能的預設值,例如:

// Add a config capability named `fuchsia.tandem.MaxConnections` to the config package.
builder.set_config_capability(
    "fuchsia.tandem.MaxConnections",
    Config::new(ConfigValueType::Int32, tandem_config.max_connections.into()),
)?;

組件會將所有預設設定功能新增至 BootFS 中的設定套件,因此能力必須從 /root 元件領域轉送至您的元件。

在元件中使用平台定義的設定功能

當元件需要使用平台定義及提供的設定能力 (透過子系統中的 builder.set_config_capability),元件的 CML 檔案必須包含 use 宣告。即使值是由上層領域提供,這項宣告仍必須指定設定值的 type

元件 CML 範例 (my_component.cml):

{
    use: [
        {
            config: "fuchsia.tandem.MaxConnections", // The capability name
            from: "parent",
            key: "max_conn", // The key used in this component's structured config
            type: "int32",     // The type MUST be specified here
        },
    ],
    // ... other parts of the manifest
    config: {
        max_conn: { type: "int32" },
    },
}

元件代碼:

您也必須更新元件的原始碼,以便在結構化設定中預期會出現這個金鑰。這通常需要更新可還原序列化設定值的結構體,通常是由 ffx component config get 指令或類似工具產生。定義用於還原序列化設定的結構體:

// Example in the component's config.rs (e.g., src/config.rs)
use serde::Deserialize;

// This struct should match the keys and types in the CML 'config' block.
#[derive(Debug, Deserialize)]
pub struct TandemComponentConfig {
    pub max_conn: i32,
    // ... other config fields
}

然後在元件的初始化程式碼中,擷取設定:

let config = fuchsia_component::config::Config::take_from_startup_handle();
let tandem_config: TandemComponentConfig = config.get();

重點:

  • type (例如 "bool""int32""string") 必須納入平台提供的設定的 use 節。
  • 如果類型為 "string",則必須同時納入 max_size
  • use 節中的 key 會將能力對應至元件本身 config 結構定義中的欄位名稱,進而對應至用於在元件程式碼中載入設定的結構體中的欄位。
  • 確認元件的程式碼 (例如 Rust、C++),以處理新的設定鍵。

網域設定:如果是複雜的設定、項目清單或需要自訂類型的設定,建議使用網域設定,而非設定功能。雖然通常可以將複雜的設定「扁平化」為一組簡單的鍵/值組合,以用於設定功能,但這可能會變得難以管理。

舉例來說,假設某個元件需要網路端點清單,而每個端點都有網址、通訊埠和通訊協定。使用設定功能時,您可能需要將這個項目扁平化為一系列鍵。例如:

// This approach is NOT recommended for lists or complex types.
"endpoint.0.url": "host1.example.com",
"endpoint.0.port": 443,
"endpoint.1.url": "host2.example.com",
"endpoint.1.port": 8080,

這會導致管理困難,尤其當端點數量不固定時更是如此。在這種情況下,網域設定是更簡潔的解決方案。您可以在套件中提供單一 JSON 檔案,供元件在執行階段剖析:

// A domain config file (e.g., tandem_config.json)
{
  "endpoints": [
    {
      "url": "host1.example.com",
      "port": 443,
      "protocol": "HTTPS"
    },
    {
      "url": "host2.example.com",
      "port": 8080,
      "protocol": "HTTP"
    }
  ]
}

網域設定是 Fuchsia 套件,可為元件提供設定檔,以便在執行階段讀取及剖析,例如:

// Create a new domain config in BlobFS with a file at "config/tandem_config.json".
builder.add_domain_config(PackageSetDestination::Blob(PackageDestination::TandemConfigPkg))
      .directory("config")
      .entry(FileEntry {
          source: config_src,
          destination: "tandem_config.json".into(),
      })?;

您的元件必須以子項形式啟動網域設定套件,並use目錄,例如:

{
    children: [
        {
            name: "tandem-config",
            url: "fuchsia-pkg://fuchsia.com/tandem-config#meta/tandem-config.cm",
        },
    ],
    use: [
       {
            directory: "config",
            from: "#tandem-config",
            path: "/config",
        },
    ],
}

核心引數:核心引數僅用於啟用核心功能。組件會建構指令列,在執行階段傳遞至核心,例如:

builder.kernel_arg(KernelArg::TandemEngDebug);

4. 在產品/看板中啟用這項功能

實作子系統後,您可以在產品或主機板設定檔中,設定步驟 1 中定義的旗標,啟用這項功能。

在產品設定中啟用

如要為特定產品啟用「雙人」功能,請修改其 fuchsia_product_configuration 目標 (通常位於 BUILD.bazel 檔案中):

fuchsia_product_configuration(
    name = "my_product",
    product_config_json = {
        platform = {
            # ... other platform settings
            tandem = {
                enabled = True,
                max_connections = 20,
            },
        },
    },
    # ... other attributes
)

根據主機板功能啟用

開發板功能可讓開發板聲明支援特定硬體。平台子系統可以讀取主機板 context.board_config.provided_features,有條件地啟用或停用功能。

如果子系統邏輯會檢查主機板功能,請確保主機板設定 (例如 //boards/my_board/BUILD.bazel) 包含在內。 例如:

fuchsia_board_configuration(
    name = "my_board",
    provided_features = [
        "tandem_hw",  # This will be seen by context.board_config.provided_features
    ],
    # ...
)

修改設定後,重建產品組合時,系統會納入 Tandem 功能及其在子系統中定義的設定。