RFC-0140:運作範圍建構工具

RFC-0140:運作範圍建構工具
狀態已接受
領域
  • 元件架構
說明

我們會簡要說明 Realm Builder 的設計功能組合

毛皮變化
作者
審查人員
提交日期 (年-月-日)2021-09-06
審查日期 (年-月-日)2021-11-10

摘要

Realm builder 是 Rust 樹狀結構內提供的程式庫, 可讓使用者透過程式組合元件的 C++ 運作範圍,因此這些領域或許能包含 和處理中實作的元件這個程式庫已實作 其中包含名為 Realm Builder 伺服器的補充資訊子元件 顯示在任何使用該程式庫的元件資訊清單中。這個伺服器會代管 大部分的實作,而且程式庫會透過 FIDL 與程式庫通訊。

此 RFC 概述了 FIDL API 和運作範圍的主要設計 建構器,並提議使用此 API、C++ 用戶端程式庫 發布 Realm Builder 伺服器的資訊清單和二進位檔 (即預先建構的版本) 。

提振精神

元件的整合測試是一項非常重要的工作 架構團隊希望盡可能簡化並營造輕鬆愉快的體驗。很遺憾 元件架構的許多功能都提供優異的安全性 將屬性隔離到正式環境元件,導致測試變得複雜 情境如要在不同的環境中測試某個元件,則 每個環境的獨立元件資訊清單都必須手動維護。如果 他們想為受測試的元件提供功能 動態宣告功能,提供給 收集資料是一大挑戰如果受測試的元件 會連線至某個能力的模擬提供者,那麼該模擬必須 必須透過 FIDL 通訊,因此必須維護測試專用的 FIDL 和該通訊專用的 API

運作範圍建立工具能大幅改善整合測試的體驗 和應用程式整合測試的品質。透過實作 resolver 能力) 可建立新的 元件資訊清單,並在執行階段提供給元件架構。變更者: 領域建構工具程式庫可以實作 執行器能力 將本機元件插入這些建構運作範圍 同流程中的實作方式,因此可使用 程序內的工具協調 和測試中的邏輯相符整合測試的常見工作 例如設定儲存空間功能或提供虛假設定 也可以自動化和簡化

這個程式庫已見過採用,並成功在 Fuchsia 樹狀結構,透過 SDK 提供該結構時, 。

相關人員

誰會負責確認 RFC 是否已獲接受?(這個部分為選填,但 encouraged.)

講師:hjfreyer@google.com

審查者:

  • Yaneury Fermin (yaneury@google.com) - 全部
  • Gary Bressler (geb@google.com) - 全部
  • Peter Johnston (peterjohnston@google.com) - 功能
  • Jaeheon Yi (jaeheon@google.com) - 可用性

諮詢:

列出應審查 RFC,但不需經過核准的人員。

社交:這個 RFC 是根據具有顯著性的程式庫而建立 感謝各方意見和改進 目前已經採用這項功能的團隊。特別針對網路堆疊,Wlan 藍牙與 SWD 團隊已將此程式庫納入整合之中 測試

設計

總覽

領域建立工具有兩個重要部分:用戶端程式庫 Realm Builder 伺服器,開發人員練習使用用戶端程式庫 他們想要建構的領域,然後再指示程式庫 用戶端程式庫會共同完成這些工作 與 Realm Builder 伺服器通訊

