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 级别,以便平台知道软件包是否与运行的系统兼容。

利益相关方

主持人:abarth@google.com

Reviewers:

由于此 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 平台介绍了一种机制,其中组件管理器可以使用软件包 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-1,而 C 支持 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 添加 8 KiB 的未压缩数据,其中前 4 KiB 来自 FAR 数据块,如果 DIRNAMES 部分导致第一个内容块被推送到下一个 4 KiB 对齐偏移,则第二个 4 KiB 来自该部分。不过,这在实践中应该不会产生重大影响,因为 blobfs 采用 8 KiB 的块对齐方式。此外,它还可以使用默认压缩器 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 修订版是软件包的内在属性。确保 ABI 修订版在 TUF 代码库或基础软件包列表中正确表达可能很困难。
  • 打包元数据仍会包含 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 添加少量字节,而不是大约 4 KiB。

使用 FIDL 的缺点:

  • 我们无法保证基于持久性 FIDL 的 RFC 会被接受,也无法保证实现该 RFC 需要多长时间。
  • 虽然基于持久性 FIDL 的方法具有版本号,但内部版本无法帮助我们将打包元数据迁移到其他文件格式。例如,如果我们决定从永久性 FIDL 切换到 JSON 文件。使用新的 ABI 修订版更容易告知打包系统查找与之前不同的文件格式。

在先技术和参考文档

Android

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

Windows

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

macOS

macOS 应用通过在应用 bundle 的 Info.plist 文件中指定 LSMinimumSystemVersion 来定位操作系统版本。

iOS

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