RFC-0239:平台版本控制实际应用

RFC-0239:平台版本控制实践
状态已接受
区域
  • 常规
  • 治理
说明

Fuchsia 上的兼容性概念模型和保证。

Gerrit 变更
作者
审核人
提交日期(年-月-日)2023-11-08
审核日期(年-月-日)2024-01-30

摘要

此 RFC 的目标是通过以下方式扩展 RFC-0002、RFC-0083 等:

  • 为 Fuchsia 上 API 级别和 ABI 修订版本的实际使用方式制定概念模型,
  • 根据该概念模型向我们的合作伙伴提供 API 和 ABI 兼容性保证,以及
  • 概述了我们计划如何满足这些兼容性保证。

简要总结:

  • 每个 Fuchsia 里程碑都有一个随附的 API 级别。API 级别 15 可以被视为“Fuchsia 平台接口的第 15 个版本,该版本最初由 F15 支持”。
  • 每个 Fuchsia 版本都支持多个 API 级别。例如,F15 里程碑版本支持 API 级别 11、12、13、14 和 15。
  • 最终开发者在构建组件时会选择一个目标 API 级别。以 API 级别 13 为目标平台的组件将看到 Fuchsia 平台在 API 级别 13 中的原样,并将在支持 API 级别 13 的任何 Fuchsia 系统上运行。
  • Fuchsia 内核和平台组件通过同时实现多个 API 级别来支持这些级别。

动机

如 RFC-0227 中所述,Fuchsia 平台版本的主要组件包括:

  • 集成商开发套件 (IDK),这是一组用于构建和运行 Fuchsia 组件的小型库、软件包和工具,以及
  • 操作系统 (OS) 二进制文件,包括内核、引导加载程序、软件包、工具以及配置和组装 Fuchsia 产品包所需的其他组件。

IDK 是 Fuchsia SDK 的前身,与构建系统无关,它描述了构成 Fuchsia 平台表面的 API 元素(例如协议、方法、字段、参数、库),并允许最终开发者编写使用这些 API 元素的组件。IDK 中的一些接口由操作系统二进制文件(例如内核和平台组件)实现,另一些接口则由外部组件(例如应用或驱动程序)实现,并预期操作系统二进制文件会调用它们。

Fuchsia 平台表面在不断发展,操作系统二进制文件也会不断更新以与之匹配。这些变更会迅速1传导至下游的最终开发者和产品所有者。在引入 API 级别之前,IDK 仅包含平台接口的单一说明。周一构建的 IDK 描述的 Fuchsia 平台表面版本与该周晚些时候(周五)构建的操作系统实现的版本不同。这过去常常导致问题:如果 FIDL 方法及其实现于周三被删除,那么使用周一的 IDK 构建的外部组件可能会尝试调用该方法,但周五的操作系统会告知该方法不存在。

简单来说,我们可以尝试禁止 IDK 和操作系统版本不匹配。我们可以说,操作系统只会运行使用完全相同的 Fuchsia 版本中的 IDK 构建的组件(即操作系统版本 2.20210213.2.5 只会运行使用 IDK 版本 2.20210213.2.5 构建的组件)。不过,这是不可行的,因为我们无法要求最终开发者在我们发布新版 Fuchsia 时不断更新和重新构建其组件。因此,一般来说,构建外部组件的 IDK 和运行这些组件的操作系统可能来自不同的 Fuchsia 版本,甚至可能来自不同的里程碑。

这就引出了一个问题:在什么情况下,使用某个版本的 IDK 构建的组件能够在另一个版本的操作系统上成功运行?

RFC-0002 引入了 API 级别和 ABI 修订版本作为工具来帮助回答该问题,但并未具体说明在实践中应如何使用它们,也没有提供具体的兼容性保证。此 RFC 填补了这些空白,展示了 API 级别和 ABI 修订版本在实践中的使用方式,建立了兼容性保证政策,并简要讨论了遵守该政策的策略。

