RFC-0135:软件包 ABI 修订版本

RFC-0135:软件包 ABI 修订版本
状态已接受
领域
  • 软件交付
说明

将系统 ABI 修订版本编码为软件包。

问题
  • 85820
Gerrit 更改
  • 588486
作者
审核人
提交日期(年-月-日)2021-09-30
审核日期(年-月-日)2021-11-05

总结

将系统 ABI 修订版本编码为软件包的设计。

设计初衷

RFC-0002 引入了 API 级别ABI 修订版本的概念。API 级别是一个人类可理解的单调递增数字,对应于构建应用时可用的一组 API。ABI 修订版本对应于应用希望平台提供的 Fuchsia 系统接口提供的特定语义。API 级别对应于一个特定的 ABI 修订版本,但多个 API 级别可以引用同一个 ABI 修订版本。此功能使 Fuchsia 平台能够随着时间的推移不断改进,同时继续为旧应用提供支持。

实现平台版本控制需要软件包对希望针对的 ABI 级别进行编码,以便平台可以了解软件包是否与正在运行的系统兼容。

利益相关方

教员:abarth@google.com

审核者

在本 RFC 讨论 Fuchsia 工具、软件交付时,我们选择了以下利益相关方,他们最终将影响组件框架选择要呈现给应用的组件系统接口。

  • abarth@google.com(FEC 成员)
  • computedruid@google.com (SWD)
  • geb@google.com (CF)
  • mkember@google.com (FIDL)
  • raggi@google.com(工具)
  • wittrock@google.com (SWD)

咨询人员

  • 丽佳明 (PDK)

社交

本 RFC 经过了软件交付和 Fuchsia 工具团队的设计审核。

术语

Fuchsia 软件包是一个分发单元,用于分发和整理 Fuchsia 程序、组件和服务的文件。

meta.far 是软件包元数据归档。它包含面向用户的文件名到底层内容寻址 blob 的映射。打包系统使用该方法来下载软件包内容,以及构建命名空间层次结构。它还包含用户提供的自定义文件。

设计

此设计引入了“软件包 ABI 修订版本”的概念,软件包是软件包使用的 Fuchsia 平台接口的目标 ABI 修订版本。此值将作为无符号小端字节 64 位整数写入,并写入 meta/fuchsia.abi/abi-revision 文件的 meta.far 中。

所有软件包生成工具都将更新,要求在软件包构建期间指定 API 级别或 ABI 修订版本。指定 API 级别后,工具应对 SDK 版本记录中找到的相应 ABI 修订版本进行编码,或出错。同样,该工具应强制 SDK 支持指定的 ABI 修订版本,否则会出错。这样应该可以避免使用旧版 SDK 创建组件,但仅指定新版 SDK 中存在的 ABI。这可能会导致组件无法运行。最糟糕的是,这可能会导致该组件出现奇怪或危险的 bug。

软件包 ABI 修订版本的用例

此方案仅涵盖如何将 ABI 修订版本编码到软件包中。软件包 ABI 修订版本的用户将在未来的设计中定义。不过,下面这些潜在用例有助于说明相应的使用方式:

  • RFC-0002 平台描述了一种机制,在该机制中,组件管理器可以使用软件包 ABI 修订版本来选择在运行组件时使用的平台接口。
  • 系统组装是从一组软件包和其他已编译工件构成 Fuchsia 系统映像的过程。然后,您可以使用系统映像通过其他方式(OTA、刷写、铺砌等)提供 Fuchsia。如果系统不支持所需的软件包 ABI,编译器可以使用软件包 ABI 修订版本拒绝集成软件包。
  • 同样,花瓣或应用开发者可以使用软件包 ABI 修订版本了解其复杂树中的所有组件能否在其目标 Fuchsia 版本上运行。
  • 临时软件包是按需下载的软件包,而不是内置在系统映像中的软件包。软件包解析器可以利用软件包 ABI 修订版本拒绝下载其确定无法在系统上执行的软件包。不过请注意,这可能无法用于系统更新。 如需了解详情,请参阅系统更新注意事项

目标 API 级别或 ABI 修订版本选择

