本文档介绍了 FIDL 的 API 版本控制功能。如需有关如何演化 Fuchsia 平台 API 的指南,请参阅 Fuchsia API 演化指南。
摘要
借助 FIDL 版本控制,您可以表示一段时间内对 FIDL 库所做的更改。进行更改时,您可以使用 @available
属性来描述更改时间(即更改版本的时间)。如需生成绑定,您可以将 --available
标志传递给 fidlc,指定一个或多个版本。
FIDL 版本控制提供 API 版本控制,而不是 ABI 版本控制。无法在运行时查询版本。变更可能会破坏 API,但应与 ABI 兼容。FIDL 编译器会执行一些基本验证,但无法保证 ABI 兼容性。
概念
版本控制的单元是一组库,称为“平台”。按照惯例,库的命名应以平台名称开头。例如,库 fuchsia.mem
和 fuchsia.web
属于 fuchsia
平台。
每个平台都有线性版本历史记录。版本是 1 到 2^31-1(含)之间的整数,或者是特殊版本 NEXT
和 HEAD
之一。NEXT
版本用于计划在下一个编号版本中执行的更改。HEAD
版本用于最新的不稳定更改。
如果 FIDL 库没有任何 @available
属性,则它属于 unversioned
平台。此平台只有一个版本,即 HEAD
。fuchsia.git
中的 FIDL 库必须指定 @available
属性。
目标版本
定位到单个版本时,绑定会包含 FIDL 文件中的 @available
参数指定的该版本中提供的所有元素。
定位到一组版本时,绑定会包含该组中任何版本中都提供的所有元素。对于 replaced
元素,绑定仅包含最新的定义。
无论您定位到哪组版本,如果 FIDL 编译成功,则也保证该组的所有子集以及所有可能的单例集都将成功编译。
语法
任何 FIDL 元素都可以使用 @available
属性。它接受以下参数:
参数 | 类型 | 说明 |
---|---|---|
platform |
string |
库组名称(请参阅概念);仅允许在 library 上使用 |
added |
uint64 |
整数、NEXT 或 HEAD |
deprecated |
uint64 |
整数、NEXT 或 HEAD |
removed |
uint64 |
整数、NEXT 或 HEAD |
replaced |
uint64 |
整数、NEXT 或 HEAD |
note |
string |
为 deprecated 、removed 和/或 replaced 提供上下文 |
renamed |
string |
removed 或 replaced 元素的新名称;仅适用于成员 |
参数有一些限制:
- 所有参数都是可选的,但必须至少提供一个参数。
- 参数必须是字面量,而不是对
const
声明的引用。 removed
和replaced
参数是互斥的。- 参数必须遵循
added <= deprecated < removed
或added <= deprecated < replaced
。 - 如果未指定,
added
、deprecated
、removed
和replaced
参数会继承父元素。
例如:
@available(added=1, deprecated=2, removed=3)
const ANSWER uint64 = 42;
如果在库中的任何位置使用 @available
,则必须也出现在库声明中。对于单文件库,这很简单。对于包含两个或更多 .fidl
文件的库,只有一个文件的库声明可以带有注解。(在逻辑上,库被视为包含从每个文件合并的属性的单个元素,因此为多个文件添加注解会导致属性重复错误。)FIDL 样式指南建议为此创建一个名为 overview.fidl
的文件。
在库声明中,@available
属性需要 added
参数,并允许 platform
参数。如果省略 platform
,则默认为库名称的第一个组件。例如:
// Equivalent to `@available(platform="fuchsia", added=1)`.
@available(added=1)
library fuchsia.examples.docs;
修饰符
通过 FIDL 版本控制,您可以在特定版本中添加或移除修饰符。在修饰符后面,您可以像在 @available
后面一样在括号中编写参数。不过,仅允许使用 added
和 removed
参数。
以下示例展示了如何将枚举从严格更改为灵活:
type Color = strict(removed=2) flexible(added=2) enum {
RED = 1;
};
所有修饰符都支持此语法:strict
、flexible
、resource
、closed
、ajar
和 open
。但是,不允许更改没有错误语法的双向方法的 strict
或 flexible
修饰符。
当您以一组版本为目标时,编译器会使用最新的修饰符。在上面的示例中,如果集合包含任何等于或高于 2 的版本,枚举将是灵活的,即使它还包含 1 也是如此。
继承
@available
的参数从库声明流向顶级声明,再从每个顶级声明流向其成员。例如,如果表是在版本 5 中添加的,则无需对其成员重复此注释,因为这些成员不能存在于表本身之前。下面是一个更复杂的继承示例:
@available(added=2, deprecated=3)
open protocol Versioned {
// Equivalent to `@available(added=2, deprecated=3, removed=4)`.
@available(removed=4)
flexible Removed(table {
// Equivalent to `@available(added=3, deprecated=3, removed=4)`.
@available(added=3)
1: message string;
});
};
弃用
弃用用于表示某个元素将在将来被移除。废弃某个元素时,您应在文档注释中添加 # Deprecation
部分并提供详细说明,并在 @available
属性中添加 note
参数并提供简短说明。例如:
open protocol Example {
/// (Description of the method.)
///
/// # Deprecation
///
/// (Detailed explanation of why the method is deprecated, the timeline for
/// removing it, and what should be used instead.)
@available(deprecated=5, removed=6, note="use Replacement")
flexible Deprecated();
@available(added=5)
flexible Replacement();
};
自 2024 年 6 月起,弃用对绑定没有影响。不过,FIDL 团队计划让其以目标语言发出废弃注解。例如,上述示例可能会在 Rust 绑定中生成 #[deprecated = "Use
Replacement."]
。
身份
FIDL 版本控制可以区分移除和替换元素。为此,它依赖于 API 和 ABI 身份的概念。元素的 API 身份是其名称。ABI 标识取决于元素的类型:
- 位/枚举成员:值,例如
VALUE = 5;
中的 5 - 结构体成员:偏移量,例如第一个成员为 0
- 表/联合成员:序数,例如
5: name string;
中的 5 - 协议成员:选择器,例如
library example; protocol Foo { Bar(); };
中的“example/Foo.Bar”
其他元素(例如类型声明和协议)没有 ABI 标识。
正在替换
借助 replaced
参数,您可以通过编写全新的定义来更改特定版本的元素。这是更改 FIDL 元素的某些方面的唯一方法,包括:
- 常量的值
- 结构体、表或联合体成员的类型
- 声明的类型,例如将结构体更改为别名
- 方法中存在
error
语法 - 元素的属性
如需替换版本为 N
的元素,请为旧定义添加 @available(replaced=N)
注解,为新定义添加 @available(added=N)
注解。例如,您可以通过以下方式更改常量的值:
@available(replaced=5)
const MAX_NAME_LEN uint32 = 32;
@available(added=5)
const MAX_NAME_LEN uint32 = 64;
再举一个示例,您可以通过以下方式更改表字段的类型:
type Data = resource table {
@available(replaced=5)
1: name string:32;
@available(added=5)
1: name string:64;
};
FIDL 编译器会验证每个 @available(replaced=N)
元素是否都有一个具有相同标识的匹配 @available(added=N)
元素。它还会验证每个 @available(removed=N)
元素是否具有此类替换项。此验证仅适用于直接注解的元素,而不适用于继承 removed
或 replaced
参数的元素。
重命名
如需重命名成员,请将其替换为新定义,并使用旧定义的 renamed
参数指定新名称。例如:
type User = table {
@available(replaced=2, renamed="first_name")
1: name string;
@available(added=2)
1: first_name string;
};
renamed
参数仅适用于成员,因为 FIDL 编译器依赖于其 ABI 身份进行验证。如需重命名声明,只需移除旧定义并改用新定义即可:
@available(deprecated=2, removed=3, note="renamed to Information")
type Info = table {};
@available(added=2)
type Information = table {};
移除后
通常,renamed
参数与 replaced=N
搭配使用,但您也可以将其与 removed=N
搭配使用。这样,您就可以在移除成员后使用新名称来引用该成员。具体运作方式取决于一组目标版本:
- 如果您仅定位到低于
N
的版本,则绑定将使用旧名称。 - 如果您仅定位到等于或高于
N
的版本,则绑定将完全不包含该成员。 - 如果您定位的集合包含低于
N
的版本且包含大于或等于N
的版本,则绑定将使用新名称。
这样做的一个原因是,不鼓励重新使用某个 API,同时继续支持其实现。例如:
open protocol Door {
@available(removed=5, renamed="DeprecatedOpen")
flexible Open() -> ();
};
如果 Door
服务器是在面向版本集 {4, 5} 的代码库中实现,则此方法将被命名为 DeprecatedOpen
,这会阻止开发者添加新的方法。如果另一个代码库以版本 4 或更低版本为目标平台,则此方法将被命名为 Open
。如果它以版本 5 为目标平台,则该方法根本不会显示。
使用此功能的另一个原因是重复使用新 ABI 的名称。例如,考虑更改 Open
方法以返回错误:
open protocol Door2 {
@available(removed=5, renamed="DeprecatedOpen")
flexible Open() -> ();
@available(added=5)
@selector("NewOpen")
flexible Open() -> () error uint32;
};
我们需要定义一个新方法,因为预计会出错的客户端在收到错误响应后会关闭通道。不过,只要我们满足以下条件,就可以继续使用 Open
这个名称:(1) 使用 @selector
为新方法提供不同的 ABI 标识,(2) 对旧定义使用 renamed
,以允许版本集 {4, 5} 的绑定包含这两种方法。
引用 FIDL 类型
一个 FIDL 元素可以通过多种方式引用另一个 FIDL 元素。例如:
const VALUE uint32 = 5;
const REFERENCES_VALUE uint32 = VALUE;
type Type = struct {};
type ReferencesType = table {
1: t Type;
};
alias ReferencesTypeAndValue = vector<Type>:VALUE;
引用元素时,您必须遵循 @available
属性。例如,以下代码无效,因为 A
从版本 1 开始存在,但它试图引用仅存在于版本 2 的 B
:
// Does not compile!
@available(added=1)
const A bool = B;
@available(added=2, removed=3)
const B bool = true;
同样,非已废弃的元素引用已废弃的元素也是无效的。例如,以下代码在版本 1 中无效,因为 A
引用了 B
,但 B
已废弃,而 A
未废弃。
// Does not compile!
@available(deprecated=2)
const A bool = B;
@available(deprecated=1)
const B bool = true;
fidlc
命令行
FIDL 编译器接受 --available
标志来指定平台版本。例如,假设 example.fidl
在 fuchsia
平台中定义了一个没有依赖项的库,您可以按如下方式将其编译为版本 22:
fidlc --available fuchsia:22 --out example.json --files example.fidl
您可以定位多个版本,只需用英文逗号分隔即可,例如 --available fuchsia:19,22,23,NEXT,HEAD
。
如果库 A
依赖于其他平台的库 B
,您可以使用 --available
标志两次为这两个平台指定版本。不过,A
必须在其整个版本记录中与为 B
选择的固定版本兼容。