利益相关方

教员:jamesr@google.com

Reviewers:

  • abarth@google.com
  • ianloic@google.com
  • mkember@google.com
  • yaar@google.com

已咨询:

  • chaselatta@google.com
  • ddorwin@google.com
  • sethladd@google.com

共同化:

此 RFC 中的模型和政策在过去几年中逐步构建,但到目前为止尚未在任何 RFC 中进行编码。这项特定提案是通过许多文档和对话(主要在平台版本控制工作组的成员之间进行)得出的。

假设

在描述公开 API 的组件与使用该 API 的另一个组件之间的兼容性时,我们假设交互的一方是树外构建的外部组件(即通过 IDK 在 Fuchsia 平台构建之外构建),而交互的另一方是树内构建的操作系统二进制文件,例如内核或 Fuchsia 平台组件(即在 Fuchsia 平台的 GN 构建图中构建)。为外部组件之间的交互定义版本控制做法和兼容性不在本文档的讨论范围内,留待日后研究。

设计

本部分将介绍 Fuchsia 平台版本控制背后的概念模型。此处介绍的许多概念之前已在 RFC-0002 中介绍过,但此处提供了更具体的细节和限制。

此“设计”部分中的文本应视为参考信息,而非规范。下文的政策变更部分将重复此文本中代表现有政策变更的所有部分。

API 级别

Fuchsia 平台表面在不断变化,新的 Fuchsia 版本也在不断创建(每天多次)。我们不会考虑每个 Fuchsia 版本与每个其他版本的 Fuchsia 平台接口的兼容性,因为这会让人应接不暇,而是考虑版本与有限数量的 API 级别的兼容性。

您可以将 API 级别视为构成 Fuchsia 平台 surface 的 API 的“版本”或“快照”。API 级别 10 是“Fuchsia API 接口的第 10 个版本”。API 级别与里程碑同步,因此 API 级别 10 也可以视为“F10 发布时 Fuchsia 平台接口的最新版本”。

您可以使用 API 级别来做出如下声明:“fuchsia.lightsensor.LightSensorData FIDL 表在 API 级别 10 中有三个字段(rgbc、calculated_lux 和 correlated_color_temperature),但在 API 级别 11 中又添加了两个字段(si_rgbc 和 is_calibrated)。自 API 级别 11 以来,它有五个字段,但我们可能会在 API 级别 16 中移除一些字段或添加更多字段。”

与教科书的版本一样,API 级别是不可变的。教科书的内容可能会随着时间的推移而发生变化,但教科书第 2 版的内容在发布后将永远不会发生变化。这意味着,任何两个了解给定 API 级别的 Fuchsia 版本都会就构成该 API 级别的 API 元素集达成一致。

Fuchsia 平台接口不限于 FIDL,还包括系统调用、C++ 库及其中的方法、持久性文件格式等。从概念上讲,这些都是按 API 级别进行版本控制的,但在实践中,FIDL 库的版本控制支持比 Fuchsia 平台接口的其他方面要先进得多。

以某个 API 级别为目标

最终开发者在构建组件时会选择一个目标 API 级别。如果开发者以 API 级别 11 为目标平台,其组件将只能访问 API 级别 11 中提供的 API 元素。如果某个方法是在 API 级别 12 中添加的,则该组件不会在其生成的绑定中看到该方法。相反,如果 API 级别 11 中的某个方法在 API 级别 12 中被移除,该组件将能够继续使用该方法。

在构建组件的过程中,IDK 中的工具会将目标 API 级别关联的 ABI 修订版本嵌入到组件的软件包中。这通常称为组件的“ABI 修订版本标记”。