合作模式提供了一些不錯的屬性,例如更方便地支持 使用不同語言的用戶端程式庫 (伺服器可在 用戶端語言,但用戶端和伺服器的片段 領域建立工具必須能在整合測試情境中使用 測試執行器,用於執行測試中的各種案例 元件的傳出目錄控制代碼,無法提供給 元件本身導致您無法宣告 以及任何需要這項權限的工作 (例如 領域建構工具宣告的解析器和執行元件功能) 必須移至 獨立的元件

運作範圍建構工具的設計可讓用戶端使用的 FIDL API 程式庫看起來會盡量相似 (而且在某些區域直接對應至) 提供給開發人員的用戶端程式庫

建議您使用用戶端程式庫,不要使用原始 FIDL 繫結,因為 也能提供更優質的開發人員體驗,以及執行某些工作 (例如 本機元件實作的狀態) 會相當繁瑣,且需要 所需的重要樣板,無需用戶端程式庫即可處理。

運作範圍初始化

建立新的領域時,用戶端程式庫會建立新的運作範圍 與 Realm Builder 伺服器建立連線。

let mut builder = RealmBuilder::new().await?;

這個結構在初始化後會連線至 Realm Builder 伺服器,並使用 fuchsia.component.test.RealmBuilderFactory 通訊協定。這項通訊協定 簡單:呼叫 New 方法即可建立兩個新管道。一種用途 建立新的領域,另一個則用於完成變更。 此外,這個呼叫會為 Realm Builder 伺服器提供 測試的套件目錄,用來載入 相對網址

@discoverable
protocol RealmBuilderFactory {
    New(resource struct {
        pkg_dir_handle client_end:fuchsia.io.Directory;
        realm_server_end server_end:Realm;
        builder_server_end server_end:Builder;
    });
}

建立 RealmBuilder 管道後,客戶現在可以新增元件 領域。

將元件新增至領域

type ChildProperties = table {
    1: startup fuchsia.component.decl.StartupMode;
    2: environment fuchsia.component.name;
    3: on_terminate fuchsia.component.decl.OnTerminate;
};

protocol Realm {
    /// Adds the given component to the realm. If a component already
    /// exists at the given name, then an error will be returned.
    AddChild(struct {
        /// The name, relative to the realm's root, for the component that is
        /// being added.
        name fuchsia.component.name;

        /// The component's URL
        url fuchsia.url.Url;

        /// Additional properties for the component
        properties ChildProperties;
    }) -> () error RealmBuilderError;

    /// Modifies this realm to contain a legacy component. If a component
    /// already exists with the given name, then an error will be returned.
    /// When the component is launched, realm builder will reach out to appmgr
    /// to assist with launching the component, and the component will be able
    /// to utilize all of the features of the [legacy Component
    /// Framework](https://fuchsia.dev/fuchsia-src/concepts/components/v1). Note
    /// that _only_ protocol capabilities may be routed to this component.
    /// Capabilities of any other type (such as a directory) are unsupported for
    /// legacy components launched by realm builder, and this legacy component
    /// should instead use the legacy features to access things such as storage.
    AddLegacyChild(struct {
        /// The name, relative to the realm's root, for the component that is
        /// being added.
        name fuchsia.component.name;

        /// The component's legacy URL (commonly ends with `.cmx`)
        legacy_url fuchsia.url.Url;

        /// Additional properties for the component
        properties ChildProperties;
    }) -> () error RealmBuilderError;

    /// Modifies this realm to contain a component whose declaration is set to
    /// `decl. If a component already exists at the given name, then an error
    /// will be returned.
    AddChildFromDecl(struct {
        /// The name, relative to the realm's root, for the component that is
        /// being added.
        name fuchsia.component.name;

        /// The component's declaration
        decl fuchsia.component.decl.Component;

        /// Additional properties for the component
        properties ChildProperties;
    }) -> () error RealmBuilderError;

    // Other entries omitted
    ...
};

用戶端程式庫會傳回包裝元件名稱的物件, 以便在之後連接領域時,輕鬆提供相同的名稱 這種打動方式

impl RealmBuilder {
    pub async fn add_child(
        &self,
        name: impl Into<String>,
        url: impl Into<String>,
        child_props: ChildProperties
    ) -> Result<ComponentName, Error> {
        ...
        return ComponentName { name: name.into() };
    }
}

struct ComponentName {
    name: String,
}

impl Into<String> for &ComponentName {
    fn into(input: &ComponentName) -> String {
        input.name.clone()
    }
}
// echo_server is a struct that contains the string "echo_server", which can
// be given to other functions later to reference this component
let echo_server = builder.add_child(
    "echo-server",
    "#meta/echo_server.cm",
    ChildProperties::new(),
).await?;

let echo_client = builder.add_legacy_child(
    "echo-client",
    "fuchsia-pkg://fuchsia.com/echo#meta/client.cmx",
    ChildProperties::new().eager(),
).await?;

let echo_client_2 = builder.add_child_from_decl(
    "echo-client-2",
    ComponentDecl {
        program: Some(ProgramDecl {
            runner: Some("elf".into()),
            info: Dictionary {
                entries: vec![
                    DictionaryEntry {
                        key: "binary".to_string(),
                        value: Some(Box::new(DictionaryValue::Str(
                            // This binary exists in the test package
                            "/bin/echo_client",
                        ))),
                    },
                ],
                ..Dictionary::EMPTY
            },
        }),
        uses: vec![
            UseDecl::Protocol(UseProtocolDecl {
                source: UseSource::Parent,
                source_name: EchoMarker::PROTOCOL_NAME,
                target_path: format!("/svc/{}", EchoMarker::PROTOCOL_NAME).into(),
                dependency_type: DependencyType::Strong,
            }),
        ],
        ..ComponentDecl::default()
    },
    ChildProperties::new().eager(),
).await?;

Realm Builder 伺服器會維護元件的內部樹狀結構 以領域為背景搭配絕對網址 (非相對) 網址使用 add_child 時, 父項元件的資訊清單會變更,以便保留含有 ChildDecl 的 指定網址。至於其他新增元件的方式,則元件的資訊清單 會保留在伺服器的樹狀結構中,並在領域 已建立。

新增包含本機實作的元件

用戶端也可以在運作領域中加入元件,而該運作範圍是由 本機日常安排如此一來,使用者就能在 並與測試邏輯存取同一個檔案,並在處理程序中 協調這些元件與測試本身。

protocol Realm {
    /// Sets a component to have a new local component implementation. When this
    /// component should be started, the runner channel passed into `Build` will
    /// receive a start request for a component whose `ProgramDecl` contains the
    /// name for the component that is to be run under the key
    /// `LOCAL_COMPONENT_NAME`. If a component already exists at the given
    /// name, then an error will be returned.
    AddLocalChild(struct {
        /// The name, relative to the realm's root, for the component that is
        /// being added.
        child_name fuchsia.component.name;

        /// Additional properties for the child
        properties ChildProperties:optional;
    }) -> () error RealmBuilderError;

    // Other entries omitted
    ...
}

請注意,這表示每個用戶端程式庫都必須實作必要的邏輯 執行及管理本機任務的本機工作生命週期 元件。如要進一步瞭解相關細節,請參閱這篇文章中的 「建立領域和本機元件實作」

以下程式碼就是一個 echo 用戶端的程序內實作。

let echo_client_3 = builder.add_local_child(
    "echo-client-3",
    move |handles: LocalComponentHandles| {
        Box::pin(async move {
            let echo_proxy = handles.connect_to_service::<EchoMarker>()?;
            echo_proxy.echo_string("hello, world!").await?;
            Ok(())
        })
    },
    ChildProperties::new().eager(),
).await?;

同樣地,這段程式碼也會為 echo 伺服器新增處理中實作。

let (send_echo_server_called, mut receive_echo_server_called) = mpsc::channel(1);
let echo_server_2 = builder.add_local_child(
    "echo-server-2",
    move |handles: LocalComponentHandles| {
        let mut send_echo_server_called = send_echo_server_called.clone();
        Box::pin(async move {
            let mut fs = fserver::ServiceFs::new();
            let mut tasks = vec![];

            let mut send_echo_server_called = send_echo_server_called.clone();
            fs.dir("svc").add_fidl_service(move |mut stream: fecho::EchoRequestStream| {
                let mut send_echo_server_called = send_echo_server_called.clone();
                tasks.push(fasync::Task::local(async move {
                    while let Some(fecho::EchoRequest::EchoString { value, responder }) =
                        stream.try_next().await.expect("failed to serve echo service")
                    {
                        responder.send(value.as_ref().map(|s| &**s)).expect("failed to send echo response");

                        // Use send_echo_server_called to report back that we successfully received a
                        // message and it aligned with our expectations
                        send_echo_server_called.send(()).await.expect("failed to send results");
                    }
                }));
            });

            // Run the ServiceFs on the outgoing directory handle from the mock handles
            fs.serve_connection(mock_handles.outgoing_dir.into_channel())?;
            fs.collect::<()>().await;
            Ok(())
        })
    },
    ChildProperties::new().eager(),
).await?;

將元件串連在一起

將元件新增至領域後,領域必須 來連結新增的元件,公開 測試。

手動操控元件資訊清單

透過使用 GetComponentDecl:擷取元件的資訊清單,變更資訊清單 然後使用 ReplaceComponentDecl。請注意,如果領域建立工具伺服器 是使用不同版本的 fuchsia.component.decl API 所建構 都可能導致用戶端不小心省略了

protocol Realm {
    /// Returns the component decl for the given component. `name` must
    /// refer to a component that is one of the following:
    ///
    /// - A component with a local implementation
    /// - A legacy component
    /// - A component added with a relative URL
    /// - A descendent of a component added with a relative URL
    /// - An automatically generated realm (ex: the root)
    ///
    /// If the component was added to the realm with a modern (i.e. non-legacy),
    /// absolute (i.e. non-relative) URL, then an error will be returned, as
    /// realm builder is unable to retrieve or alter the declarations for these
    /// components.
    GetComponentDecl(struct {
        name fuchsia.component.name;
    }) -> (struct {
        component_decl fuchsia.component.decl.Component;
    }) error RealmBuilderError;

    /// Sets the component decl for the given component. If the component
    /// was added to the realm with a modern (i.e. non-legacy), absolute (i.e.
    /// non-relative) URL, then an error will be returned, as realm builder is
    /// unable to retrieve or alter the declarations for these components.
    ReplaceComponentDecl(struct {
        name fuchsia.component.name;
        component_decl fuchsia.component.decl.Component;
    }) -> () error RealmBuilderError;

    // Other entries omitted
    ...
};
let echo_server = builder.add_child(
    "echo-server",
    "#meta/echo_server.cm",
).await?;
let mut echo_decl = builder.get_component_decl(&echo_server).await?;
echo_decl.offer.push(OfferDecl { ... });
builder.replace_component_decl(&echo_server, echo_decl).await?;

let mut root_decl = builder.get_component_decl(RealmBuilder::root()).await?;
root_decl.offer.push(OfferDecl { ... });
builder.replace_component_decl(builder.root(), root_decl).await?;

能力轉送

使用領域建構工具的開發人員相當常見 可用在元件上執行做法如上方所示,其中 相關元件的資訊清單 (尤其是 與任何舊版或本機子項) 擷取、變動後, 運作領域,但這個方法有一些不理想的屬性:

  • 無法在OfferDecl中指定多個目標。
  • 您無法在 OfferDeclExposeDecl 中指定多項功能。
  • 元件會宣告 Realm Builder 伺服器針對本機進行合成 及舊版元件也必須更新,以符合功能 使用行為或應提供的功能,因此請新增優惠或 對根元件公開,並不能使用 來處理這類元件的問題

為了協助開發人員在領域內移動功能, AddRoute 函式已存在。

protocol Realm {
    AddRoute(table {
        1: capabilities vector<RouteCapability>;
        2: from fuchsia.component.decl.Ref;
        3: to vector<fuchsia.component.decl.Ref>;
    }) -> () error RealmBuilderError;

    // Other entries omitted
    ...
};

type Parent = struct {};
type Debug = struct {};
type Framework = struct {
    scope string:fuchsia.component.MAX_PATH_LENGTH;
};

type RouteCapability = flexible union {
    1. protocol RouteCapabilityProtocol;
    2. directory RouteCapailityDirectory;

    // Routes for all the other capability types
    ...
};

type RouteCapabilityProtocol = table {
    1: name fuchsia.component.name;
    2: as fuchsia.component.name; // optional
    3: type_ fuchsia.component.decl.DependencyType; // optional
};

type RouteCapabilityDirectory = table {
    1: name fuchsia.component.name;
    2: as fuchsia.component.name; // optional
    3: type_ fuchsia.component.decl.DependencyType; // optional
    4: rights fuchsia.io.Rights; // optional
    5: subdir string:fuchsia.component.MAX_PATH_LENGTH; // optional
};

這個函式可讓開發人員指定一組應該 從單一來源轉送至一組目標。這樣一來,開發人員 表示的相同資訊會顯示在一組 OfferDeclExposeDecl,但簡直是簡明扼要。而且這個食譜的用處 來源或目標的任何舊版或本機元件,將更新為 視需要宣告、公開及/或使用能力。

以下列舉幾個實際示例:

// Offer the LogSink protocol to components echo_server and echo_client
builder.add_route(
    vec![RouteCapability::protocol_from_marker::<LogSinkMarker>()],
    Ref::parent(),
    vec![&echo_server, &echo_client],
)).await?;
// Offer two different sub directories of dev to components echo_server and
// echo_client
builder.add_route(
    vec![
        RouteCapability::Directory(RouteCapabilityDirectory {
            name: "dev-class",
            as: "dev-class-input",
            subdir: "input",
            ..RouteCapabilityDirectory::default()
        }),
        RouteCapability::Directory(RouteCapabilityDirectory {
            name: "dev-class",
            as: "dev-class-block",
            subdir: "block",
            ..RouteCapabilityDirectory::default()
        }),
    ],
    Ref::parent(),
    vec![&echo_server, &echo_client],
).await?;
// Expose protocol fuchsia.test.Suite as fuchsia.test.Suite2 from component
// echo_client
builder.add_route(
    capabilities: vec![RouteCapability::Protocol(RouteCapabilityProtocol {
        name: "fuchsia.test.Suite",
        as: "fuchsia.test.Suite2",
        ..RouteCapabilityProtocol::default()
    })],
    from: &echo_client,
    to: vec![Ref::parent()],
).await?;