此方案旨在将 API 级别或 ABI 修订版本设为构建软件包时必需的参数,但在此功能初次发布期间,此为可选参数。考虑到这一点,最终开发者应针对目标 API 级别或 ABI 修订版本将其构建规则参数化。这样应该能够轻松针对新版本。您甚至可以设置对新 API 级别的自动测试,只需设置一个测试滚轮,用于通过实验性方式提升目标 API 级别,并查看是否有失败情况。如果这些故障是意外发生的,则可能会反馈给平台 bug 报告。

系统更新注意事项

为了使 Fuchsia 设备从一个版本更新到另一个版本,系统更新程序会解析一个特殊的软件包,称为更新软件包。本软件包介绍了运行新版 Fuchsia 所需的所有基础软件包和其他工件。这些新软件包不适用于当前系统,而是会在设备重新启动到新系统后使用这些新软件包。

有鉴于此,如果我们决定使用软件包 ABI 修订版本来拒绝下载软件包,则需要小心,以免破坏 OTA 流程。可通过以下示例了解详情:

请考虑两个 Fuchsia build A,这两个 build 仅支持 ABI-1C(支持 ABI-2)。如果我们根据 ABI 过滤软件包,则 C 的更新软件包必须为 ABI-1 才能下载。不过,C 版本基础软件包必须为 ABI-2 才能使用新 ABI,但这些软件包将被 A 版本软件包解析器拒绝。

避免出现这种情况的一种方法是通过踏步机版本引入新的 ABI 修订版本。Fuchsia 旨在让设备跳过多个版本,以更新到最新版本。踏步石版本是一个无法跳过的特殊版本。它旨在通过平台接口实现优雅过渡。

改为执行前面的示例,改为按 3 个 Fuchsia 版本依序发布:

  • A:支持 ABI-1,基础软件包为 ABI-1
  • B:支持 ABI-1ABI-2ABI-1 的基础软件包)。
  • C:支持 ABI-2,基础软件包为 ABI-2

如果我们将 B 标记为 步进版本,则运行 A 的设备会首先更新到 B,然后再更新到 C

通过将软件包定义方式与 ABI 修订版本分离开来,可以更进一步。由于我们不打算频繁对软件包布局进行向后不兼容的更改,因此我们可以对软件包布局进行版本控制,然后说明给定 ABI 修订版本支持哪些软件包布局版本。这样,我们就可以安装更多类型的更新软件包,而无需创建跳跃版本。

避免 meta.far 中的命名空间冲突

meta.far 目前包含两个软件包元数据文件(meta/packagemeta/contents)以及用户指定的任意文件。这意味着,用户文件与我们引入到软件包中的任何新元数据文件之间都有可能发生冲突。为避免这种可能性,系统会将软件包 ABI 修订版本写入目录 meta/fuchsia.abi。此目录名称遵循 Fuchsia 中其他位置(例如平台 FIDL 命名空间)使用的惯例。我们将进一步更新软件包构建工具,以防止用户在 meta/fuchsia.abi 中定义自定义文件。

由于这是一个由平台控制的目录,因此可以使用此位置存储与 ABI 相关的文件,而不会与用户文件发生冲突。这样一来,我们就可以改进软件包 ABI 修订版本的概念。例如,如果我们希望在一个软件包中支持多个 ABI 修订版本,可以添加 meta/fuchsia.abi/abi-revision-set 以包含所有受支持的修订版本。一旦所有用户都迁移到新文件,我们可以删除 meta/fuchsia.abi/abi-revision

存储开销

在最糟糕的情况下,如果将软件包 ABI 修订版本添加到元标记,会导致 8KiB(未压缩)到 meta.far 中;这种情况下,如果第一个 4KiB 来自 FAR 数据块,第二个 4KiB 来自 DIRNAMES 部分,则会导致第一个内容分块被推送到下一个 4KiB 对齐的偏移量。不过,这在实践中应该不会产生很大影响,因为 blobfs 具有 8KiB 块对齐。此外,使用默认压缩程序 zstd 可以很好地进行压缩。根据以下示例,这只会向 meta.far 中添加 47 个字节:

# Create an empty package.
% mkdir empty && cd empty
% pm init && pm build
% ls -l meta.far
-rw-r--r--  1 etryzelaar  primarygroup  12288 Oct  5 10:21 meta.far
% fx chunked-compress c meta.far meta.far.compressed
Wrote 447 bytes (97% compression)

# Then create a meta.far that contain's the abi-revision.
% cd .. && mkdir with-abi && cd with-abi
% pm init
% mkdir meta/fuchsia.abi
% python3 -c "
import struct;
abi_revision = int('0xC7003BF9', 16)
f = open('meta/fuchsia.abi/abi-revision', 'wb')
f.write(struct.pack('<Q', abi_revision))
"
% pm build
% ls -l meta.far
-rw-r--r--  1 etryzelaar  primarygroup  16384 Oct  5 10:22 meta.far
% fx chunked-compress c meta.far meta.far.compressed
Wrote 494 bytes (97% compression)

实现

所有软件包构建工具都将扩展,以支持在命令行中指定 API 级别或 ABI 修订版本。

pm CLI

pm CLI 将进行扩展,以允许在软件包初始化和软件包构建期间指定 ABI。

调用示例:

# Create a package with an API level. Under the covers this will look up the
# ABI revision from the SDK.
% pm init --api-level 5

# Create a package with an ABI revision. These commands are equivalent.
% pm init --abi-revision 0xC7003BF9
% pm init --abi-revision 3338681337

# Build a package with an ABI revision.
% pm build --abi-revision 0xC7003BF9

选项:

  • --api-level LEVEL:要编码到软件包中的 API 级别。SDK 必须支持此值。此选项与 --abi-revision 标志冲突。
  • --abi-revision REVISION:将嵌入到软件包中的 ABI 修订版本。该值可以是十进制整数或十六进制整数,必须受 SDK 支持。此选项与 --api-level 标志冲突。

最初,--api-level--abi-revision 标志是可选的,以便花瓣和最终开发者在一段时间内实现支持。最终,在生态系统过渡到指定标志后,此参数将成为必需参数。

性能

此 RFC 在构建软件包时引入了大量的额外工作,并且占用的存储空间较小,因此对性能的影响应该可以忽略不计。

工效学设计

该方案本身不会明显改变 Fuchsia 的工效学设计。不过,这最终会让产品开发者能够针对其组件使用特定的系统接口。这应该能够为其提供所需的稳定性,同时不妨碍 Fuchsia 的进化能力。

向后兼容性

此变更可向后兼容,因为此设计引入了不含使用方的新文件。不过,此功能最终将允许系统废弃并最终取消对旧 ABI 修订版本的支持。

安全注意事项

需要解析 meta/fuchsia.abi/abi-revision 文件。不过,这种格式易于解析,而且应该易于验证解析器是否正确。

如需了解详情,请参阅 RFC-0002 安全注意事项

隐私注意事项

此方案对隐私权没有有意义的影响。

测试

我们将扩展打包工具,以验证生成的软件包是否符合预期的 ABI 设置。由于会有过渡期,因此将延长软件包解析器测试,以验证它是否支持包含或不包含 meta/fuchsia.abi/abi-revision 文件的软件包。

文档

我们将更新打包文档,以讨论 ABI 在软件包中的表示方式,以及用户在构建软件包时如何选择 API 级别或 ABI 修订版本。

缺点、替代方案和未知情况

备用 ABI 修订版本文件格式

我们可以使用以下代码,而不是将 ABI 修订版本编码为有序字节的整数:

  • 直观易懂的整数字符串
  • JSON
  • 永久性 FIDL

选择小端字节序的整数,原因如下:

  • 我们预计人类不会消耗此值。相反,它们应该参考人类理解的 API 级别。
  • 此值将以小字节序整数的形式传递,因此可以避免额外的转换。
  • 它易于解析。

包含一组 ABI 修订版本的软件包

RFC-0002 应用声明,软件包可以包含多个组件,每个组件都以不同的 ABI 修订版本为目标平台。为了支持这一点,软件包 ABI 可以是所有组件 ABI 修订版本的集合,而不是对软件包中的单个 ABI 修订版本进行编码。

不过,在编写时,没有任何用例需要包含以不同系统 ABI 为目标的组件的软件包。一个数字可以简化对 ABI 支持的初始实现。如果此场景变得重要,扩展此设计将非常困难。