借助 ABI 修订版本标记,我们可以保证组件观察到的运行时行为与其目标 API 级别指定的行为一致。目前,ABI 修订版本标记的使用非常粗略:如果操作系统支持该 ABI 修订版本对应的 API 级别,则允许组件运行。否则(例如,如果外部组件以 API 级别 11 为目标 API 级别,但操作系统二进制文件不再实现属于 API 级别 11 的方法),组件管理器将阻止其启动。

版本和阶段

每个 Fuchsia 版本都支持多个 API 级别,并为每个 API 级别分配一个阶段。您可以通过下载 Fuchsia 版本的 IDK 并查看 version_history.json 文件,找到该版本中的 API 级别。

例如,假设 Fuchsia 版本 20.20240203.2.1 可能包含:

  • API 级别 17、18 和 19 处于受支持阶段。这意味着,IDK 版本 20.20240203.2.1 可以构建以这些 API 级别中的任何一个为目标的组件,而 Fuchsia 版本 20.20240203.2.1 可以运行以这些 API 级别中的任何一个为目标的组件。大多数最终开发者在大多数情况下都应以受支持的 API 级别为目标。
  • API 级别 15 和 16 处于弃用阶段。Fuchsia 仍会运行以已停用 API 级别为目标平台的组件,但 IDK 在构建组件时将不再支持以这些级别为目标平台。

这个假设的 version_history.json 还会将 API 级别 14 及更低级别列为处于“已停用”阶段。Fuchsia 不会构建或运行以已停用 API 级别为目标平台的组件,但会保留这些组件以供后代使用。

API 级别在支持阶段发布,即在其对应的里程碑分支切分之前不久。上述假设的 20.20240203.2.1 Canary 版本来自 API 级别 20 发布之前,因此 API 级别 20 明显缺失。在 F20 分支切出之前不久,API 级别 20 将发布,后续版本(例如 20.20240301.4.1)会将 API 级别 20 列为“受支持”。具体而言,所有里程碑版本都会在其支持阶段包含相应的 API 级别。

当我们选择停止支持某个 API 级别时,会先将其移至弃用阶段。这会创建一种后盾 - 针对该 API 级别的现有二进制文件会继续运行,但不会创建针对该 API 级别的新二进制文件(至少不会使用新版本的 IDK 创建)。一旦针对给定 API 级别的所有现有二进制文件都已逐步淘汰,我们就可以停用该 API 级别。

API 级别不会按照任何特定时间表停用或弃用。相反,一旦我们的合作伙伴不再针对某个 API 级别进行构建,我们就会弃用该 API 级别;一旦我们的合作伙伴不再希望运行针对某个 API 级别的二进制文件,我们就会停用该 API 级别。随着合作伙伴数量的增加,我们无法再密切关注他们的个人需求,因此,我们将来需要制定更正式的政策,但目前这样就足够了。

NEXT 和 HEAD 伪 API 级别

除了上述 API 级别之外,还有两个特殊的可变伪 API 级别:NEXT 和 HEAD。

与其他 API 级别不同,NEXT 和 HEAD 的内容在不同版本之间可能会以任意方式发生变化。可以添加、修改、替换、弃用和/或移除 API 元素。原则上,20.20240203.1.1 中的 NEXT 可能与 20.20240203.2.1 中的 NEXT 完全不同,但在实践中,更改将是增量式的。

如 RFC-0083 中所述,HEAD 代表开发的最前沿,旨在供树内客户端(例如其他平台组件或集成测试)使用,不保证稳定性。HEAD 中引入的 API 元素甚至可能无法正常运行;例如,某个方法可能已在 HEAD 中添加到协议,但协议服务器可能尚未实际实现该方法。

NEXT 表示下一个编号 API 级别的“草稿”版本。在每个里程碑分支切分之前不久,我们会将 NEXT 的内容发布为新的 API 级别。从机制上讲,我们通过创建变更列表来实现此目的,该变更列表会将 FIDL 注解和 C 预处理器宏中的 NEXT 实例替换为下一个里程碑分支的具体编号。NEXT 中的 API 元素应该可以正常运行,并且在下一个 API 级别发布之前不太可能发生变化,但仍有可能发生变化。

