软件包可以“包含”其他软件包(称为“子软件包”),从而形成嵌套软件包的层次结构。 组件可以利用子软件包来整理嵌套组件的层次结构,其中每个组件都封装在自己的软件包中,并带来自己的一组依赖项。
子软件包启用:
- 封装的依赖项(打包的组件仅声明其直接依赖项)
- 独立的
/pkg
目录(分组的组件无需将其文件、库和元数据合并到单个共享命名空间中) - 可靠的依赖项解析(系统和构建工具可确保子软件包始终与其软件包“同行”)
与 Fuchsia 组件的关系
Fuchsia 使用软件包来“分发”其软件(例如,将软件加载到设备上)。不包含任何包含的依赖项的单个组件通常包含在单个软件包中。用于启动其他组件的组件可以使用子软件包定义一个软件包,该软件包包含其子组件的特定版本(在构建时确定)。
以这种方式进行整理后,子软件包层次结构会反映组件父子关系。然后,系统会从父组件软件包的声明的子软件包加载子组件。封装了软件包边界上的ABI 依赖项。
组件还可以使用子软件包声明对不可执行组件(不包含 program
声明的组件)的依赖项,并使用目录功能访问其中的 /pkg
数据。通过将软件包数据公开为标准目录功能,这些组件使用功能路由来限制对特定软件包子目录的访问权限,从而遵循最小权限原则。
软件包依赖项反映组件依赖项
Fuchsia 系统由组件层次结构定义。从第一个组件(层次结构的根)开始,组件通过启动提供这些功能的 children
(子组件)向系统添加功能。每个组件都有机会启动自己的子树组件。
如需实例化 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.com
或 alt-repo.com
(或其他)解析的绝对组件网址,因为这些信息直到运行时才会知道。
通过使用相对软件包路径,子软件包子组件的实现由包含子软件包名称的相对组件网址(子软件包网址,其中 URI 片段指定了指向组件清单的路径)标识,例如 some-child#meta/default.cm
。从子软件包名称 some-child
的映射在 build 配置中声明,并在 build 时解析,方法是将子软件包的软件包哈希存储在父级组件的软件包元数据中,并映射到子软件包名称。
依赖项是传递依赖项且封装
组件软件实现不会use
其他组件。组件 use
功能。组件的功能可以来自其父级(由父级直接或间接路由,而组件不知情)或子级。重要的是,子级公开的 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
目录设置任何氛围权限
为了支持 Fuchsia 组件的基本运行时要求,组件可以通过 /pkg
目录功能访问包含其软件包内容的目录。
如上所述,通过子软件包,软件包可以将其组件依赖项声明为分层的封装组件软件包。这种模型不需要为每个组件单独创建一个软件包,但我们还是建议这样做。Fuchsia 运行时和工具旨在让声明、构建和运行单独打包的组件的过程变得自然且高效。
反之,在单个软件包中组合的多个组件共用一个合并的 /pkg
目录。在单个软件包中捆绑多个组件后,每个组件不仅可以访问相同的数据,还可以访问该软件包中其他组件的元数据,而无需显式进行 capability 路由。
在某些情况下,如果多个组件共享对同一数据的访问权限,这可能很方便。但是,如果组件需要访问不同的数据集,或者一个组件使用了不应向另一个组件公开的数据,将组件打包在一起可能会破坏最小特权原则,因此子软件包更适合。
某个组件可能不会利用这种附带特权,这不是一个令人欣慰的事实,而是一个值得关注的问题,因为情况可能并非总是如此,而且这种特权会为某个组件利用另一个组件的数据打开意外的机会。
与在单个软件包中使用多个组件相比的优势
目前,Fuchsia 允许单个软件包包含多个组件。此功能早于子软件包的出现,它提供了另一种通过相对网址声明子组件的方法;即通过 URI 片段,该片段通过组件清单的资源路径来标识组件。采用 #meta/some-child.cm
格式的组件网址会通知 Fuchsia 组件解析器从包含父级组件清单的同一软件包中加载 some-child
的组件实现。
用于共享软件包资源的内置访问权限控制
组件框架通过要求组件明确声明其功能需求,并让父级组件负责从已知功能来源(来自父级的父级或来自其他子级)路由任何外部功能(包括资源),有助于强制执行 Fuchsia 的功能访问控制政策。
如果一个组件需要另一个组件软件包中的资源,组件框架功能路由声明允许源组件公开特定子目录,以便目标组件只能访问其父组件明确提供的所需资源。
这支持通过依赖于对常用软件包中的共享 /pkg
目录的访问来满足的任何用例,而无需公开整个 /pkg
目录。
子软件包隔离的 /pkg
目录与组件框架 capability 路由相结合,可提供与 Fuchsia 架构一致的方式来控制对软件包资源的访问和共享。
对传递依赖项所做的更改不会破坏封装
将组件依赖项合并到单个软件包中时,所有组件都共享单个扁平命名空间,并且还必须包含传递依赖项。
例如,如果单个软件包 SP
捆绑了组件 A
和组件 B
,但 B
还通过相对 URI 片段 (#meta/C.cm
) 依赖于 C
,则软件包 SP
必须捆绑 A
、B
和 C
。如果 B
稍后被修改为将 C
替换为两个新组件 D
和 E
,则软件包 SP
的定义必须更改为捆绑 A
、B
、D
和 E
,并移除 C
,除非(为方便起见)D
或 E
(或两者)也依赖于 C
。
虽然某些构建环境允许组件构建目标声明传递组件依赖项,但这种做法会增加将这些组件的内容合并到单个命名空间中的风险。如果组件或其任何依赖项发生变化,新文件可能会覆盖该软件包中组件子树的任何部分中的其他组件中的文件,以未定义且可能灾难性的方式破坏实现。
子软件包通过将传递依赖项封装在每个子软件包的定义中,大大简化了传递依赖项,因此软件包 SP
可以替换为仅依赖于子软件包 B
(包含组件 B
)的软件包 A
(包含组件 A
)。软件包 A
不需要任何其他依赖项,并且不会发生变化,即使 B
的依赖项发生变化也是如此。
子软件包实现是构建时保证
使用相对 URI fragment 组件网址(例如 #meta/some-child.cm
)实际上并不能保证“同一软件包”中的父级组件和子组件之间的 ABI 或 API 兼容性,因为它们实际上可以从该软件包的不同版本解析。
如果软件包是暂时性解析的(从软件包服务器解析)。在父级组件解析完毕到需要并加载子级组件之间的时间段内,可以重新发布同一软件包的新版本。子实现可能与软件包原始版本中包含的实现不同。
这并不是罕见或人为的用例:在组件框架中,组件(默认情况下)仅在需要时才会解析。除非某个其他组件实际需要服务 S
,否则系统不会加载公开单个服务 S
的组件。父级组件启动后,系统可能会在几分钟或几小时(或更长时间)后调用 S
,具体取决于程序的业务逻辑。
示例
向子软件包声明构建依赖项
支持 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
构建目标的列表。默认情况下,子软件包名称(包含软件包将用于引用该软件包的名称)取自子软件包的 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();