FIDL 版本控制

本文档介绍了 FIDL 的 API 版本控制功能。如需了解如何改进 Fuchsia API,请参阅 Fuchsia API 演变指南

设计初衷

借助 FIDL 版本控制功能,您可以随时间更改 FIDL 库,同时保留为旧版库生成绑定的能力。您可以通过多种方式手动执行此操作:

  • .fidl 文件存储在 v1/ 目录中。如需进行更改,请将 v1/ 复制到 v2/,然后在其中更改文件。如需为旧版本生成绑定,请使用 v1/ 库而不是 v2/

  • .fidl 文件存储在 Git 代码库中,并在提交中进行更改。如需为旧版本生成绑定,请查看代码库的较旧版本。

第一种解决方案非常繁琐,会产生大量的重复内容。第二个解决方案在包含特定 FIDL 库之外许多内容的大型仓库(例如主 Fuchsia 仓库)中效果不佳。

FIDL 版本控制可以实现相同的目的,但不存在这些缺点。进行更改时,您可以使用 @available 属性来描述更改的时间(即在哪个版本)。如需为旧版本生成绑定,请将 --available 标志传递给 fidlc 并指定旧版本。

关于 FIDL 版本控制,需要注意以下两点:

  • 仅影响 API。版本仅在编译时存在,对运行时行为没有影响。

  • 它可以表示任何在语法上有效的更改。虽然您可以通过版本控制来表示某项更改,但这并不表示该更改可以安全地进行。

概念

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

每个平台都有一个线性版本历史记录。版本是 1 到 2^63-1(含 1 和 2^63-1)之间的整数,或者是特殊版本 HEADLEGACY 之一。HEAD 版本用于最新的不稳定更改。LEGACY 版本与 HEAD 类似,但它还包含旧版元素

如果 FIDL 库没有任何 @available 属性,则它属于 unversioned 平台。此平台只有一个版本,即 HEAD

命令行

FIDL 编译器接受 --available 标志来指定平台版本。例如,假设 example.fidlfuchsia 平台中定义了一个没有依赖项的库,您可以在版本 8 中对其进行编译,如下所示:

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

无论您选择哪个版本,fidlc 都会始终验证所有可能的版本。例如,即使错误仅在版本 5 中出现,上述命令也可以报告错误。

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

语法

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

参数 类型 备注
platform string 只能在 library 上使用
added uint64 整数或 HEAD
deprecated uint64 整数或 HEAD
removed uint64 整数或 HEAD
replaced uint64 整数或 HEAD
note string 搭配“deprecated
legacy boolean 搭配“removed

这些参数存在一些限制:

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

例如:

@available(added=1, deprecated=2, removed=3, legacy=true)
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;

继承

@available 的参数从库声明流向顶级声明,再从每个顶级声明流向其成员。例如,如果某个表是在版本 5 中添加的,则无需对其成员重复此注释,因为它们在表本身之前不能存在。下面是一个更复杂的继承示例:

@available(added=2, deprecated=3)
protocol Versioned {
    // Equivalent to `@available(added=2, deprecated=3, removed=4, legacy=true)`.
    @available(removed=4, legacy=true)
    Removed(table {
        // Equivalent to `@available(added=3, deprecated=3, removed=4, legacy=true)`.
        @available(added=3)
        1: message string;
        // Equivalent to `@available(added=2, deprecated=3, removed=4, legacy=false)`.
        @available(legacy=false)
        2: count uint32;
    });
};

弃用

弃用用于表示某个元素将来会被移除。 弃用元素时,您应在文档注释中添加 # Deprecation 部分并提供详细说明,并为 @available 属性添加一个 note 参数,并附上简要说明。例如:

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")
    Deprecated();

    @available(added=5)
    Replacement();
};

自 2022 年 5 月起,废弃对绑定没有任何影响。不过,FIDL 团队计划使其以目标语言发出弃用注释。例如,上面的示例可以在 Rust 绑定中生成 #[deprecated = "use Replacement"]

正在替换

借助 replaced 参数,您可以通过编写全新的定义来更改特定版本中的元素。这是更改 FIDL 元素某些方面的唯一方式,包括:

  • 常量的值
  • 结构体、表或联合成员的类型
  • 表或联合成员的序数
  • 声明的类型,例如将结构体更改为别名
  • 方法中存在 error 语法
  • 元素的其他属性,例如 @selector
  • 修饰符,例如 strictflexibleresource

如需替换版本 N 中的元素,请使用 @available(replaced=N) 为旧定义添加注解,并使用 @available(added=N) 为新定义添加注解。例如,以下代码段展示了如何将枚举从 strict 更改为 flexible

@available(replaced=5)
type Color = strict enum {
    RED = 1;
};

@available(added=5)
type Color = flexible enum { // Note: flexible instead of strict
    RED = 1;
};

您还可以使用替换来清理因长期的 API 变更而变得杂乱无章的 FIDL 库。只需将所有元素替换为新的定义,然后将旧定义移至名为 history.fidl 这样的单独文件中即可。

FIDL 编译器会验证是否每个 @available(replaced=N) 元素都有匹配的 @available(added=N) 替换。它还会验证是否每个 @available(removed=N) 元素都没有这样的替换项。此验证仅适用于直接添加注解的元素,不适用于继承 removedreplaced 参数的元素。

旧版

移除元素时,您可以使用 legacy=true 将其保留在 LEGACY 版本中。这样一来,您就可以在移除 API 级别之前为目标 API 级别的客户端保留 ABI,因为 Fuchsia 系统映像是基于 LEGACY FIDL 绑定构建的。例如:

protocol LegacyExample {
    @available(deprecated=5, removed=6, legacy=true, note="...")
    LegacyMethod();
};

在此示例中,LegacyMethod 未出现在版本 6 或更高版本的绑定中,也未出现在 HEAD 中,但它会在 LEGACY 版本中重新添加。

参考编号

一个 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 属性。例如,以下代码无效,因为从版本 1 开始就存在 A,但它尝试引用仅存在于版本 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;