RFC-0106:Fuchsia SDK 中包含的组件清单

RFC-0106:Fuchsia SDK 中包含的组件清单
状态已接受
区域
  • 组件框架
说明

在 SDK 中创建一个包含分片 sysroot 的组件清单,并将其分发给外部集成商。

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2021-05-12
审核日期(年-月-日)2021-06-23

摘要

本文档提出了向集成开发套件 (IDK) 添加组件清单分片的方案。这将为向树外 (OOT) 开发者发布组件清单分片创建标准功能,并建立一个常见模式,以便在不同的开发者环境之间实现清单分片的可移植性。

读者应熟悉以下内容:

设计初衷

使跨环境的工作流保持一致

自推出以来,组件清单分片以及 cmlcmx 中的 include 语法已广泛采用。它们可用于执行各种任务,例如减少样板代码封装实现细节简化开发者工作流,以及在核心领域变体中使用。

目前,处理清单的机制依赖于 Fuchsia 树的源代码布局。这对于位于 Fuchsia 树中的清单非常适用,但无法很好地移植到 OOT 开发。例如,请参阅有关在 C/C++ 组件中进行日志记录的指南。该指南首先指示开发者将以下内容添加到其组件清单中:

{
  include: [ "sdk/lib/diagnostics/syslog/client.shard.cml" ],
}

不过,该指南指出,上述方法仅适用于树内 (IT)。由于本指南的目标受众群体还包括 OOT 开发者,因此本指南会指示这些开发者改为在其组件清单中添加以下内容,以有效内嵌分片的内容:

{
  use: [
    { protocol: "fuchsia.logger.LogSink" },
  ],
}

其他地方也存在同样的问题。例如,测试运行程序的 Test Runner 框架目录(用于支持 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 开发者分别指定一组说明。

简化系统功能的使用

另一个更复杂的示例是,在使用 Web 引擎时,您可能需要直接作为 FIDL 客户端(而不是像前面的示例中那样通过客户端库)使用各种功能。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"
        ]
    }
}

而若要在全景视图中渲染 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 都会在其包含目录中添加一个路径,该路径下包含文件 lib/zx/process.h。在 Fuchsia 检出中,相应的包含目录为 //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" ]
  • //sdk/lib/syslog/client.shard.cml 下找到了 IT,因此我们将使用 --includepath $FUCHSIA_CHECKOUT/sdk/lib/ 配置 cmc IT。
  • $FUCHSIA_SDK/pkg/syslog/client.shard.cml 下找到了 OOT,因此我们将使用 --includepath $FUCHSIA_SDK_ROOT/pkg/ 配置 cmc OOT。

便携式分片与本地分片

预计部分分片将面向 OOT 开发者提供,而其他分片仅供 IT 使用。上面,我们介绍了一些可在 OOT 中使用的分片示例。仅对 IT 有用的分片示例是此分片,用于在两个组件定义(其中一个是系统组件,另一个是该组件的测试双)之间共享大量复杂的 capability 路由。此特定分片 OOT 没有用处。

因此,某些分片应可移植并发布在 IDK 中,而其他分片应保留为特定代码库的私有分片。

我们建议使用通用的相对路径和绝对路径符号来对此进行编码化。清单 include 指令中使用的路径应解析为便携分片,且不应包含前导 //,例如 syslog/client.shard.cml。这些符号将根据分片的 sysroot 解析。 另一方面,完全位于特定代码库本地的分片的路径应以 // 开头,并根据其代码库的源根目录(或检出根目录)解析。例如,此分片应通过路径 //src/sys/test_manager/meta/common.shard.cml 包含 IT。

使用 cmc 构建系统集成

构建系统(例如树内 GN 和 Ninja build 以及任何以 Fuchsia 为目标平台的树外 build)已与 cmc 集成。此类集成需要进行修改,以适应新的包含行为。具体而言,以下 cmc 子命令

  • compile
  • include
  • check-includes

cmc 的调用需要指定以下标志:

  • --includeroot:用于解析带有 // 前缀的包含路径的路径。
  • --includepath:零个或多个路径,用于按指定的顺序(第一个匹配项)解析其他路径。

将分片用作 SDK 原子