将 ABI 修订版本直接编码到 meta.far 中

通过以下任一方式,meta.far 可以直接嵌入 ABI 修订版本:

  • 将 ABI 添加到 FAR 索引分块中。
    • 优点:
    • 仅增加了 8 个字节的开销。
    • 缺点:
    • 索引分块没有任何预留字节,因此将此字节添加到索引中将需要对 FAR 格式进行重大更改。要进行此更改,我们必须先实现对在不使用新 FAR 格式的情况下读取该格式的支持,然后创建踏板版本,最后迁移到该格式。
    • 使用者阅读 ABI 修订版本会比较困难。软件包解析器不会直接将 meta.far 作为文件公开给用户,因此它需要添加一个 API 来公开此信息。
  • 创建新的 ABI 修订版本块类型。
    • 优点:
    • 添加新的分块类型应该是向后兼容的。
    • 以字节为单位的开销应该要小得多,因为我们不需要创建 4096 字节对齐的内容块。分块是 64 位对齐的,并且索引开销条目大小为 24 个字节,因此此方法应该仅向未压缩的 FAR 格式添加 32 个字节。
    • 缺点:
    • 我们尚未向 FAR 格式添加新的分块类型,因此 FAR 库在遇到新的分块类型时,可能会出错或出现意外行为。
    • 我们必须更新所有 FAR 库,才能理解新的分块类型。过去,对 FAR 库所做的任何更改都成本极高。
    • 使用者阅读 ABI 修订版本会比较困难。软件包解析器不会直接将 meta.far 作为文件公开给用户,因此它需要添加一个 API 来公开此信息。

由于存储开销微乎其微,尤其是在进行压缩时,因此我们认为节省的空间不值得定义新的分块类型。

代码库元数据中的 ABI 修订版本

我们可以将 ABI 修订版本添加到软件包代码库中的软件包元数据中,而不是直接将 ABI 编码到软件包。这样做具有以下缺点:

  • ABI 修订版本是软件包的固有属性。可能很难确保在 TUF 代码库或基本软件包列表中正确表示 ABI 修订版本。
  • 打包元数据仍会包含 ABI 修订版本,因此打包系统仍必须承担开销。

即将推出的永久性 FIDL 软件包元数据中的 ABI 修订版本

软件交付机制正在编写一个 RFC,提出新的永久性 FIDL 打包元数据。我们可以通过将建议的架构更新为以下内容,将 ABI 修订版本存储在此元数据中:

flexible union Contents {
    1: ContentsV1 v1;
};

table ContentsV1 {
    1: vector<fuchsia.io.Path>:MAX paths;
    2: vector<fuchsia.pkg.BlobId>:MAX hashes;
    3: vector<uint64>:MAX blob_sizes;
    4: uint64 abi_revision;
};

优点:

  • 将所有打包元数据整合到单个 FIDL 文件的优势在于,我们利用 FIDL 工具使文档和验证保持最新状态。使用多个文件会让文档和验证更容易相互偏移。
  • 我们只需在未压缩的 meta.far 中添加少量字节,而不是向约 4KiB 的字节添加少量字节。

使用 FIDL 的缺点:

  • 我们无法保证接受基于永久性 FIDL 的 RFC,也无法保证一定能完成实现。
  • 虽然基于永久性 FIDL 的方法带有版本编号,但内部版本无法帮助我们将打包元数据迁移到其他文件格式。例如,如果我们决定从永久性 FIDL 转换为 JSON 文件。这样,使用新的 ABI 修订版本来告知打包系统查找与以前不同的文件格式会更容易。

早期技术和参考资料

Android

Android 应用通过在应用清单中指定 uses-sdk 元素来定位 SDK 版本。

Windows

Windows 应用通过指定应用清单中列出的 SupportedOS GUID 来确定操作系统版本。

macOS

macOS 应用通过在应用软件包的 Info.plist 文件中指定 LSMinimumSystemVersion 以指定操作系统版本。

iOS

iOS 应用通过在应用软件包的 Info.plist 文件中指定 MinimumOSVersion 来定位操作系统版本。