唯讀目錄虛設常式

許多元件都使用 config-data,這是一個唯讀目錄, 向 config-data 下的元件提供設定資料 名稱。針對這個目錄,為每個建構的領域提供一個虛設常式為 使用本機元件實作,但這需要 樣板

為簡化這個模式,以及其他用於唯讀用途的用途 目錄後,Realm Builder Server 就能與 唯讀目錄,而且會提供 將內容新增至領域中的元件方法是在程式碼中自動插入 「內建」並轉換為區域元件 並透過實際元件宣告 但其實作是由 Realm Builder 伺服器提供,而非 要求領域建立工具用戶端提供實作項目

protocol Realm {
    ReadOnlyDirectory(table {
        1: name fuchsia.component.name;
        2: directory_name: string:fuchsia.component.MAX_NAME_LENGTH;
        3: directory_contents: vector<DirectoryEntry>:MAX_DIR_ENTRIES;
    }) -> () error RealmBuilderError;

    // Other entries omitted
    ...
};

const MAX_DIR_ENTRIES uint32 = 1024;

type DirectoryEntry = struct {
    path string:fuchsia.component.MAX_PATH_LENGTH;
    contents fuchsia.mem.Data;
};
builder.read_only_directory(
    &echo_server,
    "config-data".into(),
    vec![
        DirectoryEntry("file1", b"{ \"config_key\": \"config_value\" }"),
        DirectoryEntry("dir1/file2", b"{ \"foo\": \"bar\" }"),
    ],
).await?;

