| RFC-0106:Fuchsia SDK 中包含的组件清单 | |
|---|---|
| 状态 | 已接受 |
| 区域 |
|
| 说明 | 创建包含 SDK 中分片 sysroot 的组件清单,并将其分发给树外集成者。 |
| 问题 | |
| Gerrit 更改 | |
| 作者 | |
| 审核人 | |
| 提交日期(年-月-日) | 2021-05-12 |
| 审核日期(年-月-日) | 2021-06-23 |
摘要
本文档建议向集成商开发套件 (IDK) 添加组件清单分片。这将为向树外 (OOT) 开发者发布组件清单分片创建标准可供性,并建立一种通用模式,使清单分片可在不同的开发者环境之间移植。
读者应熟悉以下内容:
- 组件清单
- 尤其是组件清单中的
include语法。
设计初衷
跨环境对齐工作流
自推出以来,组件清单分片以及 cml 和 cmx 中的 include 语法已得到广泛采用。它们可用于各种任务,例如减少样板代码、封装实现细节、简化开发者工作流程,以及用于核心领域变体。
目前,处理清单包含的机制依赖于 Fuchsia 树的源代码布局。这对于位于 Fuchsia 树中的清单来说效果很好,但无法很好地移植到 OOT 开发中。例如,您可以参阅这篇关于在 C/C++ 组件中记录日志的指南。该指南首先指示开发者向其组件清单添加以下内容:
{
include: [ "sdk/lib/diagnostics/syslog/client.shard.cml" ],
}
不过,该指南指出,上述方法仅适用于树内 (IT) 驱动程序。由于 OOT 开发者也是本指南的目标受众群体,因此本指南指示这些开发者改为将以下内容添加到其组件清单中,从而有效地内嵌分片的内容:
{
use: [
{ protocol: "fuchsia.logger.LogSink" },
],
}
其他位置也存在相同的问题。例如,测试运行程序框架的测试运行程序清单(用于支持 Fuchsia 上的各种类型的测试,例如 C/C++ GoogleTest、Rust libtest、Go 上的 gotest 等)包含仅适用于 IT 的说明。这会阻碍 OOT 开发者采用良好的测试实践。
这种妥协会使最初包含清单分片的初衷落空,因为客户端会暴露于 Fuchsia 日志记录的实现细节。 此外,它还使得未来难以协调大规模变更,例如计划未来软过渡到用于从组件发布日志的不同协议。最后,它增加了 IT 和 OOT 开发者工作流程之间的摩擦。
模糊实现细节
如果我们重新审视 LogSink 分片示例,我们建议 syslog 库的客户端包含此分片,因为此分片可将使该客户端库正常运行所需的功能引入到其组件的沙盒中。
目前,这组功能非常简单,仅包含一个协议,但预计未来会发生变化。例如,我们可能会引入一种系统,用于以专用格式将结构化数据写入 VMO,并在客户端和服务器之间轮换这些数据,这与当前的跟踪工作方式类似,而不是像当前协议那样,客户端将套接字连接到 syslog,然后将字符缓冲到该套接字中以记录它们。
将这些详细信息隐藏在分片后面,可减轻组件作者的认知负荷,他们只想写入 syslog(从他们的角度来看,这是一个常用实用程序),而不在乎这些详细信息。但这也允许 syslog 维护者随着时间的推移改进这些细节,而无需组件所有者关注。
这种方式也适用于反向操作(即公开组件的功能,而不是使用它们),并且适用于不同类型的功能(例如目录功能,而不是协议功能)。例如,您可以参阅检查发现和托管指南。该指南说明,开发者必须将以下内容添加到其组件清单中:
{
capabilities: [
{
directory: "diagnostics",
rights: [ "connect" ],
path: "/diagnostics",
},
],
expose: [
{
directory: "diagnostics",
from: "self",
to: "framework",
},
],
}
或者,开发者也可以只包含一个分片。这样一来,开发者就无需编写 14 行样板代码,也无需处理向框架公开目录功能等概念(大多数组件开发者不需要熟悉这些概念),并且将来如果需要更改此机制,也无需费心费力。最后,如果分片可以在 IT 和 OOT 组件清单之间移植,那么检查指南会短得多,因为它不需要为 IT 和 OOT 开发者分别指定一组指令。
简化系统功能的消耗
另一个更复杂的示例是,直接作为 FIDL 客户端(而不是像上一个示例中那样通过客户端库)使用 Web 引擎时可能需要的各种功能。Web 运行时功能强大且稳健,如今的 Web 应用在具备所需功能的情况下,能够执行许多特权设备操作。fuchsia.web 定义了 Web 引擎实现需要具备的不同功能,才能托管某些 Web 应用。
为了简化从环境中使用这些相同功能,然后将这些功能提供给 Web 引擎的过程,我们提供了按常见类别划分这些功能的分片。例如,此分片定义了 Web 引擎所需的一组基本功能:
{
"sandbox": {
"services": [
"fuchsia.device.NameProvider",
"fuchsia.fonts.Provider",
"fuchsia.intl.PropertyProvider",
"fuchsia.logger.LogSink",
"fuchsia.memorypressure.Provider",
"fuchsia.process.Launcher",
"fuchsia.sysmem.Allocator",
]
}
}
此分片定义了在以下情况下所需的其他功能:Web 应用不是本地应用和/或需要网络。
{
"sandbox": {
"services": [
"fuchsia.net.name.Lookup",
"fuchsia.net.interfaces.State",
"fuchsia.posix.socket.Provider"
]
}
}
而以下分片是必需的,才能在 Scenic 视图中呈现 Web 应用:
{
"sandbox": {
"services": [
"fuchsia.accessibility.semantics.SemanticsManager",
"fuchsia.ui.input.ImeService",
"fuchsia.ui.input.ImeVisibilityService",
"fuchsia.ui.scenic.Scenic"
]
}
}
还有一些其他分片,例如用于解锁图形硬件加速或硬件媒体编解码器的分片。
这是相当多的信息。将其保留在自己的分片中,比将其添加到组件沙盒中存在的一堆其他服务中更整洁。此外,这再次简化了演变过程。
请注意,这只是一个假设性示例。这些分片目前存在于 IT 中。这只是对将这些分片移至 IDK 以使其可 OOT 使用的可能性进行探索,而不是承诺会这样做。此外,上述分片的内容仍在不断变化,此处仅用作说明性示例,而非参考文档。
实现
在 IDK 分发中打包清单分片
通过将包含路径(通过 --include-directory 或 -I 标志指定给编译器)设置为包含特定嵌套子目录和头文件层次结构的一个或多个目录,可以进行以 Fuchsia 为目标的 C/C++ 开发。这样一来,包含特定于 Fuchsia 的头文件的代码就可以在 IT 和 OOT build 之间移植。
例如,以下 C 代码行在 IT 和 OOT 中均有效:
#include <lib/zx/process.h>
这是因为面向 Fuchsia 的 IT build 和 OOT build 都会向其 include 目录添加一个包含文件 lib/zx/process.h 的路径。在 Fuchsia 代码库中,相应的 include 目录为 //zircon/system/ulib/zx/include/,而在 OOT build 中,此路径需要为 $FUCHSIA_SDK/pkg/zx/include/。另请参阅:IDK 布局。
在包含路径中设置目录以使包含指令可移植也称为设置“sysroot”。
我们将以类似方式部署组件清单分片,在 $FUCHSIA_SDK/pkg/ 下的子路径中部署,这些子路径在概念上与分片的用途相关联,并在 IT 和 OOT build 中相应地在 cmc 中设置 --includepath 标志。
例如,上面用作示例的 syslog 分片可能是:
- 包含的内容如下:
include: [ "syslog/client.shard.cml" ]。 - 我们发现 IT 位于
//sdk/lib/syslog/client.shard.cml下,因此我们将使用--includepath $FUCHSIA_CHECKOUT/sdk/lib/配置cmcIT。 - 在
$FUCHSIA_SDK/pkg/syslog/client.shard.cml下找到了 OOT,因此我们将使用--includepath $FUCHSIA_SDK_ROOT/pkg/配置cmcOOT。
可移植分片与本地分片
预计部分分片将提供给 OOT 开发者,而其他分片仅供 IT 使用。上文中,我们回顾了一些可用于 OOT 的分片示例。仅对 IT 有用的分片的一个示例是此分片,它用于在两个组件定义之间共享大部分复杂的功能路由,其中一个组件定义是系统组件,另一个是该组件的测试替身。此特定分片 OOT 没有用途。
因此,某些分片应可移植并发布在 IDK 中,而其他分片应保持对特定代码库的私密性。
我们建议使用相对路径和绝对路径的通用表示法来明确区分这两种路径。清单 include 指令中使用的应解析为可移植分片的路径不应有前导 //,例如 syslog/client.shard.cml。这些问题将针对分片的 sysroot 进行解析。
另一方面,完全属于特定代码库的 shard 路径应以 // 开头,并根据其代码库的源根目录(或检出根目录)进行解析。
例如,此分片应通过路径 //src/sys/test_manager/meta/common.shard.cml 包含在 IT 中。
构建与 cmc 的系统集成
构建系统(例如树内 GN 和 Ninja 构建)以及以 Fuchsia 为目标的任何树外构建都已与 cmc 集成。需要修改此类集成,以适应新的包含行为。具体而言,对于以下 cmc 子命令:
compileincludecheck-includes
cmc 的调用需要指定以下标志:
--includeroot:用于解析以//为前缀的 include 路径的路径。--includepath:零个或多个用于解析其他路径的路径,按指定顺序(首次匹配)进行解析。
以 SDK 原子形式存在的 Shard
分片将由 build 系统包含在 IDK 中,与其他 IDK 元素的处理方式类似。我们将重用现有的 sdk_atom() 模板,并根据我们希望的 IDK 布局指定 id 参数。
向 IDK 添加分片的流程
分片可以指定可能与平台表面重叠的合同预期。例如,syslog 分片引用了 Fuchsia 命名空间中的协议 - fuchsia.logger.LogSink - 该协议是 Fuchsia 系统组件提供的已知协议。因此,在 IDK 中发布的 shard 将被视为 API 和 SDK atom,并将通过与当前用于添加或修改在 IDK 中发布的 FIDL 文件相同的流程进行 API 审核。您还可以使用类别的 sdk_atom() 概念,例如先将分片作为“内部”分片(不进行 OOT 分发)引入,然后通过现有流程将其提升到更高曝光度的类别。
未来的工作
端口 expect_includes()
Fuchsia 的 GN build 提供了一个模板,用于预期依赖组件在其清单中包含某个 shard(请参阅本指南)。这样做的好处之一是,它会引导添加了对特定服务(例如,客户端库)的依赖项的开发者在其组件中也包含一个清单分片,以确保其库在运行时能够访问正常运行所需的功能。
IT 开发者对此给予了非常积极的反馈,一些 OOT 开发者也表达了兴趣。此模板或其概念可以使用 GN 元数据移植到 GN SDK,也可以使用 Bazel Aspect 或 Bazel Provider 移植到 Bazel/Blaze SDK。
另请参阅:https://fxbug.dev/42156975
性能
此提案不会影响运行时性能,因为所有工作都是在 build 时完成的。
在 build 时进行处理会增加一些额外的工作。不过,预计这不会对干净构建的实际耗时产生影响。我们从之前的研究中得知,构建所做的不在关键路径上的工作通常不会导致构建延迟,并且直接从来源(例如 cmc 调用、fidlc 调用等)派生的工作在关键路径上几乎没有存在感,因为这些工作可以很好地并行化,并且可以非常灵活地进行调度。
举例来说,此变更将 cmc 中的常见操作速度提高了约 300 毫秒,但尽管 build 中有数千次 cmc 调用,我们并未发现对 build 实际运行时间有任何影响。
工效学设计
我们一直致力于让清单包含机制保持简单透明。例如,只需一个简单的命令,即可生成后处理组件清单,其中包含指令已替换为所含文件的内容(如果需要,可以传递)。
fx cmc include manifest --includepath $(fx get-src-dir)这在原理上类似于对 C/C++ 源代码运行 C 预处理器(请参阅:man cpp)。
此外,cmc 会生成易于排查的简单错误,包括针对包含周期等异常错误情况的错误。所有支持的错误都经过了单元测试。
cmc 工具本身已作为预构建版本捆绑在 Fuchsia IDK 中。它完全密封,可在没有任何外部依赖项的情况下运行。它可以从命令行调用,无需集成到 build 系统中。
向后兼容性
更改 IDK 中的分片会影响新构建的 OOT 组件(一旦它们采用最新的 IDK 版本),但不会影响旧的预构建组件。必须谨慎行事,避免出现重大变更,并使用标准实践来处理平台变更:在多个版本中进行软过渡、支持窗口以及与利益相关者沟通。
与所有平台发展事宜一样,更改应经过全面测试,并且应以某种方式管理重大更改,以应对中断情况,例如使用某种版本控制机制。请注意,平台版本控制问题以及此问题的子集(例如 FIDL 版本控制)目前仍未解决,并且不在本 RFC 的范围内。
安全注意事项
组件清单定义了功能路由和沙盒,这些内容直接影响安全性。不过,清单包含的目标不是最终生成不同的清单,而是以更符合人体工程学的方式生成相同的清单。只要在处理 include 之后生成相同的最终清单,就不会对安全性产生影响。
为了帮助开发者了解清单在处理包含项后的样子,他们可以使用上面演示的 cmc include。
测试
cmc 中的现有功能已通过单元测试和集成测试覆盖。cmc 的测试覆盖率超过 90%,尤其是 include 子命令的覆盖率已达到 100%。需要对 Fuchsia IDK 或 OOT 集成做出的任何未完成的更改都将按照 SDK 团队的指示和预期进行测试。
就 CTS 测试覆盖率而言,IDK 中的组件清单应被视为任何其他 IDK 制品。
文档
include 语法已记录在案。
cmc include 命令通过 cmc help include 自行记录。
目前指示 OOT 开发者使用清单包含的替代项的现有文档将进行更新,以便在不再需要区分 IT 开发和 OOT 开发时,不再进行这种区分。
缺点、替代方案和未知因素
不进行任何操作
我们可以维持现状,但代价是无法解决上述“动机”部分中提到的问题。
将 IDK 中的分片整理为顶级制品
除了在 IDK 中各种可能的位置(例如 $FUCHSIA_SDK_ROOT/pkg 或 $FUCHSIA_SDK_ROOT/fidl 下,具体取决于分片的逻辑关联)发布组件清单分片之外,我们还探索了为组件清单分片建立单个基本目录的替代方案。例如:$FUCHSIA_SDK_ROOT/component_manifests/。这类似于所有 .fidl 文件在 $FUCHSIA_SDK_ROOT/fidl/ 下的组织方式,也就是说,它们是按类型(即 FIDL 文件)而非其他逻辑关联(例如 pkg/async/、pkg/memfs/、pkg/zx/)进行聚合的。
根据 SDK 客户的反馈,此替代方案被拒绝,因为客户表示他们更喜欢 SDK 内容的逻辑关联,而不是让不同类型的文件分散在不同的基本目录中。接受的设计允许实例将使用 syslog 的组件清单分片放在 C++ 标头旁边,以便从以 C++ 编写的组件写入 syslog。
在先技术和参考资料
组件清单包含项的灵感源自 C/C++ 包含项。
cmc include 命令的灵感来自 cpp 命令,后者可在给定文件上运行 C 预处理器并打印后处理结果。请参阅 man cpp。