子打包组件

- 组件可以利用子打包来整理嵌套组件的层次结构,其中每个组件都封装在各自的软件包中,并带来自己的一组依赖项。

子软件包可实现以下功能

  • 封装的依赖项(打包的组件仅声明其直接依赖项)
  • 隔离的 /pkg 目录(已分组的组件不需要将其文件、库和元数据合并到单个共享命名空间中)
  • 可靠的依赖项解析(系统和构建工具可确保子软件包始终“随其软件包一起移动”)

与 Fuchsia 组件的关系

Fuchsia 使用软件包来“分发”其软件(例如,将软件加载到设备上)。不包含依赖项的单个组件通常包含在单个软件包中。用于启动其他组件的组件可以定义一个软件包,其中包含使用子打包的子组件特定版本(在构建时确定)。

以这种方式组织时,子软件包层次结构会镜像组件父子关系。然后,系统会从父组件软件包的已声明子软件包中加载子组件。 ABI

组件还可以使用子软件包来声明对不可执行组件(即没有 program 声明的组件)的依赖,并使用目录功能获取对其中 /pkg 数据的访问权限。通过将软件包数据公开为标准“目录功能”,组件可使用“功能路由”来限制对特定软件包子目录的访问,从而遵循最小权限原则。

软件包依赖项镜像组件依赖项

Fuchsia 系统由层次结构的组件定义。从第一个组件(层次结构的根)开始,组件通过启动提供相应 capability 的 children(子组件)向系统添加这些 capability。每个组件都有机会启动自己的组件子树。

如需实例化 child 组件,父级需使用“组件网址”(软件包网址与组件清单的软件包内资源位置组合)按子项在软件包系统中的位置标识子项的来源(实现软件);例如 fuchsia-pkg://fuchsia.com/package#meta/component.cm

重要的是,在组件框架下,组件只有在声明子项时才能按组件网址引用运行时依赖项。组件网址不用于定义对对等组件或其本地子树之外的任何其他组件的依赖。

