FIDL 版本控制

本文档介绍了 FIDL 的 API 版本控制功能。如需有关如何演化 Fuchsia 平台 API 的指南,请参阅 Fuchsia API 演化指南

摘要

借助 FIDL 版本控制,您可以表示一段时间内对 FIDL 库所做的更改。进行更改时,您可以使用 @available 属性来描述更改时间(即更改版本的时间)。如需生成绑定,您可以将 --available 标志传递给 fidlc,指定一个或多个版本。

FIDL 版本控制提供 API 版本控制,而不是 ABI 版本控制。无法在运行时查询版本。变更可能会破坏 API,但应与 ABI 兼容。FIDL 编译器会执行一些基本验证,但无法保证 ABI 兼容性。

概念

版本控制的单元是一组库,称为“平台”。按照惯例,库的命名应以平台名称开头。例如,库 fuchsia.memfuchsia.web 属于 fuchsia 平台。

每个平台都有线性版本历史记录。版本是 1 到 2^31-1(含)之间的整数,或者是特殊版本 NEXTHEAD 之一。NEXT 版本用于计划在下一个编号版本中执行的更改。HEAD 版本用于最新的不稳定更改。

如果 FIDL 库没有任何 @available 属性,则它属于 unversioned 平台。此平台只有一个版本,即 HEADfuchsia.git 中的 FIDL 库必须指定 @available 属性。

目标版本

定位到单个版本时,绑定会包含 FIDL 文件中的 @available 参数指定的该版本中提供的所有元素。

定位到一组版本时,绑定会包含该组中任何版本中都提供的所有元素。对于 replaced 元素,绑定仅包含最新的定义。

无论您定位到哪组版本,如果 FIDL 编译成功,则也保证该组的所有子集以及所有可能的单例集都将成功编译。

语法

任何 FIDL 元素都可以使用 @available 属性。它接受以下参数:

参数 类型 说明
platform string 库组名称(请参阅概念);仅允许在 library 上使用
added uint64 整数、NEXTHEAD
deprecated uint64 整数、NEXTHEAD
removed uint64 整数、NEXTHEAD
replaced uint64 整数、NEXTHEAD
note string deprecatedremoved 和/或 replaced 提供上下文
renamed string removedreplaced 元素的新名称;仅适用于成员

参数有一些限制:

  • 所有参数都是可选的,但必须至少提供一个参数。
  • 参数必须是字面量,而不是对 const 声明的引用。
  • removedreplaced 参数是互斥的。
  • 参数必须遵循 added <= deprecated < removedadded <= deprecated < replaced
  • 如果未指定,addeddeprecatedremovedreplaced 参数会继承父元素。

例如:

@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 后面一样在括号中编写参数。不过,仅允许使用 addedremoved 参数。

以下示例展示了如何将枚举从严格更改为灵活:

type Color = strict(removed=2) flexible(added=2) enum {
    RED = 1;
};

所有修饰符都支持此语法:strictflexibleresourceclosedajaropen。但是,不允许更改没有错误语法的双向方法的 strictflexible 修饰符。

当您以一组版本为目标时,编译器会使用最新的修饰符。在上面的示例中,如果集合包含任何等于或高于 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 = "UseReplacement."]

身份

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) 元素是否具有此类替换项。此验证仅适用于直接注解的元素,而不适用于继承 removedreplaced 参数的元素。

重命名

如需重命名成员,请将其替换为新定义,并使用旧定义的 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.fidlfuchsia 平台中定义了一个没有依赖项的库,您可以按如下方式将其编译为版本 22:

fidlc --available fuchsia:22 --out example.json --files example.fidl

您可以定位多个版本,只需用英文逗号分隔即可,例如 --available fuchsia:19,22,23,NEXT,HEAD

如果库 A 依赖于其他平台的库 B,您可以使用 --available 标志两次为这两个平台指定版本。不过,A 必须在其整个版本记录中与为 B 选择的固定版本兼容。