mirror of
https://github.com/jlengrand/kotlin.git
synced 2026-03-10 08:31:29 +00:00
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
|
* Saves information about the given (Kotlin) class to this cache, and stores changes between this class and its previous version into
|
||||||
* version into the given [ChangesCollector].
|
* 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 sourceFiles The source files that the given class was generated from, or `null` if this information is not available
|
||||||
* @param changesCollector A [ChangesCollector]
|
* @param changesCollector A [ChangesCollector]
|
||||||
*/
|
*/
|
||||||
@@ -129,11 +129,13 @@ open class IncrementalJvmCache(
|
|||||||
val className = kotlinClassInfo.className
|
val className = kotlinClassInfo.className
|
||||||
|
|
||||||
dirtyOutputClassesMap.notDirty(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
|
if (kotlinClassInfo.classId.isLocal) return
|
||||||
|
|
||||||
@@ -149,8 +151,8 @@ open class IncrementalJvmCache(
|
|||||||
inlineFunctionsMap.process(kotlinClassInfo, changesCollector)
|
inlineFunctionsMap.process(kotlinClassInfo, changesCollector)
|
||||||
}
|
}
|
||||||
KotlinClassHeader.Kind.MULTIFILE_CLASS -> {
|
KotlinClassHeader.Kind.MULTIFILE_CLASS -> {
|
||||||
val partNames = kotlinClassInfo.classHeaderData?.toList()
|
val partNames = kotlinClassInfo.classHeaderData.toList()
|
||||||
?: throw AssertionError("Multifile class has no parts: $className")
|
check(partNames.isNotEmpty()) { "Multifile class has no parts: $className" }
|
||||||
multifileFacadeToParts[className] = partNames
|
multifileFacadeToParts[className] = partNames
|
||||||
// When a class is replaced with a facade with the same name,
|
// When a class is replaced with a facade with the same name,
|
||||||
// the class' proto wouldn't ever be deleted,
|
// the class' proto wouldn't ever be deleted,
|
||||||
@@ -315,8 +317,8 @@ open class IncrementalJvmCache(
|
|||||||
val oldData = storage[key]
|
val oldData = storage[key]
|
||||||
val newData = ProtoMapValue(
|
val newData = ProtoMapValue(
|
||||||
kotlinClassInfo.classKind != KotlinClassHeader.Kind.CLASS,
|
kotlinClassInfo.classKind != KotlinClassHeader.Kind.CLASS,
|
||||||
BitEncoding.decodeBytes(kotlinClassInfo.classHeaderData!!),
|
BitEncoding.decodeBytes(kotlinClassInfo.classHeaderData),
|
||||||
kotlinClassInfo.classHeaderStrings!!
|
kotlinClassInfo.classHeaderStrings
|
||||||
)
|
)
|
||||||
storage[key] = newData
|
storage[key] = newData
|
||||||
|
|
||||||
@@ -380,7 +382,8 @@ open class IncrementalJvmCache(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// todo: reuse code with InlineFunctionsMap?
|
// 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 =
|
operator fun contains(className: JvmClassName): Boolean =
|
||||||
className.internalName in storage
|
className.internalName in storage
|
||||||
@@ -419,7 +422,7 @@ open class IncrementalJvmCache(
|
|||||||
storage.remove(className.internalName)
|
storage.remove(className.internalName)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dumpValue(value: Map<String, Any>): String =
|
override fun dumpValue(value: LinkedHashMap<String, Any>): String =
|
||||||
value.dumpMap(Any::toString)
|
value.dumpMap(Any::toString)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,12 +502,12 @@ open class IncrementalJvmCache(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun addToClassStorage(classInfo: KotlinClassInfo, srcFile: File) {
|
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)
|
addToClassStorage(proto, nameResolver, srcFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class InlineFunctionsMap(storageFile: File) :
|
private inner class InlineFunctionsMap(storageFile: File) :
|
||||||
BasicStringMap<Map<String, Long>>(storageFile, StringToLongMapExternalizer) {
|
BasicStringMap<LinkedHashMap<String, Long>>(storageFile, LinkedHashMapExternalizer(StringExternalizer, LongExternalizer)) {
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun process(kotlinClassInfo: KotlinClassInfo, changesCollector: ChangesCollector) {
|
fun process(kotlinClassInfo: KotlinClassInfo, changesCollector: ChangesCollector) {
|
||||||
@@ -537,7 +540,7 @@ open class IncrementalJvmCache(
|
|||||||
storage.remove(className.internalName)
|
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) }
|
value.dumpMap { java.lang.Long.toHexString(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -596,18 +599,18 @@ fun <T : Comparable<T>> Collection<T>.dumpCollection(): String =
|
|||||||
"[${sorted().joinToString(", ", transform = Any::toString)}]"
|
"[${sorted().joinToString(", ", transform = Any::toString)}]"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimal information about a kotlinc-generated class that will be used to compute recompilation-triggered changes to support incremental
|
* Minimal information about a Kotlin class to compute recompilation-triggering changes during an incremental run of the `KotlinCompile`
|
||||||
* compilation (see [IncrementalJvmCache.saveClassToCache]).
|
* 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
|
* 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,
|
* `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.
|
* and should not contain private method signatures, or method implementations.
|
||||||
*/
|
*/
|
||||||
class KotlinClassInfo private constructor(
|
class KotlinClassInfo constructor(
|
||||||
val classId: ClassId,
|
val classId: ClassId,
|
||||||
val classKind: KotlinClassHeader.Kind,
|
val classKind: KotlinClassHeader.Kind,
|
||||||
val classHeaderData: Array<String>?,
|
val classHeaderData: Array<String>, // Can be empty
|
||||||
val classHeaderStrings: Array<String>?,
|
val classHeaderStrings: Array<String>, // Can be empty
|
||||||
@Suppress("SpellCheckingInspection") val multifileClassName: String?,
|
@Suppress("SpellCheckingInspection") val multifileClassName: String?,
|
||||||
val constantsMap: LinkedHashMap<String, Any>,
|
val constantsMap: LinkedHashMap<String, Any>,
|
||||||
val inlineFunctionsMap: LinkedHashMap<String, Long>
|
val inlineFunctionsMap: LinkedHashMap<String, Long>
|
||||||
@@ -628,22 +631,22 @@ class KotlinClassInfo private constructor(
|
|||||||
return KotlinClassInfo(
|
return KotlinClassInfo(
|
||||||
kotlinClass.classId,
|
kotlinClass.classId,
|
||||||
kotlinClass.classHeader.kind,
|
kotlinClass.classHeader.kind,
|
||||||
kotlinClass.classHeader.data,
|
kotlinClass.classHeader.data ?: emptyArray(),
|
||||||
kotlinClass.classHeader.strings,
|
kotlinClass.classHeader.strings ?: emptyArray(),
|
||||||
kotlinClass.classHeader.multifileClassName,
|
kotlinClass.classHeader.multifileClassName,
|
||||||
getConstantsMap(kotlinClass.fileContents),
|
getConstantsMap(kotlinClass.fileContents),
|
||||||
getInlineFunctionsMap(kotlinClass.classHeader, 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? {
|
fun tryCreateFrom(classContents: ByteArray): KotlinClassInfo? {
|
||||||
return FileBasedKotlinClass.create(classContents) { classId, _, classHeader, _ ->
|
return FileBasedKotlinClass.create(classContents) { classId, _, classHeader, _ ->
|
||||||
KotlinClassInfo(
|
KotlinClassInfo(
|
||||||
classId,
|
classId,
|
||||||
classHeader.kind,
|
classHeader.kind,
|
||||||
classHeader.data,
|
classHeader.data ?: emptyArray(),
|
||||||
classHeader.strings,
|
classHeader.strings ?: emptyArray(),
|
||||||
classHeader.multifileClassName,
|
classHeader.multifileClassName,
|
||||||
getConstantsMap(classContents),
|
getConstantsMap(classContents),
|
||||||
getInlineFunctionsMap(classHeader, classContents)
|
getInlineFunctionsMap(classHeader, classContents)
|
||||||
|
|||||||
@@ -94,13 +94,12 @@ object ProtoMapValueExternalizer : DataExternalizer<ProtoMapValue> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
abstract class StringMapExternalizer<T> : DataExternalizer<Map<String, T>> {
|
abstract class StringMapExternalizer<T> : DataExternalizer<Map<String, T>> {
|
||||||
override fun save(output: DataOutput, map: Map<String, T>?) {
|
override fun save(output: DataOutput, map: Map<String, T>?) {
|
||||||
output.writeInt(map!!.size)
|
output.writeInt(map!!.size)
|
||||||
|
|
||||||
for ((key, value) in map.entries) {
|
for ((key, value) in map.entries) {
|
||||||
IOUtil.writeString(key, output)
|
output.writeString(key)
|
||||||
writeValue(output, value)
|
writeValue(output, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,7 +109,7 @@ abstract class StringMapExternalizer<T> : DataExternalizer<Map<String, T>> {
|
|||||||
val map = HashMap<String, T>(size)
|
val map = HashMap<String, T>(size)
|
||||||
|
|
||||||
repeat(size) {
|
repeat(size) {
|
||||||
val name = IOUtil.readString(input)!!
|
val name = input.readString()
|
||||||
map[name] = readValue(input)
|
map[name] = readValue(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +120,6 @@ abstract class StringMapExternalizer<T> : DataExternalizer<Map<String, T>> {
|
|||||||
protected abstract fun readValue(input: DataInput): T
|
protected abstract fun readValue(input: DataInput): T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
object StringToLongMapExternalizer : StringMapExternalizer<Long>() {
|
object StringToLongMapExternalizer : StringMapExternalizer<Long>() {
|
||||||
override fun readValue(input: DataInput): Long = input.readLong()
|
override fun readValue(input: DataInput): Long = input.readLong()
|
||||||
|
|
||||||
@@ -130,58 +128,43 @@ object StringToLongMapExternalizer : StringMapExternalizer<Long>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object ConstantsMapExternalizer : DataExternalizer<Map<String, Any>> {
|
/** [DataExternalizer] for a Kotlin constant. */
|
||||||
override fun save(output: DataOutput, map: Map<String, Any>?) {
|
object ConstantExternalizer : DataExternalizer<Any> {
|
||||||
output.writeInt(map!!.size)
|
|
||||||
for (name in map.keys.sorted()) {
|
override fun save(output: DataOutput, value: Any) {
|
||||||
IOUtil.writeString(name, output)
|
when (value) {
|
||||||
val value = map[name]!!
|
is Int -> {
|
||||||
when (value) {
|
output.writeByte(Kind.INT.ordinal)
|
||||||
is Int -> {
|
output.writeInt(value)
|
||||||
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}")
|
|
||||||
}
|
}
|
||||||
|
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> {
|
override fun read(input: DataInput): Any {
|
||||||
val size = input.readInt()
|
return when (Kind.values()[input.readByte().toInt()]) {
|
||||||
val map = HashMap<String, Any>(size)
|
Kind.INT -> input.readInt()
|
||||||
|
Kind.FLOAT -> input.readFloat()
|
||||||
repeat(size) {
|
Kind.LONG -> input.readLong()
|
||||||
val name = IOUtil.readString(input)!!
|
Kind.DOUBLE -> input.readDouble()
|
||||||
val kind = Kind.values()[input.readByte().toInt()]
|
Kind.STRING -> input.readString()
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return map
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum class Kind {
|
private enum class Kind {
|
||||||
@@ -190,11 +173,18 @@ object ConstantsMapExternalizer : DataExternalizer<Map<String, Any>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object IntExternalizer : DataExternalizer<Int> {
|
object IntExternalizer : DataExternalizer<Int> {
|
||||||
|
override fun save(output: DataOutput, value: Int) = output.writeInt(value)
|
||||||
override fun read(input: DataInput): Int = input.readInt()
|
override fun read(input: DataInput): Int = input.readInt()
|
||||||
|
}
|
||||||
|
|
||||||
override fun save(output: DataOutput, value: Int) {
|
object LongExternalizer : DataExternalizer<Long> {
|
||||||
output.writeInt(value)
|
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 StringCollectionExternalizer : CollectionExternalizer<String>(EnumeratorStringDescriptor(), { HashSet() })
|
||||||
|
|
||||||
object IntCollectionExternalizer : CollectionExternalizer<Int>(IntExternalizer, { 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
package org.jetbrains.kotlin.gradle.incremental
|
package org.jetbrains.kotlin.gradle.incremental
|
||||||
|
|
||||||
import org.jetbrains.kotlin.incremental.KotlinClassInfo
|
import org.jetbrains.kotlin.incremental.KotlinClassInfo
|
||||||
import java.io.*
|
|
||||||
|
|
||||||
/** Snapshot of a classpath. It consists of a list of [ClasspathEntrySnapshot]s. */
|
/** Snapshot of a classpath. It consists of a list of [ClasspathEntrySnapshot]s. */
|
||||||
class ClasspathSnapshot(val classpathEntrySnapshots: List<ClasspathEntrySnapshot>)
|
class ClasspathSnapshot(val classpathEntrySnapshots: List<ClasspathEntrySnapshot>)
|
||||||
@@ -19,64 +18,20 @@ class ClasspathEntrySnapshot(
|
|||||||
* jar).
|
* jar).
|
||||||
*/
|
*/
|
||||||
val classSnapshots: LinkedHashMap<String, ClassSnapshot>
|
val classSnapshots: LinkedHashMap<String, ClassSnapshot>
|
||||||
) : Serializable {
|
)
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val serialVersionUID = 0L
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Snapshot of a class. It contains information to compute the source files that need to be recompiled during an incremental run of the
|
* Snapshot of a class. It contains minimal information about a class to compute the source files that need to be recompiled during an
|
||||||
* `KotlinCompile` task.
|
* incremental run of the `KotlinCompile` task.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
*/
|
*/
|
||||||
abstract class ClassSnapshot : Serializable {
|
sealed class ClassSnapshot
|
||||||
|
|
||||||
companion object {
|
/** [ClassSnapshot] of a Kotlin class. */
|
||||||
private const val serialVersionUID = 0L
|
class KotlinClassSnapshot(val classInfo: KotlinClassInfo) : ClassSnapshot()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** [ClassSnapshot] of a kotlinc-generated class. */
|
/** [ClassSnapshot] of a Java class. */
|
||||||
class KotlinClassSnapshot(val classInfo: KotlinClassInfo) : ClassSnapshot(), Serializable {
|
object JavaClassSnapshot : ClassSnapshot()
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val serialVersionUID = 0L
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** [ClassSnapshot] of a non-kotlinc-generated class. */
|
|
||||||
class JavaClassSnapshot : ClassSnapshot(), Serializable {
|
|
||||||
|
|
||||||
// TODO WORK-IN-PROGRESS
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val serialVersionUID = 0L
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Utility to read/write a [ClasspathSnapshot] from/to a file. */
|
|
||||||
object ClasspathSnapshotSerializer {
|
|
||||||
|
|
||||||
fun readFromFiles(classpathEntrySnapshotFiles: List<File>): ClasspathSnapshot {
|
|
||||||
return ClasspathSnapshot(classpathEntrySnapshotFiles.map { ClasspathEntrySnapshotSerializer.readFromFile(it) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Utility to read/write a [ClasspathEntrySnapshot] from/to a file. */
|
|
||||||
object ClasspathEntrySnapshotSerializer {
|
|
||||||
|
|
||||||
fun readFromFile(classpathEntrySnapshotFile: File): ClasspathEntrySnapshot {
|
|
||||||
check(classpathEntrySnapshotFile.isFile) { "`${classpathEntrySnapshotFile.path}` does not exist (or is a directory)." }
|
|
||||||
return ObjectInputStream(FileInputStream(classpathEntrySnapshotFile).buffered()).use {
|
|
||||||
it.readObject() as ClasspathEntrySnapshot
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun writeToFile(classpathEntrySnapshot: ClasspathEntrySnapshot, classpathEntrySnapshotFile: File) {
|
|
||||||
check(classpathEntrySnapshotFile.parentFile.exists()) { "Parent dir of `${classpathEntrySnapshotFile.path}` does not exist." }
|
|
||||||
ObjectOutputStream(FileOutputStream(classpathEntrySnapshotFile).buffered()).use {
|
|
||||||
it.writeObject(classpathEntrySnapshot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.gradle.incremental
|
||||||
|
|
||||||
|
import com.intellij.util.io.DataExternalizer
|
||||||
|
import org.jetbrains.kotlin.incremental.KotlinClassInfo
|
||||||
|
import org.jetbrains.kotlin.incremental.storage.*
|
||||||
|
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
|
||||||
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import java.io.*
|
||||||
|
|
||||||
|
/** Utility to serialize a [ClasspathSnapshot]. */
|
||||||
|
object ClasspathSnapshotSerializer {
|
||||||
|
|
||||||
|
fun load(classpathEntrySnapshotFiles: List<File>): ClasspathSnapshot {
|
||||||
|
return ClasspathSnapshot(classpathEntrySnapshotFiles.map {
|
||||||
|
ClasspathEntrySnapshotSerializer.load(it)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object ClasspathEntrySnapshotSerializer : DataSerializer<ClasspathEntrySnapshot> {
|
||||||
|
|
||||||
|
override fun save(output: DataOutput, snapshot: ClasspathEntrySnapshot) {
|
||||||
|
LinkedHashMapExternalizer(StringExternalizer, ClassSnapshotDataSerializer).save(output, snapshot.classSnapshots)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(input: DataInput): ClasspathEntrySnapshot {
|
||||||
|
return ClasspathEntrySnapshot(
|
||||||
|
classSnapshots = LinkedHashMapExternalizer(StringExternalizer, ClassSnapshotDataSerializer).read(input)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object ClassSnapshotDataSerializer : DataSerializer<ClassSnapshot> {
|
||||||
|
|
||||||
|
override fun save(output: DataOutput, snapshot: ClassSnapshot) {
|
||||||
|
output.writeString(snapshot.javaClass.name)
|
||||||
|
when (snapshot) {
|
||||||
|
is KotlinClassSnapshot -> KotlinClassSnapshotExternalizer.save(output, snapshot)
|
||||||
|
is JavaClassSnapshot -> JavaClassSnapshotExternalizer.save(output, snapshot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(input: DataInput): ClassSnapshot {
|
||||||
|
return when (val className = input.readString()) {
|
||||||
|
KotlinClassSnapshot::class.java.name -> KotlinClassSnapshotExternalizer.read(input)
|
||||||
|
JavaClassSnapshot::class.java.name -> JavaClassSnapshotExternalizer.read(input)
|
||||||
|
else -> error("Unrecognized class: $className")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object KotlinClassSnapshotExternalizer : DataExternalizer<KotlinClassSnapshot> {
|
||||||
|
|
||||||
|
override fun save(output: DataOutput, snapshot: KotlinClassSnapshot) {
|
||||||
|
KotlinClassInfoExternalizer.save(output, snapshot.classInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(input: DataInput): KotlinClassSnapshot {
|
||||||
|
return KotlinClassSnapshot(classInfo = KotlinClassInfoExternalizer.read(input))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object KotlinClassInfoExternalizer : DataExternalizer<KotlinClassInfo> {
|
||||||
|
|
||||||
|
override fun save(output: DataOutput, info: KotlinClassInfo) {
|
||||||
|
ClassIdExternalizer.save(output, info.classId)
|
||||||
|
output.writeInt(info.classKind.id)
|
||||||
|
ListExternalizer(StringExternalizer).save(output, info.classHeaderData.toList())
|
||||||
|
ListExternalizer(StringExternalizer).save(output, info.classHeaderStrings.toList())
|
||||||
|
NullableValueExternalizer(StringExternalizer).save(output, info.multifileClassName)
|
||||||
|
LinkedHashMapExternalizer(StringExternalizer, ConstantExternalizer).save(output, info.constantsMap)
|
||||||
|
LinkedHashMapExternalizer(StringExternalizer, LongExternalizer).save(output, info.inlineFunctionsMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(input: DataInput): KotlinClassInfo {
|
||||||
|
return KotlinClassInfo(
|
||||||
|
classId = ClassIdExternalizer.read(input),
|
||||||
|
classKind = KotlinClassHeader.Kind.getById(input.readInt()),
|
||||||
|
classHeaderData = ListExternalizer(StringExternalizer).read(input).toTypedArray(),
|
||||||
|
classHeaderStrings = ListExternalizer(StringExternalizer).read(input).toTypedArray(),
|
||||||
|
multifileClassName = NullableValueExternalizer(StringExternalizer).read(input),
|
||||||
|
constantsMap = LinkedHashMapExternalizer(StringExternalizer, ConstantExternalizer).read(input),
|
||||||
|
inlineFunctionsMap = LinkedHashMapExternalizer(StringExternalizer, LongExternalizer).read(input)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object ClassIdExternalizer : DataExternalizer<ClassId> {
|
||||||
|
|
||||||
|
override fun save(output: DataOutput, classId: ClassId) {
|
||||||
|
FqNameExternalizer.save(output, classId.packageFqName)
|
||||||
|
FqNameExternalizer.save(output, classId.relativeClassName)
|
||||||
|
output.writeBoolean(classId.isLocal)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(input: DataInput): ClassId {
|
||||||
|
return ClassId(
|
||||||
|
/* packageFqName */ FqNameExternalizer.read(input),
|
||||||
|
/* relativeClassName */ FqNameExternalizer.read(input),
|
||||||
|
/* isLocal */ input.readBoolean()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object FqNameExternalizer : DataExternalizer<FqName> {
|
||||||
|
|
||||||
|
override fun save(output: DataOutput, fqName: FqName) {
|
||||||
|
output.writeString(fqName.asString())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(input: DataInput): FqName {
|
||||||
|
return FqName(input.readString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object JavaClassSnapshotExternalizer : DataExternalizer<JavaClassSnapshot> {
|
||||||
|
|
||||||
|
override fun save(output: DataOutput, snapshot: JavaClassSnapshot) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(input: DataInput): JavaClassSnapshot {
|
||||||
|
return JavaClassSnapshot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DataSerializer<T> : DataExternalizer<T> {
|
||||||
|
|
||||||
|
fun save(file: File, value: T) {
|
||||||
|
return FileOutputStream(file).buffered().use {
|
||||||
|
it.writeValue(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun load(file: File): T {
|
||||||
|
return FileInputStream(file).buffered().use {
|
||||||
|
it.readValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toByteArray(value: T): ByteArray {
|
||||||
|
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||||
|
byteArrayOutputStream.buffered().use {
|
||||||
|
it.writeValue(value)
|
||||||
|
}
|
||||||
|
return byteArrayOutputStream.toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fromByteArray(byteArray: ByteArray): T {
|
||||||
|
return ByteArrayInputStream(byteArray).buffered().use {
|
||||||
|
it.readValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun OutputStream.writeValue(value: T) {
|
||||||
|
DataOutputStream(this).use {
|
||||||
|
save(it, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun InputStream.readValue(): T {
|
||||||
|
return DataInputStream(this).use {
|
||||||
|
read(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,9 +32,8 @@ object ClasspathEntrySnapshotter {
|
|||||||
object ClassSnapshotter {
|
object ClassSnapshotter {
|
||||||
|
|
||||||
fun snapshot(classContents: ByteArray): ClassSnapshot {
|
fun snapshot(classContents: ByteArray): ClassSnapshot {
|
||||||
// TODO Add custom serialization to KotlinClassInfo then return it here. For now, use JavaClassSnapshot.
|
return KotlinClassInfo.tryCreateFrom(classContents)?.let { KotlinClassSnapshot(it) }
|
||||||
KotlinClassInfo.tryCreateFrom(classContents)?.let { KotlinClassSnapshot(it) }
|
?: JavaClassSnapshot
|
||||||
return JavaClassSnapshot()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import org.gradle.api.artifacts.transform.*
|
|||||||
import org.gradle.api.file.FileSystemLocation
|
import org.gradle.api.file.FileSystemLocation
|
||||||
import org.gradle.api.provider.Provider
|
import org.gradle.api.provider.Provider
|
||||||
import org.gradle.api.tasks.Classpath
|
import org.gradle.api.tasks.Classpath
|
||||||
import org.jetbrains.kotlin.gradle.incremental.ClasspathEntrySnapshotter
|
|
||||||
import org.jetbrains.kotlin.gradle.incremental.ClasspathEntrySnapshotSerializer
|
import org.jetbrains.kotlin.gradle.incremental.ClasspathEntrySnapshotSerializer
|
||||||
|
import org.jetbrains.kotlin.gradle.incremental.ClasspathEntrySnapshotter
|
||||||
|
|
||||||
/** Transform to create a snapshot ([CLASSPATH_ENTRY_SNAPSHOT_ARTIFACT_TYPE]) of a classpath entry (directory or jar). */
|
/** Transform to create a snapshot of a classpath entry (directory or jar). */
|
||||||
@CacheableTransform
|
@CacheableTransform
|
||||||
abstract class ClasspathEntrySnapshotTransform : TransformAction<TransformParameters.None> {
|
abstract class ClasspathEntrySnapshotTransform : TransformAction<TransformParameters.None> {
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ abstract class ClasspathEntrySnapshotTransform : TransformAction<TransformParame
|
|||||||
val snapshotFile = outputs.file(CLASSPATH_ENTRY_SNAPSHOT_FILE_NAME)
|
val snapshotFile = outputs.file(CLASSPATH_ENTRY_SNAPSHOT_FILE_NAME)
|
||||||
|
|
||||||
val snapshot = ClasspathEntrySnapshotter.snapshot(classpathEntry)
|
val snapshot = ClasspathEntrySnapshotter.snapshot(classpathEntry)
|
||||||
ClasspathEntrySnapshotSerializer.writeToFile(snapshot, snapshotFile)
|
ClasspathEntrySnapshotSerializer.save(snapshotFile, snapshot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -778,8 +778,8 @@ abstract class KotlinCompile @Inject constructor(
|
|||||||
val currentSnapshotFiles = classpathSnapshotProperties.classpathSnapshot.files.toList()
|
val currentSnapshotFiles = classpathSnapshotProperties.classpathSnapshot.files.toList()
|
||||||
val previousSnapshotFiles = getClasspathSnapshotFilesInDir(classpathSnapshotProperties.classpathSnapshotDir.get().asFile)
|
val previousSnapshotFiles = getClasspathSnapshotFilesInDir(classpathSnapshotProperties.classpathSnapshotDir.get().asFile)
|
||||||
|
|
||||||
val currentSnapshot = ClasspathSnapshotSerializer.readFromFiles(currentSnapshotFiles)
|
val currentSnapshot = ClasspathSnapshotSerializer.load(currentSnapshotFiles)
|
||||||
val previousSnapshot = ClasspathSnapshotSerializer.readFromFiles(previousSnapshotFiles)
|
val previousSnapshot = ClasspathSnapshotSerializer.load(previousSnapshotFiles)
|
||||||
|
|
||||||
return ClasspathChangesComputer.getChanges(currentSnapshot, previousSnapshot)
|
return ClasspathChangesComputer.getChanges(currentSnapshot, previousSnapshot)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.gradle.incremental
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
abstract class ClasspathSnapshotSerializerTest : ClasspathSnapshotTestCommon() {
|
||||||
|
|
||||||
|
protected abstract val testSourceFile: ChangeableTestSourceFile
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test ClassSnapshotDataSerializer`() {
|
||||||
|
val originalSnapshot = testSourceFile.compileAndSnapshot()
|
||||||
|
val serializedSnapshot = ClassSnapshotDataSerializer.toByteArray(originalSnapshot)
|
||||||
|
val deserializedSnapshot = ClassSnapshotDataSerializer.fromByteArray(serializedSnapshot)
|
||||||
|
|
||||||
|
assertEquals(originalSnapshot.toGson(), deserializedSnapshot.toGson())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class KotlinClassesClasspathSnapshotSerializerTest : ClasspathSnapshotSerializerTest() {
|
||||||
|
override val testSourceFile = SimpleKotlinClass(tmpDir)
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
package org.jetbrains.kotlin.gradle.incremental
|
package org.jetbrains.kotlin.gradle.incremental
|
||||||
|
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import org.jetbrains.kotlin.incremental.KotlinClassInfo
|
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.rules.TemporaryFolder
|
import org.junit.rules.TemporaryFolder
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -35,7 +34,7 @@ abstract class ClasspathSnapshotTestCommon {
|
|||||||
check(relativePath.endsWith(".class"))
|
check(relativePath.endsWith(".class"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun snapshot() = KotlinClassSnapshot(KotlinClassInfo.tryCreateFrom(asFile().readBytes())!!)
|
fun snapshot() = ClassSnapshotter.snapshot(asFile().readBytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
open class TestSourceFile(val sourceFile: SourceFile, val tmpDir: TemporaryFolder) {
|
open class TestSourceFile(val sourceFile: SourceFile, val tmpDir: TemporaryFolder) {
|
||||||
@@ -100,4 +99,4 @@ abstract class ClasspathSnapshotTestCommon {
|
|||||||
// Use Gson to compare objects
|
// Use Gson to compare objects
|
||||||
private val gson by lazy { GsonBuilder().setPrettyPrinting().create() }
|
private val gson by lazy { GsonBuilder().setPrettyPrinting().create() }
|
||||||
protected fun Any.toGson(): String = gson.toJson(this)
|
protected fun Any.toGson(): String = gson.toJson(this)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user