当子组件由绝对组件网址(如 fuchsia-pkg://fuchsia.com/package#meta/component.cm)定义时,组件开发者会转移该依赖项的实现控制,以供在产品组装时或运行时由临时来源(软件包服务器)确定。

通过子打包,开发者可以改用构建时解析来声明软件包依赖项,“烘焙”预期的组件实现(包括已知 ABI 和行为),同时不影响软件包边界的封装和隔离优势。这样可以确保具有组件依赖项的软件包具有封闭实现,并且如果不重新构建父组件的软件包,其子组件的行为不会改变。

子打包组件网址还可以避免绝对组件网址固有的问题:例如,如果父组件从 fuchsia-pkg://alt-repo.com/package#meta/parent.cm 等备用代码库加载,其子组件可能也位于该 alt-repo 中,并且无法静态定义在运行时之前可通过 fuchsia.comalt-repo.com(或其他)未知的绝对组件网址。

通过使用相对软件包路径,子打包子组件的实现由带有子软件包名称(子软件包网址,其中 URI 片段指定组件清单的路径)的相对组件网址(例如 some-child#meta/default.cm)进行标识。子软件包名称 some-child 的映射在 build 配置中声明,并通过将子软件包的软件包哈希存储在父组件的软件包元数据中(映射到子软件包名称)在构建时进行解析。

依赖项具有传递性并进行了封装

组件软件实现不会 use 其他组件。组件 use capability。组件的功能可能来自其父级(在父级不知情的情况下直接或间接路由)或子级。重要的是,子项提供的 capability 也可以是直接或间接的。子级的实现是封装的,因此它公开的 capability 可以由该子级实现,也可以从该子级的一个子级进行路由。

通过子打包,组件可以完全封装其实现,包括对子组件的任何依赖项。

如果某个组件使用绝对组件网址声明子项,则在运行时选择该子项的特定实现。对于某些用例,这可能是需要的,但代价是父组件并不封闭:在新环境中很难重复使用父组件。分发和移植非封闭代码也需要跟踪所有外部依赖项,然后确保这些依赖项在每个新环境中始终可用。

    children: [
        {
            name: "intl_property_provider",
            url: "fuchsia-pkg://fuchsia.com/intl_property_manager#meta/intl_property_manager.cm",
        },
        ...
    ]

如果不需要运行时解析,父组件可以更新其子项以使用相对路径网址,并将子组件的软件包声明为子软件包依赖项(在构建时进行解析)。这样,当组件将子组件子打包时,子组件的软件包本身就会引入其所有子打包组件,而不会将这些依赖项公开给可能使用它的其他组件和运行时环境。

    children: [
        {
            name: "intl_property_provider",
            url: "intl_property_manager#meta/intl_property_manager.cm",
        },
        ...
    ]

通过 /pkg 目录没有 Ambient 授权

为了支持 Fuchsia 组件的基本运行时要求,组件可以通过 /pkg 目录功能访问包含其软件包内容的目录。

如上所述,子打包允许软件包将其组件依赖项声明为分层的封装组件软件包。此模式不需要为每个组件使用单独的软件包,但鼓励使用,并且 Fuchsia 运行时和工具旨在使声明、构建和运行单独打包组件的过程自然且高性能。

相反,组合到单个软件包中的多个组件共享单个合并后的 /pkg 目录。通过将多个组件捆绑在一个软件包中,每个组件不仅可以访问相同的数据,还可以访问该软件包中其他组件的元数据,而无需进行显式功能路由。

在某些情况下,如果多个组件共享对同一数据的访问权限,这可能会很方便。但是,如果组件需要访问不同的数据集,或者一个组件使用的数据不应公开给另一个组件,将多个组件打包在一起可能会破坏最小权限原则,使得子软件包更合适。

组件可能无法利用这一继发权限更是令人担忧的事,而不是缓解措施,因为实际情况未必总是如此,并且此权限为一个组件开辟了意外机会来利用另一个组件的数据。

与在一个软件包中使用多个组件相比的优势

目前,Fuchsia 允许单个软件包包含多个组件。此功能早于子软件包的存在,并且提供了另一种通过相对网址声明子组件的方法,即通过按组件清单的资源路径标识组件的 URI 片段。格式为 #meta/some-child.cm 的组件网址会通知 Fuchsia 组件解析器从包含父组件清单的同一软件包中加载 some-child 的组件实现。

用于共享软件包资源的内置访问权限控制

组件框架通过以下方式帮助强制执行 Fuchsia 的功能访问权限控制政策:要求组件明确声明其功能需求,并使父级组件负责从已知功能来源(来自父级的父级或其他子级)路由任何外部功能(包括资源)。

如果一个组件需要另一个组件的软件包中的资源,组件框架功能路由声明允许源组件公开特定的子目录,使目标组件只能访问其父组件明确提供的所需资源。

这支持通过依赖于从公共软件包访问共享 /pkg 目录而不公开整个 /pkg 目录而可能满足的任何用例。

子软件包隔离的 /pkg 目录与组件框架功能路由相结合,为 Fuchsia 架构提供了一致的方法来控制对软件包资源的访问和共享。

更改了传递依赖项以免破坏封装

将组件依赖项组合到单个软件包中时,所有组件共享一个平面命名空间,并且还必须包含传递依赖项。

例如,如果单个软件包 SP 捆绑了组件 A 和组件 B,但 B 还依赖于相对 URI fragment (#meta/C.cm) 的 C,则软件包 SP 必须捆绑 ABC。如果后来修改了 B,以将 C 替换为两个新组件 DE,则软件包 SP 的定义必须更改为软件包 ABDE,并舍弃 C除非为便于参数,D 和/或 E 也依赖于 C

虽然某些构建环境允许组件构建目标声明传递组件依赖项,但这种做法会增大将这些组件的内容合并到单个命名空间的风险。如果某个组件或其任何依赖项发生了更改,新文件可能会覆盖该软件包中组件子树任何部分内其他组件的文件,导致未定义且可能具有灾难性的方式破坏实现。

子软件包通过将传递依赖项封装在每个子软件包的定义中,大幅简化了传递依赖项,因此软件包 SP 可替换为仅依赖于子软件包 B(包含组件 B)的软件包 A(包含组件 A)。软件包 A 无需其他依赖项,并且即使 B 的依赖项发生变化也是如此。

子打包实现是构建时保证

使用相对的 URI 片段组件网址(例如 #meta/some-child.cm)实际上并不能保证“同一软件包”中的父组件和子组件之间的 API 兼容性,因为它们实际上可以通过该软件包的不同版本进行解析。

软件包是否临时解析(通过软件包服务器)。从父组件解析到子组件需要并加载的这段时间内,可以重新发布同一软件包的新版本。子实现可能与软件包的原始版本中包含的实现不同。

这并不罕见或人为创造的用例:在组件框架中,组件(默认情况下)仅在需要时解析。提供单个服务 S 的组件将不会加载,除非某个其他组件确实需要服务 S。根据程序的业务逻辑,可能会在父组件启动后的几分钟或几小时(或更长时间)内调用 S

示例

将 build 依赖项声明到子软件包

支持 Fuchsia 的构建框架应包含用于声明 Fuchsia 软件包及其内容的模式。如果也启用了支持子软件包的功能,软件包声明将通过直接包含的方式列出其依赖的子软件包。

例如,在 fuchsia.git 中,用于声明 Fuchsia 软件包的 GN 模板支持两个可选列表:subpackages 和(不太常用)renameable_subpackages。可以添加其中一项或两项。renameable_ 版本允许软件包为子软件包分配一个软件包专用名称,在通过软件包网址或组件网址引用子软件包时使用:

fuchsia_test_package("subpackage-examples") {
  test_components = [ ":subpackage-examples-component" ]
  subpackages = [
    "//examples/components/routing/rust/echo_client",
    ":echo_client_with_subpackaged_server",
    "//src/lib/fuchsia-component-test/realm_builder_server:pkg",
  ]
  renameable_subpackages = [
    {
      name = "my-echo-server"
      package = "//examples/components/routing/rust/echo_server"
    },
  ]
}

subpackages 列表包含 GN fuchsia_package build 目标的列表。默认情况下,子软件包名称(包含的软件包将用于引用软件包的名称)取自子软件包的 fuchsia_package 目标的已定义 package_name

您也可以使用 renameable_subpackages 列表中的 package 变量声明子软件包目标。renameable_targets 还包含一个可选的 name 变量,用于替换子软件包的默认名称。

声明子封装子项

子软件包仅对其父软件包以及该软件包中的组件可见。因此,子软件包名称只需在该父软件包中是唯一的。如果两个子软件包目标同名(或出于任何其他原因),父软件包可以自由分配自己的子软件包名称(例如,在 GN 中通过 renameable_subpackages 分配)。

在 CML 中声明子打包的子组件时,url 应为相对的子打包组件的网址,如以下示例所示:

children: [
    {
        name: "echo_server",
        url: "echo_server#meta/default.cm",
    },
],

您也可以在运行时声明中引用子打包的子组件,例如,在通过 Realm Builder API 声明子组件时。例如:

// Add the server component to the realm, fetched from a subpackage
let echo_server = builder
    .add_child("echo_server", "my-echo-server#meta/default.cm", ChildOptions::new())
    .await
    .unwrap();

// Add the client component to the realm, fetched from a subpackage, using a
// name that is still scoped to the parent package, but the name matches the
// package's top-level name. In `BUILD.gn` the subpackage name defaults to
// the referenced package name, unless an explicit subpackage name is
// declared.
let echo_client = builder
    .add_child("echo_client", "echo_client#meta/default.cm", ChildOptions::new().eager())
    .await
    .unwrap();