可變動儲存空間

許多元件都會使用可變動儲存空間,因此在測試時很實用 查看及變更元件的儲存空間 執行狀態提供給 Cloud Storage 儲存空間的隔離屬性 測試領域會防止測試存取其他元件的儲存空間 因此並不適合此應用程式測試可以託管可變動的 透過本機元件為受測試的元件提供目錄本身 但這需要大量樣板

為簡化這個模式,您可以要求 Realm Builder 伺服器 為運作領域中的子項元件儲存空間能力 元件儲存空間的存取路徑做法是新增 「內建」來提供儲存空間能力 唯讀目錄虛設常式中所述的 函式。

protocol Realm {
    HostStorage(table {
        1: name fuchsia.component.name;
        2: storage_name string:fuchsia.component.MAX_NAME_LENGTH;

        /// If set, will be connected to the component's isolated storage
        /// directory and can be immediately used (even before the realm is
        /// created).
        3: directory_server_end server_end:fuchsia.io.Directory;
    }) -> () error RealmBuilderError;

    // Other entries omitted
    ...
};
let component_storage_proxy = builder.host_storage(
    &echo_server,
    "data",
).await?;
let file_proxy = fuchsia_fs::directory::open_file_no_describe(
    &component_storage_proxy,
    "config-file.json",
    fio::OpenFlags::RIGHT_WRITABLE|fio::OpenFlags::CREATE,
)?;
fuchsia_fs::file::write(&file_proxy, "{ \"foo\": \"bar\"}").await?;
let realm_instance = builder.create().await?;

