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

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

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

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

摘要

设计用于将系统 ABI 修订版本编码到软件包中。

设计初衷

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

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

利益相关方

Facilitator: abarth@google.com

审核者

之所以选择以下利益相关者,是因为此 RFC 涉及 Fuchsia 工具、软件交付,最终会影响组件框架选择向应用呈现的组件系统接口的方式。

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

已咨询

  • lijiaming (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 平台描述了一种机制,其中 Component Manager 可以使用软件包 ABI 修订版本来选择在运行组件时要使用的平台接口。
  • 系统组装是指从一组软件包和其他已编译的制品中组合出 Fuchsia 系统映像的过程。然后,可以使用该系统映像通过其他方式(OTA、刷写、铺设等)交付 Fuchsia。汇编器可以使用软件包 ABI 修订版本来拒绝集成软件包,如果系统不支持所需的软件包 ABI。
  • 同样,花瓣或应用开发者可以使用软件包 ABI 修订版本来了解复杂树中的所有组件是否可以在目标 Fuchsia 版本上运行。
  • 临时软件包是指按需下载的软件包,而不是内置到系统映像中的软件包。软件包解析器可以利用软件包 ABI 修订版本来拒绝下载它知道在系统上不可执行的软件包。不过请注意,这可能不适用于系统更新。 如需了解详情,请参阅系统更新注意事项

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

此提案旨在使 API 级别或 ABI 修订版本成为构建软件包时的必需实参,但在该功能的初始推出阶段,此实参将是可选的。考虑到这一点,最终开发者应针对目标 API 级别或 ABI 修订版本对其 build 规则进行参数化。这样一来,您应该可以轻松定位新版本。您甚至可以设置一个测试滚轮,通过实验性地推进目标 API 级别来设置新 API 级别的自动测试,并查看是否有任何失败情况。如果这些失败出乎意料,则可能会反馈到平台 bug 报告中。

系统更新注意事项

为了让 Fuchsia 设备从一个版本更新到另一个版本,系统更新程序会解析一个名为更新软件包的特殊软件包。此软件包描述了新版 Fuchsia 运行所需的所有基本软件包和其他制品。这些新软件包并非旨在用于当前系统,而是在设备重启到新系统后使用。

鉴于此,如果我们决定使用软件包 ABI 修订版本来拒绝下载软件包,则需要谨慎操作,以免中断 OTA 流程。以下示例展示了这一点:

假设有两个 Fuchsia build:A 仅支持 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-2,基础软件包位于 ABI-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 修订版本添加到元数据中,在最坏的情况下会使 meta.far 的未压缩大小增加 8KiB,其中前 4KiB 来自 FAR 数据块,如果 DIRNAMES 部分导致第一个内容块被推送到下一个 4KiB 对齐的偏移量,则会增加第二个 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 格式添加新的 chunk 类型,因此如果 FAR 库遇到新的 chunk 类型,可能会出错或表现出意外行为。
    • 我们必须更新所有 FAR 库才能了解新的 chunk 类型。从历史上看,对 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 会被接受,也无法保证实现该 RFC 需要多长时间。
  • 虽然基于持久性 FIDL 的方法是版本化的,但内部版本无法帮助我们将封装元数据迁移到其他文件格式。例如,如果我们决定从持久性 FIDL 切换到 JSON 文件。使用新的 ABI 修订版本来告知打包系统寻找与之前不同的文件格式会更简单。

在先技术和参考资料

Android

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

Windows

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

macOS

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

iOS

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