RFC-0140:Realm Builder

RFC-0140:Realm Builder
状态已接受
领域
  • 组件框架
说明

概述并描述了 Realm Builder 的设计功能集

Gerrit 更改
作者
审核人
提交日期(年-月-日)2021-09-06
审核日期(年-月-日)2021-11-10

摘要

Realm builder 是 Rust 中可用的树内库, 可让用户以编程方式汇编组件的 C++ 领域,并使这些领域能够包含本地 由进程内实现提供支持的组件此库在实现 包含一个名为 Realm Builder Server 的 Sidecar 子组件 。此服务器托管 大部分实现,并且库通过 FIDL 与其进行通信。

此 RFC 概述了 FIDL API 和领域的客户端库的主要设计 并提供了此 API、C++ 客户端库和 发布 Realm Builder Server 的清单和二进制文件(作为预构建) 。

设计初衷

组件集成测试是一项非常重要的任务,组件 框架团队希望尽可能简单、愉快地完成任务。很遗憾 该组件框架的许多功能可提供卓越的安全性和 将隔离属性应用于生产组件,最终使测试复杂化 场景。要在不同的环境中测试某个组件, 必须手动维护每种环境的单独组件清单。如果 测试希望为被测组件提供功能、无法 来动态声明功能,并将其提供给 就会增加难度。如果被测组件 与 capability 的模拟提供程序连接,则模拟必须是 通过 FIDL 进行通信,因此需要维护一个测试专用 FIDL 该通信的 API。

Realm Builder 旨在显著改善集成测试的体验 作者,进而影响集成测试的质量。通过实现 Realm Builder 库可以创建新的 resolver 功能 并在运行时将其提供给组件框架。修改者 实现 Realm Builder 库可以运行的 runner 功能 将本地组件插入到这些构造领域,这些领域由 进程内实现,因此可以使用进程内工具来协调 逻辑。集成测试常见的任务 作者,例如设置存储功能或提供虚构配置 可以自动执行和简化操作

该库已在 通过 SDK 提供此树,它也可以 许多在 Fuchsia 树之外工作的开发者都会用到这个工具。

利益相关方

谁对是否接受此 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 以一个经过重大变革的库为基础, 得益于不同团队的反馈和改进, 已经采用此技术的团队。具体而言,Netstack、Wlan、 蓝牙和 SWD 团队已将该库集成到他们的集成中 测试。

设计

概览

Realm Builder 有两个关键部分:客户端库和 Realm Builder 服务器。开发者使用客户端库来描述 他们想要构建什么大区,然后指示库创建 而客户端库通过协同合作来完成这些任务 与 Realm Builder Server 进行 FIDL 连接。

这种合作具有一些不错的属性, 不同语言的客户端库(服务器可以在 不同的客户端语言),但客户端和服务器之间的拆分位于 Realm Builder 在集成测试场景中可用所需的事实。 执行测试中存在的各种情况的测试运行程序会使用 组件的传出目录句柄,并且不将其提供给 组件本身。因此,您将无法声明和提供任何 功能,以及任何需要此功能的任务(例如 解析器和运行程序功能)必须移入 一个单独的组件。

Realm 构建器的设计方式应确保客户端使用的 FIDL API (在某些区域会直接映射到) 客户端库向开发者提供的 API。

我们建议使用客户端库而不是原始 FIDL 绑定,因为 它们可以提供更好的开发者体验,并完成一些任务(例如管理 本地组件实现的状态)会很繁琐,并且需要 大型样板文件。

Realm 初始化

要创建新领域时,客户端库会建立一个新的 与 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。不过请注意,如果 Realm Builder 服务器 使用不同版本的 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?;

功能路由

对于使用 Realm Builder 的开发者来说,一项非常常见的任务是 组件可用。上述操作方法如下所示,其中 相关组件清单(特别是 任何旧子节点或本地子节点的数据)都被提取、更改,然后将其发送回 Realm Builder 服务器,但此方法具有一些不良属性:

  • 不能在一个 OfferDecl 中指定多个目标。
  • 不能在 OfferDeclExposeDecl 中指定多项 capability。
  • 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 服务器本身提供的, 要求 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 Server 托管一个 为领域中的子组件存储容量,并提供协议 访问组件的存储空间这是通过将 “内置”组件添加到领域,以提供存储功能, 只读目录存根下的说明 函数。

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 可用于读取和写入 文件。 不过,一旦域被销毁,此代理就会关闭,因此,如果测试 希望在组件停止运行后访问该存储空间,则测试 应通过访问组件的生命周期来手动停止组件 控制器

计划声明操纵

有时,组件清单的 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?;

大区创建和本地组件实现

完成大区设置后,可以通过对BuildRealmBuilderFactory.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 函数接受组件运行程序的客户端, 使用 Realm builder 的组件应该托管一个组件运行程序, 由本地(即进程内)组件实现支持的 领域。此函数会返回一个组件网址, fuchsia.component/Realm.CreateChild 调用中的客户端来创建构造的 Realm.此子级应放置在具有 Realm 构建器的集合中 环境中可用的解析器和运行程序功能。此类集合 包含在 Realm Builder shard(部分组件)中 清单(用户需要合并到其组件清单中)且可以 任何集合(只要集合具有适当配置) 环境也能正常工作。