在上述範例中,component_storage_proxy 可以用來讀取及寫入 複製到元件儲存領域中 不過,此 Proxy 會在該領域刪除後關閉,因此如果測試 想要在元件停止後存取這個儲存空間,則 必須存取元件的生命週期,手動停止元件 控制器

計畫聲明操弄

有時候,元件資訊清單的 program 部分內容需要用到 無法變更,且使用 GetComponentDeclReplaceComponentDecl 時, 運用大量樣板來操控程式的內容 宣告內容

let mut echo_client_decl = builder.get_component_decl(&echo_client).await?;
for entry in echo_client_decl.program.as_mut().unwrap().info.entries.as_mut().unwrap() {
    if entry.key.as_str() == "args" {
        entry.value = Some(Box::new(fdata::DictionaryValue::StrVec(vec![
            "Whales".to_string(),
            "rule!".to_string(),
        ])));
    }
}
builder.replace_component_decl(&echo_client, echo_client_decl).await?;

如要解決這個問題,MutateProgramDecl 函式可以提供協助。

protocol Realm {
    MutateProgramDecl(table {
        1: name fuchsia.component.name;
        2: field_name string:fuchsia.component.MAX_NAME_LENGTH;
        3: mutation ProgramFieldMutation;
    }) -> () error RealmBuilderError;

    // Other entries omitted
    ...
};

type ProgramFieldMutation = flexible union {
    /// Sets the field to the given string. Overwrites any pre-existing value
    /// for this field.
    1: set_value string:fuchsia.data.MAX_VALUE_LENGTH

    /// Sets the field to the given vector. Overwrites any pre-existing value
    /// for this field.
    2: set_vector vector<string:fuchsia.data.MAX_VALUE_LENGTH>:fuchsia.data.MAX_NUM_VALUE_ITEMS;

    /// Appends the given values to the field. If the field is not already a
    /// vector, it will be converted into one before the append is applied (a
    /// single value turns into a singleton vector, a missing field turns into
    /// an empty vector).
    3: append_to_vector vector<string:fuchsia.data.MAX_VALUE_LENGTH>:fuchsia.data.MAX_NUM_VALUE_ITEMS;
};
builder.mutate_program_decl(
    &echo_client,
    "args",
    ProgramFieldMutation::SetToVector(
        vec!["Whales".to_string(), "Rule".to_string()],
    ),
).await?;

