Incremental KAPT - invalidate obsolete types

Once set of dirty symbols is computed, filter these types
when they are requested from the JavaFileManager. This is accomplished
by tracking all declared types in the sources and generated sources.
It is not necessary track types in generated class files, as these will
be removed before the APs are incrementally run.

Motivation: APs (e.g. Dagger) may use Elements.getTypeElement(String)
to determine if type is already present, and if it is, they will not
generate it. Therefore, whenever generated sources is invalidated, types
it defines need to be filtered in the JavaFileManager.

Issue is https://youtrack.jetbrains.com/issue/KT-23880
This commit is contained in:
Ivan Gavrilovic
2019-03-15 17:50:10 +00:00
committed by Alexey Tsvetkov
parent 2f3d234516
commit b2ad82b7d2
14 changed files with 271 additions and 48 deletions
@@ -50,8 +50,9 @@ class IncrementalKaptTest {
val logger = WriterBackedKaptLogger(isVerbose = true)
KaptContext(options, true, logger).use {
val toReprocess = it.cacheManager!!.invalidateAndGetDirtyFiles(options.changedFiles)
it.doAnnotationProcessing(
options.collectJavaSourceFiles(it.cacheManager), listOf(SimpleProcessor().toIsolating())
options.collectJavaSourceFiles(toReprocess), listOf(SimpleProcessor().toIsolating())
)
}
@@ -77,11 +78,12 @@ class IncrementalKaptTest {
}.build()
KaptContext(optionsForSecondRun, true, logger).use {
val sourcesToReprocess = optionsForSecondRun.collectJavaSourceFiles(it.cacheManager)
val sourcesToReprocess =
it.cacheManager!!.invalidateAndGetDirtyFiles(optionsForSecondRun.changedFiles)
assertFalse(outputDir.resolve("test/UserGenerated.java").exists())
it.doAnnotationProcessing(
sourcesToReprocess, listOf(SimpleProcessor().toIsolating())
optionsForSecondRun.collectJavaSourceFiles(sourcesToReprocess), listOf(SimpleProcessor().toIsolating())
)
}
@@ -90,12 +92,80 @@ class IncrementalKaptTest {
sourcesDir.resolve("User.java").delete()
KaptContext(optionsForSecondRun, true, logger).use {
val sourcesToReprocess = it.cacheManager!!.invalidateAndGetDirtyFiles(optionsForSecondRun.changedFiles)
it.doAnnotationProcessing(
optionsForSecondRun.collectJavaSourceFiles(it.cacheManager), listOf(SimpleProcessor().toIsolating())
optionsForSecondRun.collectJavaSourceFiles(sourcesToReprocess), listOf(SimpleProcessor().toIsolating())
)
}
assertEquals(addressTimestamp, outputDir.resolve("test/AddressGenerated.java").lastModified())
assertFalse(outputDir.resolve("test/UserGenerated.java").exists())
}
@Test
fun testGeneratedCompiledAreIgnored() {
val sourcesDir = tmp.newFolder().resolve("test").also { base ->
base.mkdir()
listOf("User.java", "Address.java", "Observable.java").map {
TEST_DATA_DIR.resolve(it).copyTo(base.resolve(it))
}
}
val outputDir = tmp.newFolder()
val incrementalCacheDir = tmp.newFolder()
val classpathHistory = tmp.newFolder().also {
it.resolve("0").createNewFile()
}
val options = KaptOptions.Builder().apply {
projectBaseDir = tmp.newFolder()
javaSourceRoots.add(sourcesDir)
sourcesOutputDir = outputDir
classesOutputDir = outputDir
stubsOutputDir = outputDir
incrementalDataOutputDir = outputDir
incrementalCache = incrementalCacheDir
classpathFqNamesHistory = classpathHistory
}.build()
val logger = WriterBackedKaptLogger(isVerbose = true)
KaptContext(options, true, logger).use {
val toReprocess = it.cacheManager!!.invalidateAndGetDirtyFiles(options.changedFiles)
it.doAnnotationProcessing(
options.collectJavaSourceFiles(toReprocess), listOf(SimpleGeneratingIfTypeDoesNotExist().toIsolating())
)
}
val classesOutput = tmp.newFolder()
compileSources(sourcesDir.listFiles().asIterable(), classesOutput)
compileSources(listOf(outputDir.resolve("test/UserGenerated.java")), classesOutput)
val optionsForSecondRun = KaptOptions.Builder().apply {
projectBaseDir = tmp.newFolder()
sourcesOutputDir = outputDir
classesOutputDir = outputDir
stubsOutputDir = outputDir
incrementalDataOutputDir = outputDir
incrementalCache = incrementalCacheDir
classpathFqNamesHistory = classpathHistory
compiledSources.add(classesOutput)
changedFiles.add(sourcesDir.resolve("User.java"))
}.build()
KaptContext(optionsForSecondRun, true, logger).use {
val sourcesToReprocess =
it.cacheManager!!.invalidateAndGetDirtyFiles(optionsForSecondRun.changedFiles)
assertFalse(outputDir.resolve("test/UserGenerated.java").exists())
it.doAnnotationProcessing(
optionsForSecondRun.collectJavaSourceFiles(sourcesToReprocess), listOf(SimpleGeneratingIfTypeDoesNotExist().toIsolating())
)
}
assertTrue(outputDir.resolve("test/UserGenerated.java").exists())
}
}
@@ -119,6 +119,30 @@ class SimpleCreatingClassFilesAndResources : SimpleProcessor() {
filer.createResource(StandardLocation.SOURCE_OUTPUT, "test", "${it.simpleName}GeneratedResource", it).openWriter().use { it.write("") }
}
return false
}
}
class SimpleGeneratingIfTypeDoesNotExist: SimpleProcessor() {
override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment): Boolean {
if (annotations.isEmpty()) return false
roundEnv.getElementsAnnotatedWith(annotations.single()).forEach { element ->
element as TypeElement
val generatedName = "${element.qualifiedName}Generated"
if (processingEnv.elementUtils.getTypeElement(generatedName) == null) {
filer.createSourceFile(generatedName, element).openWriter().use {
it.write(
"""
package test;
public class ${element.simpleName}Generated {}
""".trimIndent()
)
}
}
}
return false
}
}