在 RFC-0083 的语言中,HEAD 比 NEXT 更新,这意味着 NEXT 中的所有 API 更改也包含在 HEAD 中。

最终开发者可以定位 HEAD 或 NEXT,但这样做会使 API 和 ABI 兼容性保证失效。如需详细了解从树外定位 NEXT 或 HEAD 的影响,请参阅介绍这些保证的部分。

政策变更

本部分列出了此 RFC 提议的具体政策变更。

模型变更

此 RFC 对平台版本控制模型进行了以下更改:

  • 编号 API 级别现在不可变。除了特殊的“开发中”API 级别之外,对于所有 API 级别,此属性在实践中都已为 true。 此 RFC 实际上弃用了“开发中”API 级别,转而使用 NEXT。
  • 引入 API 级别阶段。“受支持”和“已停用”在实践中已经存在,不过后者目前称为“不受支持”。此 RFC 建议将其重命名,并添加“停用”。
  • 介绍 NEXT。这是另一个特殊的 API 级别,类似于 HEAD。HEAD“新于”NEXT。这取代了将最新 API 级别指定为“开发中”API 级别的现行做法。
  • 允许对 NEXT 进行向后不兼容的更改。当前政策(由工具强制执行,但未在任何 RFC 中确立)规定,只能对开发中的 API 级别进行向后兼容的更改。此 RFC 移除了该限制,并且不会将其应用于 NEXT。如需了解详情,请参阅下文。
  • 将 API 级别与里程碑相关联。API 级别 N 将在 FN 里程碑分支切分之前不久根据 NEXT 的内容创建。这在实践中是正确的,但尚未在任何 RFC 中得到批准。
  • API 级别会尽快通过各个阶段,具体取决于合作伙伴。一旦我们的合作伙伴都不再需要构建以某个受支持的 API 级别为目标平台的二进制文件,我们就会立即弃用该 API 级别,不会提前。一旦没有任何合作伙伴希望在新的 Fuchsia 版本上运行任何以某个已弃用 API 级别为目标平台的二进制文件,我们就会立即停用该 API 级别,但不会提前停用。

API 兼容性保证

Fuchsia 现在针对编号 API 级别提供以下 API(即构建时)兼容性保证(受以下注意事项约束):

如果最终开发者可以使用 IDK 的某个版本成功构建其组件(以编号 API 级别 N 为目标),那么他们将能够使用 IDK 的任何版本(N 处于受支持阶段)成功构建该相同源代码。

换句话说,最终开发者可以放心地升级或回滚其 IDK,而不会破坏其 build,直到其选择的目标 API 级别被弃用。如果更新到其他 IDK 而不更改其目标 API 级别导致其 build 损坏(例如,如果他们使用的 API 元素不再存在),则会被视为 Fuchsia 平台 bug。

另一方面,对于可变的伪 API 级别 NEXT 和 HEAD,不保证 API 兼容性。以 NEXT 或 HEAD 为目标平台时,最终开发者可能会发现,更新其 IDK 会导致其代码无法再编译。

实际上,我们当前的合作伙伴与 Fuchsia 团队密切合作,我们将尽最大努力避免破坏针对 NEXT 的最终开发者的 build。我们几乎不会努力避免破坏以 HEAD 为目标的最终开发者的 build。

ABI 兼容性保证

Fuchsia 现在针对编号 API 级别提供以下 ABI(即运行时)兼容性保证(受以下注意事项约束):

针对编号 API 级别 N 构建的组件将在 Fuchsia 的任何版本(N 处于受支持或弃用阶段)上成功运行,并且在版本之间不会看到任何令人反感的行为更改。

换言之,在所选目标 API 级别停用之前,最终开发者无需更改或重新编译代码即可在较新版本的 Fuchsia 上运行。如果平台行为发生变化,导致组件在不同版本的 Fuchsia 上无法正常运行,则会被视为 Fuchsia 平台 bug。

