Rework KotlinModuleMetadata to align it with KotlinClassMetadata:

- Public constructor with KmModule and JvmMetadataVersion
- make write() member function
This commit is contained in:
Leonid Startsev
2023-11-21 17:58:41 +01:00
committed by Space Team
parent 6e9a62c317
commit 048802e143
6 changed files with 102 additions and 93 deletions
+3 -3
View File
@@ -202,11 +202,11 @@ The only difference is that the source for the reader (and the result of the wri
```kotlin
// Read the module metadata
val bytes = File("META-INF/main.kotlin_module").readBytes()
val metadata = KotlinModuleMetadata.read(bytes)
val module = metadata.kmModule
val moduleMetadata = KotlinModuleMetadata.read(bytes)
val module = moduleMetadata.kmModule
...
// Write the module metadata
val bytes = KotlinModuleMetadata.write(module)
val bytes = moduleMetadata.write()
File("META-INF/main.kotlin_module").writeBytes(bytes)
```
@@ -1638,11 +1638,15 @@ public final class kotlinx/metadata/jvm/KotlinClassMetadata$Unknown$Companion {
public final class kotlinx/metadata/jvm/KotlinModuleMetadata {
public static final field Companion Lkotlinx/metadata/jvm/KotlinModuleMetadata$Companion;
public synthetic fun <init> ([BLkotlinx/metadata/internal/metadata/jvm/deserialization/ModuleMapping;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lkotlinx/metadata/jvm/KmModule;Lkotlinx/metadata/jvm/JvmMetadataVersion;)V
public final fun accept (Lkotlinx/metadata/jvm/KmModuleVisitor;)V
public final fun getKmModule ()Lkotlinx/metadata/jvm/KmModule;
public final fun getVersion ()Lkotlinx/metadata/jvm/JvmMetadataVersion;
public static final fun read ([B)Lkotlinx/metadata/jvm/KotlinModuleMetadata;
public final fun setKmModule (Lkotlinx/metadata/jvm/KmModule;)V
public final fun setVersion (Lkotlinx/metadata/jvm/JvmMetadataVersion;)V
public final fun toKmModule ()Lkotlinx/metadata/jvm/KmModule;
public final fun write ()[B
public static final fun write (Lkotlinx/metadata/jvm/KmModule;)[B
public static final fun write (Lkotlinx/metadata/jvm/KmModule;Lkotlinx/metadata/jvm/JvmMetadataVersion;)[B
}
@@ -4,27 +4,24 @@
*/
@file:Suppress(
"MemberVisibilityCanBePrivate",
"DEPRECATION", // For KmModule.annotations — remove reading when deprecation is raised to error
"DEPRECATION_ERROR", // deprecated .accept implementation
"INVISIBLE_MEMBER", // InconsistentKotlinMetadataException
"INVISIBLE_REFERENCE",
"DEPRECATION", // COMPATIBLE_METADATA_VERSION
"DEPRECATION_ERROR", // KmModule.annotations
"UNUSED_PARAMETER" // For deprecated Writer.write
)
package kotlinx.metadata.jvm
import kotlinx.metadata.*
import kotlinx.metadata.internal.toKmClass
import kotlinx.metadata.jvm.KotlinClassMetadata.Companion.COMPATIBLE_METADATA_VERSION
import kotlinx.metadata.jvm.internal.JvmReadUtils.readModuleMetadataImpl
import kotlinx.metadata.jvm.internal.JvmReadUtils.throwIfNotCompatible
import kotlinx.metadata.jvm.internal.wrapIntoMetadataExceptionWhenNeeded
import kotlinx.metadata.jvm.internal.wrapWriteIntoIAE
import org.jetbrains.kotlin.metadata.jvm.JvmModuleProtoBuf
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion as CompilerMetadataVersion
import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping
import org.jetbrains.kotlin.metadata.jvm.deserialization.PackageParts
import org.jetbrains.kotlin.metadata.jvm.deserialization.serializeToByteArray
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion as CompilerMetadataVersion
/**
* Represents the parsed metadata of a Kotlin JVM module file.
@@ -34,22 +31,21 @@ import org.jetbrains.kotlin.metadata.jvm.deserialization.serializeToByteArray
*
* `.kotlin_module` file is produced per Kotlin compilation, and contains auxiliary information, such as a map of all single- and multi-file facades ([KmModule.packageParts]),
* `@OptionalExpectation` declarations ([KmModule.optionalAnnotationClasses]), and module annotations ([KmModule.annotations).
*
* @property bytes the byte array representing the contents of a `.kotlin_module` file
*/
@UnstableMetadataApi
public class KotlinModuleMetadata private constructor(
private val bytes: ByteArray,
private val data: ModuleMapping,
) {
public class KotlinModuleMetadata public constructor(
/**
* Returns the [KmModule] representation of this metadata.
* [KmModule] representation of this metadata.
*
* Returns the same (mutable) [KmModule] instance every time.
*/
public val kmModule: KmModule = readImpl()
public var kmModule: KmModule,
/**
* Version of this metadata.
*/
public var version: JvmMetadataVersion,
) {
/**
* Visits metadata of this module with a new [KmModule] instance and returns that instance.
*/
@@ -60,6 +56,49 @@ public class KotlinModuleMetadata private constructor(
)
public fun toKmModule(): KmModule = KmModule().apply { kmModule.accept(this) }
/**
* Encodes and writes this metadata of the Kotlin module file.
*
* This method encodes all available data, including [version].
*
* @throws IllegalArgumentException if [kmModule] is not correct and cannot be written or if [version] is not supported for writing.
*/
public fun write(): ByteArray {
val b = JvmModuleProtoBuf.Module.newBuilder()
kmModule.packageParts.forEach { (fqName, packageParts) ->
PackageParts(fqName).apply {
for (fileFacade in packageParts.fileFacades) {
addPart(fileFacade, null)
}
for ((multiFileClassPart, multiFileFacade) in packageParts.multiFileClassParts) {
addPart(multiFileClassPart, multiFileFacade)
}
addTo(b)
}
}
// visitAnnotation
/*
// TODO: move StringTableImpl to module 'metadata' and support module annotations here
b.addAnnotation(ProtoBuf.Annotation.newBuilder().apply {
id = annotation.className.name // <-- use StringTableImpl here
})
*/
// visitOptionalAnnotationClass
/*
return object : ClassWriter(TODO() /* use StringTableImpl here */) {
override fun visitEnd() {
b.addOptionalAnnotationClass(t)
}
}
*/
// The only flag present in module metadata is STRICT_SEMANTICS, and it seems fine to ignore it and set 0 here.
return b.build().serializeToByteArray(CompilerMetadataVersion(version.toIntArray(), false), 0)
}
/**
* A [KmModuleVisitor] that generates the metadata of a Kotlin JVM module file.
@@ -69,40 +108,6 @@ public class KotlinModuleMetadata private constructor(
level = DeprecationLevel.ERROR
)
public class Writer {
private val b = JvmModuleProtoBuf.Module.newBuilder()
private fun writeModule(kmModule: KmModule) {
kmModule.packageParts.forEach { (fqName, packageParts) ->
PackageParts(fqName).apply {
for (fileFacade in packageParts.fileFacades) {
addPart(fileFacade, null)
}
for ((multiFileClassPart, multiFileFacade) in packageParts.multiFileClassParts) {
addPart(multiFileClassPart, multiFileFacade)
}
addTo(b)
}
}
// visitAnnotation
/*
// TODO: move StringTableImpl to module 'metadata' and support module annotations here
b.addAnnotation(ProtoBuf.Annotation.newBuilder().apply {
id = annotation.className.name // <-- use StringTableImpl here
})
*/
// visitOptionalAnnotationClass
/*
return object : ClassWriter(TODO() /* use StringTableImpl here */) {
override fun visitEnd() {
b.addOptionalAnnotationClass(t)
}
}
*/
}
/**
* Returns the metadata of the module file that was written with this writer.
*
@@ -126,26 +131,6 @@ public class KotlinModuleMetadata private constructor(
@Deprecated(VISITOR_API_MESSAGE, level = DeprecationLevel.ERROR)
public fun accept(v: KmModuleVisitor): Unit = kmModule.accept(v)
private fun readImpl(): KmModule {
val v = KmModule()
for ((fqName, parts) in data.packageFqName2Parts) {
val (fileFacades, multiFileClassParts) = parts.parts.partition { parts.getMultifileFacadeName(it) == null }
v.packageParts[fqName] = KmPackageParts(
fileFacades.toMutableList(),
multiFileClassParts.associateWith { parts.getMultifileFacadeName(it)!! }.toMutableMap()
)
}
for (annotation in data.moduleData.annotations) {
v.annotations.add(KmAnnotation(annotation, emptyMap()))
}
for (classProto in data.moduleData.optionalAnnotations) {
v.optionalAnnotationClasses.add(classProto.toKmClass(data.moduleData.nameResolver))
}
return v
}
/**
* Collection of methods for reading and writing [KotlinModuleMetadata].
*/
@@ -161,12 +146,16 @@ public class KotlinModuleMetadata private constructor(
@UnstableMetadataApi
public fun read(bytes: ByteArray): KotlinModuleMetadata {
return wrapIntoMetadataExceptionWhenNeeded {
val result = dataFromBytes(bytes)
val result = ModuleMapping.loadModuleMapping(
bytes, "KotlinModuleMetadata", skipMetadataVersionCheck = false,
isJvmPackageNameSupported = true
) { throwIfNotCompatible(it, lenient = false /* TODO */) }
when (result) {
ModuleMapping.EMPTY, ModuleMapping.CORRUPTED ->
throw InconsistentKotlinMetadataException("Data is not the content of a .kotlin_module file, or it has been corrupted.")
throw IllegalArgumentException("Data is not the content of a .kotlin_module file, or it has been corrupted.")
}
KotlinModuleMetadata(bytes, result)
val module = readModuleMetadataImpl(result)
KotlinModuleMetadata(module, JvmMetadataVersion(result.version.toArray()))
}
}
@@ -179,18 +168,11 @@ public class KotlinModuleMetadata private constructor(
* @throws IllegalArgumentException if [kmModule] is not correct and cannot be written or if [metadataVersion] is not supported for writing.
*/
@UnstableMetadataApi
@Deprecated("Use a KotlinModuleMetadata instance and its write() member function", level = DeprecationLevel.WARNING)
@JvmStatic
@JvmOverloads
public fun write(kmModule: KmModule, metadataVersion: JvmMetadataVersion = JvmMetadataVersion.CURRENT): ByteArray = wrapWriteIntoIAE {
val w = Writer().also { it.writeModule(kmModule) }
return w.b.build().serializeToByteArray(CompilerMetadataVersion(metadataVersion.toIntArray(), false), 0)
}
private fun dataFromBytes(bytes: ByteArray): ModuleMapping {
return ModuleMapping.loadModuleMapping(
bytes, "KotlinModuleMetadata", skipMetadataVersionCheck = false,
isJvmPackageNameSupported = true
) { throwIfNotCompatible(it, lenient = false /* TODO */) }
return KotlinModuleMetadata(kmModule, metadataVersion).write()
}
}
}
@@ -5,15 +5,20 @@
package kotlinx.metadata.jvm.internal
import kotlinx.metadata.KmAnnotation
import kotlinx.metadata.KmClass
import kotlinx.metadata.KmLambda
import kotlinx.metadata.KmPackage
import kotlinx.metadata.internal.toKmClass
import kotlinx.metadata.internal.toKmLambda
import kotlinx.metadata.internal.toKmPackage
import kotlinx.metadata.jvm.KmModule
import kotlinx.metadata.jvm.KmPackageParts
import kotlinx.metadata.jvm.KotlinClassMetadata
import kotlinx.metadata.jvm.UnstableMetadataApi
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping
internal object JvmReadUtils {
internal fun readKmClass(annotationData: Metadata): KmClass {
@@ -50,7 +55,29 @@ internal object JvmReadUtils {
}
}
internal fun checkMetadataVersionForRead(annotationData: Metadata, lenient: Boolean) {
@UnstableMetadataApi
internal fun readModuleMetadataImpl(data: ModuleMapping): KmModule {
val v = KmModule()
for ((fqName, parts) in data.packageFqName2Parts) {
val (fileFacades, multiFileClassParts) = parts.parts.partition { parts.getMultifileFacadeName(it) == null }
v.packageParts[fqName] = KmPackageParts(
fileFacades.toMutableList(),
multiFileClassParts.associateWith { parts.getMultifileFacadeName(it)!! }.toMutableMap()
)
}
for (annotation in data.moduleData.annotations) {
@Suppress("DEPRECATION_ERROR")
v.annotations.add(KmAnnotation(annotation, emptyMap()))
}
for (classProto in data.moduleData.optionalAnnotations) {
v.optionalAnnotationClasses.add(classProto.toKmClass(data.moduleData.nameResolver))
}
return v
}
private fun checkMetadataVersionForRead(annotationData: Metadata, lenient: Boolean) {
if (annotationData.metadataVersion.isEmpty())
throw IllegalArgumentException("Provided Metadata instance does not have metadataVersion in it and therefore is malformed and cannot be read.")
val jvmMetadataVersion = JvmMetadataVersion(
@@ -44,7 +44,7 @@ class Kotlinp(private val settings: KotlinpSettings) {
@OptIn(UnstableMetadataApi::class)
internal fun readModuleFile(file: File): KotlinModuleMetadata? =
KotlinModuleMetadata.read(file.readBytes())
runCatching { KotlinModuleMetadata.read(file.readBytes()) }.getOrNull()
}
data class KotlinpSettings(
@@ -69,7 +69,7 @@ private fun compileAndPrintAllFiles(
when (outputFile.extension) {
"kotlin_module" -> {
val moduleFile = kotlinp.readModuleFile(outputFile)!!
val transformedWithNodes = KotlinModuleMetadata.read(transformModuleFileWithNodes(moduleFile))
val transformedWithNodes = KotlinModuleMetadata.read(moduleFile.write())
for ((sb, moduleFileToRender) in listOf(
main to moduleFile, afterNodes to transformedWithNodes
@@ -128,7 +128,3 @@ private fun StringBuilder.appendFileName(file: File) {
appendLine("// ${file.invariantSeparatorsPath}")
appendLine("// ------------------------------------------")
}
@OptIn(UnstableMetadataApi::class)
private fun transformModuleFileWithNodes(moduleFile: KotlinModuleMetadata): ByteArray =
KotlinModuleMetadata.write(moduleFile.kmModule)