KT-36113: Make KAPT classpath snapshot deterministic

When snapshotting a classpath entry, sort information about types
so there is no dependency on the order of entries in jar.

Test: ClasspathAnalyzerTest
This commit is contained in:
Ivan Gavrilovic
2020-01-27 18:42:51 +00:00
committed by Yan Zhulanow
parent 9b68e4fe56
commit 6d4eb9ae52
2 changed files with 52 additions and 16 deletions
@@ -105,32 +105,38 @@ class ClasspathEntryData : Serializable {
var classDependencies = mutableMapOf<String, ClassDependencies>()
private fun writeObject(output: ObjectOutputStream) {
val names = mutableMapOf<String, Int>()
classAbiHash.keys.forEach { names[it] = names.size }
classDependencies.values.forEach {
it.abiTypes.forEach {
if (!names.containsKey(it)) names[it] = names.size
// Sort only classDependencies, as all keys in this map are keys of classAbiHash map.
val sortedClassDependencies =
classDependencies.toSortedMap().mapValues { ClassDependencies(it.value.abiTypes.sorted(), it.value.privateTypes.sorted()) }
val names = LinkedHashMap<String, Int>()
sortedClassDependencies.forEach {
names[it.key] = names.size
it.value.abiTypes.forEach { type ->
if (type !in names) names[type] = names.size
}
it.privateTypes.forEach {
if (!names.containsKey(it)) names[it] = names.size
it.value.privateTypes.forEach { type ->
if (type !in names) names[type] = names.size
}
}
output.writeInt(names.size)
names.forEach { key, value ->
names.forEach { (key, value) ->
output.writeInt(value)
output.writeUTF(key)
}
output.writeInt(classAbiHash.size)
classAbiHash.forEach {
output.writeInt(names[it.key]!!)
output.writeInt(it.value.size)
output.write(it.value)
sortedClassDependencies.forEach { (key, _) ->
output.writeInt(names[key]!!)
classAbiHash[key]!!.let {
output.writeInt(it.size)
output.write(it)
}
}
output.writeInt(classDependencies.size)
classDependencies.forEach {
output.writeInt(sortedClassDependencies.size)
sortedClassDependencies.forEach {
output.writeInt(names[it.key]!!)
output.writeInt(it.value.abiTypes.size)
@@ -7,8 +7,7 @@ package org.jetbrains.kotlin.gradle.internal.kapt.incremental
import org.jetbrains.org.objectweb.asm.ClassWriter
import org.jetbrains.org.objectweb.asm.Opcodes
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
@@ -82,6 +81,37 @@ class ClasspathAnalyzerTest {
assertEquals(emptySet<String>(), data.classDependencies["test/B"]!!.privateTypes)
}
@Test
fun testJarWithEntriesShuffled() {
val jarA = tmp.newFile("inputA.jar").also { jar ->
ZipOutputStream(jar.outputStream()).use {
it.putNextEntry(ZipEntry("test/A.class"))
it.write(emptyClass("test/A"))
it.closeEntry()
it.putNextEntry(ZipEntry("test/B.class"))
it.write(emptyClass("test/B"))
it.closeEntry()
}
}
val outputA = StructureArtifactTransform().also { it.outputDirectory = tmp.newFolder() }.transform(jarA).single()
val jarB = tmp.newFile("inputB.jar").also { jar ->
ZipOutputStream(jar.outputStream()).use {
it.putNextEntry(ZipEntry("test/B.class"))
it.write(emptyClass("test/B"))
it.closeEntry()
it.putNextEntry(ZipEntry("test/A.class"))
it.write(emptyClass("test/A"))
it.closeEntry()
}
}
val outputB = StructureArtifactTransform().also { it.outputDirectory = tmp.newFolder() }.transform(jarB).single()
assertArrayEquals(outputA.readBytes(), outputB.readBytes())
}
@Test
fun emptyInput() {
val inputDir = tmp.newFolder("input")