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 平台。此平台只有一个版本,即 HEAD

命令行

FIDL 编译器接受 --available 标志来指定平台版本。 例如,假设 example.fidlfuchsia 平台中定义了一个库 可以按如下方式在版本 8 中编译它:

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

您可以定位多个版本,只需使用英文逗号进行分隔即可,例如 --available fuchsia:7,8,9

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

目标版本

以单个版本为目标时,绑定会包含 该版本中可用(由 FIDL 中的 @available 参数指定) 文件。

当您以一组版本为目标时,绑定会包含 任何版本的可用情况对于 replaced,则绑定仅包含最新定义。

无论您以哪组版本为目标,如果 FIDL 编译成功, 也保证对该集合的所有子集都成功, 单例集。

语法

任何 FIDL 元素都允许使用 @available 属性。需要 以下参数:

参数 类型 备注
platform string 只能在 library使用
added uint64 整数、NEXTHEAD
deprecated uint64 整数、NEXTHEAD
removed uint64 整数、NEXTHEAD
replaced uint64 整数、NEXTHEAD
note string deprecated搭配使用
renamed string removedreplaced 搭配使用;仅允许用于成员

参数有一些限制:

  • 所有参数都是可选的,但必须至少提供一个。
  • 参数必须是字面量,而不是对 const 声明的引用。
  • removedreplaced 参数是互斥的。
  • 参数必须遵循 added <= deprecated < removed,或 added <= 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;
};

所有修饰符都支持此语法:strictflexibleresourceclosed ajaropen。不过,更改对象上的 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 部分添加到 包含详细说明和 note 参数的 @available 属性。例如:

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 标识取决于元素的类型:

  • 位/枚举成员:值,例如5 英寸(VALUE = 5;
  • 结构成员:offset,例如第一个成员为 0
  • 表/联合成员:序数,例如5 英寸(5: name string;
  • 协议成员:选择器,例如“example/Foo.Bar”在 library example; protocol 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 元素。例如:

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;