New kotlinx-metadata-jvm release

Update changelog for kotlinx-metadata-jvm 0.6.0, add migration guide, and actualize ReadMe.md

Document releasing process

Co-authored-by: Alexander Udalov <Alexander.Udalov@jetbrains.com>

Merge-request: KT-MR-8206
Merged-by: Leonid Startsev <leonid.startsev@jetbrains.com>
This commit is contained in:
Leonid Startsev
2023-01-10 12:25:38 +00:00
committed by Space Team
parent 2dc13506f5
commit d646906437
5 changed files with 158 additions and 18 deletions
@@ -1,5 +1,15 @@
# kotlinx-metadata-jvm
## 0.6.0
This release features several significant API changes. To help with migration, we've prepared a special [guide](Migration.md).
- Update to Kotlin 1.8 with metadata version 1.8, support reading/writing metadata of version 1.9 which will be used in Kotlin 1.9
- Deprecate Visitors API
- Replace usages of `KotlinClassHeader` with direct usage of `kotlin.Metadata` annotation. Former reserved exclusively for use from Java clients.
- `impl` package renamed to `internal`
- Writers are deprecated. Special function family `KotlinClassMetadata.write` is introduced instead.
## 0.5.0
- Update to Kotlin 1.7 with metadata version 1.7, support reading/writing metadata of version 1.8 which will be used in Kotlin 1.8.
@@ -0,0 +1,94 @@
# Kotlinx-metadata migration guide
Starting with 0.6.0 release, Kotlin team is focused on revisiting and improving kotlinx-metadata API, with an aim to provide a stable release
in the near future. As a result, the API was reshaped, with cuts here and there, so we've provided a migration guide to help you with updates.
## Migrating from 0.5.0 to 0.6.0+
There are several significant changes between 0.5.0 and 0.6.0:
### Visitors are deprecated
There are two major APIs that allow introspecting Kotlin metadata: Visitor API (`KmClassVisitor`, `KmFunctionVisitor`, etc) and Nodes API
(`KmClass`, `KmFunction`, etc). After careful consideration, we've decided to deprecate Visitor API completely.
It is a more verbose and hard-to-use API that does not have any significant advantages. Everything that can be done using visitors can also be achieved with Nodes, usually with shorter and more idiomatic code.
As these APIs represent different paradigms, migration can't be automated and requires some manual work. In short, replace every `visitXxx` with access to corresponding property, e.g.
`KmClassVisitor.visitFunction(flags, name)` with `KmClass.functions`:
**Before:**
```kotlin
/**
* Visitor that gets names of all public functions which start with 'test'
*/
class TestFunctionFinder : KmClassVisitor() {
val result = mutableListOf<String>()
override fun visitFunction(flags: Flags, name: String): KmFunctionVisitor? {
if (Flag.Common.IS_PUBLIC(flags) && name.startsWith("test")) result.add(name)
return null
}
}
// Helper function to invoke visitor
fun KmClass.testFunctions(): List<String> = TestFunctionFinder().also { this.accept(it) }.result.toList()
```
**After:**
```kotlin
/**
* Extension function that gets names of all public functions which start with 'test'
*/
fun KmClass.testFunctions(): List<String> = this.functions.mapNotNull { f ->
if (Flag.Common.IS_PUBLIC(f.flags) && f.name.startsWith("test")) f.name else null
}
```
As a result, complexity and line count are significantly reduced.
For a more sophisticated example, take a look at [this commit](https://github.com/JetBrains/kotlin/commit/c4d409608cf6b246a24f095bc7b30ff8d2efc373) that refactors an internal utility `kotlinp` which renders Kotlin metadata to text.
Note that for now, Km nodes still implement visitors (e.g. `KmClass` implements `KmClassVisitor`). This relation will be removed in the future.
### Metadata annotation class can be used directly
Parameter type of `KotlinClassMetadata.read` function was changed to `kotlin.Metadata` from `kotlinx.metadata.jvm.KotlinClassHeader`.
It allows writing less boilerplate code as there's no need to copy parameters `d1`, `d2`, etc.
If you have obtained `Metadata` instance reflectively, you
can use it right away. In case you are reading binary metadata, you can create `Metadata` instance using
a [helper function](https://github.com/JetBrains/kotlin/blob/3d679b76bce04a9bfbb7c0a2f769d5838d2c3bf9/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/jvmMetadataUtil.kt#L27)
or by [directly calling the annotation constructor](https://kotlinlang.org/docs/annotations.html#instantiation).
> Note: as annotation instantiation is not available for Java clients, `KotlinClassHeader` is still present and reserved for construction from Java.
It implements `kotlin.Metadata` annotation interface, so they can be used interchangeably.
Additionally, the property `KotlinClassMetadata.header: KotlinClassHeader` was changed into `KotlinClassMetadata.annotationData: Metadata`, so it is
also possible to use `Metadata` directly to write metadata back (see example in the section below).
### Writers are streamlined
To ease metadata manipulation, writing API was simplified. `KotlinClassMetadata.Class.Writer()` and other writers are deprecated;
Appropriate writer functions (e.g. `KotlinClassMetadata.writeClass`) should be used instead.
To migrate, simply replace calls to writers with new functions:
**Before:**
```kotlin
fun saveClass(kmClass: KmClass) {
val writer = KotlinClassMetadata.Class.Writer()
kmClass.accept(writer)
val classMetadata: KotlinClassMetadata.Class = writer.write()
val kotlinClassHeader: KotlinClassHeader = classMetadata.header
// Write kotlinClassHeader.data1, data2, etc using ASM
}
```
**After:**
```kotlin
fun saveClass(kmClass: KmClass) {
val classMetadata: KotlinClassMetadata.Class = KotlinClassMetadata.writeClass(kmClass)
val metadata: Metadata = classMetadata.annotationData
// Write Metadata.data1, data2, etc using ASM
}
```
+27 -17
View File
@@ -35,17 +35,20 @@ dependencies {
## Overview
The entry point for reading the Kotlin metadata of a `.class` file is [`KotlinClassMetadata.read`](src/kotlinx/metadata/jvm/KotlinClassMetadata.kt). The data it takes is encapsulated in [`KotlinClassHeader`](src/kotlinx/metadata/jvm/KotlinClassHeader.kt) which is basically what is written in the [`kotlin.Metadata`](../../stdlib/jvm/runtime/kotlin/Metadata.kt) annotation on the class file generated by the Kotlin compiler. Construct `KotlinClassHeader` by reading the values from `kotlin.Metadata` reflectively or from some other resource, and then use `KotlinClassMetadata.read` to obtain the correct instance of the class metadata.
The entry point for reading the Kotlin metadata of a `.class` file is [`KotlinClassMetadata.read`](src/kotlinx/metadata/jvm/KotlinClassMetadata.kt).
The data it takes is the [`kotlin.Metadata`](../../stdlib/jvm/runtime/kotlin/Metadata.kt) annotation on the class file generated by the Kotlin compiler.
Obtain the `kotlin.Metadata` annotation reflectively or construct it from binary representation (e.g. by reading classfile with `org.objectweb.asm.ClassReader`),
and then use `KotlinClassMetadata.read` to obtain the correct instance of the class metadata.
```kotlin
val header = KotlinClassHeader(
...
/* pass Metadata.k, Metadata.d1, Metadata.d2, etc as arguments ... */
val metadataAnnotation = Metadata(
// pass arguments here
)
val metadata = KotlinClassMetadata.read(header)
val metadata = KotlinClassMetadata.read(metadataAnnotation)
```
`KotlinClassMetadata` is a sealed class, with subclasses representing all the different kinds of classes generated by the Kotlin compiler. Unless you're sure that you're reading a class of a specific kind and can do a simple cast, a `when` is a good choice to handle all the possibilities:
`KotlinClassMetadata` is a sealed class, with subclasses representing all the different kinds of classes generated by the Kotlin compiler.
Unless you're sure that you're reading a class of a specific kind and can do a simple cast, a `when` is a good choice to handle all the possibilities:
```kotlin
when (metadata) {
@@ -58,7 +61,8 @@ when (metadata) {
}
```
Let's assume we've obtained an instance of `KotlinClassMetadata.Class`; other kinds of classes are handled similarly, except some of them have metadata in a slightly different form. The main way to make sense of the underlying metadata is to invoke `toKmClass`, which returns an instance of `KmClass` (`Km` is a shorthand for “Kotlin metadata”):
Let's assume we've obtained an instance of `KotlinClassMetadata.Class`; other kinds of classes are handled similarly, except some of them have metadata in a slightly different form.
The main way to make sense of the underlying metadata is to invoke `toKmClass()`, which returns an instance of `KmClass` (`Km` is a shorthand for “Kotlin metadata”):
```kotlin
val klass = metadata.toKmClass()
@@ -70,7 +74,10 @@ Please refer to [`MetadataSmokeTest.listInlineFunctions`](test/kotlinx/metadata/
## Flags
Numerous objects have a property named `flags` of type `Flags`. These flags represent modifiers or other boolean attributes of a declaration or a type. To check if a certain flag is present, call one of the flags in [`Flag`](../src/kotlinx/metadata/Flag.kt) on the given integer value. The set of applicable flags is documented on each property or the corresponding `visit*` method. For example, for functions, this is common declaration flags (visibility, modality) plus `Flag.Function` flags:
Numerous objects have a property named `flags` of type `Flags`. These flags represent modifiers or other boolean attributes of a declaration or a type.
To check if a certain flag is present, call one of the flags in [`Flag`](../src/kotlinx/metadata/Flag.kt) on the given integer value.
The set of applicable flags is documented for each Node property which has type `Flags`.
For example, functions have common declaration flags (visibility, modality) plus `Flag.Function` flags:
```kotlin
val function: KmFunction = ...
@@ -84,7 +91,8 @@ if (Flag.Function.IS_SUSPEND(function.flags)) {
## Writing metadata
To create metadata of a Kotlin class file from scratch, construct an instance of `KmClass`/`KmPackage`/`KmLambda`, fill it with the data and call `accept` with the `Writer` class declared in the corresponding `KotlinClassMetadata` subclass. Finally, use `KotlinClassMetadata.header` to obtain the raw data and write it to the `kotlin.Metadata` annotation on a class file.
To create metadata of a Kotlin class file from scratch, construct an instance of `KmClass`/`KmPackage`/`KmLambda`, fill it with the data and call corresponding `KotlinClassMetadata.write` function.
Resulting `KotlinClassMetadata.annotationData` can be used to write `kotlin.Metadata` annotation on a class file.
When using metadata writers from Kotlin source code, it's very convenient to use Kotlin scoping functions such as `apply` to reduce boilerplate:
@@ -105,18 +113,19 @@ val klass = KmClass().apply {
...
}
// Finally writing everything to arrays of bytes
val header = KotlinClassMetadata.Class.Writer().apply(klass::accept).write().header
val annotation = KotlinClassMetadata.writeClass(klass).annotationData
// Use header.kind, header.data1, header.data2, etc. to write values to kotlin.Metadata
...
// Write annotation directly or use annotation.kind, annotation.data1, annotation.data2, etc.
```
Please refer to [`MetadataSmokeTest.produceKotlinClassFile`](test/kotlinx/metadata/test/MetadataSmokeTest.kt) for an example where metadata of a simple Kotlin class is created, and then the class file is produced with ASM and loaded by Kotlin reflection.
Please refer to [`MetadataSmokeTest.produceKotlinClassFile`](test/kotlinx/metadata/test/MetadataSmokeTest.kt) for an example where metadata of a simple Kotlin class is created,
and then the class file is produced with ASM and loaded by Kotlin reflection.
## Module metadata
Similarly to how `KotlinClassMetadata` is used to read/write metadata of Kotlin `.class` files, [`KotlinModuleMetadata`](src/kotlinx/metadata/jvm/KotlinModuleMetadata.kt) is the entry point for reading/writing `.kotlin_module` files. Use `KotlinModuleMetadata.read` or `KotlinModuleMetadata.Writer` in very much the same fashion as with the class files. The only difference is that the source for the reader (and the result of the writer) is a simple byte array, not the structured data loaded from `kotlin.Metadata`:
Similarly to how `KotlinClassMetadata` is used to read/write metadata of Kotlin `.class` files, [`KotlinModuleMetadata`](src/kotlinx/metadata/jvm/KotlinModuleMetadata.kt)
is the entry point for reading/writing `.kotlin_module` files. Use `KotlinModuleMetadata.read` or `KotlinModuleMetadata.write` in very much the same fashion as with the class files.
The only difference is that the source for the reader (and the result of the writer) is a simple byte array, not the structured data loaded from `kotlin.Metadata`:
```kotlin
// Read the module metadata
@@ -126,13 +135,14 @@ val module = metadata.toKmModule()
...
// Write the module metadata
val bytes = KotlinModuleMetadata.Writer().apply(module::accept).write().bytes
val bytes = KotlinModuleMetadata.write(module).bytes
File("META-INF/main.kotlin_module").writeBytes(bytes)
```
## Laziness
Note that until you load the actual underlying data of a `KotlinClassMetadata` or `KotlinModuleMetadata` instance by invoking `accept` or one of the `toKm...` methods, the data is not completely parsed and verified. If you need to check if the data is not horribly corrupted before proceeding, ensure that either of those is called:
Note that until you load the actual underlying data of a `KotlinClassMetadata` or `KotlinModuleMetadata` instance by invoking one of the `toKm...` methods,
the data is not completely parsed and verified. If you need to check if the data is not horribly corrupted before proceeding, ensure that either of those is called:
```kotlin
val metadata: KotlinClassMetadata.Class = ...
@@ -0,0 +1,21 @@
# kotlinx-metadata-jvm releasing guide
Release is usually done from `master` branch, unless specific circumstances occur (e.g. incompatibility of protobuf between master and current Kotlin's release branch).
To release version `N` of `kotlinx-metadata-jvm`:
1. Update `ChangeLog.md` and other documentation (`ReadMe.md`, `Migration.md`) if necessary.
If release is done with different version of Kotlin than the previous one, add note about metadata version update.
2. If changes are large: Send changes for the review as a separate branch and merge them into `master` after approve.
3. Run the [TeamCity build](https://teamcity.jetbrains.com/buildConfiguration/KotlinTools_KotlinxMetadata_PublishJvm?mode=builds) with parameters:
* `deployVersion` is a version of Kotlin and kotlin-stdlib which `kotlinx-metadata-jvm` should depend on.
* `kotlinxMetadataDeployVersion` is a version `N` you want to release.
4. In [Sonatype](https://oss.sonatype.org/#stagingRepositories) admin interface:
* Close the repository and wait for it to verify.
* Release it.
5. Announce new version in [forum topic](https://discuss.kotlinlang.org/t/announcing-kotlinx-metadata-jvm-library-for-reading-modifying-metadata-of-kotlin-jvm-class-files/7980).
Additionally, you may announce it in the #compiler [public Slack channel](https://kotlinlang.slack.com) and in the internal channel #ext-google-compiler.
@@ -140,7 +140,12 @@ class KotlinModuleMetadata(@Suppress("CanBeParameter", "MemberVisibilityCanBePri
}
}
// TODO: docs
/**
* Writes the metadata of the Kotlin module file.
*
* @param metadataVersion metadata version to be written to the metadata (see [Metadata.metadataVersion]),
* [KotlinClassMetadata.COMPATIBLE_METADATA_VERSION] by default
*/
fun write(kmModule: KmModule, metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION): KotlinModuleMetadata =
Writer().also { kmModule.accept(it) }.write(metadataVersion)
}