From 251d8ccd97b65ad9f3dc7733d5a960f4c6dc2cef Mon Sep 17 00:00:00 2001 From: Ivan Gavrilovic Date: Tue, 18 Jun 2019 19:34:01 +0900 Subject: [PATCH] KT-31840: Fix caching for incremental annotation processing Artifact transform that uses ASM to analyze KAPT classpath stored absolute paths in the output artifact. This resulted in remote build cache misses. This commit changes how analysis is stored. Actual analysis file is the output of the transform, and there is not need to use a marker file any more. Output does not store the classpath entry absolute path. Instead, it uses task action incremental information to find analysis outputs that changed. Additionally, class that handles analysis snapshot comparison has been simplified, and lazy loading of the structural information is handled in a more straightforward way. --- .../jetbrains/kotlin/gradle/BaseGradleIT.kt | 8 +- .../jetbrains/kotlin/gradle/BuildCacheIT.kt | 49 +++--- .../kotlin/gradle/internal/kapt/KaptTask.kt | 21 ++- .../kapt/incremental/ClasspathAnalyzer.kt | 24 +-- .../kapt/incremental/ClasspathSnapshot.kt | 151 ++++++++++-------- .../kapt/incremental/ClasspathAnalyzerTest.kt | 15 +- .../kapt/incremental/ClasspathSnapshotTest.kt | 59 +++---- 7 files changed, 152 insertions(+), 175 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt index ee03b3a1dd1..03d84c7d7fa 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt @@ -286,7 +286,12 @@ abstract class BaseGradleIT { build(*params, options = options.copy(kotlinDaemonDebugPort = debugPort), check = check) } - fun Project.build(vararg params: String, options: BuildOptions = defaultBuildOptions(), check: CompiledProject.() -> Unit) { + fun Project.build( + vararg params: String, + options: BuildOptions = defaultBuildOptions(), + projectDir: File = File(workingDir, projectName), + check: CompiledProject.() -> Unit + ) { val wrapperVersion = chooseWrapperVersionOrFinishTest() val env = createEnvironmentVariablesMap(options) @@ -295,7 +300,6 @@ abstract class BaseGradleIT { println("<=== Test build: ${this.projectName} $cmd ===>") - val projectDir = File(workingDir, projectName) if (!projectDir.exists()) { setupWorkingDir() } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildCacheIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildCacheIT.kt index 162d1fdb1fb..55c2218fa4f 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildCacheIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildCacheIT.kt @@ -135,34 +135,33 @@ class BuildCacheIT : BaseGradleIT() { } @Test - fun testKaptCachingEnabledByDefault() = with(Project("simple", GRADLE_VERSION, directoryPrefix = "kapt2")) { - prepareLocalBuildCache() + fun testKaptCachingWithIncrementalApt() { + with(Project("kaptAvoidance", GRADLE_VERSION, directoryPrefix = "kapt2")) { + prepareLocalBuildCache() - build("clean", "build") { - assertSuccessful() - assertContains("Packing task ':kaptGenerateStubsKotlin'") - assertContains("Packing task ':kaptKotlin'") - } - - build("clean", "build") { - assertSuccessful() - assertContains(":kaptGenerateStubsKotlin FROM-CACHE") - assertContains(":kaptKotlin FROM-CACHE") - } - - File(projectDir, "build.gradle").appendText( - "\n" + """ - afterEvaluate { - kaptKotlin.useBuildCache = false + val options = defaultBuildOptions().copy( + kaptOptions = KaptOptions( + verbose = true, + useWorkers = false, + incrementalKapt = true, + includeCompileClasspath = false + ) + ) + build(options = options, params = *arrayOf("clean", ":app:build")) { + assertSuccessful() + assertContains("Packing task ':app:kaptGenerateStubsKotlin'") + assertContains("Packing task ':app:kaptKotlin'") } - """.trimIndent() - ) - build("clean", "build") { - assertSuccessful() - assertContains(":kaptGenerateStubsKotlin FROM-CACHE") - assertNotContains(":kaptKotlin FROM-CACHE") - assertContains("Caching disabled for task ':kaptKotlin': 'Caching is disabled for kapt") + // copy project to a new location + val copyProject = projectDir.resolveSibling("copy_${projectDir.name}").also { it.mkdirs() } + copyRecursively(projectDir, copyProject) + + build(options = options, projectDir = copyProject.resolve(projectName), params = *arrayOf("clean", "build")) { + assertSuccessful() + assertContains(":app:kaptGenerateStubsKotlin FROM-CACHE") + assertContains(":app:kaptKotlin FROM-CACHE") + } } } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptTask.kt index d1d050b82a5..3010a3dd6fe 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptTask.kt @@ -6,8 +6,8 @@ import org.gradle.api.file.FileCollection import org.gradle.api.internal.ConventionTask import org.gradle.api.tasks.* import org.gradle.api.tasks.incremental.IncrementalTaskInputs -import org.jetbrains.kotlin.gradle.internal.kapt.incremental.KaptClasspathChanges import org.jetbrains.kotlin.gradle.internal.kapt.incremental.ClasspathSnapshot +import org.jetbrains.kotlin.gradle.internal.kapt.incremental.KaptClasspathChanges import org.jetbrains.kotlin.gradle.internal.kapt.incremental.KaptIncrementalChanges import org.jetbrains.kotlin.gradle.internal.kapt.incremental.UnknownSnapshot import org.jetbrains.kotlin.gradle.internal.tasks.TaskWithLocalState @@ -171,21 +171,21 @@ abstract class KaptTask : ConventionTask(), TaskWithLocalState { clearLocalState() return KaptIncrementalChanges.Unknown } + val allDataFiles = classpathStructure!!.files if (!inputs.isIncremental) { clearLocalState() - findClasspathChanges(classpath.files) + findClasspathChanges(allDataFiles, allDataFiles) return KaptIncrementalChanges.Unknown } val changedFiles = with(mutableSetOf()) { inputs.outOfDate { this.add(it.file) } inputs.removed { this.add(it.file) } - return@with this.toList() + return@with this } - val classpathChanges = classpath.files.let { cp -> changedFiles.filter { cp.contains(it) } } - - val classpathStatus = findClasspathChanges(classpathChanges) + val changedDataFiles = allDataFiles.filterTo(HashSet()) { it in changedFiles } + val classpathStatus = findClasspathChanges(allDataFiles, changedDataFiles) return when (classpathStatus) { is KaptClasspathChanges.Unknown -> KaptIncrementalChanges.Unknown is KaptClasspathChanges.Known -> KaptIncrementalChanges.Known( @@ -194,18 +194,17 @@ abstract class KaptTask : ConventionTask(), TaskWithLocalState { } } - private fun findClasspathChanges(changedClasspath: Iterable): KaptClasspathChanges { + private fun findClasspathChanges(allDataFile: Set, changedDataFiles: Set): KaptClasspathChanges { val incAptCacheDir = incAptCache!! incAptCacheDir.mkdirs() val startTime = System.currentTimeMillis() val previousSnapshot = ClasspathSnapshot.ClasspathSnapshotFactory.loadFrom(incAptCacheDir) - val currentSnapshot = ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent( - incAptCacheDir, classpath.files.toList(), classpathStructure!!.files - ) + val currentSnapshot = + ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(incAptCacheDir, classpath.files.toList(), allDataFile) - val classpathChanges = currentSnapshot.diff(previousSnapshot, changedClasspath.toSet()) + val classpathChanges = currentSnapshot.diff(previousSnapshot, changedDataFiles) currentSnapshot.writeToCache() if (logger.isInfoEnabled) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/incremental/ClasspathAnalyzer.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/incremental/ClasspathAnalyzer.kt index ce5c6b80e6b..7b33aa7fd09 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/incremental/ClasspathAnalyzer.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/incremental/ClasspathAnalyzer.kt @@ -26,10 +26,7 @@ class StructureArtifactTransform : ArtifactTransform() { val dataFile = outputDirectory.resolve("output.bin") data.saveTo(dataFile) - val lazyStructureFile = outputDirectory.resolve("lazy-output.bin") - LazyClasspathEntryData(input, dataFile).saveToFile(lazyStructureFile) - - return mutableListOf(lazyStructureFile) + return mutableListOf(dataFile) } catch (e: Throwable) { throw e } @@ -86,25 +83,6 @@ private fun analyzeInputStream(input: InputStream, internalName: String, entryDa ClassDependencies(typeDependenciesExtractor.getAbiTypes(), typeDependenciesExtractor.getPrivateTypes()) } -class LazyClasspathEntryData(val classpathEntry: File, private val dataFile: File) : Serializable { - - object LazyClasspathEntrySerializer { - fun loadFromFile(file: File): LazyClasspathEntryData { - ObjectInputStream(BufferedInputStream(file.inputStream())).use { - return it.readObject() as LazyClasspathEntryData - } - } - } - - fun saveToFile(file: File) { - ObjectOutputStream(BufferedOutputStream(file.outputStream())).use { - it.writeObject(this) - } - } - - fun getClasspathEntryData(): ClasspathEntryData = ClasspathEntryData.ClasspathEntrySerializer.loadFrom(dataFile) -} - class ClasspathEntryData : Serializable { object ClasspathEntrySerializer { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/incremental/ClasspathSnapshot.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/incremental/ClasspathSnapshot.kt index fe44f91156a..5e177539fd7 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/incremental/ClasspathSnapshot.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/incremental/ClasspathSnapshot.kt @@ -7,61 +7,46 @@ package org.jetbrains.kotlin.gradle.internal.kapt.incremental import java.io.* import java.util.* +import kotlin.collections.HashMap + +private const val CLASSPATH_ENTRIES_FILE = "classpath-entries.bin" +private const val CLASSPATH_STRUCTURE_FILE = "classpath-structure.bin" open class ClasspathSnapshot protected constructor( private val cacheDir: File, - private val classpath: Iterable, - val dataForFiles: (Set) -> Map + private val classpath: List, + private val dataForFiles: MutableMap ) { - val classpathData: (Set) -> Map = { files -> - val missingFiles = files.filter { !computedClasspathData.keys.contains(it) }.toSet() - - if (!missingFiles.isEmpty()) { - val computedData = dataForFiles(missingFiles) - computedClasspathData.putAll(computedData) - } - computedClasspathData - } - private val computedClasspathData: MutableMap = mutableMapOf() - object ClasspathSnapshotFactory { fun loadFrom(cacheDir: File): ClasspathSnapshot { - val classpathEntries = cacheDir.resolve("classpath-entries.bin") - val classpathStructureData = cacheDir.resolve("classpath-structure.bin") + val classpathEntries = cacheDir.resolve(CLASSPATH_ENTRIES_FILE) + val classpathStructureData = cacheDir.resolve(CLASSPATH_STRUCTURE_FILE) if (!classpathEntries.exists() || !classpathStructureData.exists()) { return UnknownSnapshot } val classpathFiles = ObjectInputStream(BufferedInputStream(classpathEntries.inputStream())).use { @Suppress("UNCHECKED_CAST") - it.readObject() as Iterable + it.readObject() as List } - val classpathData = { _: Set -> - loadPreviousData(classpathStructureData) - } - return ClasspathSnapshot(cacheDir, classpathFiles, classpathData) + val dataForFiles = + ObjectInputStream(BufferedInputStream(classpathStructureData.inputStream())).use { + @Suppress("UNCHECKED_CAST") + it.readObject() as MutableMap + } + return ClasspathSnapshot(cacheDir, classpathFiles, dataForFiles) } - fun createCurrent(cacheDir: File, classpath: Iterable, lazyClasspathData: Set): ClasspathSnapshot { - val lazyData = lazyClasspathData.map { LazyClasspathEntryData.LazyClasspathEntrySerializer.loadFromFile(it) } - val data = { files: Set -> - lazyData.filter { files.contains(it.classpathEntry) }.associate { it.classpathEntry to it.getClasspathEntryData() } - } + fun createCurrent(cacheDir: File, classpath: List, allStructureData: Set): ClasspathSnapshot { + val data = allStructureData.associateTo(HashMap(allStructureData.size)) { it to null } return ClasspathSnapshot(cacheDir, classpath, data) } - - private fun loadPreviousData(file: File): Map { - ObjectInputStream(BufferedInputStream(file.inputStream())).use { - @Suppress("UNCHECKED_CAST") - return it.readObject() as Map - } - } } private fun isCompatible(snapshot: ClasspathSnapshot) = - this != UnknownSnapshot && classpath == snapshot.classpath + this != UnknownSnapshot && snapshot != UnknownSnapshot && classpath == snapshot.classpath /** Compare this snapshot with the specified one only for the specified files. */ fun diff(previousSnapshot: ClasspathSnapshot, changedFiles: Set): KaptClasspathChanges { @@ -69,54 +54,88 @@ open class ClasspathSnapshot protected constructor( return KaptClasspathChanges.Unknown } - val currentData = classpathData(changedFiles) - val previousData = previousSnapshot.classpathData(changedFiles) + loadEntriesFor(changedFiles) + + val currentHashAbiSize = changedFiles.sumBy { dataForFiles[it]!!.classAbiHash.size } + val currentHashesToAnalyze = + HashMap(currentHashAbiSize).also { hashes -> + changedFiles.forEach { + hashes.putAll(dataForFiles[it]!!.classAbiHash) + } + } + + val currentUnchanged = dataForFiles.keys.filter { it !in changedFiles } + val previousChanged = previousSnapshot.dataForFiles.keys.filter { it !in currentUnchanged } + + check(changedFiles.size == previousChanged.size) { + """ + Number of changed files in snapshots differs. Reported changed files: $changedFiles + Current snapshot data files: ${dataForFiles.keys} + Previous snapshot data files: ${previousSnapshot.dataForFiles.keys} + """.trimIndent() + } + + val previousHashAbiSize = previousChanged.sumBy { previousSnapshot.dataForFiles.get(it)?.classAbiHash?.size ?: 0 } + val previousHashesToAnalyze = + HashMap(previousHashAbiSize).also { hashes -> + for (c in previousChanged) { + previousSnapshot.dataForFiles[c]?.let { + hashes.putAll(it.classAbiHash) + } + } + } val changedClasses = mutableSetOf() - - for (changed in changedFiles) { - val previous = previousData.getValue(changed) - val current = currentData.getValue(changed) - - for (key in previous.classAbiHash.keys + current.classAbiHash.keys) { - val previousHash = previous.classAbiHash[key] - if (previousHash == null) { - changedClasses.add(key) - continue - } - val currentHash = current.classAbiHash[key] - if (currentHash == null) { - changedClasses.add(key) - continue - } - if (!previousHash.contentEquals(currentHash)) { - changedClasses.add(key) - } + for (key in previousHashesToAnalyze.keys + currentHashesToAnalyze.keys) { + val previousHash = previousHashesToAnalyze[key] + if (previousHash == null) { + changedClasses.add(key) + continue + } + val currentHash = currentHashesToAnalyze[key] + if (currentHash == null) { + changedClasses.add(key) + continue + } + if (!previousHash.contentEquals(currentHash)) { + changedClasses.add(key) } } // We do not compute structural data for unchanged files of the current snapshot for performance reasons. // That is why we reuse the previous snapshot as that one contains all unchanged entries. - previousData.filterTo(computedClasspathData) { (key, _) -> key !in computedClasspathData } + for (unchanged in currentUnchanged) { + dataForFiles[unchanged] = previousSnapshot.dataForFiles[unchanged]!! + } val allImpactedClasses = findAllImpacted(changedClasses) return KaptClasspathChanges.Known(allImpactedClasses) } + private fun loadEntriesFor(file: Iterable) { + for (f in file) { + if (dataForFiles[f] == null) { + dataForFiles[f] = ClasspathEntryData.ClasspathEntrySerializer.loadFrom(f) + } + } + } + + private fun loadAll() { + loadEntriesFor(dataForFiles.keys) + } + fun writeToCache() { - val classpathEntries = cacheDir.resolve("classpath-entries.bin") + loadAll() + + val classpathEntries = cacheDir.resolve(CLASSPATH_ENTRIES_FILE) ObjectOutputStream(BufferedOutputStream(classpathEntries.outputStream())).use { it.writeObject(classpath) } - val classpathStructureData = cacheDir.resolve("classpath-structure.bin") - storeCurrentStructure(classpathStructureData, classpathData(classpath.toSet())) - } - - private fun storeCurrentStructure(file: File, structure: Map) { - ObjectOutputStream(BufferedOutputStream(file.outputStream())).use { - it.writeObject(structure) + val classpathStructureData = cacheDir.resolve(CLASSPATH_STRUCTURE_FILE) + ObjectOutputStream(BufferedOutputStream(classpathStructureData.outputStream())).use { + it.writeObject(dataForFiles) } } @@ -125,8 +144,8 @@ open class ClasspathSnapshot protected constructor( val transitiveDeps = HashMap>() val nonTransitiveDeps = HashMap>() - for (entry in computedClasspathData.values) { - for ((className, classDependency) in entry.classDependencies) { + for (entry in dataForFiles.values) { + for ((className, classDependency) in entry!!.classDependencies) { for (abiType in classDependency.abiTypes) { (transitiveDeps[abiType] ?: LinkedList()).let { it.add(className) @@ -165,7 +184,7 @@ open class ClasspathSnapshot protected constructor( } } -object UnknownSnapshot : ClasspathSnapshot(File(""), emptyList(), { emptyMap() }) +object UnknownSnapshot : ClasspathSnapshot(File(""), emptyList(), mutableMapOf()) sealed class KaptIncrementalChanges { object Unknown : KaptIncrementalChanges() diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/incremental/ClasspathAnalyzerTest.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/incremental/ClasspathAnalyzerTest.kt index 7ae00c316a2..57b8378e16e 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/incremental/ClasspathAnalyzerTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/incremental/ClasspathAnalyzerTest.kt @@ -35,10 +35,7 @@ class ClasspathAnalyzerTest { val transform = StructureArtifactTransform().also { it.outputDirectory = tmp.newFolder() } val outputs = transform.transform(classesDir) - val lazyData = LazyClasspathEntryData.LazyClasspathEntrySerializer.loadFromFile(outputs.single()) - assertEquals(classesDir, lazyData.classpathEntry) - - val data = lazyData.getClasspathEntryData() + val data = ClasspathEntryData.ClasspathEntrySerializer.loadFrom(outputs.single()) assertEquals(setOf("test/A", "test/B"), data.classAbiHash.keys) assertEquals(setOf("test/A", "test/B"), data.classDependencies.keys) assertEquals(emptySet(), data.classDependencies["test/A"]!!.abiTypes) @@ -71,10 +68,7 @@ class ClasspathAnalyzerTest { val transform = StructureArtifactTransform().also { it.outputDirectory = tmp.newFolder() } val outputs = transform.transform(inputJar) - val lazyData = LazyClasspathEntryData.LazyClasspathEntrySerializer.loadFromFile(outputs.single()) - assertEquals(inputJar, lazyData.classpathEntry) - - val data = lazyData.getClasspathEntryData() + val data = ClasspathEntryData.ClasspathEntrySerializer.loadFrom(outputs.single()) assertEquals(setOf("test/A", "test/B"), data.classAbiHash.keys) assertEquals(setOf("test/A", "test/B"), data.classDependencies.keys) assertEquals(emptySet(), data.classDependencies["test/A"]!!.abiTypes) @@ -90,10 +84,7 @@ class ClasspathAnalyzerTest { val transform = StructureArtifactTransform().also { it.outputDirectory = tmp.newFolder() } val outputs = transform.transform(inputDir) - val lazyData = LazyClasspathEntryData.LazyClasspathEntrySerializer.loadFromFile(outputs.single()) - assertEquals(inputDir, lazyData.classpathEntry) - - val data = lazyData.getClasspathEntryData() + val data = ClasspathEntryData.ClasspathEntrySerializer.loadFrom(outputs.single()) assertTrue(data.classAbiHash.isEmpty()) assertTrue(data.classDependencies.isEmpty()) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/incremental/ClasspathSnapshotTest.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/incremental/ClasspathSnapshotTest.kt index 59353f67e64..9df969dfc45 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/incremental/ClasspathSnapshotTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/incremental/ClasspathSnapshotTest.kt @@ -18,93 +18,80 @@ class ClasspathSnapshotTest { @Test fun testSerialization() { - val (firstJar, lazyData) = generateLazyData( + val data = generateStructureData( ClassData("first/A"), ClassData("first/B") ) val snapshotDir = tmp.newFolder() - val currentSnapshot = ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(snapshotDir, listOf(firstJar), setOf(lazyData)) - assertEquals(KaptClasspathChanges.Unknown, currentSnapshot.diff(UnknownSnapshot, setOf(firstJar))) + val currentSnapshot = ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(snapshotDir, listOf(), setOf(data)) + assertEquals(KaptClasspathChanges.Unknown, currentSnapshot.diff(UnknownSnapshot, setOf(data))) currentSnapshot.writeToCache() val loadedSnapshot = ClasspathSnapshot.ClasspathSnapshotFactory.loadFrom(snapshotDir) - val diff = loadedSnapshot.diff(currentSnapshot, setOf(firstJar)) as KaptClasspathChanges.Known + val diff = loadedSnapshot.diff(currentSnapshot, setOf(data)) as KaptClasspathChanges.Known assertEquals(emptySet(), diff.names) } @Test fun testIncompatibleClasspaths() { - val firstSnapshot = ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(File(""), listOf(File("1.jar")), emptySet()) + val firstSnapshot = ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(File(""), listOf(File("a.jar")), emptySet()) val secondSnapshot = - ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(File(""), listOf(File("1.jar"), File("added.jar")), emptySet()) - assertEquals(KaptClasspathChanges.Unknown, firstSnapshot.diff(secondSnapshot, setOf(File("added.jar")))) + ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(File(""), listOf(File("b.jar")), setOf(File(""))) + assertEquals(KaptClasspathChanges.Unknown, firstSnapshot.diff(secondSnapshot, emptySet())) } @Test fun testChangedClassesFound() { - val (firstJar, firstLazyData) = generateLazyData( + val dataFile = generateStructureData( ClassData("first/A"), ClassData("first/B").also { it.withAbiDependencies("first/A") } ) - val firstSnapshot = ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(File(""), listOf(firstJar), setOf(firstLazyData)) - firstSnapshot.diff(UnknownSnapshot, setOf(firstJar)) + val firstSnapshot = ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(tmp.newFolder(), listOf(), setOf(dataFile)) + firstSnapshot.writeToCache() - val (_, changedLazyData) = generateLazyData( + generateStructureData( ClassData("first/A", ByteArray(1)), ClassData("first/B").also { it.withAbiDependencies("first/A") }, - jarInput = firstJar + outputFile = dataFile ) - val changedSnapshot = ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(File(""), listOf(firstJar), setOf(changedLazyData)) + val changedSnapshot = ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(tmp.newFolder(), listOf(), setOf(dataFile)) - val diff = changedSnapshot.diff(firstSnapshot, setOf(firstJar)) as KaptClasspathChanges.Known + val diff = changedSnapshot.diff(firstSnapshot, setOf(dataFile)) as KaptClasspathChanges.Known assertEquals(setOf("first/A", "first/B"), diff.names) } @Test fun testChangedClassesAcrossEntries() { - val (firstJar, firstLazyData) = generateLazyData( + val dataFile = generateStructureData( ClassData("first/A").also { it.withAbiDependencies("library/C") }, ClassData("first/B").also { it.withAbiDependencies("first/A") } ) - val (libraryJar, libraryLazyData) = generateLazyData(ClassData("library/C")) + val libraryDataFile = generateStructureData(ClassData("library/C")) val cacheDir = tmp.newFolder() val firstSnapshot = - ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent( - cacheDir, - listOf(firstJar, libraryJar), - setOf(firstLazyData, libraryLazyData) - ) - firstSnapshot.diff(UnknownSnapshot, setOf(firstJar, libraryJar)) + ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(cacheDir, listOf(), setOf(dataFile, libraryDataFile)) firstSnapshot.writeToCache() - val (_, changedLazyData) = generateLazyData(ClassData("library/C", ByteArray(1)), jarInput = libraryJar) + generateStructureData(ClassData("library/C", ByteArray(1)), outputFile = libraryDataFile) val changedSnapshot = - ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent( - cacheDir, - listOf(firstJar, libraryJar), - setOf(firstLazyData, changedLazyData) - ) + ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(cacheDir, listOf(), setOf(dataFile, libraryDataFile)) - val diff = changedSnapshot.diff(firstSnapshot, setOf(libraryJar)) as KaptClasspathChanges.Known + val diff = changedSnapshot.diff(firstSnapshot, setOf(libraryDataFile)) as KaptClasspathChanges.Known assertEquals(setOf("library/C", "first/A", "first/B"), diff.names) } - private fun generateLazyData( - vararg classData: ClassData, - jarInput: File = tmp.newFile() - ): Pair { + private fun generateStructureData(vararg classData: ClassData, outputFile: File = tmp.newFile()): File { val data = ClasspathEntryData() classData.forEach { data.classAbiHash[it.internalName] = it.hash data.classDependencies[it.internalName] = ClassDependencies(it.abiDeps, it.privateDeps) } - val serialized = tmp.newFile().also { data.saveTo(it) } - val lazyData = tmp.newFile().also { LazyClasspathEntryData(jarInput, serialized).saveToFile(it) } + data.saveTo(outputFile) - return Pair(jarInput, lazyData) + return outputFile } private class ClassData(