You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

518 lines
17 KiB

# Signature Formats
This document describes the signature file format created and used by metalava,
doclava, apicheck, etc.
There are currently 3 versions of this format:
1. The format emitted by doclava, and used for Android's signature files up
through Android P. Note that this isn't actually a single format; it evolved
over time, so older signature files vary a bit (many of these changes were
due to bugs getting fixed, such as type parameters missing from classes
and methods until they start appearing), and some were deliberate changes,
such as dropping the "final" modifier in front of every member if the
containing class is final.
2. The "new" format, which is described below, and is used in Android Q. This
format adds new information, such as annotations, parameter names and default
values, as well as cleans up a number of things (such as dropping
java.lang. prefixes on types, etc)
3. This is format v2, but with all nullness annotations replaced by a
Kotlin-syntax, e.g. "?" for nullable types, "!" for unknown/platform types,
and no suffix for non-nullable types. The initial plan was to include this
in format v2, but it was deferred since type-use annotations introduces
some complexities in the implementation.
## Motivation
Why did we change from the historical doclava signature format (v1)
to a new format?
In order to support Kotlin better (though this will also benefit Java
developers), we'd like to have nullness annotations (as well as some other
annotations) be a formal part of the SDK.
That means the annotations should be part of the signature files too -- such
that we can not just record explicitly what the API contract is, but also
enforce that changes are not only deliberate changes but also compatible
changes. (For example, you can change the return value of a final method from
nullable to non null, but not the other way around.)
And if we were going to change the signature format, we might as well make some
other changes too.
### Comments
In v2, line comments (starting with //) are allowed. This allows us to leave
reminders and other issues with the signature source (though the update-api task
will generally blow these away, so use sparingly.)
### Header
New signature files (v2+) generally include a file header comment which states
the version number. This makes it possible for tools to more safely interpret
signature files. For example, in v3 the type "String" means "@NonNull String",
but in v2 "String" means "String with unknown nullness".
The header looks like this:
```
// Signature format: 2.0
```
Here "2" is the major format version; the .0 allows for compatible minor
variations of the format.
### Include Annotations
The new signature format now includes annotations; not all annotations (such as
@Override etc); only those which are significant for the API, such as nullness
annotations, etc.
Annotations are included on the same line as the class/field/method, right
before the modifiers.
Here's how this looks:
```
method @Nullable public static Integer compute1(@Nullable java.util.List<java.lang.String>);
```
(Notice how the annotations are not using fully qualified name; that's discussed
below.)
The annotations to be included are annotations for annotation types that are not
hidden, and have class file or runtime retention.
The annotations should be sorted alphabetically by fully qualified name.
### Use Special Syntax or Nullness Annotations
(Note: Only in version format 3+)
As a special optimization, since we eventually want **all** APIs to have
explicit nullness, use Kotlin's syntax for nullness. That means that for
nullable elements, we add "?" after the type, for unknown nullness we add "!",
and otherwise there's no suffix. In other words:
<table>
<tr>
<td>
</td>
<td>Java Type
</td>
<td>Signature File Type
</td>
</tr>
<tr>
<td>Nullable
</td>
<td>@Nullable String
</td>
<td>String?
</td>
</tr>
<tr>
<td>Not nullable
</td>
<td>@NonNull String
</td>
<td>String
</td>
</tr>
<tr>
<td>Unknown nullability
</td>
<td>String
</td>
<td>String!
</td>
</tr>
</table>
The above signature line is turned into
```
method public Integer? compute1(java.util.List<java.lang.String!>?);
```
### Clean Up Terminology
Format v2 also cleans up some of the terminology used to describe the class
structure in the signature file. For example, in v1, an interface is called an
"abstract interface"; an interface extending another interface is said to
"implement" it instead of "extend"-ing it, etc; enums and annotations are just
referred to as classes that extend java.lang.Enum, or java.lang.Annotation etc.
With these changes, these lines from v1 signature files:
```
public abstract interface List<E> implements java.util.Collection { ... }
public class TimeUnit extends java.lang.Enum { ... }
public abstract class SuppressLint implements java.lang.annotation.Annotation { ... }
```
are replaced by
```
public interface List<E> extends java.util.Collection<E> { ... }
public enum TimeUnit { ... }
public @interface SuppressLint { ... }
```
### Use Generics Everywhere
The v1 signature files uses raw types in some places but not others. Note that
in the above it was missing from super interface Collection:
```
public abstract interface List<E> implements java.util.Collection { ... }
```
whereas in the v2 format it's included:
```
public interface List<E> extends java.util.Collection<E> { ... }
```
Similarly, v1 used erasure in throws clauses. For example, for this method:
```
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X
```
v1 used this signature:
```
method public <X extends java.lang.Throwable> T orElseThrow(java.util.function.Supplier<? extends X>) throws java.lang.Throwable;
```
Note how that's "throws Throwable" instead of "throws X". This results in b/110302703.
In the v2 format we instead use the correct throws type:
```
method public <X extends java.lang.Throwable> T orElseThrow(java.util.function.Supplier<? extends X>) throws X;
```
### Support Annotations
The old format was completely missing annotation type methods:
```
public static abstract class ViewDebug.ExportedProperty implements java.lang.annotation.Annotation {
}
```
We need to include annotation member methods, as well as their default values
since those are API-significant. Here's how this looks in the v2 file format
(also applying the @interface terminology change described above) :
```
public static @interface ViewDebug.ExportedProperty {
method public abstract String category() default "";
method public abstract boolean deepExport() default false;
method public abstract android.view.ViewDebug.FlagToString[] flagMapping() default {};
method public abstract boolean formatToHexString() default false;
method public abstract boolean hasAdjacentMapping() default false;
method public abstract android.view.ViewDebug.IntToString[] indexMapping() default {};
method public abstract android.view.ViewDebug.IntToString[] mapping() default {};
method public abstract String prefix() default "";
method public abstract boolean resolveId() default false;
}
```
### Support Kotlin Modifiers
This doesn't currently apply to the SDK, but the signature files are also used
in the support library, and some of these are written in Kotlin and exposes
Kotlin-specific APIs.
That means the v2 format can express API-significant aspects of Kotlin. This
includes special modifiers, such as sealed, inline, operator, infix, etc:
```
method public static operator int get(android.graphics.Bitmap, int x, int y);
method public static infix android.graphics.Rect and(android.graphics.Rect, android.graphics.Rect r);
```
### Support Kotlin Properties
Kotlin's Java support means that it wil take a Kotlin property and compile it
into getters and setters which you can call from Java. But you cannot calls
these getters and setters from Kotlin; you **must** use the property
syntax. Therefore, we need to also capture properties in the signature files. If
you have this Kotlin code:
```
var property2: String? = "initial"
```
it will get recorded in the signature files like this:
```
property public java.lang.String? property2 = "initial";
method public java.lang.String? getProperty2();
method public void setProperty2(java.lang.String? p);
```
The last two elements are "redundant"; they could be computed from the property
name (and included if the property declaration uses special annotations to name
the getters and setters away from the defaults), but it's helpful to be explicit
(and this allows us to specify the default value).
### Support Named Parameters
Kotlin supports default values for parameters, and these are a part of the API
contract, so we need to include them in the signature format.
Here's an example:
```
method public static void edit(android.content.SharedPreferences, boolean commit);
```
In v1 files we only list type names, but in v2 we allow an optional parameter
name to be specified; "commit" in the above.
Note that this isn't just for Kotlin. Just like there are special nullness
annotations to mark up the null contract for an element, we will also have a
special annotation to explicitly name a Java parameter:
@android.annotation.ParameterName (which is hidden). This obviously isn't usable
from Java, but Kotlin client code can now reference the parameter.
Therefore, the following Java code (not signature code) will also produce
exactly the same signature as the above:
```
public static void edit(SharedPreferences prefs, @ParameterName("commit") boolean ct) {…}
```
(Note how the implementation parameter doesn't have to match the public, API
name of the parameter.)
### Support Default Values
In addition to named parameters, Kotlin also supports default values. These are
also be part of the v2 signature since (as an example) removing a default value
is a compile-incompatible change.
Therefore, the v2 format allows default values to be specified after the type
and/or parameter name:
```
method public static void edit(SharedPreferences, boolean commit = false);
```
For Kotlin code, the default parameter values are extracted automatically, and
for Java, just as with parameter names, you can specify a special annotation to
record the default value for usage from languages that support default parameter
values:
```
public static void edit(SharedPreferences prefs, @DefaultValue("false") boolean ct) {…}
```
### Include Inherited Methods
Consider a scenario where a public class extends a hidden class, and that hidden
class defines a public method.
Doclava did not include these methods in the signature files, but they **were**
present in the stub files (and therefore part of the API). In the v2 signature
file format, we include these.
An example of this is StringBuilder#setLength. According to the old signature
files, that method does not exist, but clearly it's there in the SDK. The reason
this happens is that StringBuilder is a public class which extends hidden class
AbstractStringBuilder, which defines the public method setLength.
### No Hardcoded Enum Methods
Doclava always inserted two special methods in the signature files for every
enum: values() and valueOf():
```
public static final class CursorJoiner.Result extends java.lang.Enum {
method public static android.database.CursorJoiner.Result valueOf(java.lang.String);
method public static final android.database.CursorJoiner.Result[] values();
enum_constant public static final android.database.CursorJoiner.Result BOTH;
enum_constant public static final android.database.CursorJoiner.Result LEFT;
enum_constant public static final android.database.CursorJoiner.Result RIGHT;
}
```
It didn't do that in stubs, because you can't: those are special methods
generated by the compiler. There's no reason to list these in the signature
files since they're entirely implied by the enum, you can't change them, and
it's just extra noise.
In the new v2 format these are no longer present:
```
public static enum CursorJoiner.Result {
enum_constant public static final android.database.CursorJoiner.Result BOTH;
enum_constant public static final android.database.CursorJoiner.Result LEFT;
enum_constant public static final android.database.CursorJoiner.Result RIGHT;
}
```
### Remove "deprecated" Modifier
The old signature file format used "deprecated" as if it was a modifier. In the
new format, we instead list these using annotations, @Deprecated.
### Standard Modifier Order
Doclava had a "random" (but stable) order of modifiers.
In the new signature format, we're using the standard modifier order for Java
and Kotlin, wihch more closely mirrors what is done in the source code.
Version format 1 order:
```
public/protected/private default static final abstract synchronized transient volatile
```
Version format 2 order:
```
public/protected/internal/private abstract default static final transient volatile synchronized
```
The above list doesn't include the Kotlin modifiers, which are inserted
according to the Kotlin language style guide:
https://kotlinlang.org/docs/reference/coding-conventions.html#modifiers
### Sort Classes By Fully Qualified Names
In "extends" lists, the signature file can list a comma separated list of
classes. The classes are listed by fully qualified name, but in v1 it was sorted
by simple name. In the v2 format, we sort by fully qualified name instead.
### Use Wildcards Consistently
Doclava (v1) would sometimes use the type bound <?> and other times use <?
extends Object>. These are equivalent. In the v2 format, <? extends Object> is
always written as <?>.
### Annotation Simple Names
We have a number of annotations which are significant for the API -- not just
the nullness as deprecation ones (which are specially supported in v3 via the
?/! Kotlin syntax and the deprecated "modifier"), but annotations for permission
requirements, range constraints, valid constant values for an integer, and so
on.
In the codebase, these are typically in the android.annotation. package,
referencing annotation classes that are generally **not** part of the API. When
we generate the SDK, we translate these into publicly known annotations,
androidx.annotation, such that Studio, lint, the Kotlin compiler and others can
recognize the metadata.
That begs the question: which fully qualified name should we put in the
signature file? The one that appeared in the source (which is hidden, or in the
case of Kotlin code, a special JetBrains nullness annotation), or the one that
it gets translated into?
In v2 we do neither: We use only the simple name of the annotations in the
signature file, for annotations that are in the well known packages. In other
words, instead of any of these alternative declarations:
```
method public void setTitleTextColor(@android.annotation.ColorInt int);
method public void setTitleTextColor(@android.support.annotation.ColorInt int);
method public void setTitleTextColor(@androidx.annotation.ColorInt int);
```
in v2 we have simply
```
method public void setTitleTextColor(@ColorInt int);
```
### Simple Names in Java.lang
In Java files, you can implicitly reference classes in java.lang without
importing them. In v2 offer the same thing in signature files. There are several
classes from java.lang that are used in lots of places in the signature file
(java.lang.String alone is present in over 11,000 lines of the API file), and
other common occurrences are java.lang.Class, java.lang.Integer,
java.lang.Runtime, etc.
This basically builds on the same idea from having an implicit package for
annotations, and doing the same thing for java.lang: Omitting it when writing
signature files, and implicitly adding it back when reading in signature files.
This only applies to the java.lang package, not any subpackages, so for example
java.lang.reflect.Method will **not** be shortened to reflect.Method.
### Type Use Annotations
In v3, "type use annotations" are supported which means annotations can appear
within types.
### Skipping some signatures
If a method overrides another method, and the signatures are the same, the
overriding method is left out of the signature file. This basically compares the
modifiers, ignoring some that are not API significant (such as "native"). Note
also that some modifiers are implicit; for example, if a method is implementing
a method from an interface, the interface method is implicitly abstract, so the
implementation will be included in the signature file.
In v2, we take this one step further: If a method differs **only** from its
overridden method by "final", **and** if the containing class is final, then the
method is not included in the signature file. The same is the case for
deprecated.
### Miscellaneous
Some other minor tweaks in v2:
* Fix formatting for package private elements. These had two spaces of
indentation; this is probably just a bug. The new format aligns their
indentation with all other elements.
* Don't add spaces in type bounds lists (e.g. Map<X,Y>, not Map<X, Y>.)
## Historical API Files
Metalava can read and write these formats. To switch output formats, invoke it
with for example --format=v2.
The Android source tree also has checked in versions of the signatures for all
the previous API levels. Metalava can regenerate these for a new format.
For example, to update all the signature files to v3, run this command:
```
$ metalava --write-android-jar-signatures *<android source dir>* --format=v3
```