另一方面,对于以可变伪 API 级别 NEXT 或 HEAD 为目标平台的组件,不保证 ABI 兼容性。只有当运行组件的 Fuchsia 版本与构建组件的 IDK 版本完全一致时,系统才会允许运行针对 NEXT 或 HEAD 构建的组件。例如,使用 IDK 版本 16.20231103.1.1 构建的、以 NEXT 为目标的组件将在 Fuchsia 版本 16.20231103.1.1 上运行,但(默认情况下)不会在 Fuchsia 版本为 16.20231103.2.1 的设备上运行。

在大多数情况下,确保构建组件的 IDK 版本与运行该组件的操作系统版本完全一致是不可行的。值得注意的例外情况是集成测试和本地实验。 树外代码库通常可以控制使用哪个版本的 IDK 进行编译,以及使用哪个版本的操作系统进行测试。如果他们希望能够在 NEXT 或 HEAD 中测试功能,则应使两者保持同步。

禁止使用不受支持的配置

默认情况下,component_manager 会拒绝启动 ABI 修订版本标记表明其不在 ABI 兼容性保证范围内的组件。具体而言:

  • 只有当 Fuchsia 的某个版本支持或弃用目标 API 级别 N 时,该版本才会运行目标 API 级别为 N 的组件。
  • 只有当组件是使用完全相同版本的 IDK 构建时,给定版本的 Fuchsia 才会运行以 NEXT 或 HEAD 为目标的组件。

同样,产品组装也会拒绝组装包含与平台不兼容的组件的产品。

产品所有者将能够选择性地停用这些检查。例如,他们将能够将单个组件列入许可清单,以定位到 NEXT 或 HEAD,即使构建该组件的 IDK 来自不同的 Fuchsia 版本。产品所有者自行承担停用这些检查的风险。

实现

实现这些检查需要对 ABI 修订版本标记进行一些更改。这些更改的完整设计超出了本 RFC 的范围,但简而言之:

  • 与现在一样,每个 API 级别在发布时都会分配一个唯一的 ABI 修订版本。以编号 API 级别为目标的组件将带有该 API 级别对应的 ABI 修订版本。
  • 此外,每个版本都会分配一个唯一的 ABI 修订版本。针对 HEAD 或 NEXT 的组件将带有此每个版本的 ABI 修订版本。

给定版本中的操作系统二进制文件将包含处于受支持阶段或日落阶段的所有 API 级别的 ABI 修订版本列表,以及该版本的 ABI 修订版本。默认情况下,只有带有其中一个 ABI 修订版本标记的组件才会被允许运行。

未来扩展

请注意,“每个 API 级别一个 ABI 修订版本,再加上一个 NEXT/HEAD 修订版本”的安排不一定是此设计的最终版本。ABI 修订版本是我们以机器可读的方式对 ABI 兼容性保证进行编码的方式,因此,如果我们的保证变得更加细致,我们对 ABI 修订版本的分配也必须更加细致。

例如,假设我们将部分 API surface 指定为具有“长期支持 (LTS)”,并在 API 级别内继续支持 LTS API 的时间比其他 API 更长。假设的 F30 版本可能支持 API 级别 25 中的 LTS API,但不支持非 LTS API。该版本将能够运行仅使用 LTS API 且以 API 级别 25 为目标平台的组件,但无法运行使用非 LTS API 且以 API 级别 25 为目标平台的组件。因此,这两类组件需要使用不同的 ABI 修订版本进行标记,我们可以通过为每个(API 级别、LTS 状态)对提供其自己的 ABI 修订版本来实现这一点。

我们有这方面的想法,如果我们选择追求这些想法,则会在后续 RFC 中进行介绍。

平台组件同时支持所有受支持的 API 级别