與子項領域合作

針對任何需要以 直接子項,則可透過 AddChildRealm 呼叫開啟子項領域。

protocol Realm {
    AddChildRealm(struct {
        name fuchsia.component.name;
        properties ChildProperties:optional;
    }) -> (child_realm client_end:Realm) error RealmBuilderError;

    ...
}

這項呼叫類似於使用空白元件呼叫 AddChildFromDecl 主要差異在於呼叫會傳回新的 Realm 個管道。客戶可以透過這個管道,將子發布商加進 然後操控這些領域和子項領域,就像 可能來自於根領域

舉例來說,您可將名為 foo 的子項 (其子項本身命名為 bar) 新增至 領域,以及來自 bar 的能力,轉送至 foo 的父項 範例:

let foo = builder.add_child_realm("foo", ChildProperties::new()).await?;
let bar = builder.add_local_child(
    "bar",
    move |handles: MockHandles| { ... },
    ChildProperties::new(),
).await?;
foo.add_route(
    vec![RouteCapability::protocol_from_marker::<FooBarMarker>()],
    &bar,
    vec![Ref::parent()],
).await?;
builder.add_route(
    vec![RouteCapability::protocol_from_marker::<FooBarMarker>()],
    &foo,
    vec![Ref::parent()],
).await?;

建立領域,以及實作本機元件

領域設定完畢後,您可以透過呼叫 Build 上的 RealmBuilderFactory.New 呼叫傳回的 Builder 管道。一次 系統會呼叫 Build,屆時無法透過任何 這個運作範圍的 Realm 管道,因此領域中的靜態元件可能會 相關元素或物件不會有任何改變動態元件 (如集合中) 仍可以像一般元件一樣,在領域中例項化/執行/刪除 架構語意。Realm Builder 伺服器會傳回一個網址, 提供給 fuchsia.component/Realm.CreateChild 來建立領域。

@discoverable
protocol Builder {
    /// Assembles the realm being constructed and returns the URL for the root
    /// component in the realm, which may then be used to create a new component
    /// in any collection where fuchsia-test-component is properly set up.
    Build(struct {
        runner client_end:fuchsia.component.runner.ComponentRunner;
    }) -> (struct {
        root_component_url string:fuchsia.component.types.MAX_URL_LENGTH;
    }) error RealmBuilderError;
};

Build 函式會接收元件執行元件的用戶端端, 應該為使用運作領域建構工具的元件代管元件執行元件器 由本機 (即處理中) 元件實作支援的元件 以領域為背景這個函式會傳回應由 使用 fuchsia.component/Realm.CreateChild 呼叫來建立建構的 領域。這個子項應放在具有領域建構工具的集合中 環境中可用的解析器和執行元件功能。這類系列 包含在運作領域建構工具「資料分割」 (部分元件) 中 使用者所需合併的元件資訊清單),也可能 ,但任何其他具有正確設定方式的集合 環境也能順利運作

建立領域後,元件管理員就會向本機 元件傳送至 Realm Builder 伺服器,由該伺服器代理這些啟動要求 有本機元件所在領域的執行器管道

這部分是使用用戶端程式庫,而非直接 FIDL 的部分 繫結大幅儲存至樣板上,因為這可以處理 。

每個啟動要求都會包含 ProgramDecl,其中包含一個金鑰 LOCAL_COMPONENT_NAME。這個鍵的值會是為其中一個指定的名稱 加入 AddLocalChild 函式。與元件相關聯的本機處理常式 使用該函式所新增的元件應該會開始執行,並取得 方法。

每個起始要求也包含一個元件控制器管道, 在本機元件發生時,接收來自元件管理員的通知 停止執行收到停止通知後,用戶端程式庫 可以直接停止執行本機元件的工作,也可以 向其發出指示,告知應停止,並提供相關機會 進行清理

領域銷毀

一旦實現領域目標,領域就會被破壞 fuchsia.component/Realm.DestroyChild。這會導致元件管理員停止 依序在領域和用戶端之間終止運作 和其他領域一樣請注意,這是來自測試的一般 FIDL 呼叫 元件管理服務領域銷毀並非針對特定領域建立工具特有。

