This document describes FIDL's API versioning features. For guidance on how to evolve Fuchsia APIs, see Fuchsia API evolution guidelines.
FIDL versioning lets you represent changes to a FIDL library over time. When making a change, you use the @available
attribute to describe when (i.e. at which version) the change occurs. To generate bindings, you pass the --available
flag to fidlc specifying one or more versions.
FIDL versioning provides API versioning, not ABI versioning. There is no way to query versions at runtime. Changes may be API breaking, but they are expected to be ABI compatible. The FIDL compiler performs some basic validation, but it does not guarantee ABI compatibility.
The unit of versioning is a group of libraries, called a platform. By convention, libraries are named starting with the platform name. For example, the libraries fuchsia.mem
and fuchsia.web
belong to the fuchsia
platform.
Each platform has a linear version history. A version is an integer from 1 to 2^31-1 (inclusive), or one of the special versions NEXT
and HEAD
. The NEXT
version is used for changes that are planned to go in the next numbered version. The HEAD
version is used for the latest unstable changes.
If a FIDL library doesn't have any @available
attributes, it belongs to the unversioned
platform. This platform only has one version, HEAD
.
The FIDL compiler accepts the --available
flag to specify platform versions. For example, assuming example.fidl
defines a library in the fuchsia
platform with no dependencies, you can compile it at version 8 as follows:
fidlc --available fuchsia:8 --out example.json --files example.fidl
You can target multiple versions by separating them with commas, e.g. --available fuchsia:7,8,9
.
If a library A
has a dependency on a library B
from a different platform, you can specify versions for both platforms using the --available
flag twice. However, A
must be compatible across its entire version history with the fixed version chosen for B
.
When you target a single version, the bindings include all elements that are available in that version as specified by @available
arguments in the FIDL files.
When you target a set of versions, the bindings include all elements that are available in any of the versions in the set. For elements that are replaced
, the bindings only include the latest definition.
No matter what set of versions you target, if FIDL compilation succeeds, then it is also guaranteed to succeed for all subsets of that set, and for all possible singleton sets.
The @available
attribute is allowed on any FIDL element. It takes the following arguments:
Argument | Type | Note |
---|---|---|
platform | string | Only allowed on library |
added | uint64 | Integer, NEXT , or HEAD |
deprecated | uint64 | Integer, NEXT , or HEAD |
removed | uint64 | Integer, NEXT , or HEAD |
replaced | uint64 | Integer, NEXT , or HEAD |
note | string | Goes with deprecated |
renamed | string | Goes with removed or replaced ; only allowed on members |
There are some restrictions on the arguments:
const
declarations.removed
and replaced
arguments are mutually exclusive.added <= deprecated < removed
, or added <= deprecated < replaced
.added
, deprecated
, removed
, and replaced
arguments inherit from the parent element if unspecified.For example:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/fuchsia.examples.docs/versioning.test.fidl" region_tag="arguments" %}
If @available
is used anywhere in a library, it must also appear on the library declaration. For single-file libraries, this is straightforward. For libraries with two or more .fidl
files, only one file can have its library declaration annotated. (The library is logically considered a single element with attributes merged from each file, so annotating more than one file results in a duplicate attribute error.) The FIDL style guide recommends creating a file named overview.fidl
for this purpose.
On the library declaration, the @available
attribute requires the added
argument and allows the platform
argument. If the platform
is omitted, it defaults to the first component of the library name. For example:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/fuchsia.examples.docs/versioning.test.fidl" region_tag="library" %}
The arguments to @available
flow from the library declaration to top-level declarations, and from each top-level declaration to its members. For example, if a table is added at version 5, there is no need to repeat this annotation on its members because they could not exist prior to the table itself. Here is a more complicated example of inheritance:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/fuchsia.examples.docs/versioning.test.fidl" region_tag="inheritance" %}
Deprecation is used to indicate that an element will be removed in the future. When you deprecate an element, you should add a # Deprecation
section to the doc comment with a detailed explanation, and a note
argument to the @available
attribute with a brief instruction. For example:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/fuchsia.examples.docs/versioning.test.fidl" region_tag="deprecation" %}
As of June 2024 deprecation has no impact in bindings. However, the FIDL team plans to make it emit deprecation annotations in target languages. For instance, the example above could produce #[deprecated = "use Replacement"]
in the Rust bindings.
FIDL versioning distinguishes between removing and replacing elements. To do this, it relies on a notion of API and ABI identity. The API identity of an element is its name. ABI identity depends on the kind of element:
VALUE = 5;
5: name string;
library example; protocol Foo { Bar(); };
Other elements, such as type declarations and protocols, have no ABI identity.
The replaced
argument lets you change an element at a particular version by writing a completely new definition. This is the only way to change certain aspects of FIDL elements, including:
error
syntax on a methodstrict
, flexible
, and resource
To replace an element at version N
, annotate the old definition with @available(replaced=N)
and the new definition with @available(added=N)
. For example, here is how you change an enum from strict
to flexible
:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/fuchsia.examples.docs/versioning.test.fidl" region_tag="replaced" %}
The FIDL compiler verifies that for every @available(replaced=N)
element there is a matching @available(added=N)
element with the same identity. It also verifies that every @available(removed=N)
element does not have such a replacement. This validation only applies to elements directly annotated, not to elements that inherit the removed
or replaced
argument.
To rename a member, replace it with a new definition and specify the new name with the renamed
argument on the old definition. For example:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/fuchsia.examples.docs/versioning.test.fidl" region_tag="renamed" %}
The renamed
argument is only allowed on members because the FIDL compiler relies on their ABI identity to validate it. To rename a declaration, simply remove the old definition in favor of a new one:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/fuchsia.examples.docs/versioning.test.fidl" region_tag="rename_declaration" %}
Normally the renamed
argument is used with replaced=N
, but you can also use it with removed=N
. This gives a new name to refer to the member after its removal. How it works is based on the set of target versions:
N
, the bindings will use the old name.N
, the bindings won't include the member at all.N
and containing versions greater than or equal to N
, the bindings will use the new name.One reason to do this is to discourage new usage of an API while continuing to support its implementation. For example:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/fuchsia.examples.docs/versioning.test.fidl" region_tag="discourage_use" %}
If the Door
server is implemented in a codebase that targets the version set {4, 5}, then the method will be named DeprecatedOpen
, discouraging developers from adding new uses of the method. If another codebase targets version 4 or below, then the method will be named Open
. If it targets version 5, the method will not appear at all.
Another reason to use this feature is to reuse a name for a new ABI. For example, consider changing the method Open
to return an error:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/fuchsia.examples.docs/versioning.test.fidl" region_tag="reuse_name" %}
We need to define a new method, since a client that doesn't expect an error will close the channel if it receives an error response. However, we can keep using the name Open
as long as we (1) use @selector
to give the new method a different ABI identity and (2) use renamed
on the old definition, allowing bindings for the version set {4, 5} to include both methods.
There are a variety of ways one FIDL element can reference another. For example:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/fuchsia.examples.docs/versioning.test.fidl" region_tag="references" %}
When referencing elements, you must respect the @available
attributes. For example, the following code is invalid because A
exists from version 1 onward, but it tries to reference B
which only exists at version 2:
// Does not compile! @available(added=1) const A bool = B; @available(added=2, removed=3) const B bool = true;
Similarly, it is invalid for a non-deprecated element to reference a deprecated element. For example, the following code is invalid at version 1 because A
references B
, but B
is deprecated while A
is not.
// Does not compile! @available(deprecated=2) const A bool = B; @available(deprecated=1) const B bool = true;