由于我们目前没有机制可让平台组件根据连接另一端组件的 ABI 修订版本标记来改变其行为,因此支持多个 API 级别意味着同时支持多个 API 级别。平台组件的行为必须符合处于支持或停用阶段的每个 API 级别的规范。

更确切地说,平台组件不得对外部组件的目标 API 级别做出任何假设,除非该组件的目标 API 级别是受支持或已停用的 API 级别之一。

举个简单的例子,如果某个方法 Foo 在 API 级别 15 中从 FIDL 协议中移除(即 Foo 带有 @available(added=A, removed=15) 注解),但 API 级别 14 仍受支持,那么实现相关协议服务器的平台组件必须继续实现 Foo,直到包含该方法的所有 API 级别都已停用。

相反,如果该 FIDL 协议服务器是由树外驱动程序实现的,则与该驱动程序通信的平台组件必须正常运行,无论该驱动程序是否实现 Foo。

此策略不仅限制了平台组件的实现,还限制了我们可以对平台表面进行的修改类型。理论上,API 可能会在不同 API 级别之间任意更改,但实际上,某些更改(例如,重复使用 FIDL 协议方法序数)会导致我们在没有进一步信息的情况下,无法与针对不同 API 级别的外部组件正确通信。

我们可能会制定不同的策略来支持多个 API 级别,但这是未来工作的一个领域。

注意事项

API 和 ABI 兼容性保证都受一些注意事项的约束。

“保证”的含义

“保证”兼容性并不意味着 Fuchsia 永远不会违反我们的兼容性承诺,因为人非圣贤,孰能无过。这意味着,如果我们根据上述条款破坏了兼容性,我们会将其视为 bug。我们将负责在内部解决问题,除了报告 bug 之外,最终开发者无需采取任何行动。

保证失效

如果最终开发者“做出异常行为”,则 API 和 ABI 保证不适用。其中包括但不限于:

  • 修改其 IDK 副本中的文件,
  • 访问内部命名空间中的成员,
  • 手动合成旨在由 IDK 中的库构建的数据,
  • 将由不同 IDK 构建的共享库关联到单个二进制文件中,
  • 等等

一个值得注意的反例是,依赖于 Fuchsia 中的错误行为不算作“做一些奇怪的事情”。在某些不幸的情况下,修复 bug 可能会被视为“令人反感的行为更改”。如需了解详情,请参阅令人反感的更改。

本 RFC 不会详尽列出所有禁止的行为。套用美国最高法院大法官 Potter Stewart 在 Jacobellis 诉俄亥俄州一案中的话,“我今天不会尝试进一步定义 [‘做一些奇怪的事情’],而且我可能永远无法成功地做到这一点。但我一看到它就会知道。”

令人反感的更改

ABI 兼容性保证承诺,在支持组件目标 API 级别的 Fuchsia 版本之间,组件不会出现“令人反感的行为变化”。“令人反感”的定义显然具有主观性,对某些人来说是理想的更改,对其他人来说可能令人反感(相关 xkcd)。最终,我们需要根据具体情况处理有关不良行为变化的报告。

安全和隐私设置

如果事后发现 Fuchsia 平台接口的某些部分存在重大安全或隐私问题,我们可能会根据需要选择性地停止支持存在问题的部分功能,以实现我们的安全和隐私目标。

性能

保持与旧版 API 级别的向后兼容性会产生性能开销。至少,向后兼容的平台二进制文件必须更大,因为会保留更多逻辑。在某种程度上,这是不可避免的,但 API 级别会原子化地停用,这可能会加剧该问题。

向后兼容性

此 RFC 的全部内容都涉及向后和向前兼容性。

测试

测试以确保 API 和 ABI 兼容性是另一个重要主题。

Fuchsia 兼容性测试 (CTF) 旨在提供测试覆盖范围,以确认我们满足 ABI 兼容性保证。目前覆盖率较低, 但我们正在积极努力。