创建大区后,组件管理器将发送本地启动请求 Realm Builder 服务器再对这些启动请求进行代理 本地组件所在大区的运行程序渠道。

在这一部分中,我们使用客户端库而不是直接 FIDL 绑定可以显著节省样板文件 。

每个启动请求都将包含一个 ProgramDecl,其中包含 LOCAL_COMPONENT_NAME。此键的值将是以下给定名称之一: AddLocalChild 函数。与组件相关联的本地例程 之后,系统应开始执行,并获得对 句柄。

每个启动请求还包含一个组件控制器通道,该通道应 用于在本地组件 应停止执行。收到停止通知后,客户端库 可以立即停止执行本地组件的任务,也可以 并通知它自己停止指令,并给它机会

王国破坏

当王国的宗旨得到实现后,这个国度就会被毁, fuchsia.component/Realm.DestroyChild。这会导致组件管理器 有序地在客户端中终止其依赖项, 就像在其他任何领域一样请注意,这是测试中的常规 FIDL 调用 组件管理器;领域销毁并非特定于 Realm Builder。

一旦 DestroyChild 调用返回该域,它就会被销毁, 该域内的组件已停止执行。

实现

Realm Builder 在整个 fuchsia.git 代码库中广泛使用,并且 获取使用 SDK 的用户。此处详细介绍所有重大更改 因此将先通过软迁移引入,然后再进行额外的更改(例如: 只读目录函数), 迁移完毕

性能

Realm Builder 库仅适用于测试场景,因此 相较于性能,该 API 的编写更注重易用性。每个函数调用都是 同步,如果输入无效或存在其他问题, 错误。如果没有使用流水线, 对引发错误的输入做出响应,但测试设置速度会比较慢。

工效学设计

人机工程学是大区建设者的一个重要目标,客户 库和原始 FIDL 绑定应易于使用。通过 组件突变函数旨在提供引人注目的功能, 而且 API 中的每个函数都是同步的 返回尽可能靠近原点的位置。

客户端库本身也明确设计为提供更完善的 使用原始 FIDL 绑定来改进人体工程学。比较两者的示例可以是 “客户端直接使用 FIDL API”

向后兼容性

此处介绍的 API 对当前 Realm Builder 做出了一些破坏性更改 实施。这些更改旨在简化 Realm Builder 的 而无需破坏性更改,例如添加新的 函数是对 API 的简单补充。

安全注意事项

Realm 构建器客户端库标记为 testonly,因此始终为 与测试代码一起使用的受限测试环境。由于 这样,Realm Builder 无法访问任何尚未访问的资源或功能 可用于测试因此,此 RFC 没有任何安全问题。

隐私注意事项

不会通过 Realm Builder 提供用户或其他隐私数据,并且 Realm Builder 甚至可以访问此类数据。因此,Google Analytics 此 RFC。

测试

Realm Builder 已经为其编写了单元测试和集成测试, 进行调整以适应新设计。此外,还有一个相当大的 将修改为使用新的 Realm Builder 的树内测试数量 设计,这些改进将有助于提高对更改的信心。

文档

Realm 构建器具有位于 //docs 中的文档。这个 文档将会更新并扩充,以详细说明新设计和 功能

缺点、替代方案和未知问题

深度领域支持

此 RFC 中详述的函数会让客户端传入名称 每当引用领域的组件时,都会抛出该异常。这些被定义为“子级 ”名称“名称”,这些名称将会一字不差地显示在 组件清单

另一种方法是接受“相对名称”,其中组件 比顶级子节点更深, 由 AddRoute 调用引用,以便将组件连接在距离更远的地方 与直接后代或同级相比。

降低服务器端的复杂性

在添加 C++ 库之前,API 要简单得多。逻辑的正文 位于客户端库中,而 Realm Builder 服务器组件为 尽可能纤薄这也是一种合适的安排 将库移植到其他语言的成本显著增加, 并因此采用了目前的方法。

客户端直接使用 FIDL API

另一种设计是让客户联系并练习 Realm Builder 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?;

未选择此替代选项,因为样板代码更高且 执行详细的函数调用

隐式包含领域

根据“设计”部分列出的方案,部分, 功能 在组件中使用 Realm Builder 实现, 构建领域中的组件可通过 “local component”。将本地组件添加到领域后,功能 则会从该客户端路由至其客户端。

新用户可能会预料之外的这种转达方式。如果某项功能 实现,那么合理的猜测是 添加的功能路由的源为 parent

对于在parent 创建大区的父级,大区构建器可以将新组件插入 用户控制的根组件上方的域。这个组件会存储 capability 实现,以及任何来自 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 格式, 对 AddOfferAddExpose 进行 AddRoute 调用会使 Realm 构建器 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 允许 在未来的深度领域支持方面,将使您可以更轻松地探索这些领域, 通过这种方法,我们可以收集更多关于综合利用数据 Offer/expose API,而此方法会让 API 稍微简单一些。