RFC-0239:平台版本控制实践 | |
---|---|
状态 | 已接受 |
区域 |
|
说明 | 用于在 Fuchsia 上实现兼容性的概念模型和保证。 |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2023-11-08 |
审核日期(年-月-日) | 2024-01-30 |
摘要
本 RFC 的目标是通过以下方式对 RFC-0002、RFC-0083 等进行扩展:
- 阐述了在 Fuchsia 上实际使用 API 级别和 ABI 修订版本的概念模型,
- 向合作伙伴提供 API 和 ABI 兼容性保证(以该概念模型为依据);
- 概述我们计划如何实现这些兼容性保证。
简要总结:
- 每个 Fuchsia 里程碑都对应一个 API 级别。API 级别 15 可视为“F15 首次支持的第 15 版 Fuchsia 平台 Surface”。
- 每个 Fuchsia 版本都支持多个 API 级别。例如,F15 里程碑版本支持 API 级别 11、12、13、14 和 15。
- 最终开发者在构建组件时选择单个目标 API 级别。以 API 级别 13 为目标平台的组件将会看到 API 级别 13 中的 Fuchsia 平台 Surface,并将在支持 API 级别 13 的任何 Fuchsia 系统上运行。
- Fuchsia 内核和平台组件通过同时实现多个 API 级别来支持多个 API 级别。
设计初衷
如 RFC-0227 中所述,Fuchsia 平台版本的主要组件包括:
- 集成开发套件 (IDK):一小组库、软件包和工具,用于构建和运行以 Fuchsia 为目标平台的组件;以及
- 操作系统 (OS) 二进制文件,包括内核、引导加载程序、软件包、工具以及配置和组装 Fuchsia 产品软件包所需的其他组件。
IDK 是 Fuchsia SDK 的构建系统无关前身,用于描述构成 Fuchsia 平台 Surface 的 API 元素(例如协议、方法、字段、参数、库),并允许最终开发者编写使用这些 API 元素的组件。IDK 中的某些接口由操作系统二进制文件(如内核和平台组件)实现,而其他接口由外部组件(例如应用或驱动程序)实现,预计操作系统二进制文件会调用它们。
Fuchsia 平台 Surface 正在不断开发中,操作系统二进制文件也会不断更新以匹配。这些更改会快速1向下游流向最终开发者和产品所有者。在引入 API 级别之前,IDK 仅包含对平台 Surface 的单个说明。周一构建的 IDK 会描述与周五构建的操作系统实现的 Fuchsia 平台 Surface 的不同版本。这之前会导致问题:如果 FIDL 方法及其实现在星期三被删除,那么使用星期一 IDK 构建的外部组件可能会尝试调用该方法,但会被星期五的操作系统告知不存在此类方法。
粗略地说,我们可以尝试禁止 IDK 和操作系统版本之间不匹配。我们可以说,操作系统只会运行使用完全相同的 Fuchsia 版本的 IDK 构建的组件(即,操作系统版本 2.20210213.2.5
只会运行使用 IDK 版本 2.20210213.2.5
构建的组件)。不过,这并不可行,因为我们无法要求最终开发者在我们发布新版本的 Fuchsia 时不断更新和重新构建其组件。因此,一般来说,用于构建外部组件的 IDK 和运行这些组件的操作系统可能来自不同的 Fuchsia 版本,甚至可能来自不同的里程碑。
这引出了一个问题:在什么情况下,使用一个版本的 IDK 构建的组件能够在另一个版本的操作系统上成功运行?
RFC-0002 引入了 API 级别和 ABI 修订版作为帮助回答此问题的工具,但并未明确说明它们在实践中的使用方式,也没有提供任何具体的兼容性保证。本文档通过展示 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 平台 build 之外构建的组件),而互动另一侧是内部组件(即在 Fuchsia 平台的 GN build 图中构建的操作系统二进制文件,例如内核或 Fuchsia 平台组件)。定义外部组件之间互动的版本控制做法和兼容性超出了本文的范围,将留待日后的工作。
设计
本部分将介绍 Fuchsia 平台版本控制背后的概念模型。此处介绍的许多概念之前已在 RFC-0002 中介绍过,但此处介绍了更具体的详细信息和限制。
此“设计”部分的文本应被视为信息性文本,而非规范性文本。下文的政策变更部分将重复此文本中与现有政策存在差异的部分。
API 级别
Fuchsia 平台 Surface 不断变化,并且新的 Fuchsia 版本会不断创建(每天多次)。我们不会考虑每个 Fuchsia 版本与其他每个版本的 Fuchsia 平台接口的兼容性(这会令人不知所措),而是考虑每个版本与少数 API 级别的兼容性。
您可以将 API 级别视为构成 Fuchsia 平台 Surface 的 API 的“版本”“版本”或“快照”。API 级别 10 是“Fuchsia API Surface 第 10 版”。API 级别会与里程碑同步,因此 API 级别 10 也可以被视为“F10 发布时 Fuchsia 平台 Surface 的最新版本”。
您可以使用 API 级别进行声明,例如“fuchsia.lightsensor.LightSensorData
FIDL 表在 API 级别 10 中包含三个字段(rgbc
、calculated_lux
和 correlated_color_temperature
),但在 API 级别 11 中又添加了两个字段(si_rgbc
和 is_calibrated
)。从 API 级别 11 开始,它包含五个字段,但我们可能会在 API 级别 16 中移除一些字段或添加更多字段。”
与教科书的版本一样,API 级别是immutable。教科书的内容可能会随时间推移而发生变化,但教科书的第 2 版内容在发布后将永远不会发生变化。这意味着,任何两个都知道给定 API 级别的 Fuchsia 版本都会就构成该 API 级别的一组 API 元素达成一致。
Fuchsia 平台接口不仅限于 FIDL,还包括系统调用、C++ 库及其方法、永久性文件格式等。从概念上讲,这些都按 API 级别进行版本控制,但在实践中,FIDL 库的版本控制支持比 Fuchsia 平台 Surface 的其他方面要成熟得多。
定位到 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 级别 11 中的某些方法),组件管理器将阻止其启动。
版本和阶段
每个 Fuchsia 版本都支持多个 API 级别,并为每个 API 级别分配一个阶段。您可以通过下载 Fuchsia 版本的 IDK 并查看 version_history.json
文件,找到 Fuchsia 版本中的 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 级别为目标平台的二进制文件时,我们将其废弃。随着合作伙伴数量的增加,我们对其个别需求的了解会变得越来越不深入,届时我们将需要制定更正式的政策,但目前这样就足够了。
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
更新,这意味着 HEAD
中也包含 NEXT
中的所有 API 更改。
最终开发者可以定位到 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
成功构建相同的源代码。
换句话说,在所选的目标 API 级别弃用之前,最终开发者可以放心地升级或回滚 IDK,而不会破坏 build。如果在不更改目标 API 级别的情况下更新到其他 IDK 导致 build 崩溃(例如,如果他们使用的 API 元素不再存在),则会被视为 Fuchsia 平台 bug。
另一方面,对于可变的伪 API 级别 NEXT
和 HEAD
,没有 API 兼容性保证。以 NEXT
或 HEAD
为目标平台时,最终开发者可能会发现,更新 IDK 会导致其代码无法再进行编译。
实际上,我们当前的合作伙伴会与 Fuchsia 团队密切协作,并会尽最大努力避免破坏以 NEXT
为目标平台的最终开发者的 build。我们将尽量避免破坏以 HEAD
为目标平台的最终开发者的 build。
ABI 兼容性保证
Fuchsia 现在针对编号的 API 级别做出以下 ABI(即运行时)兼容性保证(受下文中的注意事项约束):
以编号 API 级别
N
为目标平台构建的组件将在N
处于受支持或弃用阶段的 任何 Fuchsia 版本上成功运行,并且在各个版本之间不会出现任何不良行为变化。
换句话说,在所选的目标 API 级别被弃用之前,最终开发者无需更改或重新编译其代码,即可在较新版本的 Fuchsia 上运行。如果平台的行为方式不同,干扰了其组件在其他版本 Fuchsia 上的功能,则会被视为 Fuchsia 平台 bug。
另一方面,对于以可变的伪 API 级别 NEXT
或 HEAD
为目标平台的组件,无法保证 ABI 兼容性。仅当构建时以 NEXT
或 HEAD
为目标平台的组件所运行的 Fuchsia 版本完全匹配构建该组件的 IDK 版本时,系统才会允许该组件运行。例如,使用 IDK 版本 16.20231103.1.1
构建的以 NEXT
为目标平台的组件将在 Fuchsia 版本 16.20231103.1.1
上运行,但不会(默认情况下)在 Fuchsia 版本 16.20231103.
2
.1
的设备上运行。
在大多数情况下,确保用于构建组件的 IDK 版本与其运行的操作系统版本完全匹配是不可行的。值得注意的例外情况是集成测试和本地实验。
外部代码库通常可以控制用于编译的 IDK 版本以及用于测试的操作系统版本。如果他们希望能够在 NEXT
或 HEAD
中测试功能,则应使这两者保持同步。
禁止使用不受支持的配置
默认情况下,如果 ABI 修订时间戳表明组件不受 ABI 兼容性保证的涵盖,component_manager
会拒绝启动该组件。具体而言:
- 在给定版本的 Fuchsia 中,只有在该版本支持
N
或N
已弃用的情况下,才会运行以 API 级别N
为目标平台的组件。 - 给定版本的 Fuchsia 仅会运行以
NEXT
或HEAD
为目标平台的组件,前提是该组件是使用完全相同的版本中的 IDK 构建的。
同样,产品组装将拒绝组装与平台不兼容的组件。
产品所有者将能够有选择地停用这些检查。例如,他们将能够将个别组件列入许可名单,以定位到 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 为目标平台的组件,但无法运行以 API 级别 25 为目标平台且使用非 LTS API 的组件。因此,这两种组件需要打上不同的 ABI 修订版本戳记,我们可以通过为每个(API 级别、LTS 状态)对分配自己的 ABI 修订版本来实现这一点。
我们有这方面的想法,如果我们选择继续推进,将在后续的 RFC 中加以说明。
平台组件同时支持所有支持的 API 级别
由于目前没有任何机制可让平台组件根据连接另一端组件的 ABI 修订戳来更改其行为,因此支持多个 API 级别意味着同时支持多个 API 级别。平台组件的行为必须符合受支持或弃用阶段的每个 API 级别的规范。
更确切地说,平台组件不得对外部组件定位的 API 级别做出任何假设,但可以假设外部组件定位的是受支持或已弃用的 API 级别之一。
举个简单的例子,如果方法 Foo
已从 API 级别 15 的 FIDL 协议中移除(即 Foo
已带有 @available(added=A, removed=15)
注解),但仍支持 API 级别 14,则实现相关协议服务器的平台组件必须继续实现 Foo
,直到包含它的所有 API 级别都已弃用为止。
反之,如果该 FIDL 协议服务器由外部驱动程序实现,则与该驱动程序通信的平台组件必须正常运行,无论该驱动程序是否实现了 Foo
。
此策略不仅约束了平台组件的实现,还约束了我们可以对平台界面进行的修改类型。从理论上讲,API 可能会在不同 API 级别之间任意更改,但实际上,如果没有更多信息,我们将无法与定位到不同 API 级别的外部组件正确通信,因为某些更改(例如重复使用 FIDL 协议方法序数)会导致这种情况。
我们可能会制定不同的策略来支持多个 API 级别,但这将是未来的工作重点。
注意事项
API 和 ABI 兼容性保证都存在一些注意事项。
“保证”的含义
“保证”兼容性并不意味着 Fuchsia 绝不会违背我们的兼容性承诺,人无完人。也就是说,如果我们根据上述条件破坏了兼容性,我们会将其视为 bug。我们将负责在内部解决此问题,最终开发者除了报告 bug 之外,无需执行任何操作。
使保修失效
如果最终开发者“做了一些奇怪的事情”,则 API 和 ABI 保证不适用。其中包括但不限于:
- 修改其 IDK 副本中的文件,
- 访问内部命名空间中的成员,
- 手动合成要由 IDK 中的库构建的数据,
- 将由不同 IDK 构建的共享库关联到单个二进制文件中,
- 等等
一个值得注意的反例是,依赖于 Fuchsia 中的 bug 行为不属于“做一些奇怪的事情”。在某些不幸的情况下,修复 bug 可能会被视为“不良行为变更”。如需了解详情,请参阅不良更改。
全面阐述禁止的行为不在本 RFC 的讨论范围内。如美国最高法院大法官 Potter Stewart 在 Jacobellis v. Ohio 一案中所言:“我今天不会再尝试进一步定义 [‘做一些奇怪的事情’],也许我永远也无法成功地清晰地定义它。但我看到了就知道。”
不当更改
ABI 兼容性保证可确保在支持组件的目标 API 级别的 Fuchsia 版本之间,组件不会出现“不良行为变化”。“不良”的定义显然是主观的,有些人认为有益的更改,可能对其他人来说是不可接受的(相关 xkcd)。最终,我们会根据具体情况来处理有关不良行为变化的举报。
安全和隐私设置
如果回过头来发现 Fuchsia 平台表面的某些部分存在严重的安全或隐私问题,我们可能会根据需要选择停止支持违规功能,以实现我们的安全和隐私保护目标。
性能
与较低 API 级别保持向后兼容会带来性能开销。无论如何,向后兼容的平台二进制文件都必须更大,因为它会保留更多逻辑。在某种程度上,这是不可避免的,但API 级别以原子化方式被弃用这一事实可能会加剧这一问题。
向后兼容性
本文档的全部内容都与向后和向前兼容性有关。
测试
测试以确保 API 和 ABI 兼容性是另一个大主题。
Fuchsia 兼容性测试 (CTF) 旨在提供测试覆盖率,以确认我们能否满足 ABI 兼容性保证。目前覆盖率较低,但我们正在积极努力提高覆盖率。
目前,我们无法编写测试来验证我们的 API 兼容性保证;我们所有树内测试都是针对 LEGACY
API 级别构建的,因此我们无法重现以编号 API 级别为目标平台的树外客户的体验。这需要得到解决,但这不应阻止接受此 RFC。
文档
在本 RFC 获得批准后,其文本将被重复使用,作为面向 Fuchsia 平台开发者的多份文档的基础:
- 兼容性政策本身。
- API 级别和兼容性指南。
- 一份文档的开头部分,其中概述了会使我们的兼容性保证失效(即“执行异常操作”)的使用准则,可能以 Abseil 的兼容性准则文档为蓝本。
文档也需要更新,以反映现有概念的重命名。
缺点、替代方案和未知情况
缺点:API 级别是原子级别
如 RFC-0002 所述,API 级别涵盖整个 Fuchsia 平台 Surface。因此,对 API 级别阶段所做的更改(例如弃用 API 级别)会影响整个 API 级别;我们无法仅弃用部分功能。同样,以给定 API 级别为目标平台的最终开发者会针对整个 Fuchsia 平台接口以该 API 级别为目标平台;例如,他们不能针对大多数 API 以 API 级别 13 为目标平台,但针对一个特定 API 以 API 级别 16 为目标平台。
未来对平台版本控制模型的扩展可能会允许进行有意义的分区,但 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 级别进行向后不兼容的更改。因此,实际上有 2 个正在开发的 API 级别,Fuchsia 平台开发者需要考虑这两个级别。
- 这会使 ABI 修订变得更加复杂。为了支持开发中的 API 级别中的向后兼容更改,我们需要无限期保留每个版本的 ABI 修订版(每天都会创建多个)。操作系统二进制文件需要支持每个版本的 ABI 修订版(其中一个支持的 API 级别为开发中的 API 级别),以及当前里程碑中到目前为止的所有版本的所有 ABI 修订版。按照我们目前的发布节奏,这将使每个 API 级别的 ABI 戳总数达到约 168 个。截至本文撰写之时,一个 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 级别与其版本方案相关联,但与 Fuchsia 不同,这两者都允许在次要修订中更改 API。不过,这似乎主要是为了将其发布版本的主要版本与公历年相关联,同时仍允许每年发布多次 API 更新。
Fuchsia 的里程碑更贴近 Chrome 的里程碑,会频繁递增主要版本号,而次要版本号则会闲置。如果我们希望更频繁地稳定 API 级别,可以通过更频繁地递增 Fuchsia 的里程碑编号来实现。
替代方案:使用编号的“开发中”API 级别
此 RFC 的早期草稿更接近目前实现的平台版本控制模型。在该模型中,不存在 NEXT
,而是最高编号的 API 级别可变,称为“开发中”API 级别。
从机制上讲,这两种模型之间只有一些细微差异:
- 目前,Fuchsia 平台开发者可以选择他们希望在哪个 API 级别发布其功能。在实现此 RFC 后,他们将对
NEXT
进行面向生产环境的 API 更改,这些更改将自动发布在下一个编号的 API 级别中。 - 目前,以开发中的 API 级别为目标平台的最终开发者会在该 API 级别发布后自动改为以受支持的版本为目标平台。此 RFC 实施后,它们将继续定位到
NEXT
。
这两种差异似乎都没有明显的正面或负面影响。
与开发中的 API 级别相比,NEXT
的主要优势在于易于通信。此 RFC 的早期草稿采用了文字游戏来表达兼容性保证:“以支持的 API 级别 N
为目标平台构建的组件将在支持相应阶段的任何支持 API 级别 N
的 Fuchsia 版本上成功运行。”在当前提案中,所有相关方都将就给定 API 级别的内容达成一致,无需任何限定符。
替代方案:不移除内容
我们可以完全禁止向后不兼容的更改,从而简化平台版本控制。每个 API 级别都支持之前的所有功能,并且由于支持最新的 API 级别,因此“平台支持所有 API 级别”属性会“免费”提供。
包括 Windows 和 Android 在内的许多平台在很大程度上采用了这种方法,但因此无法清理其 API 接口旧版中的杂物。
虽然 Fuchsia 平台工程师需要继续支持旧版 API 元素(只要它们出现在受支持或已弃用的 API 级别中),但最终开发者只需担心自己选择的目标 API 级别的内容,而无需看到冗余内容。
此外,虽然“从不移除内容”可能会使版本控制变得更简单,但绝不能完全解决问题。最终开发者仍需要根据其预期安装基准中最旧的 Fuchsia 平台版本,推理出要定位到的 API 级别。
-
我们大多数下游合作伙伴目前依赖于 Fuchsia 的 Canary 版本,这些版本每天都会生成多次。未来,我们会有更多合作伙伴使用里程碑版本分支中的 IDK 和操作系统二进制文件,但目前,我们非常重视这种安排带来的即时反馈。 ↩