目前,我们无法编写测试来验证我们的 API 兼容性保证;我们的所有树内测试都是针对 LEGACY API 级别构建的,因此我们无法复制针对编号 API 级别的树外客户的体验。此问题需要解决,但这样做不应妨碍接受此 RFC。

文档

此 RFC 获得批准后,其文本将作为多个面向 Fuchsia 平台开发者的文档的基础:

此外,还需要更新文档,以反映现有概念的重命名。

缺点、替代方案和未知因素

缺点:API 级别是原子性的

如 RFC-0002 中所述,API 级别涵盖整个 Fuchsia 平台。因此,对 API 级别阶段的更改(例如,停用某个 API 级别)会影响整个 API 级别;我们无法仅停用部分功能。同样,如果最终开发者以给定的 API 级别为目标,则会以整个 Fuchsia 平台接口的该 API 级别为目标;例如,他们不能以 API 级别 13 为大多数 API 的目标,而以 API 级别 16 为一个特定 API 的目标。

未来对平台版本控制模型的扩展可能会允许进行有意义的细分,但 IDK 中各种库之间的依赖关系使这项工作具有挑战性。

备选:无日落阶段

此 RFC 的早期草稿省略了停用阶段,将其留作“未来可能的工作”。停用阶段是对该模型的合理扩展,并且在讨论中经常被提及,因此主动将其纳入似乎是合适的。

替代方案:其他可能的伪 API 级别

如上所述,HEAD 有多种潜在用例:树内组件之间的通信、向集成测试公开的 API、实验性 API 等。这些用例是否应该由不同的伪 API 级别表示?

此 RFC 建议从 NEXT 和 HEAD 开始,但在分配数值时在它们之间留出空隙。这样,如果我们发现伪 API 级别的用例,就可以添加更多伪 API 级别。

替代方案:向后兼容的开发中 API 级别

Fuchsia 的实际版本控制政策目前规定,只能对开发中的 API 级别进行向后兼容的更改。此 RFC 会回滚该政策,因此,当最终开发者以 NEXT 为目标平台时,无法保证不同版本的 IDK 和操作系统二进制文件之间的兼容性。

如果我们在开发中的 API 级别(或 NEXT)内保持这种向后兼容性政策,我们可以做得更好:只要操作系统二进制文件来自比 IDK 更新的版本,我们就可以保证 IDK 和操作系统二进制文件兼容。直到最近,我们才开始依赖此属性,当时我们将下游代码库移至目标受支持的 API 级别。

这样做的好处是,最终开发者可以在新的 Fuchsia 功能添加到开发中的 API 级别后,立即安全地开始依赖这些功能,而无需等待该 API 级别发布。

不过,这会给系统带来很大的复杂性:

  • Fuchsia 平台开发者不仅必须保持与之前 API 级别(其 API 签名在 FIDL 和头文件本身中编码)的向后兼容性,还必须保持与开发中 API 级别的先前版本(未编码)的向后兼容性。查看这些先前版本的唯一方法是检查 git 历史记录,但实际上并不会这样做。
  • 由于我们需要能够从平台中移除功能,因此需要有一些 API 级别支持向后不兼容的更改。这意味着,Fuchsia 平台开发者必须在开发中的 API 级别之后对编号 API 级别进行向后不兼容的更改。因此,实际上有两个正在开发的 API 级别,Fuchsia 平台开发者需要同时考虑这两个级别。
  • 这会使 ABI 修订变得更加复杂。为了支持在开发中的 API 级别内进行向后兼容的更改,我们需要无限期保留每个版本的 ABI 修订版本(每天会创建多个)。操作系统二进制文件需要支持每个版本(其支持的 API 级别之一是开发中的 API 级别)的 ABI 修订版本,以及迄今为止当前里程碑版本的所有 ABI 修订版本。按照我们目前的发布节奏,每个 API 级别大约会增加 168 个 ABI 标记,而在撰写本文时,Fuchsia 的一个版本需要支持 800 多个 ABI 标记。虽然此类解决方案的内存成本不太可能很高,但目前看来,维护与数百、数千或数万个不同 ABI 的兼容性所带来的复杂性似乎不值得,尤其是因为目前没有客户使用开发中的 API 级别。