分片将由构建系统纳入 IDK,这与处理其他 IDK 元素的方式类似。我们将重复使用现有的 sdk_atom() 模板,根据 IDK 布局的预期样式指定 id 参数。

向 IDK 添加分片的流程

分片可以指定可能与平台接口重叠的合同预期。例如,syslog 分片引用 Fuchsia 命名空间中的协议 - fuchsia.logger.LogSink,该协议众所周知是由 Fuchsia 系统组件提供的。因此,在 IDK 中发布的碎片将被视为 API 和 SDK 原子,并将通过目前用于添加或修改在 IDK 中发布的 FIDL 文件的相同流程进行 API 审核。您还可以使用类别的 sdk_atom() 概念,例如,先将分片引入为“内部”(不分发到 OOT),然后通过现有流程将其提升到曝光度更高的类别。

后续工作

端口 expect_includes()

Fuchsia 的 GN build 提供了一个模板,用于预期依赖项组件在其清单中包含特定分片(请参阅此指南)。这带来的一个好处是,它会指示添加了特定服务的客户端库等依赖项的开发者,还要在其组件中添加清单分片,以确保其库能够访问其在运行时正常运行所需的功能。

IT 开发者对此给出了非常积极的反馈,一些 OOT 开发者也表示有兴趣。此模板或其概念可以使用 GN 元数据移植到 GN SDK,也可以使用 Bazel 方面或 Bazel 提供程序移植到 Bazel/Blaze SDK。

另请参阅:https://fxbug.dev/42156975

性能

由于所有工作都在构建时完成,因此此提案对运行时性能没有影响。

在构建时处理 include 会增加一些额外的工作。不过,这预计不会影响干净 build 墙时。我们从之前的研究中了解到,构建过程中不在关键路径上的工作通常不会增加构建延迟时间;此外,直接从源代码派生的工作(例如 cmc 调用、fidlc 调用等)在关键路径上几乎没有存在,因为这类工作可以很好地并行执行,并且可以非常灵活地调度。

例如,请考虑这项更改,其中 cmc 中的一项常规操作的速度提高了约 300 毫秒,但尽管 build 中进行了数千次 cmc 调用,我们测量后发现对 build 实际运行时间没有影响。

工效学设计

我们已致力于使清单包含机制保持简单透明。例如,您可以使用简单的命令生成经过后处理的组件清单,其中包含指令会替换为包含文件的内容(如果需要,会以传递方式替换)。

fx cmc include manifest --includepath $(fx get-src-dir)

这在原理上与在 C/C++ 源代码上运行 C 预处理器类似(请参阅 man cpp)。

此外,cmc 会生成简单的错误,这些错误易于排查,包括 include 循环等异常错误情况。所有受支持的错误均经过单元测试。

cmc 工具本身已作为预构建文件捆绑在 Fuchsia IDK 中。它是完全密封的,无需任何外部依赖项即可运行。它可从命令行调用,无需进行 build 系统集成。

向后兼容性

更改 IDK 中的分片后,新构建的 OOT 组件会在采用最新的 IDK 版本后受到影响,但旧的预构建组件不会受到影响。必须小心谨慎,避免破坏性更改,并针对平台更改使用标准做法:在多个版本中进行软过渡、提供支持期限,以及与利益相关方沟通。

与平台演进的所有事项一样,更改应经过彻底测试,并且应以允许破坏的方式管理破坏性更改,例如使用某种版本控制机制。请注意,平台版本控制问题以及此问题的子问题(例如 FIDL 版本控制)目前仍未解决,并且不在本 RFC 的范围内。

安全注意事项

组件清单定义了 capability 路由和沙盒,这对安全性有直接影响。不过,清单包含的目标不是最终生成不同的清单,而是以更符合人体工学的方式生成相同的清单。只要在处理包含项后生成的最终清单相同,就不会对安全性产生影响。

为了帮助开发者了解处理包含项后的清单将会是什么样子,他们可以使用上面演示的 cmc include

测试

单元测试和集成测试已涵盖 cmc 中的现有功能。cmc 的测试覆盖率高于 90%,具体而言,include 子命令的覆盖率已达到 100%。我们将按照 SDK 团队的指示和预期,测试对 Fuchsia IDK 或 OOT 集成所需的所有待处理更改。

从 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