Incremental KAPT - analyze classpath changes

Use artifact transforms to capture structure and
dependencies of classpath entries. In the KAPT task
this information is used to compare previous classpath
structure with the current one. Once changed classes are
detected, all classes that transitively depend on those
are identified, and that set is passed to KAPT invocation.

In order to avoid unrelated classpath changes, we record an
ABI snapshot of the classpath entry. This snapshot ignores
all private members, and @Metadata annotation.

 #KT-23880
This commit is contained in:
Ivan Gavrilovic
2019-03-21 16:55:56 +00:00
committed by Alexey Tsvetkov
parent c85e21d43b
commit 7c78644eb9
17 changed files with 1535 additions and 31 deletions
@@ -71,4 +71,69 @@ class KaptIncrementalWithAggregatingApt : KaptIncrementalIT() {
)
}
}
@Test
fun testClasspathChanges() {
val project = Project(
"incrementalMultiproject",
GradleVersionRequired.None
).apply {
setupWorkingDir()
val processorPath = generateProcessor("AGGREGATING")
projectDir.resolve("app/build.gradle").appendText(
"""
apply plugin: "kotlin-kapt"
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:${'$'}kotlin_version"
kapt files("${processorPath.invariantSeparatorsPath}")
}
""".trimIndent()
)
projectDir.resolve("lib/build.gradle").appendText(
"""
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:${'$'}kotlin_version"
}
""".trimIndent()
)
}
project.build("clean", ":app:build") {
assertSuccessful()
}
project.projectFile("A.kt").modify { current ->
val lastBrace = current.lastIndexOf("}")
current.substring(0, lastBrace) + "fun anotherFun() {}\n }"
}
project.build("build") {
assertSuccessful()
assertEquals(
setOf(
fileInWorkingDir("app/build/tmp/kapt3/stubs/main/foo/AA.java").absolutePath,
fileInWorkingDir("app/build/tmp/kapt3/stubs/main/foo/AAA.java").absolutePath,
fileInWorkingDir("app/build/tmp/kapt3/stubs/main/foo/BB.java").absolutePath,
fileInWorkingDir("app/build/tmp/kapt3/stubs/main/foo/FooUseAKt.java").absolutePath,
fileInWorkingDir("app/build/tmp/kapt3/stubs/main/foo/FooUseBKt.java").absolutePath,
fileInWorkingDir("app/build/tmp/kapt3/stubs/main/foo/FooUseAAKt.java").absolutePath,
fileInWorkingDir("app/build/tmp/kapt3/stubs/main/foo/FooUseBBKt.java").absolutePath
), getProcessedSources(output)
)
}
project.projectFile("A.kt").modify { current ->
val lastBrace = current.lastIndexOf("}")
current.substring(0, lastBrace) + "private fun privateFunction() {}\n }"
}
project.build("build") {
assertSuccessful()
assertTrue(getProcessedSources(output).isEmpty())
}
}
}
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.gradle.util.modify
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import java.io.File
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
@@ -70,13 +71,22 @@ private const val patternApt = "Processing java sources with annotation processo
fun getProcessedSources(output: String): Set<String> {
val logging = output.lines().single { it.contains(patternApt) }
val indexOf = logging.indexOf(patternApt) + patternApt.length
return logging.drop(indexOf).split(",").map { it.trim() }.toSet()
return logging.drop(indexOf).split(",").map { it.trim() }.filter { !it.isEmpty() }.toSet()
}
fun BaseGradleIT.Project.setupIncrementalAptProject(procType: String) {
fun BaseGradleIT.Project.setupIncrementalAptProject(procType: String, buildFile: File = projectDir.resolve("build.gradle")) {
setupWorkingDir()
val buildFile = projectDir.resolve("build.gradle")
val content = buildFile.readText()
val processorPath = generateProcessor(procType)
val updatedContent = content.replace(
Regex("^\\s*kapt\\s\"org\\.jetbrain.*$", RegexOption.MULTILINE),
" kapt files(\"${processorPath.invariantSeparatorsPath}\")"
)
buildFile.writeText(updatedContent)
}
fun BaseGradleIT.Project.generateProcessor(procType: String): File {
val processorPath = projectDir.resolve("incrementalProcessor.jar")
ZipOutputStream(processorPath.outputStream()).use {
@@ -92,10 +102,5 @@ fun BaseGradleIT.Project.setupIncrementalAptProject(procType: String) {
it.write(IncrementalProcessor::class.java.name.toByteArray())
it.closeEntry()
}
val updatedContent = content.replace(
Regex("^\\s*kapt\\s\"org\\.jetbrain.*$", RegexOption.MULTILINE),
" kapt files(\"${processorPath.invariantSeparatorsPath}\")"
)
buildFile.writeText(updatedContent)
}
return processorPath
}