此 RFC 不排除未来提供额外兼容性保证的可能性。

替代方案:将 API 级别与里程碑解耦

此 RFC 明确将 API 级别与里程碑编号相关联。这并非绝对必要,尤其是 Android 将两者分离开来。 Android 项目利用此功能,在发布操作系统次要版本的同时发布 API 变更。例如,API 级别 24 对应于 Android 7.0,API 级别 25 对应于 Android 7.1。

在我的研究中,我没有发现其他平台选择遵循这条路径。我发现其他平台会根据发布版本来描述 API 元素的可用性,例如“自 Chrome 88 起可用”或“在 iOS 6.1 中已弃用”。

macOS 和 iOS API 级别与其版本控制方案相关联,但两者都允许在次要修订版本中更改 API,这与 Fuchsia 不同。不过,这似乎主要是为了将版本的 主要版本 与日历年结合起来,同时仍允许每年多次发布 API 更新。

Fuchsia 的里程碑更接近 Chrome 的里程碑,会频繁递增主要版本,并让次要版本保持未使用状态。如果我们希望更频繁地稳定 API 级别,可以通过更频繁地递增 Fuchsia 的里程碑编号来实现。

替代方案:带编号的“开发中”API 级别

此 RFC 的早期草稿更接近当前实现的平台版本控制模型。在该模型中,NEXT 不存在,而是编号最大的 API 级别是可变的,称为“开发中”API 级别。

从机制上讲,这些模型之间只有一些细微的差异:

  1. 目前,Fuchsia 平台开发者会选择一个特定的 API 级别,以便在该级别发布其功能。此 RFC 实施后,他们将对 NEXT 进行生产级 API 更改,这些更改将在下一个编号的 API 级别中自动发布。
  2. 目前,以开发中 API 级别为目标平台的最终开发者会在该 API 级别发布后自动过渡到以该 API 级别的受支持版本为目标平台。此 RFC 实施后,它们将继续以 NEXT 为目标。

这些差异似乎都没有明显的积极或消极影响。

NEXT 相对于开发中 API 级别的主要优势在于易于沟通。此 RFC 的早期草稿通过文字游戏来表达兼容性保证:“针对受支持的 API 级别 N 构建的组件将在支持 API 级别 N 的任何 Fuchsia 版本上成功运行。”在当前提案中,所有各方都将就给定 API 级别的内容达成一致,无需任何限定符。

备选方案:不移除任何内容

我们可以通过完全禁止向后不兼容的更改来简化平台版本控制。每个 API 级别都将支持先前级别的所有功能,并且“平台支持所有 API 级别”属性将通过支持最新 API 级别而“免费”获得。

包括 Windows 和 Android 在内的许多平台都主要采用这种方法,但因此无法清理其 API 接口旧修订版本中的无用代码。

虽然 Fuchsia 平台工程师仍需继续支持旧版 API 元素(只要这些元素出现在受支持或已弃用的 API 级别中),但最终开发者只需关注所选目标 API 级别的内容,而无需看到这些旧版元素。

此外,虽然“永不移除内容”可能会使版本控制更简单,但绝不能完全解决问题。最终开发者仍需根据其目标安装量中最旧的 Fuchsia 平台版本,来确定要以哪个 API 级别为目标。


  1. 我们的大多数下游合作伙伴目前都依赖于 Fuchsia 的 Canary 版本,这些版本每天会生成多次。未来,我们的更多合作伙伴将使用里程碑版本分支中的 IDK 和操作系统二进制文件,但目前,我们重视这种安排所带来的即时反馈。 ↩