Tor Norbye | 3e42ef0 | 2018-11-14 13:47:28 -0800 | [diff] [blame] | 1 | # Signature Formats |
| 2 | |
| 3 | This document describes the signature file format created and used by metalava, |
| 4 | doclava, apicheck, etc. |
| 5 | |
| 6 | There are currently 3 versions of this format: |
| 7 | |
Paul Duffin | dcb37b4 | 2023-08-10 12:09:34 +0100 | [diff] [blame] | 8 | 1. ~~The format emitted by doclava, and used for Android's signature files up |
Tor Norbye | 3e42ef0 | 2018-11-14 13:47:28 -0800 | [diff] [blame] | 9 | through Android P. Note that this isn't actually a single format; it evolved |
| 10 | over time, so older signature files vary a bit (many of these changes were |
| 11 | due to bugs getting fixed, such as type parameters missing from classes |
| 12 | and methods until they start appearing), and some were deliberate changes, |
| 13 | such as dropping the "final" modifier in front of every member if the |
Paul Duffin | dcb37b4 | 2023-08-10 12:09:34 +0100 | [diff] [blame] | 14 | containing class is final.~~ |
| 15 | |
| 16 | <br/>**This version is deprecated and can no longer be specified as an output |
| 17 | format. It can still be consumed but there are no guarantees that it will |
| 18 | behave as expected.**<br/><br/> |
Tor Norbye | 9584e4f | 2020-06-16 10:55:33 -0700 | [diff] [blame] | 19 | |
Tor Norbye | 3e42ef0 | 2018-11-14 13:47:28 -0800 | [diff] [blame] | 20 | 2. The "new" format, which is described below, and is used in Android Q. This |
| 21 | format adds new information, such as annotations, parameter names and default |
| 22 | values, as well as cleans up a number of things (such as dropping |
| 23 | java.lang. prefixes on types, etc) |
Tor Norbye | 9584e4f | 2020-06-16 10:55:33 -0700 | [diff] [blame] | 24 | |
| 25 | 3. This is format v2, but with all nullness annotations replaced by a |
Tor Norbye | 3e42ef0 | 2018-11-14 13:47:28 -0800 | [diff] [blame] | 26 | Kotlin-syntax, e.g. "?" for nullable types, "!" for unknown/platform types, |
| 27 | and no suffix for non-nullable types. The initial plan was to include this |
| 28 | in format v2, but it was deferred since type-use annotations introduces |
Tor Norbye | 9584e4f | 2020-06-16 10:55:33 -0700 | [diff] [blame] | 29 | some complexities in the implementation. |
| 30 | |
Tor Norbye | 3e42ef0 | 2018-11-14 13:47:28 -0800 | [diff] [blame] | 31 | |
| 32 | ## Motivation |
| 33 | |
| 34 | Why did we change from the historical doclava signature format (v1) |
| 35 | to a new format? |
| 36 | |
| 37 | In order to support Kotlin better (though this will also benefit Java |
| 38 | developers), we'd like to have nullness annotations (as well as some other |
| 39 | annotations) be a formal part of the SDK. |
| 40 | |
| 41 | That means the annotations should be part of the signature files too -- such |
| 42 | that we can not just record explicitly what the API contract is, but also |
| 43 | enforce that changes are not only deliberate changes but also compatible |
| 44 | changes. (For example, you can change the return value of a final method from |
| 45 | nullable to non null, but not the other way around.) |
| 46 | |
| 47 | And if we were going to change the signature format, we might as well make some |
| 48 | other changes too. |
| 49 | |
| 50 | |
| 51 | ### Comments |
| 52 | |
| 53 | In v2, line comments (starting with //) are allowed. This allows us to leave |
| 54 | reminders and other issues with the signature source (though the update-api task |
| 55 | will generally blow these away, so use sparingly.) |
| 56 | |
| 57 | ### Header |
| 58 | |
| 59 | New signature files (v2+) generally include a file header comment which states |
| 60 | the version number. This makes it possible for tools to more safely interpret |
| 61 | signature files. For example, in v3 the type "String" means "@NonNull String", |
| 62 | but in v2 "String" means "String with unknown nullness". |
| 63 | |
| 64 | The header looks like this: |
| 65 | |
| 66 | ``` |
| 67 | // Signature format: 2.0 |
| 68 | ``` |
| 69 | |
| 70 | Here "2" is the major format version; the .0 allows for compatible minor |
| 71 | variations of the format. |
| 72 | |
| 73 | ### Include Annotations |
| 74 | |
| 75 | The new signature format now includes annotations; not all annotations (such as |
| 76 | @Override etc); only those which are significant for the API, such as nullness |
| 77 | annotations, etc. |
| 78 | |
| 79 | Annotations are included on the same line as the class/field/method, right |
| 80 | before the modifiers. |
| 81 | |
| 82 | Here's how this looks: |
| 83 | |
| 84 | |
| 85 | ``` |
| 86 | method @Nullable public static Integer compute1(@Nullable java.util.List<java.lang.String>); |
| 87 | ``` |
| 88 | |
| 89 | |
| 90 | (Notice how the annotations are not using fully qualified name; that's discussed |
| 91 | below.) |
| 92 | |
| 93 | The annotations to be included are annotations for annotation types that are not |
| 94 | hidden, and have class file or runtime retention. |
| 95 | |
| 96 | The annotations should be sorted alphabetically by fully qualified name. |
| 97 | |
| 98 | |
| 99 | ### Use Special Syntax or Nullness Annotations |
| 100 | |
| 101 | (Note: Only in version format 3+) |
| 102 | |
| 103 | As a special optimization, since we eventually want **all** APIs to have |
| 104 | explicit nullness, use Kotlin's syntax for nullness. That means that for |
| 105 | nullable elements, we add "?" after the type, for unknown nullness we add "!", |
| 106 | and otherwise there's no suffix. In other words: |
| 107 | |
| 108 | |
| 109 | <table> |
| 110 | <tr> |
| 111 | <td> |
| 112 | </td> |
| 113 | <td>Java Type |
| 114 | </td> |
| 115 | <td>Signature File Type |
| 116 | </td> |
| 117 | </tr> |
| 118 | <tr> |
| 119 | <td>Nullable |
| 120 | </td> |
| 121 | <td>@Nullable String |
| 122 | </td> |
| 123 | <td>String? |
| 124 | </td> |
| 125 | </tr> |
| 126 | <tr> |
| 127 | <td>Not nullable |
| 128 | </td> |
| 129 | <td>@NonNull String |
| 130 | </td> |
| 131 | <td>String |
| 132 | </td> |
| 133 | </tr> |
| 134 | <tr> |
| 135 | <td>Unknown nullability |
| 136 | </td> |
| 137 | <td>String |
| 138 | </td> |
| 139 | <td>String! |
| 140 | </td> |
| 141 | </tr> |
| 142 | </table> |
| 143 | |
| 144 | |
| 145 | The above signature line is turned into |
| 146 | |
| 147 | |
| 148 | ``` |
| 149 | method public Integer? compute1(java.util.List<java.lang.String!>?); |
| 150 | ``` |
| 151 | |
| 152 | |
Tor Norbye | 9584e4f | 2020-06-16 10:55:33 -0700 | [diff] [blame] | 153 | ### Clean Up Terminology |
Tor Norbye | 3e42ef0 | 2018-11-14 13:47:28 -0800 | [diff] [blame] | 154 | |
| 155 | Format v2 also cleans up some of the terminology used to describe the class |
| 156 | structure in the signature file. For example, in v1, an interface is called an |
| 157 | "abstract interface"; an interface extending another interface is said to |
| 158 | "implement" it instead of "extend"-ing it, etc; enums and annotations are just |
| 159 | referred to as classes that extend java.lang.Enum, or java.lang.Annotation etc. |
| 160 | |
| 161 | With these changes, these lines from v1 signature files: |
| 162 | |
| 163 | |
| 164 | ``` |
| 165 | public abstract interface List<E> implements java.util.Collection { ... } |
| 166 | public class TimeUnit extends java.lang.Enum { ... } |
| 167 | public abstract class SuppressLint implements java.lang.annotation.Annotation { ... } |
| 168 | ``` |
| 169 | |
| 170 | |
| 171 | are replaced by |
| 172 | |
| 173 | |
| 174 | ``` |
| 175 | public interface List<E> extends java.util.Collection<E> { ... } |
| 176 | public enum TimeUnit { ... } |
| 177 | public @interface SuppressLint { ... } |
| 178 | ``` |
| 179 | |
| 180 | |
| 181 | |
Tor Norbye | 9584e4f | 2020-06-16 10:55:33 -0700 | [diff] [blame] | 182 | ### Use Generics Everywhere |
Tor Norbye | 3e42ef0 | 2018-11-14 13:47:28 -0800 | [diff] [blame] | 183 | |
| 184 | The v1 signature files uses raw types in some places but not others. Note that |
| 185 | in the above it was missing from super interface Collection: |
| 186 | |
| 187 | |
| 188 | ``` |
| 189 | public abstract interface List<E> implements java.util.Collection { ... } |
| 190 | ``` |
| 191 | |
| 192 | |
| 193 | whereas in the v2 format it's included: |
| 194 | |
| 195 | |
| 196 | ``` |
| 197 | public interface List<E> extends java.util.Collection<E> { ... } |
| 198 | ``` |
| 199 | |
| 200 | |
| 201 | Similarly, v1 used erasure in throws clauses. For example, for this method: |
| 202 | |
| 203 | |
| 204 | ``` |
| 205 | public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X |
| 206 | ``` |
| 207 | |
| 208 | v1 used this signature: |
| 209 | |
| 210 | |
| 211 | ``` |
| 212 | method public <X extends java.lang.Throwable> T orElseThrow(java.util.function.Supplier<? extends X>) throws java.lang.Throwable; |
| 213 | ``` |
| 214 | |
| 215 | Note how that's "throws Throwable" instead of "throws X". This results in b/110302703. |
| 216 | |
| 217 | In the v2 format we instead use the correct throws type: |
| 218 | |
| 219 | ``` |
| 220 | method public <X extends java.lang.Throwable> T orElseThrow(java.util.function.Supplier<? extends X>) throws X; |
| 221 | ``` |
| 222 | |
| 223 | |
| 224 | ### Support Annotations |
| 225 | |
| 226 | The old format was completely missing annotation type methods: |
| 227 | |
| 228 | ``` |
| 229 | public static abstract class ViewDebug.ExportedProperty implements java.lang.annotation.Annotation { |
| 230 | } |
| 231 | ``` |
| 232 | |
| 233 | We need to include annotation member methods, as well as their default values |
| 234 | since those are API-significant. Here's how this looks in the v2 file format |
| 235 | (also applying the @interface terminology change described above) : |
| 236 | |
| 237 | |
| 238 | ``` |
| 239 | public static @interface ViewDebug.ExportedProperty { |
| 240 | method public abstract String category() default ""; |
| 241 | method public abstract boolean deepExport() default false; |
| 242 | method public abstract android.view.ViewDebug.FlagToString[] flagMapping() default {}; |
| 243 | method public abstract boolean formatToHexString() default false; |
| 244 | method public abstract boolean hasAdjacentMapping() default false; |
| 245 | method public abstract android.view.ViewDebug.IntToString[] indexMapping() default {}; |
| 246 | method public abstract android.view.ViewDebug.IntToString[] mapping() default {}; |
| 247 | method public abstract String prefix() default ""; |
| 248 | method public abstract boolean resolveId() default false; |
| 249 | } |
| 250 | ``` |
| 251 | |
| 252 | |
Tor Norbye | 9584e4f | 2020-06-16 10:55:33 -0700 | [diff] [blame] | 253 | ### Support Kotlin Modifiers |
Tor Norbye | 3e42ef0 | 2018-11-14 13:47:28 -0800 | [diff] [blame] | 254 | |
| 255 | This doesn't currently apply to the SDK, but the signature files are also used |
| 256 | in the support library, and some of these are written in Kotlin and exposes |
| 257 | Kotlin-specific APIs. |
| 258 | |
| 259 | That means the v2 format can express API-significant aspects of Kotlin. This |
| 260 | includes special modifiers, such as sealed, inline, operator, infix, etc: |
| 261 | |
| 262 | ``` |
| 263 | method public static operator int get(android.graphics.Bitmap, int x, int y); |
| 264 | method public static infix android.graphics.Rect and(android.graphics.Rect, android.graphics.Rect r); |
| 265 | ``` |
| 266 | |
Tor Norbye | 9584e4f | 2020-06-16 10:55:33 -0700 | [diff] [blame] | 267 | ### Support Kotlin Properties |
Tor Norbye | 3e42ef0 | 2018-11-14 13:47:28 -0800 | [diff] [blame] | 268 | |
| 269 | Kotlin's Java support means that it wil take a Kotlin property and compile it |
| 270 | into getters and setters which you can call from Java. But you cannot calls |
| 271 | these getters and setters from Kotlin; you **must** use the property |
| 272 | syntax. Therefore, we need to also capture properties in the signature files. If |
| 273 | you have this Kotlin code: |
| 274 | |
| 275 | |
| 276 | ``` |
| 277 | var property2: String? = "initial" |
| 278 | ``` |
| 279 | |
| 280 | it will get recorded in the signature files like this: |
| 281 | |
| 282 | ``` |
| 283 | property public java.lang.String? property2 = "initial"; |
| 284 | method public java.lang.String? getProperty2(); |
| 285 | method public void setProperty2(java.lang.String? p); |
| 286 | ``` |
| 287 | |
| 288 | The last two elements are "redundant"; they could be computed from the property |
| 289 | name (and included if the property declaration uses special annotations to name |
| 290 | the getters and setters away from the defaults), but it's helpful to be explicit |
| 291 | (and this allows us to specify the default value). |
| 292 | |
Tor Norbye | 9584e4f | 2020-06-16 10:55:33 -0700 | [diff] [blame] | 293 | ### Support Named Parameters |
Tor Norbye | 3e42ef0 | 2018-11-14 13:47:28 -0800 | [diff] [blame] | 294 | |
| 295 | Kotlin supports default values for parameters, and these are a part of the API |
| 296 | contract, so we need to include them in the signature format. |
| 297 | |
| 298 | Here's an example: |
| 299 | |
| 300 | ``` |
| 301 | method public static void edit(android.content.SharedPreferences, boolean commit); |
| 302 | ``` |
| 303 | |
| 304 | In v1 files we only list type names, but in v2 we allow an optional parameter |
| 305 | name to be specified; "commit" in the above. |
| 306 | |
| 307 | Note that this isn't just for Kotlin. Just like there are special nullness |
| 308 | annotations to mark up the null contract for an element, we will also have a |
| 309 | special annotation to explicitly name a Java parameter: |
| 310 | @android.annotation.ParameterName (which is hidden). This obviously isn't usable |
| 311 | from Java, but Kotlin client code can now reference the parameter. |
| 312 | |
| 313 | Therefore, the following Java code (not signature code) will also produce |
| 314 | exactly the same signature as the above: |
| 315 | |
| 316 | ``` |
| 317 | public static void edit(SharedPreferences prefs, @ParameterName("commit") boolean ct) {…} |
| 318 | ``` |
| 319 | |
| 320 | (Note how the implementation parameter doesn't have to match the public, API |
| 321 | name of the parameter.) |
| 322 | |
Tor Norbye | 9584e4f | 2020-06-16 10:55:33 -0700 | [diff] [blame] | 323 | ### Support Default Values |
Tor Norbye | 3e42ef0 | 2018-11-14 13:47:28 -0800 | [diff] [blame] | 324 | |
| 325 | In addition to named parameters, Kotlin also supports default values. These are |
| 326 | also be part of the v2 signature since (as an example) removing a default value |
| 327 | is a compile-incompatible change. |
| 328 | |
| 329 | Therefore, the v2 format allows default values to be specified after the type |
| 330 | and/or parameter name: |
| 331 | |
| 332 | ``` |
| 333 | method public static void edit(SharedPreferences, boolean commit = false); |
| 334 | ``` |
| 335 | |
| 336 | For Kotlin code, the default parameter values are extracted automatically, and |
| 337 | for Java, just as with parameter names, you can specify a special annotation to |
| 338 | record the default value for usage from languages that support default parameter |
| 339 | values: |
| 340 | |
| 341 | ``` |
| 342 | public static void edit(SharedPreferences prefs, @DefaultValue("false") boolean ct) {…} |
| 343 | ``` |
| 344 | |
| 345 | |
| 346 | ### Include Inherited Methods |
| 347 | |
| 348 | Consider a scenario where a public class extends a hidden class, and that hidden |
| 349 | class defines a public method. |
| 350 | |
| 351 | Doclava did not include these methods in the signature files, but they **were** |
| 352 | present in the stub files (and therefore part of the API). In the v2 signature |
| 353 | file format, we include these. |
| 354 | |
| 355 | An example of this is StringBuilder#setLength. According to the old signature |
| 356 | files, that method does not exist, but clearly it's there in the SDK. The reason |
| 357 | this happens is that StringBuilder is a public class which extends hidden class |
| 358 | AbstractStringBuilder, which defines the public method setLength. |
| 359 | |
| 360 | |
| 361 | ### No Hardcoded Enum Methods |
| 362 | |
| 363 | Doclava always inserted two special methods in the signature files for every |
| 364 | enum: values() and valueOf(): |
| 365 | |
| 366 | ``` |
| 367 | public static final class CursorJoiner.Result extends java.lang.Enum { |
| 368 | method public static android.database.CursorJoiner.Result valueOf(java.lang.String); |
| 369 | method public static final android.database.CursorJoiner.Result[] values(); |
| 370 | enum_constant public static final android.database.CursorJoiner.Result BOTH; |
| 371 | enum_constant public static final android.database.CursorJoiner.Result LEFT; |
| 372 | enum_constant public static final android.database.CursorJoiner.Result RIGHT; |
| 373 | } |
| 374 | ``` |
| 375 | |
| 376 | It didn't do that in stubs, because you can't: those are special methods |
| 377 | generated by the compiler. There's no reason to list these in the signature |
| 378 | files since they're entirely implied by the enum, you can't change them, and |
| 379 | it's just extra noise. |
| 380 | |
| 381 | In the new v2 format these are no longer present: |
| 382 | |
| 383 | ``` |
| 384 | public static enum CursorJoiner.Result { |
| 385 | enum_constant public static final android.database.CursorJoiner.Result BOTH; |
| 386 | enum_constant public static final android.database.CursorJoiner.Result LEFT; |
| 387 | enum_constant public static final android.database.CursorJoiner.Result RIGHT; |
| 388 | } |
| 389 | ``` |
| 390 | |
| 391 | ### Remove "deprecated" Modifier |
| 392 | |
| 393 | The old signature file format used "deprecated" as if it was a modifier. In the |
| 394 | new format, we instead list these using annotations, @Deprecated. |
| 395 | |
| 396 | ### Standard Modifier Order |
| 397 | |
| 398 | Doclava had a "random" (but stable) order of modifiers. |
| 399 | |
| 400 | In the new signature format, we're using the standard modifier order for Java |
| 401 | and Kotlin, wihch more closely mirrors what is done in the source code. |
| 402 | |
| 403 | Version format 1 order: |
| 404 | |
| 405 | ``` |
| 406 | public/protected/private default static final abstract synchronized transient volatile |
| 407 | ``` |
| 408 | |
| 409 | Version format 2 order: |
| 410 | |
| 411 | ``` |
| 412 | public/protected/internal/private abstract default static final transient volatile synchronized |
| 413 | ``` |
| 414 | |
| 415 | The above list doesn't include the Kotlin modifiers, which are inserted |
| 416 | according to the Kotlin language style guide: |
| 417 | https://kotlinlang.org/docs/reference/coding-conventions.html#modifiers |
| 418 | |
| 419 | ### Sort Classes By Fully Qualified Names |
| 420 | |
| 421 | In "extends" lists, the signature file can list a comma separated list of |
| 422 | classes. The classes are listed by fully qualified name, but in v1 it was sorted |
| 423 | by simple name. In the v2 format, we sort by fully qualified name instead. |
| 424 | |
| 425 | ### Use Wildcards Consistently |
| 426 | |
| 427 | Doclava (v1) would sometimes use the type bound <?> and other times use <? |
| 428 | extends Object>. These are equivalent. In the v2 format, <? extends Object> is |
| 429 | always written as <?>. |
| 430 | |
| 431 | ### Annotation Simple Names |
| 432 | |
| 433 | We have a number of annotations which are significant for the API -- not just |
| 434 | the nullness as deprecation ones (which are specially supported in v3 via the |
| 435 | ?/! Kotlin syntax and the deprecated "modifier"), but annotations for permission |
| 436 | requirements, range constraints, valid constant values for an integer, and so |
| 437 | on. |
| 438 | |
| 439 | In the codebase, these are typically in the android.annotation. package, |
| 440 | referencing annotation classes that are generally **not** part of the API. When |
| 441 | we generate the SDK, we translate these into publicly known annotations, |
| 442 | androidx.annotation, such that Studio, lint, the Kotlin compiler and others can |
| 443 | recognize the metadata. |
| 444 | |
| 445 | That begs the question: which fully qualified name should we put in the |
| 446 | signature file? The one that appeared in the source (which is hidden, or in the |
| 447 | case of Kotlin code, a special JetBrains nullness annotation), or the one that |
| 448 | it gets translated into? |
| 449 | |
| 450 | In v2 we do neither: We use only the simple name of the annotations in the |
| 451 | signature file, for annotations that are in the well known packages. In other |
| 452 | words, instead of any of these alternative declarations: |
| 453 | |
| 454 | ``` |
| 455 | method public void setTitleTextColor(@android.annotation.ColorInt int); |
Tor Norbye | 3e42ef0 | 2018-11-14 13:47:28 -0800 | [diff] [blame] | 456 | method public void setTitleTextColor(@androidx.annotation.ColorInt int); |
| 457 | ``` |
| 458 | |
Tor Norbye | 9584e4f | 2020-06-16 10:55:33 -0700 | [diff] [blame] | 459 | in v2 we have simply |
Tor Norbye | 3e42ef0 | 2018-11-14 13:47:28 -0800 | [diff] [blame] | 460 | |
| 461 | ``` |
| 462 | method public void setTitleTextColor(@ColorInt int); |
| 463 | ``` |
| 464 | |
| 465 | ### Simple Names in Java.lang |
| 466 | |
| 467 | In Java files, you can implicitly reference classes in java.lang without |
| 468 | importing them. In v2 offer the same thing in signature files. There are several |
| 469 | classes from java.lang that are used in lots of places in the signature file |
| 470 | (java.lang.String alone is present in over 11,000 lines of the API file), and |
| 471 | other common occurrences are java.lang.Class, java.lang.Integer, |
| 472 | java.lang.Runtime, etc. |
| 473 | |
| 474 | This basically builds on the same idea from having an implicit package for |
| 475 | annotations, and doing the same thing for java.lang: Omitting it when writing |
| 476 | signature files, and implicitly adding it back when reading in signature files. |
| 477 | |
| 478 | This only applies to the java.lang package, not any subpackages, so for example |
| 479 | java.lang.reflect.Method will **not** be shortened to reflect.Method. |
| 480 | |
| 481 | ### Type Use Annotations |
| 482 | |
| 483 | In v3, "type use annotations" are supported which means annotations can appear |
| 484 | within types. |
| 485 | |
Tor Norbye | 942042f | 2019-01-02 10:02:56 -0800 | [diff] [blame] | 486 | ### Skipping some signatures |
| 487 | |
| 488 | If a method overrides another method, and the signatures are the same, the |
| 489 | overriding method is left out of the signature file. This basically compares the |
| 490 | modifiers, ignoring some that are not API significant (such as "native"). Note |
| 491 | also that some modifiers are implicit; for example, if a method is implementing |
| 492 | a method from an interface, the interface method is implicitly abstract, so the |
| 493 | implementation will be included in the signature file. |
| 494 | |
| 495 | In v2, we take this one step further: If a method differs **only** from its |
| 496 | overridden method by "final", **and** if the containing class is final, then the |
| 497 | method is not included in the signature file. The same is the case for |
| 498 | deprecated. |
| 499 | |
Tor Norbye | 3e42ef0 | 2018-11-14 13:47:28 -0800 | [diff] [blame] | 500 | ### Miscellaneous |
| 501 | |
| 502 | Some other minor tweaks in v2: |
| 503 | |
| 504 | * Fix formatting for package private elements. These had two spaces of |
| 505 | indentation; this is probably just a bug. The new format aligns their |
| 506 | indentation with all other elements. |
| 507 | * Don't add spaces in type bounds lists (e.g. Map<X,Y>, not Map<X, Y>.) |
| 508 | |
| 509 | ## Historical API Files |
| 510 | |
| 511 | Metalava can read and write these formats. To switch output formats, invoke it |
| 512 | with for example --format=v2. |
| 513 | |
| 514 | The Android source tree also has checked in versions of the signatures for all |
| 515 | the previous API levels. Metalava can regenerate these for a new format. |
| 516 | For example, to update all the signature files to v3, run this command: |
| 517 | |
| 518 | ``` |
Paul Duffin | 36d865f | 2023-06-28 11:21:04 +0100 | [diff] [blame] | 519 | $ metalava android-jars-to-signatures *<android source dir>* --format=v3 |
Tor Norbye | 3e42ef0 | 2018-11-14 13:47:28 -0800 | [diff] [blame] | 520 | ``` |