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.
This commit is contained in:
committed by
Yan Zhulanow
parent
6963424dfc
commit
251d8ccd97
+6
-2
@@ -286,7 +286,12 @@ abstract class BaseGradleIT {
|
|||||||
build(*params, options = options.copy(kotlinDaemonDebugPort = debugPort), check = check)
|
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 wrapperVersion = chooseWrapperVersionOrFinishTest()
|
||||||
|
|
||||||
val env = createEnvironmentVariablesMap(options)
|
val env = createEnvironmentVariablesMap(options)
|
||||||
@@ -295,7 +300,6 @@ abstract class BaseGradleIT {
|
|||||||
|
|
||||||
println("<=== Test build: ${this.projectName} $cmd ===>")
|
println("<=== Test build: ${this.projectName} $cmd ===>")
|
||||||
|
|
||||||
val projectDir = File(workingDir, projectName)
|
|
||||||
if (!projectDir.exists()) {
|
if (!projectDir.exists()) {
|
||||||
setupWorkingDir()
|
setupWorkingDir()
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-25
@@ -135,34 +135,33 @@ class BuildCacheIT : BaseGradleIT() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testKaptCachingEnabledByDefault() = with(Project("simple", GRADLE_VERSION, directoryPrefix = "kapt2")) {
|
fun testKaptCachingWithIncrementalApt() {
|
||||||
prepareLocalBuildCache()
|
with(Project("kaptAvoidance", GRADLE_VERSION, directoryPrefix = "kapt2")) {
|
||||||
|
prepareLocalBuildCache()
|
||||||
|
|
||||||
build("clean", "build") {
|
val options = defaultBuildOptions().copy(
|
||||||
assertSuccessful()
|
kaptOptions = KaptOptions(
|
||||||
assertContains("Packing task ':kaptGenerateStubsKotlin'")
|
verbose = true,
|
||||||
assertContains("Packing task ':kaptKotlin'")
|
useWorkers = false,
|
||||||
}
|
incrementalKapt = true,
|
||||||
|
includeCompileClasspath = false
|
||||||
build("clean", "build") {
|
)
|
||||||
assertSuccessful()
|
)
|
||||||
assertContains(":kaptGenerateStubsKotlin FROM-CACHE")
|
build(options = options, params = *arrayOf("clean", ":app:build")) {
|
||||||
assertContains(":kaptKotlin FROM-CACHE")
|
assertSuccessful()
|
||||||
}
|
assertContains("Packing task ':app:kaptGenerateStubsKotlin'")
|
||||||
|
assertContains("Packing task ':app:kaptKotlin'")
|
||||||
File(projectDir, "build.gradle").appendText(
|
|
||||||
"\n" + """
|
|
||||||
afterEvaluate {
|
|
||||||
kaptKotlin.useBuildCache = false
|
|
||||||
}
|
}
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
|
|
||||||
build("clean", "build") {
|
// copy project to a new location
|
||||||
assertSuccessful()
|
val copyProject = projectDir.resolveSibling("copy_${projectDir.name}").also { it.mkdirs() }
|
||||||
assertContains(":kaptGenerateStubsKotlin FROM-CACHE")
|
copyRecursively(projectDir, copyProject)
|
||||||
assertNotContains(":kaptKotlin FROM-CACHE")
|
|
||||||
assertContains("Caching disabled for task ':kaptKotlin': 'Caching is disabled for kapt")
|
build(options = options, projectDir = copyProject.resolve(projectName), params = *arrayOf("clean", "build")) {
|
||||||
|
assertSuccessful()
|
||||||
|
assertContains(":app:kaptGenerateStubsKotlin FROM-CACHE")
|
||||||
|
assertContains(":app:kaptKotlin FROM-CACHE")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-11
@@ -6,8 +6,8 @@ import org.gradle.api.file.FileCollection
|
|||||||
import org.gradle.api.internal.ConventionTask
|
import org.gradle.api.internal.ConventionTask
|
||||||
import org.gradle.api.tasks.*
|
import org.gradle.api.tasks.*
|
||||||
import org.gradle.api.tasks.incremental.IncrementalTaskInputs
|
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.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.KaptIncrementalChanges
|
||||||
import org.jetbrains.kotlin.gradle.internal.kapt.incremental.UnknownSnapshot
|
import org.jetbrains.kotlin.gradle.internal.kapt.incremental.UnknownSnapshot
|
||||||
import org.jetbrains.kotlin.gradle.internal.tasks.TaskWithLocalState
|
import org.jetbrains.kotlin.gradle.internal.tasks.TaskWithLocalState
|
||||||
@@ -171,21 +171,21 @@ abstract class KaptTask : ConventionTask(), TaskWithLocalState {
|
|||||||
clearLocalState()
|
clearLocalState()
|
||||||
return KaptIncrementalChanges.Unknown
|
return KaptIncrementalChanges.Unknown
|
||||||
}
|
}
|
||||||
|
val allDataFiles = classpathStructure!!.files
|
||||||
if (!inputs.isIncremental) {
|
if (!inputs.isIncremental) {
|
||||||
clearLocalState()
|
clearLocalState()
|
||||||
findClasspathChanges(classpath.files)
|
findClasspathChanges(allDataFiles, allDataFiles)
|
||||||
return KaptIncrementalChanges.Unknown
|
return KaptIncrementalChanges.Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
val changedFiles = with(mutableSetOf<File>()) {
|
val changedFiles = with(mutableSetOf<File>()) {
|
||||||
inputs.outOfDate { this.add(it.file) }
|
inputs.outOfDate { this.add(it.file) }
|
||||||
inputs.removed { 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 changedDataFiles = allDataFiles.filterTo(HashSet<File>()) { it in changedFiles }
|
||||||
|
val classpathStatus = findClasspathChanges(allDataFiles, changedDataFiles)
|
||||||
val classpathStatus = findClasspathChanges(classpathChanges)
|
|
||||||
return when (classpathStatus) {
|
return when (classpathStatus) {
|
||||||
is KaptClasspathChanges.Unknown -> KaptIncrementalChanges.Unknown
|
is KaptClasspathChanges.Unknown -> KaptIncrementalChanges.Unknown
|
||||||
is KaptClasspathChanges.Known -> KaptIncrementalChanges.Known(
|
is KaptClasspathChanges.Known -> KaptIncrementalChanges.Known(
|
||||||
@@ -194,18 +194,17 @@ abstract class KaptTask : ConventionTask(), TaskWithLocalState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findClasspathChanges(changedClasspath: Iterable<File>): KaptClasspathChanges {
|
private fun findClasspathChanges(allDataFile: Set<File>, changedDataFiles: Set<File>): KaptClasspathChanges {
|
||||||
val incAptCacheDir = incAptCache!!
|
val incAptCacheDir = incAptCache!!
|
||||||
incAptCacheDir.mkdirs()
|
incAptCacheDir.mkdirs()
|
||||||
|
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
|
|
||||||
val previousSnapshot = ClasspathSnapshot.ClasspathSnapshotFactory.loadFrom(incAptCacheDir)
|
val previousSnapshot = ClasspathSnapshot.ClasspathSnapshotFactory.loadFrom(incAptCacheDir)
|
||||||
val currentSnapshot = ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(
|
val currentSnapshot =
|
||||||
incAptCacheDir, classpath.files.toList(), classpathStructure!!.files
|
ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(incAptCacheDir, classpath.files.toList(), allDataFile)
|
||||||
)
|
|
||||||
|
|
||||||
val classpathChanges = currentSnapshot.diff(previousSnapshot, changedClasspath.toSet())
|
val classpathChanges = currentSnapshot.diff(previousSnapshot, changedDataFiles)
|
||||||
currentSnapshot.writeToCache()
|
currentSnapshot.writeToCache()
|
||||||
|
|
||||||
if (logger.isInfoEnabled) {
|
if (logger.isInfoEnabled) {
|
||||||
|
|||||||
+1
-23
@@ -26,10 +26,7 @@ class StructureArtifactTransform : ArtifactTransform() {
|
|||||||
val dataFile = outputDirectory.resolve("output.bin")
|
val dataFile = outputDirectory.resolve("output.bin")
|
||||||
data.saveTo(dataFile)
|
data.saveTo(dataFile)
|
||||||
|
|
||||||
val lazyStructureFile = outputDirectory.resolve("lazy-output.bin")
|
return mutableListOf(dataFile)
|
||||||
LazyClasspathEntryData(input, dataFile).saveToFile(lazyStructureFile)
|
|
||||||
|
|
||||||
return mutableListOf(lazyStructureFile)
|
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
@@ -86,25 +83,6 @@ private fun analyzeInputStream(input: InputStream, internalName: String, entryDa
|
|||||||
ClassDependencies(typeDependenciesExtractor.getAbiTypes(), typeDependenciesExtractor.getPrivateTypes())
|
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 {
|
class ClasspathEntryData : Serializable {
|
||||||
|
|
||||||
object ClasspathEntrySerializer {
|
object ClasspathEntrySerializer {
|
||||||
|
|||||||
+85
-66
@@ -7,61 +7,46 @@ package org.jetbrains.kotlin.gradle.internal.kapt.incremental
|
|||||||
|
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.util.*
|
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(
|
open class ClasspathSnapshot protected constructor(
|
||||||
private val cacheDir: File,
|
private val cacheDir: File,
|
||||||
private val classpath: Iterable<File>,
|
private val classpath: List<File>,
|
||||||
val dataForFiles: (Set<File>) -> Map<File, ClasspathEntryData>
|
private val dataForFiles: MutableMap<File, ClasspathEntryData?>
|
||||||
) {
|
) {
|
||||||
val classpathData: (Set<File>) -> Map<File, ClasspathEntryData> = { 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<File, ClasspathEntryData> = mutableMapOf()
|
|
||||||
|
|
||||||
object ClasspathSnapshotFactory {
|
object ClasspathSnapshotFactory {
|
||||||
fun loadFrom(cacheDir: File): ClasspathSnapshot {
|
fun loadFrom(cacheDir: File): ClasspathSnapshot {
|
||||||
val classpathEntries = cacheDir.resolve("classpath-entries.bin")
|
val classpathEntries = cacheDir.resolve(CLASSPATH_ENTRIES_FILE)
|
||||||
val classpathStructureData = cacheDir.resolve("classpath-structure.bin")
|
val classpathStructureData = cacheDir.resolve(CLASSPATH_STRUCTURE_FILE)
|
||||||
if (!classpathEntries.exists() || !classpathStructureData.exists()) {
|
if (!classpathEntries.exists() || !classpathStructureData.exists()) {
|
||||||
return UnknownSnapshot
|
return UnknownSnapshot
|
||||||
}
|
}
|
||||||
|
|
||||||
val classpathFiles = ObjectInputStream(BufferedInputStream(classpathEntries.inputStream())).use {
|
val classpathFiles = ObjectInputStream(BufferedInputStream(classpathEntries.inputStream())).use {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
it.readObject() as Iterable<File>
|
it.readObject() as List<File>
|
||||||
}
|
}
|
||||||
|
|
||||||
val classpathData = { _: Set<File> ->
|
val dataForFiles =
|
||||||
loadPreviousData(classpathStructureData)
|
ObjectInputStream(BufferedInputStream(classpathStructureData.inputStream())).use {
|
||||||
}
|
@Suppress("UNCHECKED_CAST")
|
||||||
return ClasspathSnapshot(cacheDir, classpathFiles, classpathData)
|
it.readObject() as MutableMap<File, ClasspathEntryData?>
|
||||||
|
}
|
||||||
|
return ClasspathSnapshot(cacheDir, classpathFiles, dataForFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createCurrent(cacheDir: File, classpath: Iterable<File>, lazyClasspathData: Set<File>): ClasspathSnapshot {
|
fun createCurrent(cacheDir: File, classpath: List<File>, allStructureData: Set<File>): ClasspathSnapshot {
|
||||||
val lazyData = lazyClasspathData.map { LazyClasspathEntryData.LazyClasspathEntrySerializer.loadFromFile(it) }
|
val data = allStructureData.associateTo(HashMap<File, ClasspathEntryData?>(allStructureData.size)) { it to null }
|
||||||
val data = { files: Set<File> ->
|
|
||||||
lazyData.filter { files.contains(it.classpathEntry) }.associate { it.classpathEntry to it.getClasspathEntryData() }
|
|
||||||
}
|
|
||||||
|
|
||||||
return ClasspathSnapshot(cacheDir, classpath, data)
|
return ClasspathSnapshot(cacheDir, classpath, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadPreviousData(file: File): Map<File, ClasspathEntryData> {
|
|
||||||
ObjectInputStream(BufferedInputStream(file.inputStream())).use {
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
return it.readObject() as Map<File, ClasspathEntryData>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isCompatible(snapshot: ClasspathSnapshot) =
|
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. */
|
/** Compare this snapshot with the specified one only for the specified files. */
|
||||||
fun diff(previousSnapshot: ClasspathSnapshot, changedFiles: Set<File>): KaptClasspathChanges {
|
fun diff(previousSnapshot: ClasspathSnapshot, changedFiles: Set<File>): KaptClasspathChanges {
|
||||||
@@ -69,54 +54,88 @@ open class ClasspathSnapshot protected constructor(
|
|||||||
return KaptClasspathChanges.Unknown
|
return KaptClasspathChanges.Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentData = classpathData(changedFiles)
|
loadEntriesFor(changedFiles)
|
||||||
val previousData = previousSnapshot.classpathData(changedFiles)
|
|
||||||
|
val currentHashAbiSize = changedFiles.sumBy { dataForFiles[it]!!.classAbiHash.size }
|
||||||
|
val currentHashesToAnalyze =
|
||||||
|
HashMap<String, ByteArray>(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<String, ByteArray>(previousHashAbiSize).also { hashes ->
|
||||||
|
for (c in previousChanged) {
|
||||||
|
previousSnapshot.dataForFiles[c]?.let {
|
||||||
|
hashes.putAll(it.classAbiHash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val changedClasses = mutableSetOf<String>()
|
val changedClasses = mutableSetOf<String>()
|
||||||
|
for (key in previousHashesToAnalyze.keys + currentHashesToAnalyze.keys) {
|
||||||
for (changed in changedFiles) {
|
val previousHash = previousHashesToAnalyze[key]
|
||||||
val previous = previousData.getValue(changed)
|
if (previousHash == null) {
|
||||||
val current = currentData.getValue(changed)
|
changedClasses.add(key)
|
||||||
|
continue
|
||||||
for (key in previous.classAbiHash.keys + current.classAbiHash.keys) {
|
}
|
||||||
val previousHash = previous.classAbiHash[key]
|
val currentHash = currentHashesToAnalyze[key]
|
||||||
if (previousHash == null) {
|
if (currentHash == null) {
|
||||||
changedClasses.add(key)
|
changedClasses.add(key)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
val currentHash = current.classAbiHash[key]
|
if (!previousHash.contentEquals(currentHash)) {
|
||||||
if (currentHash == null) {
|
changedClasses.add(key)
|
||||||
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.
|
// 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.
|
// 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)
|
val allImpactedClasses = findAllImpacted(changedClasses)
|
||||||
|
|
||||||
return KaptClasspathChanges.Known(allImpactedClasses)
|
return KaptClasspathChanges.Known(allImpactedClasses)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun loadEntriesFor(file: Iterable<File>) {
|
||||||
|
for (f in file) {
|
||||||
|
if (dataForFiles[f] == null) {
|
||||||
|
dataForFiles[f] = ClasspathEntryData.ClasspathEntrySerializer.loadFrom(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadAll() {
|
||||||
|
loadEntriesFor(dataForFiles.keys)
|
||||||
|
}
|
||||||
|
|
||||||
fun writeToCache() {
|
fun writeToCache() {
|
||||||
val classpathEntries = cacheDir.resolve("classpath-entries.bin")
|
loadAll()
|
||||||
|
|
||||||
|
val classpathEntries = cacheDir.resolve(CLASSPATH_ENTRIES_FILE)
|
||||||
ObjectOutputStream(BufferedOutputStream(classpathEntries.outputStream())).use {
|
ObjectOutputStream(BufferedOutputStream(classpathEntries.outputStream())).use {
|
||||||
it.writeObject(classpath)
|
it.writeObject(classpath)
|
||||||
}
|
}
|
||||||
|
|
||||||
val classpathStructureData = cacheDir.resolve("classpath-structure.bin")
|
val classpathStructureData = cacheDir.resolve(CLASSPATH_STRUCTURE_FILE)
|
||||||
storeCurrentStructure(classpathStructureData, classpathData(classpath.toSet()))
|
ObjectOutputStream(BufferedOutputStream(classpathStructureData.outputStream())).use {
|
||||||
}
|
it.writeObject(dataForFiles)
|
||||||
|
|
||||||
private fun storeCurrentStructure(file: File, structure: Map<File, ClasspathEntryData>) {
|
|
||||||
ObjectOutputStream(BufferedOutputStream(file.outputStream())).use {
|
|
||||||
it.writeObject(structure)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,8 +144,8 @@ open class ClasspathSnapshot protected constructor(
|
|||||||
val transitiveDeps = HashMap<String, MutableList<String>>()
|
val transitiveDeps = HashMap<String, MutableList<String>>()
|
||||||
val nonTransitiveDeps = HashMap<String, MutableList<String>>()
|
val nonTransitiveDeps = HashMap<String, MutableList<String>>()
|
||||||
|
|
||||||
for (entry in computedClasspathData.values) {
|
for (entry in dataForFiles.values) {
|
||||||
for ((className, classDependency) in entry.classDependencies) {
|
for ((className, classDependency) in entry!!.classDependencies) {
|
||||||
for (abiType in classDependency.abiTypes) {
|
for (abiType in classDependency.abiTypes) {
|
||||||
(transitiveDeps[abiType] ?: LinkedList()).let {
|
(transitiveDeps[abiType] ?: LinkedList()).let {
|
||||||
it.add(className)
|
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 {
|
sealed class KaptIncrementalChanges {
|
||||||
object Unknown : KaptIncrementalChanges()
|
object Unknown : KaptIncrementalChanges()
|
||||||
|
|||||||
+3
-12
@@ -35,10 +35,7 @@ class ClasspathAnalyzerTest {
|
|||||||
val transform = StructureArtifactTransform().also { it.outputDirectory = tmp.newFolder() }
|
val transform = StructureArtifactTransform().also { it.outputDirectory = tmp.newFolder() }
|
||||||
val outputs = transform.transform(classesDir)
|
val outputs = transform.transform(classesDir)
|
||||||
|
|
||||||
val lazyData = LazyClasspathEntryData.LazyClasspathEntrySerializer.loadFromFile(outputs.single())
|
val data = ClasspathEntryData.ClasspathEntrySerializer.loadFrom(outputs.single())
|
||||||
assertEquals(classesDir, lazyData.classpathEntry)
|
|
||||||
|
|
||||||
val data = lazyData.getClasspathEntryData()
|
|
||||||
assertEquals(setOf("test/A", "test/B"), data.classAbiHash.keys)
|
assertEquals(setOf("test/A", "test/B"), data.classAbiHash.keys)
|
||||||
assertEquals(setOf("test/A", "test/B"), data.classDependencies.keys)
|
assertEquals(setOf("test/A", "test/B"), data.classDependencies.keys)
|
||||||
assertEquals(emptySet<String>(), data.classDependencies["test/A"]!!.abiTypes)
|
assertEquals(emptySet<String>(), data.classDependencies["test/A"]!!.abiTypes)
|
||||||
@@ -71,10 +68,7 @@ class ClasspathAnalyzerTest {
|
|||||||
val transform = StructureArtifactTransform().also { it.outputDirectory = tmp.newFolder() }
|
val transform = StructureArtifactTransform().also { it.outputDirectory = tmp.newFolder() }
|
||||||
val outputs = transform.transform(inputJar)
|
val outputs = transform.transform(inputJar)
|
||||||
|
|
||||||
val lazyData = LazyClasspathEntryData.LazyClasspathEntrySerializer.loadFromFile(outputs.single())
|
val data = ClasspathEntryData.ClasspathEntrySerializer.loadFrom(outputs.single())
|
||||||
assertEquals(inputJar, lazyData.classpathEntry)
|
|
||||||
|
|
||||||
val data = lazyData.getClasspathEntryData()
|
|
||||||
assertEquals(setOf("test/A", "test/B"), data.classAbiHash.keys)
|
assertEquals(setOf("test/A", "test/B"), data.classAbiHash.keys)
|
||||||
assertEquals(setOf("test/A", "test/B"), data.classDependencies.keys)
|
assertEquals(setOf("test/A", "test/B"), data.classDependencies.keys)
|
||||||
assertEquals(emptySet<String>(), data.classDependencies["test/A"]!!.abiTypes)
|
assertEquals(emptySet<String>(), data.classDependencies["test/A"]!!.abiTypes)
|
||||||
@@ -90,10 +84,7 @@ class ClasspathAnalyzerTest {
|
|||||||
val transform = StructureArtifactTransform().also { it.outputDirectory = tmp.newFolder() }
|
val transform = StructureArtifactTransform().also { it.outputDirectory = tmp.newFolder() }
|
||||||
val outputs = transform.transform(inputDir)
|
val outputs = transform.transform(inputDir)
|
||||||
|
|
||||||
val lazyData = LazyClasspathEntryData.LazyClasspathEntrySerializer.loadFromFile(outputs.single())
|
val data = ClasspathEntryData.ClasspathEntrySerializer.loadFrom(outputs.single())
|
||||||
assertEquals(inputDir, lazyData.classpathEntry)
|
|
||||||
|
|
||||||
val data = lazyData.getClasspathEntryData()
|
|
||||||
assertTrue(data.classAbiHash.isEmpty())
|
assertTrue(data.classAbiHash.isEmpty())
|
||||||
assertTrue(data.classDependencies.isEmpty())
|
assertTrue(data.classDependencies.isEmpty())
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-36
@@ -18,93 +18,80 @@ class ClasspathSnapshotTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testSerialization() {
|
fun testSerialization() {
|
||||||
val (firstJar, lazyData) = generateLazyData(
|
val data = generateStructureData(
|
||||||
ClassData("first/A"), ClassData("first/B")
|
ClassData("first/A"), ClassData("first/B")
|
||||||
)
|
)
|
||||||
|
|
||||||
val snapshotDir = tmp.newFolder()
|
val snapshotDir = tmp.newFolder()
|
||||||
val currentSnapshot = ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(snapshotDir, listOf(firstJar), setOf(lazyData))
|
val currentSnapshot = ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(snapshotDir, listOf(), setOf(data))
|
||||||
assertEquals(KaptClasspathChanges.Unknown, currentSnapshot.diff(UnknownSnapshot, setOf(firstJar)))
|
assertEquals(KaptClasspathChanges.Unknown, currentSnapshot.diff(UnknownSnapshot, setOf(data)))
|
||||||
currentSnapshot.writeToCache()
|
currentSnapshot.writeToCache()
|
||||||
|
|
||||||
val loadedSnapshot = ClasspathSnapshot.ClasspathSnapshotFactory.loadFrom(snapshotDir)
|
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<String>(), diff.names)
|
assertEquals(emptySet<String>(), diff.names)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testIncompatibleClasspaths() {
|
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 =
|
val secondSnapshot =
|
||||||
ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(File(""), listOf(File("1.jar"), File("added.jar")), emptySet())
|
ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(File(""), listOf(File("b.jar")), setOf(File("")))
|
||||||
assertEquals(KaptClasspathChanges.Unknown, firstSnapshot.diff(secondSnapshot, setOf(File("added.jar"))))
|
assertEquals(KaptClasspathChanges.Unknown, firstSnapshot.diff(secondSnapshot, emptySet()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testChangedClassesFound() {
|
fun testChangedClassesFound() {
|
||||||
val (firstJar, firstLazyData) = generateLazyData(
|
val dataFile = generateStructureData(
|
||||||
ClassData("first/A"),
|
ClassData("first/A"),
|
||||||
ClassData("first/B").also { it.withAbiDependencies("first/A") }
|
ClassData("first/B").also { it.withAbiDependencies("first/A") }
|
||||||
)
|
)
|
||||||
val firstSnapshot = ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(File(""), listOf(firstJar), setOf(firstLazyData))
|
val firstSnapshot = ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(tmp.newFolder(), listOf(), setOf(dataFile))
|
||||||
firstSnapshot.diff(UnknownSnapshot, setOf(firstJar))
|
firstSnapshot.writeToCache()
|
||||||
|
|
||||||
val (_, changedLazyData) = generateLazyData(
|
generateStructureData(
|
||||||
ClassData("first/A", ByteArray(1)),
|
ClassData("first/A", ByteArray(1)),
|
||||||
ClassData("first/B").also { it.withAbiDependencies("first/A") },
|
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)
|
assertEquals(setOf("first/A", "first/B"), diff.names)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testChangedClassesAcrossEntries() {
|
fun testChangedClassesAcrossEntries() {
|
||||||
val (firstJar, firstLazyData) = generateLazyData(
|
val dataFile = generateStructureData(
|
||||||
ClassData("first/A").also { it.withAbiDependencies("library/C") },
|
ClassData("first/A").also { it.withAbiDependencies("library/C") },
|
||||||
ClassData("first/B").also { it.withAbiDependencies("first/A") }
|
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 cacheDir = tmp.newFolder()
|
||||||
val firstSnapshot =
|
val firstSnapshot =
|
||||||
ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(
|
ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(cacheDir, listOf(), setOf(dataFile, libraryDataFile))
|
||||||
cacheDir,
|
|
||||||
listOf(firstJar, libraryJar),
|
|
||||||
setOf(firstLazyData, libraryLazyData)
|
|
||||||
)
|
|
||||||
firstSnapshot.diff(UnknownSnapshot, setOf(firstJar, libraryJar))
|
|
||||||
firstSnapshot.writeToCache()
|
firstSnapshot.writeToCache()
|
||||||
|
|
||||||
val (_, changedLazyData) = generateLazyData(ClassData("library/C", ByteArray(1)), jarInput = libraryJar)
|
generateStructureData(ClassData("library/C", ByteArray(1)), outputFile = libraryDataFile)
|
||||||
val changedSnapshot =
|
val changedSnapshot =
|
||||||
ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(
|
ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(cacheDir, listOf(), setOf(dataFile, libraryDataFile))
|
||||||
cacheDir,
|
|
||||||
listOf(firstJar, libraryJar),
|
|
||||||
setOf(firstLazyData, changedLazyData)
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
assertEquals(setOf("library/C", "first/A", "first/B"), diff.names)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateLazyData(
|
private fun generateStructureData(vararg classData: ClassData, outputFile: File = tmp.newFile()): File {
|
||||||
vararg classData: ClassData,
|
|
||||||
jarInput: File = tmp.newFile()
|
|
||||||
): Pair<File, File> {
|
|
||||||
val data = ClasspathEntryData()
|
val data = ClasspathEntryData()
|
||||||
classData.forEach {
|
classData.forEach {
|
||||||
data.classAbiHash[it.internalName] = it.hash
|
data.classAbiHash[it.internalName] = it.hash
|
||||||
data.classDependencies[it.internalName] = ClassDependencies(it.abiDeps, it.privateDeps)
|
data.classDependencies[it.internalName] = ClassDependencies(it.abiDeps, it.privateDeps)
|
||||||
}
|
}
|
||||||
val serialized = tmp.newFile().also { data.saveTo(it) }
|
data.saveTo(outputFile)
|
||||||
val lazyData = tmp.newFile().also { LazyClasspathEntryData(jarInput, serialized).saveToFile(it) }
|
|
||||||
|
|
||||||
return Pair(jarInput, lazyData)
|
return outputFile
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ClassData(
|
private class ClassData(
|
||||||
|
|||||||
Reference in New Issue
Block a user