一旦 DestroyChild 呼叫傳回領域,就會遭到刪除 領域中的元件已停止執行

實作

Fuchsia.git 存放區廣泛使用運作範圍建構工具。 導入 SDK 的功能這裡詳細列出了所有破壞性變更 會經由軟體遷移導入,然後進行累加變更 (例如 唯讀目錄函式),可在 遷移完成。

成效

領域建構工具程式庫僅適用於測試情境,因此 API 的設計是為了兼顧可用性,而非效能。每個函式呼叫都是 並且可能會在輸入無效或其他問題時傳回錯誤 。缺少管道代表錯誤 視為降低測試設定的價格。

人體工學

人體工學是領域建構人員的重要目標,也是客戶 程式庫和原始 FIDL 繫結應易於使用。進階 提供引人注目的功能 API 中的每個函式都會保持同步,因此錯誤 盡可能接近起點位置傳回的值

用戶端程式庫本身也經過明確設計 比使用原始 FIDL 繫結還要多出來的運算作業。以比較兩者的差異為例 (位於「用戶端直接使用 FIDL API」下方)

回溯相容性

此處描述的 API 在使用目前領域建構工具時有一些破壞性變更 。這些變更旨在簡化領域建構工具 功能,例如新增 函式是 API 的簡單新增項目。

安全性考量

Realm 建構工具用戶端程式庫標示為 testonly,因此一律會 與測試程式碼搭配使用。因為 因此領域建構工具無法存取任何資源或功能 各種測試中的公開資料因此沒有關於這個 RFC 的安全疑慮。

隱私權注意事項

不會透過運作領域建構工具提供使用者或其他私人資料 運作範圍更是如此因此,客戶對隱私權 的要求不會構成隱私權疑慮 墨西哥的 RFC。

測試

Realm 建構工具已編寫單元和整合測試, 適應新設計。此外,您也可以使用 採用全新領域建構工具的樹狀結構內測試數量 這些措施能讓您更有自信地瞭解這些變動。

說明文件

Realm 建構工具的說明文件位於 //docs 中。這個 說明文件隨即會更新並擴充,提供詳細的全新設計和 功能。

缺點、替代方案和未知

深度領域支援

這個 RFC 中詳述的函式會讓用戶端傳入名稱 就會觸發這個元件。這些項目會定義為 ,且會在廣告產生的報表中完整顯示的名稱 元件資訊清單。

替代方法是接受「相對名稱」,其中元件 可能比頂層子項更深層 AddRoute 呼叫參照,以便將元件放置在 而非直接子系或同層級組合的執行個體樹狀結構。

減少伺服器端的複雜度

在 C++ 程式庫新增 API 之前,這個 API 會更加簡單。邏輯的大量 存在於用戶端程式庫,而運作領域伺服器元件 最輕薄這種安排方式很合適 將程式庫移植到其他語言的成本大幅提高 因此採用現行做法

用戶端直接使用 FIDL API

替代設計有一種替代方法是讓用戶端連線,練習 領域建構工具 FIDL 直接繫結,而非依賴用戶端程式庫 以便為他們提供所需支援我們仍希望維護並推薦 呼叫 Build 後,所需的執行元件相關工作,因為這些工作 能為客戶產生大量樣板

這會產生以下 Rust 程式碼範例...

let builder = RealmBuilder::new().await?;
let echo_server = builder.add_child(
    "echo-server",
    "#meta/echo_server.cm",
    ChildProperties::new(),
).await?;
let echo_client = builder.add_legacy_child(
    "echo-client",
    "fuchsia-pkg://fuchsia.com/echo#meta/client.cmx",
    ChildProperties::new().eager(),
).await?;
builder.add_route(
    vec![RouteCapability::protocol_from_marker::<EchoMarker>()],
    &echo_server,
    vec![&echo_client],
)).await?;
let realm_instance = builder.build().await?;

...看起來就像這樣:

let rb_factory_proxy = connect_to_service::<RealmBuilderFactoryMarker>().await?;
let pkg_dir_proxy = fuchsia_fs::directory::open_in_namespace(
    "/pkg",
    fuchsia_fs::OpenFlags::RIGHT_READABLE | fuchsia_fs::OpenFlags::RIGHT_EXECUTABLE,
)?;
let pkg_dir_client_end =
    ClientEnd::from(pkg_dir_proxy.into_channel().unwrap().into_zx_channel());
let (realm_proxy, realm_server_end) = create_proxy::<RealmMarker>?().await?;
let (builder_proxy, builder_server_end) = create_proxy::<BuilderMarker>?().await?;
rb_factory_proxy.new(
    pkg_dir_client_end,
    realm_server_end,
    builder_server_end,
)?;

