KT-45777: Add custom serialization for classpath snapshot
Also add a few commonly-used classes/methods to externalizers.kt to allow reuse. Bug: KT-45777 Test: New ClasspathSnapshotSerializerTest
This commit is contained in:
committed by
nataliya.valtman
parent
0d2a514a70
commit
16dfdb620e
@@ -118,10 +118,10 @@ open class IncrementalJvmCache(
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves information about the given (kotlinc-generated) class to this cache, and stores changes between this class and its previous
|
||||
* version into the given [ChangesCollector].
|
||||
* Saves information about the given (Kotlin) class to this cache, and stores changes between this class and its previous version into
|
||||
* the given [ChangesCollector].
|
||||
*
|
||||
* @param kotlinClassInfo A kotlin-generated class
|
||||
* @param kotlinClassInfo Information about a Kotlin class
|
||||
* @param sourceFiles The source files that the given class was generated from, or `null` if this information is not available
|
||||
* @param changesCollector A [ChangesCollector]
|
||||
*/
|
||||
@@ -129,11 +129,13 @@ open class IncrementalJvmCache(
|
||||
val className = kotlinClassInfo.className
|
||||
|
||||
dirtyOutputClassesMap.notDirty(className)
|
||||
sourceFiles?.forEach {
|
||||
sourceToClassesMap.add(it, className)
|
||||
}
|
||||
|
||||
sourceFiles?.let { internalNameToSource[className.internalName] = it }
|
||||
if (sourceFiles != null) {
|
||||
sourceFiles.forEach {
|
||||
sourceToClassesMap.add(it, className)
|
||||
}
|
||||
internalNameToSource[className.internalName] = sourceFiles
|
||||
}
|
||||
|
||||
if (kotlinClassInfo.classId.isLocal) return
|
||||
|
||||
@@ -149,8 +151,8 @@ open class IncrementalJvmCache(
|
||||
inlineFunctionsMap.process(kotlinClassInfo, changesCollector)
|
||||
}
|
||||
KotlinClassHeader.Kind.MULTIFILE_CLASS -> {
|
||||
val partNames = kotlinClassInfo.classHeaderData?.toList()
|
||||
?: throw AssertionError("Multifile class has no parts: $className")
|
||||
val partNames = kotlinClassInfo.classHeaderData.toList()
|
||||
check(partNames.isNotEmpty()) { "Multifile class has no parts: $className" }
|
||||
multifileFacadeToParts[className] = partNames
|
||||
// When a class is replaced with a facade with the same name,
|
||||
// the class' proto wouldn't ever be deleted,
|
||||
@@ -315,8 +317,8 @@ open class IncrementalJvmCache(
|
||||
val oldData = storage[key]
|
||||
val newData = ProtoMapValue(
|
||||
kotlinClassInfo.classKind != KotlinClassHeader.Kind.CLASS,
|
||||
BitEncoding.decodeBytes(kotlinClassInfo.classHeaderData!!),
|
||||
kotlinClassInfo.classHeaderStrings!!
|
||||
BitEncoding.decodeBytes(kotlinClassInfo.classHeaderData),
|
||||
kotlinClassInfo.classHeaderStrings
|
||||
)
|
||||
storage[key] = newData
|
||||
|
||||
@@ -380,7 +382,8 @@ open class IncrementalJvmCache(
|
||||
}
|
||||
|
||||
// todo: reuse code with InlineFunctionsMap?
|
||||
private inner class ConstantsMap(storageFile: File) : BasicStringMap<Map<String, Any>>(storageFile, ConstantsMapExternalizer) {
|
||||
private inner class ConstantsMap(storageFile: File) :
|
||||
BasicStringMap<LinkedHashMap<String, Any>>(storageFile, LinkedHashMapExternalizer(StringExternalizer, ConstantExternalizer)) {
|
||||
|
||||
operator fun contains(className: JvmClassName): Boolean =
|
||||
className.internalName in storage
|
||||
@@ -419,7 +422,7 @@ open class IncrementalJvmCache(
|
||||
storage.remove(className.internalName)
|
||||
}
|
||||
|
||||
override fun dumpValue(value: Map<String, Any>): String =
|
||||
override fun dumpValue(value: LinkedHashMap<String, Any>): String =
|
||||
value.dumpMap(Any::toString)
|
||||
}
|
||||
|
||||
@@ -499,12 +502,12 @@ open class IncrementalJvmCache(
|
||||
}
|
||||
|
||||
private fun addToClassStorage(classInfo: KotlinClassInfo, srcFile: File) {
|
||||
val (nameResolver, proto) = JvmProtoBufUtil.readClassDataFrom(classInfo.classHeaderData!!, classInfo.classHeaderStrings!!)
|
||||
val (nameResolver, proto) = JvmProtoBufUtil.readClassDataFrom(classInfo.classHeaderData, classInfo.classHeaderStrings)
|
||||
addToClassStorage(proto, nameResolver, srcFile)
|
||||
}
|
||||
|
||||
private inner class InlineFunctionsMap(storageFile: File) :
|
||||
BasicStringMap<Map<String, Long>>(storageFile, StringToLongMapExternalizer) {
|
||||
BasicStringMap<LinkedHashMap<String, Long>>(storageFile, LinkedHashMapExternalizer(StringExternalizer, LongExternalizer)) {
|
||||
|
||||
@Synchronized
|
||||
fun process(kotlinClassInfo: KotlinClassInfo, changesCollector: ChangesCollector) {
|
||||
@@ -537,7 +540,7 @@ open class IncrementalJvmCache(
|
||||
storage.remove(className.internalName)
|
||||
}
|
||||
|
||||
override fun dumpValue(value: Map<String, Long>): String =
|
||||
override fun dumpValue(value: LinkedHashMap<String, Long>): String =
|
||||
value.dumpMap { java.lang.Long.toHexString(it) }
|
||||
}
|
||||
}
|
||||
@@ -596,18 +599,18 @@ fun <T : Comparable<T>> Collection<T>.dumpCollection(): String =
|
||||
"[${sorted().joinToString(", ", transform = Any::toString)}]"
|
||||
|
||||
/**
|
||||
* Minimal information about a kotlinc-generated class that will be used to compute recompilation-triggered changes to support incremental
|
||||
* compilation (see [IncrementalJvmCache.saveClassToCache]).
|
||||
* Minimal information about a Kotlin class to compute recompilation-triggering changes during an incremental run of the `KotlinCompile`
|
||||
* task (see [IncrementalJvmCache.saveClassToCache]).
|
||||
*
|
||||
* It's important that this class contain only the minimal required information, as it will be part of the classpath snapshot of the
|
||||
* `KotlinCompile` task and the task needs to support compile avoidance. For example, this class should contain public method signatures,
|
||||
* and should not contain private method signatures, or method implementations.
|
||||
*/
|
||||
class KotlinClassInfo private constructor(
|
||||
class KotlinClassInfo constructor(
|
||||
val classId: ClassId,
|
||||
val classKind: KotlinClassHeader.Kind,
|
||||
val classHeaderData: Array<String>?,
|
||||
val classHeaderStrings: Array<String>?,
|
||||
val classHeaderData: Array<String>, // Can be empty
|
||||
val classHeaderStrings: Array<String>, // Can be empty
|
||||
@Suppress("SpellCheckingInspection") val multifileClassName: String?,
|
||||
val constantsMap: LinkedHashMap<String, Any>,
|
||||
val inlineFunctionsMap: LinkedHashMap<String, Long>
|
||||
@@ -628,22 +631,22 @@ class KotlinClassInfo private constructor(
|
||||
return KotlinClassInfo(
|
||||
kotlinClass.classId,
|
||||
kotlinClass.classHeader.kind,
|
||||
kotlinClass.classHeader.data,
|
||||
kotlinClass.classHeader.strings,
|
||||
kotlinClass.classHeader.data ?: emptyArray(),
|
||||
kotlinClass.classHeader.strings ?: emptyArray(),
|
||||
kotlinClass.classHeader.multifileClassName,
|
||||
getConstantsMap(kotlinClass.fileContents),
|
||||
getInlineFunctionsMap(kotlinClass.classHeader, kotlinClass.fileContents)
|
||||
)
|
||||
}
|
||||
|
||||
/** Creates [KotlinClassInfo] from the given classContents, or returns `null` if the class is not a kotlinc-generated class. */
|
||||
/** Creates [KotlinClassInfo] from the given classContents, or returns `null` if the class is not a Kotlin class. */
|
||||
fun tryCreateFrom(classContents: ByteArray): KotlinClassInfo? {
|
||||
return FileBasedKotlinClass.create(classContents) { classId, _, classHeader, _ ->
|
||||
KotlinClassInfo(
|
||||
classId,
|
||||
classHeader.kind,
|
||||
classHeader.data,
|
||||
classHeader.strings,
|
||||
classHeader.data ?: emptyArray(),
|
||||
classHeader.strings ?: emptyArray(),
|
||||
classHeader.multifileClassName,
|
||||
getConstantsMap(classContents),
|
||||
getInlineFunctionsMap(classHeader, classContents)
|
||||
|
||||
@@ -94,13 +94,12 @@ object ProtoMapValueExternalizer : DataExternalizer<ProtoMapValue> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
abstract class StringMapExternalizer<T> : DataExternalizer<Map<String, T>> {
|
||||
override fun save(output: DataOutput, map: Map<String, T>?) {
|
||||
output.writeInt(map!!.size)
|
||||
|
||||
for ((key, value) in map.entries) {
|
||||
IOUtil.writeString(key, output)
|
||||
output.writeString(key)
|
||||
writeValue(output, value)
|
||||
}
|
||||
}
|
||||
@@ -110,7 +109,7 @@ abstract class StringMapExternalizer<T> : DataExternalizer<Map<String, T>> {
|
||||
val map = HashMap<String, T>(size)
|
||||
|
||||
repeat(size) {
|
||||
val name = IOUtil.readString(input)!!
|
||||
val name = input.readString()
|
||||
map[name] = readValue(input)
|
||||
}
|
||||
|
||||
@@ -121,7 +120,6 @@ abstract class StringMapExternalizer<T> : DataExternalizer<Map<String, T>> {
|
||||
protected abstract fun readValue(input: DataInput): T
|
||||
}
|
||||
|
||||
|
||||
object StringToLongMapExternalizer : StringMapExternalizer<Long>() {
|
||||
override fun readValue(input: DataInput): Long = input.readLong()
|
||||
|
||||
@@ -130,58 +128,43 @@ object StringToLongMapExternalizer : StringMapExternalizer<Long>() {
|
||||
}
|
||||
}
|
||||
|
||||
object ConstantsMapExternalizer : DataExternalizer<Map<String, Any>> {
|
||||
override fun save(output: DataOutput, map: Map<String, Any>?) {
|
||||
output.writeInt(map!!.size)
|
||||
for (name in map.keys.sorted()) {
|
||||
IOUtil.writeString(name, output)
|
||||
val value = map[name]!!
|
||||
when (value) {
|
||||
is Int -> {
|
||||
output.writeByte(Kind.INT.ordinal)
|
||||
output.writeInt(value)
|
||||
}
|
||||
is Float -> {
|
||||
output.writeByte(Kind.FLOAT.ordinal)
|
||||
output.writeFloat(value)
|
||||
}
|
||||
is Long -> {
|
||||
output.writeByte(Kind.LONG.ordinal)
|
||||
output.writeLong(value)
|
||||
}
|
||||
is Double -> {
|
||||
output.writeByte(Kind.DOUBLE.ordinal)
|
||||
output.writeDouble(value)
|
||||
}
|
||||
is String -> {
|
||||
output.writeByte(Kind.STRING.ordinal)
|
||||
IOUtil.writeString(value, output)
|
||||
}
|
||||
else -> throw IllegalStateException("Unexpected constant class: ${value::class.java}")
|
||||
/** [DataExternalizer] for a Kotlin constant. */
|
||||
object ConstantExternalizer : DataExternalizer<Any> {
|
||||
|
||||
override fun save(output: DataOutput, value: Any) {
|
||||
when (value) {
|
||||
is Int -> {
|
||||
output.writeByte(Kind.INT.ordinal)
|
||||
output.writeInt(value)
|
||||
}
|
||||
is Float -> {
|
||||
output.writeByte(Kind.FLOAT.ordinal)
|
||||
output.writeFloat(value)
|
||||
}
|
||||
is Long -> {
|
||||
output.writeByte(Kind.LONG.ordinal)
|
||||
output.writeLong(value)
|
||||
}
|
||||
is Double -> {
|
||||
output.writeByte(Kind.DOUBLE.ordinal)
|
||||
output.writeDouble(value)
|
||||
}
|
||||
is String -> {
|
||||
output.writeByte(Kind.STRING.ordinal)
|
||||
output.writeString(value)
|
||||
}
|
||||
else -> throw IllegalStateException("Unexpected constant class: ${value::class.java}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): Map<String, Any> {
|
||||
val size = input.readInt()
|
||||
val map = HashMap<String, Any>(size)
|
||||
|
||||
repeat(size) {
|
||||
val name = IOUtil.readString(input)!!
|
||||
val kind = Kind.values()[input.readByte().toInt()]
|
||||
|
||||
val value: Any = when (kind) {
|
||||
Kind.INT -> input.readInt()
|
||||
Kind.FLOAT -> input.readFloat()
|
||||
Kind.LONG -> input.readLong()
|
||||
Kind.DOUBLE -> input.readDouble()
|
||||
Kind.STRING -> IOUtil.readString(input)!!
|
||||
}
|
||||
|
||||
map[name] = value
|
||||
override fun read(input: DataInput): Any {
|
||||
return when (Kind.values()[input.readByte().toInt()]) {
|
||||
Kind.INT -> input.readInt()
|
||||
Kind.FLOAT -> input.readFloat()
|
||||
Kind.LONG -> input.readLong()
|
||||
Kind.DOUBLE -> input.readDouble()
|
||||
Kind.STRING -> input.readString()
|
||||
}
|
||||
|
||||
return map
|
||||
}
|
||||
|
||||
private enum class Kind {
|
||||
@@ -190,11 +173,18 @@ object ConstantsMapExternalizer : DataExternalizer<Map<String, Any>> {
|
||||
}
|
||||
|
||||
object IntExternalizer : DataExternalizer<Int> {
|
||||
override fun save(output: DataOutput, value: Int) = output.writeInt(value)
|
||||
override fun read(input: DataInput): Int = input.readInt()
|
||||
}
|
||||
|
||||
override fun save(output: DataOutput, value: Int) {
|
||||
output.writeInt(value)
|
||||
}
|
||||
object LongExternalizer : DataExternalizer<Long> {
|
||||
override fun save(output: DataOutput, value: Long) = output.writeLong(value)
|
||||
override fun read(input: DataInput): Long = input.readLong()
|
||||
}
|
||||
|
||||
object StringExternalizer : DataExternalizer<String> {
|
||||
override fun save(output: DataOutput, value: String) = IOUtil.writeString(value, output)
|
||||
override fun read(input: DataInput): String = IOUtil.readString(input)
|
||||
}
|
||||
|
||||
|
||||
@@ -244,3 +234,69 @@ open class CollectionExternalizer<T>(
|
||||
object StringCollectionExternalizer : CollectionExternalizer<String>(EnumeratorStringDescriptor(), { HashSet() })
|
||||
|
||||
object IntCollectionExternalizer : CollectionExternalizer<Int>(IntExternalizer, { HashSet() })
|
||||
|
||||
fun DataOutput.writeString(value: String) = StringExternalizer.save(this, value)
|
||||
|
||||
fun DataInput.readString(): String = StringExternalizer.read(this)
|
||||
|
||||
class ListExternalizer<T>(
|
||||
private val elementExternalizer: DataExternalizer<T>
|
||||
) : DataExternalizer<List<T>> {
|
||||
|
||||
override fun save(output: DataOutput, value: List<T>) {
|
||||
output.writeInt(value.size)
|
||||
value.forEach {
|
||||
elementExternalizer.save(output, it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): List<T> {
|
||||
val size = input.readInt()
|
||||
val list = ArrayList<T>(size)
|
||||
repeat(size) {
|
||||
list.add(elementExternalizer.read(input))
|
||||
}
|
||||
return list
|
||||
}
|
||||
}
|
||||
|
||||
class LinkedHashMapExternalizer<K, V>(
|
||||
private val keyExternalizer: DataExternalizer<K>,
|
||||
private val valueExternalizer: DataExternalizer<V>
|
||||
) : DataExternalizer<LinkedHashMap<K, V>> {
|
||||
|
||||
override fun save(output: DataOutput, map: LinkedHashMap<K, V>) {
|
||||
output.writeInt(map.size)
|
||||
for ((key, value) in map) {
|
||||
keyExternalizer.save(output, key)
|
||||
valueExternalizer.save(output, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): LinkedHashMap<K, V> {
|
||||
val size = input.readInt()
|
||||
val map = LinkedHashMap<K, V>(size)
|
||||
repeat(size) {
|
||||
val key = keyExternalizer.read(input)
|
||||
val value = valueExternalizer.read(input)
|
||||
map[key] = value
|
||||
}
|
||||
return map
|
||||
}
|
||||
}
|
||||
|
||||
class NullableValueExternalizer<T>(private val valueExternalizer: DataExternalizer<T>) : DataExternalizer<T> {
|
||||
|
||||
override fun save(output: DataOutput, value: T?) {
|
||||
output.writeBoolean(value != null)
|
||||
value?.let {
|
||||
valueExternalizer.save(output, it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): T? {
|
||||
return if (input.readBoolean()) {
|
||||
valueExternalizer.read(input)
|
||||
} else null
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user