realm_proxy.add_child(
    &"echo-server".to_string(),
    &"#meta/echo_server.cm".to_string(),
    &mut ChildProperties::EMPTY,
).await??;

realm_proxy.add_child(
    &"echo-client".to_string(),
    &"fuchsia-pkg://fuchsia.com/echo#meta/client.cmx".to_string(),
    &mut ChildProperties {
        startup: fsys::StartupMode::Eager,
        ..ChildProperties::EMPTY
    },
).await??;

realm_proxy.add_route(
    &mut CapabilityRoute {
        capabilities: vec![
            RouteCapability::Protocol(RouteCapabilityProtocol {
                name: EchoMarker::PROTOCOL_NAME.to_string(),
                ..RouteCapabilityProtocol::EMPTY
            }),
        ],
        from: Ref::Child(ChildRef {
            name: "echo-server".to_string(),
            collection: None,
        }),
        to: vec![Ref::Child(ChildRef {
            name: "echo-client".to_string(),
            collection: None,
        })],
        ..CapabilityRoute::EMPTY
    },
).await??;

// We omit the component runner because there are no local components in this
// realm.
let root_component_url = builder_proxy.build(None).await?;
let realm_instance =
    ScopedInstance::new("collection-name", root_component_url).await?;

由於樣板較高等,因此未選擇這個替代選項 詳細函式呼叫

含領域的隱含

根據「設計」中提出的提案區段、功能 透過運作建構工具 (可在元件中存取) 建構領域中的元件能夠透過 「本機元件」將本機元件新增至領域後 只能轉送給客戶

這種間接性可能會意外成為新使用者。如果能力是 那是合理的猜測 新增的能力路徑的來源是 parent

如要讓路徑擁有 parent 來源,以便用於 建構領域的父項時,領域建構工具可將新元件插入 也就是由使用者控制的根元件上方的領域這個元件 能力實作項目,以及任何來自 parent 且「不是」的路徑 這項元件導入後,就會產生來源為 parent 的優惠 新增至隱含插入的元件中

例如,這個程式碼...

let builder = RealmBuilder::new().await?;
let echo_client = builder.add_child(
    "echo-client",
    "#meta/echo_client.cm",
    ChildProperties::new().eager(),
).await?;
builder.add_local_capability(
    RouteCapability::protocol_from_marker::<EchoMarker>(),
    |stream: EchoRequestStream| { echo_server_implementation(stream).boxed() },
).await?;
builder.add_route(
    vec![RouteCapability::protocol_from_marker::<EchoMarker>()],
    Ref::parent(),
    vec![&echo_client],
)).await?;
builder.add_route(
    vec![RouteCapability::protocol_from_marker::<LogSinkMarker>()],
    Ref::parent(),
    vec![&echo_client],
)).await?;
let realm_instance = builder.build().await?;

...會導致這種領域結構...

implicit_local_component
          |
         root
          |
     echo_server

...implicit_local_component 的資訊清單會包含以下內容:

{
    offer: [
        {
            protocol: "fuchsia.logger.LogSink",
            from: "parent",
            to: "root",
        },
        {
            protocol: "fuchsia.example.Echo",
            from: "self",
            to: "root",
        },
    ],
    capabilities: [
        {
            protocol: "fuchsia.example.Echo",
        },
    ],
}

目前並未選擇這個替代選項,因為這沒有必要 實現專區。

提供與公開,而非單一路線定義

上方所提議 AddRoute 的 API 不需要使用者區分 之間的關聯父項可以自由參照為來源或 附加至路徑這點與 CML 和 CM 格式不同。 呼叫 AddOfferAddExposeAddRoute 呼叫會觸發領域建立工具 API 在概念上與元件架構的其餘部分更一致。

protocol Realm {
    AddOffer(table {
        1: capabilities vector<RouteCapability>;
        2: from fuchsia.component.decl.Ref;
        // An error will be returned if `to` contains `Ref::Parent`
        3: to vector<fuchsia.component.decl.Ref>;
    }) -> () error RealmBuilderError;

    AddExpose(table {
        1: capabilities vector<RouteCapability>;
        // An error will be returned if `from` contains `Ref::Parent`
        2: from fuchsia.component.decl.Ref;
        3: to fuchsia.component.decl.Ref;
    }) -> () error RealmBuilderError;

    // Other entries omitted
    ...
};

由於目前的 API 允許使用 方便日後探索深度領域支援。 這種做法可讓我們在合併採用 提供/公開 API,而此方法會讓 API 變得更簡單。