Incremental annotation processing with KAPT

Add support for incremental annotation processors in KAPT. These
processors conform to https://docs.gradle.org/current/userguide/java_plugin.html#sec:incremental_annotation_processing
specification.

Support is provided by using javac compiler APIs and
recording the source file structure. At runtime, processors
are instrumented with custom Filer that is used to keep track of generated
files. In order to support classpath changes, stub generation task is
used to generated a list of changed FQCNs, and this is simply used by KAPT.
Both worker and non-worker mode are supported.

 #KT-23880
This commit is contained in:
Ivan Gavrilovic
2019-03-02 12:58:45 +00:00
committed by Alexey Tsvetkov
parent 600a955a51
commit 9f14daa682
56 changed files with 2649 additions and 58 deletions
@@ -11,6 +11,8 @@ import org.jetbrains.kotlin.base.kapt3.KaptFlag
import org.jetbrains.kotlin.base.kapt3.KaptOptions
import org.jetbrains.kotlin.kapt3.base.KaptContext
import org.jetbrains.kotlin.kapt3.base.doAnnotationProcessing
import org.jetbrains.kotlin.kapt3.base.incremental.DeclaredProcType
import org.jetbrains.kotlin.kapt3.base.incremental.IncrementalProcessor
import org.jetbrains.kotlin.kapt3.base.util.KaptBaseError
import org.jetbrains.kotlin.kapt3.base.util.WriterBackedKaptLogger
import org.junit.Test
@@ -25,32 +27,36 @@ class JavaKaptContextTest : TestCase() {
companion object {
private val TEST_DATA_DIR = File("plugins/kapt3/kapt3-base/testData/runner")
fun simpleProcessor() = object : AbstractProcessor() {
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
for (annotation in annotations) {
val annotationName = annotation.simpleName.toString()
val annotatedElements = roundEnv.getElementsAnnotatedWith(annotation)
fun simpleProcessor() = IncrementalProcessor(
object : AbstractProcessor() {
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
for (annotation in annotations) {
val annotationName = annotation.simpleName.toString()
val annotatedElements = roundEnv.getElementsAnnotatedWith(annotation)
for (annotatedElement in annotatedElements) {
val generatedClassName = annotatedElement.simpleName.toString().capitalize() + annotationName.capitalize()
val file = processingEnv.filer.createSourceFile("generated." + generatedClassName)
file.openWriter().use {
it.write("""
for (annotatedElement in annotatedElements) {
val generatedClassName = annotatedElement.simpleName.toString().capitalize() + annotationName.capitalize()
val file = processingEnv.filer.createSourceFile("generated." + generatedClassName)
file.openWriter().use {
it.write(
"""
package generated;
class $generatedClassName {}
""".trimIndent())
""".trimIndent()
)
}
}
}
return true
}
return true
}
override fun getSupportedAnnotationTypes() = setOf("test.MyAnnotation")
}
override fun getSupportedAnnotationTypes() = setOf("test.MyAnnotation")
}, DeclaredProcType.NON_INCREMENTAL
)
}
private fun doAnnotationProcessing(javaSourceFile: File, processor: Processor, outputDir: File) {
private fun doAnnotationProcessing(javaSourceFile: File, processor: IncrementalProcessor, outputDir: File) {
val options = KaptOptions.Builder().apply {
projectBaseDir = javaSourceFile.parentFile
@@ -93,7 +99,7 @@ class JavaKaptContextTest : TestCase() {
}
try {
doAnnotationProcessing(File(TEST_DATA_DIR, "Simple.java"), processor, TEST_DATA_DIR)
doAnnotationProcessing(File(TEST_DATA_DIR, "Simple.java"), IncrementalProcessor(processor, DeclaredProcType.NON_INCREMENTAL), TEST_DATA_DIR)
} catch (e: KaptBaseError) {
assertEquals(KaptBaseError.Kind.EXCEPTION, e.kind)
assertEquals("Here we are!", e.cause!!.message)
@@ -0,0 +1,75 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt.base.test.org.jetbrains.kotlin.kapt3.base.incremental
import org.jetbrains.kotlin.kapt3.base.incremental.RuntimeProcType
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.File
class AggregatingIncrementalProcessorTest {
@JvmField
@Rule
val tmp: TemporaryFolder = TemporaryFolder()
private lateinit var generatedSources: File
@Before
fun setUp() {
generatedSources = tmp.newFolder()
}
@Test
fun testDependenciesRecorded() {
val srcFiles = listOf("User.java", "Address.java", "Observable.java").map { File(TEST_DATA_DIR, it) }
val aggregating = SimpleProcessor().toAggregating()
runAnnotationProcessing(srcFiles, listOf(aggregating), generatedSources)
assertEquals(RuntimeProcType.AGGREGATING, aggregating.getRuntimeType())
assertEquals(
mapOf(
generatedSources.resolve("test/UserGenerated.java") to null,
generatedSources.resolve("test/AddressGenerated.java") to null
),
aggregating.getGeneratedToSources()
)
}
@Test
fun testNoSourcesToProcess() {
val srcFiles = listOf("Observable.java").map { File(TEST_DATA_DIR, it) }
val aggregating = SimpleProcessor().toAggregating()
runAnnotationProcessing(srcFiles, listOf(aggregating), generatedSources)
assertEquals(RuntimeProcType.AGGREGATING, aggregating.getRuntimeType())
assertEquals(emptyMap<File, File>(), aggregating.getGeneratedToSources())
}
@Test
fun testGeneratingSourcesClassesResources() {
val srcFiles = listOf("User.java", "Address.java", "Observable.java").map { File(TEST_DATA_DIR, it) }
val aggregating = SimpleCreatingClassFilesAndResources().toAggregating()
runAnnotationProcessing(srcFiles, listOf(aggregating), generatedSources)
assertEquals(RuntimeProcType.AGGREGATING, aggregating.getRuntimeType())
assertEquals(
mapOf(
generatedSources.resolve("test/UserGenerated.java") to null,
generatedSources.resolve("test/UserGeneratedClass.class") to null,
generatedSources.resolve("test/UserGeneratedResource") to null,
generatedSources.resolve("test/AddressGenerated.java") to null,
generatedSources.resolve("test/AddressGeneratedClass.class") to null,
generatedSources.resolve("test/AddressGeneratedResource") to null
),
aggregating.getGeneratedToSources()
)
}
}
@@ -0,0 +1,44 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt.base.test.org.jetbrains.kotlin.kapt3.base.incremental
import org.jetbrains.kotlin.kapt3.base.incremental.AnnotationProcessorDependencyCollector
import org.jetbrains.kotlin.kapt3.base.incremental.RuntimeProcType
import org.junit.Assert.assertEquals
import org.junit.Test
import java.io.File
class AnnotationProcessorDependencyCollectorTest {
@Test
fun testAggregating() {
val aggregating = AnnotationProcessorDependencyCollector(RuntimeProcType.AGGREGATING)
val generated = listOf("GeneratedA.java", "GeneratedB.java", "GeneratedC.java").map { File(it).toURI() }
generated.forEach { aggregating.add(it, emptyArray()) }
assertEquals(aggregating.getGeneratedToSources(), generated.map { File(it) to null }.toMap())
assertEquals(aggregating.getRuntimeType(), RuntimeProcType.AGGREGATING)
}
@Test
fun testIsolatingWithoutOrigin() {
val isolating = AnnotationProcessorDependencyCollector(RuntimeProcType.ISOLATING)
isolating.add(File("GeneratedA.java").toURI(), emptyArray())
assertEquals(isolating.getRuntimeType(), RuntimeProcType.NON_INCREMENTAL)
assertEquals(isolating.getGeneratedToSources(), emptyMap<File, File?>())
}
@Test
fun testNonIncremental() {
val nonIncremental = AnnotationProcessorDependencyCollector(RuntimeProcType.NON_INCREMENTAL)
nonIncremental.add(File("GeneratedA.java").toURI(), emptyArray())
nonIncremental.add(File("GeneratedB.java").toURI(), emptyArray())
assertEquals(nonIncremental.getRuntimeType(), RuntimeProcType.NON_INCREMENTAL)
assertEquals(nonIncremental.getGeneratedToSources(), emptyMap<File, File?>())
}
}
@@ -0,0 +1,75 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt.base.test.org.jetbrains.kotlin.kapt3.base.incremental
import org.jetbrains.kotlin.kapt3.base.incremental.RuntimeProcType
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.File
class DynamicIncrementalProcessorTest {
@JvmField
@Rule
val tmp: TemporaryFolder = TemporaryFolder()
private lateinit var generatedSources: File
@Before
fun setUp() {
generatedSources = tmp.newFolder()
}
@Test
fun testIfIsolating() {
val srcFiles = listOf("User.java", "Address.java", "Observable.java").map { File(TEST_DATA_DIR, it) }
val dynamic = DynamicProcessor(kind = RuntimeProcType.ISOLATING).toDynamic()
runAnnotationProcessing(srcFiles, listOf(dynamic), generatedSources)
assertEquals(RuntimeProcType.ISOLATING, dynamic.getRuntimeType())
assertEquals(
mapOf(
generatedSources.resolve("test/UserGenerated.java") to File("plugins/kapt3/kapt3-base/testData/runner/incremental/User.java").absoluteFile,
generatedSources.resolve("test/AddressGenerated.java") to File("plugins/kapt3/kapt3-base/testData/runner/incremental/Address.java").absoluteFile
),
dynamic.getGeneratedToSources()
)
}
@Test
fun testIfAggregating() {
val srcFiles = listOf("User.java", "Address.java", "Observable.java").map { File(TEST_DATA_DIR, it) }
val dynamic = DynamicProcessor(kind = RuntimeProcType.AGGREGATING).toDynamic()
runAnnotationProcessing(srcFiles, listOf(dynamic), generatedSources)
assertEquals(RuntimeProcType.AGGREGATING, dynamic.getRuntimeType())
assertEquals(
mapOf(
generatedSources.resolve("test/UserGenerated.java") to null,
generatedSources.resolve("test/AddressGenerated.java") to null
),
dynamic.getGeneratedToSources()
)
}
@Test
fun testIfNonIncremental() {
val srcFiles = listOf("User.java", "Address.java", "Observable.java").map { File(TEST_DATA_DIR, it) }
val dynamic = DynamicProcessor(kind = RuntimeProcType.NON_INCREMENTAL).toDynamic()
runAnnotationProcessing(srcFiles, listOf(dynamic), generatedSources)
assertEquals(RuntimeProcType.NON_INCREMENTAL, dynamic.getRuntimeType())
assertEquals(
emptyMap<File, File>(),
dynamic.getGeneratedToSources()
)
}
}
@@ -0,0 +1,101 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt.base.test.org.jetbrains.kotlin.kapt3.base.incremental
import org.jetbrains.kotlin.base.kapt3.KaptOptions
import org.jetbrains.kotlin.base.kapt3.collectJavaSourceFiles
import org.jetbrains.kotlin.kapt3.base.KaptContext
import org.jetbrains.kotlin.kapt3.base.doAnnotationProcessing
import org.jetbrains.kotlin.kapt3.base.util.WriterBackedKaptLogger
import org.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
class IncrementalKaptTest {
@Rule
@JvmField
var tmp = TemporaryFolder()
@Test
fun testIncrementalRun() {
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 {
it.doAnnotationProcessing(
options.collectJavaSourceFiles(it.cacheManager), listOf(SimpleProcessor().toIsolating())
)
}
val classesOutput = tmp.newFolder()
compileSources(sourcesDir.listFiles().asIterable(), classesOutput)
val addressTimestamp = outputDir.resolve("test/AddressGenerated.java").lastModified()
assertTrue(outputDir.resolve("test/UserGenerated.java").exists())
assertTrue(outputDir.resolve("test/AddressGenerated.java").exists())
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 = optionsForSecondRun.collectJavaSourceFiles(it.cacheManager)
assertFalse(outputDir.resolve("test/UserGenerated.java").exists())
it.doAnnotationProcessing(
sourcesToReprocess, listOf(SimpleProcessor().toIsolating())
)
}
assertEquals(addressTimestamp, outputDir.resolve("test/AddressGenerated.java").lastModified())
assertTrue(outputDir.resolve("test/UserGenerated.java").exists())
sourcesDir.resolve("User.java").delete()
KaptContext(optionsForSecondRun, true, logger).use {
it.doAnnotationProcessing(
optionsForSecondRun.collectJavaSourceFiles(it.cacheManager), listOf(SimpleProcessor().toIsolating())
)
}
assertEquals(addressTimestamp, outputDir.resolve("test/AddressGenerated.java").lastModified())
assertFalse(outputDir.resolve("test/UserGenerated.java").exists())
}
}
@@ -0,0 +1,105 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt.base.test.org.jetbrains.kotlin.kapt3.base.incremental
import org.jetbrains.kotlin.kapt3.base.incremental.DeclaredProcType
import org.jetbrains.kotlin.kapt3.base.incremental.getIncrementalProcessorsFromClasspath
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
class IncrementalProcessorDiscoveryTest {
private val markerFileContent = """
Input1Processor1,AGGREGATING
Input1Processor2,ISOLATING
Input1Processor3,DYNAMIC
Input1Processor4,UNKNOWN
Input1Processor5 this is malformed input
""".trimIndent();
@Rule
@JvmField
var tmp = TemporaryFolder()
@Test
fun locateInJars() {
val inputJar = tmp.root.resolve("inputJar.jar")
ZipOutputStream(inputJar.outputStream()).use {
it.putNextEntry(ZipEntry("META-INF/gradle/incremental.annotation.processors"))
it.write(markerFileContent.toByteArray())
it.closeEntry()
}
val info = getIncrementalProcessorsFromClasspath(
setOf("Input1Processor4", "Input1Processor3", "Input1Processor2", "Input1Processor1"),
listOf(inputJar)
)
assertEquals(
mapOf(
"Input1Processor1" to DeclaredProcType.AGGREGATING,
"Input1Processor2" to DeclaredProcType.ISOLATING,
"Input1Processor3" to DeclaredProcType.DYNAMIC
),
info
)
}
@Test
fun locateInDir() {
val inputDir = tmp.root.resolve("inputDir")
inputDir.resolve("META-INF/gradle/incremental.annotation.processors").let {
it.parentFile.mkdirs()
it.writeText(markerFileContent)
}
val info = getIncrementalProcessorsFromClasspath(
setOf("Input1Processor4", "Input1Processor3", "Input1Processor2", "Input1Processor1"),
listOf(inputDir)
)
assertEquals(
mapOf(
"Input1Processor1" to DeclaredProcType.AGGREGATING,
"Input1Processor2" to DeclaredProcType.ISOLATING,
"Input1Processor3" to DeclaredProcType.DYNAMIC
),
info
)
}
@Test
fun locateInJarsAndDirs() {
val inputJar = tmp.root.resolve("inputJar.jar")
ZipOutputStream(inputJar.outputStream()).use {
it.putNextEntry(ZipEntry("META-INF/gradle/incremental.annotation.processors"))
it.write("InputJarProcessor,ISOLATING".toByteArray())
it.closeEntry()
}
val inputDir = tmp.root.resolve("inputDir")
inputDir.resolve("META-INF/gradle/incremental.annotation.processors").let {
it.parentFile.mkdirs()
it.writeText("InputDirProcessor,DYNAMIC")
}
val info = getIncrementalProcessorsFromClasspath(
setOf("InputJarNonIncrementalProcessor", "InputJarProcessor", "InputDirNonIncrementalProcessor", "InputDirProcessor"),
listOf(inputJar, inputDir)
)
assertEquals(
mapOf(
"InputJarProcessor" to DeclaredProcType.ISOLATING,
"InputDirProcessor" to DeclaredProcType.DYNAMIC
),
info
)
}
}
@@ -0,0 +1,110 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt.base.test.org.jetbrains.kotlin.kapt3.base.incremental
import org.jetbrains.kotlin.kapt3.base.incremental.RuntimeProcType
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.File
class IsolationgIncrementalProcessorTest {
@JvmField
@Rule
val tmp: TemporaryFolder = TemporaryFolder()
private lateinit var generatedSources: File
@Before
fun setUp() {
generatedSources = tmp.newFolder()
}
@Test
fun testDependenciesRecorded() {
val srcFiles = listOf("User.java", "Address.java", "Observable.java").map { File(TEST_DATA_DIR, it) }
val isolating = SimpleProcessor().toIsolating()
runAnnotationProcessing(srcFiles, listOf(isolating), generatedSources)
assertEquals(RuntimeProcType.ISOLATING, isolating.getRuntimeType())
assertEquals(
mapOf(
generatedSources.resolve("test/UserGenerated.java") to File("plugins/kapt3/kapt3-base/testData/runner/incremental/User.java").absoluteFile,
generatedSources.resolve("test/AddressGenerated.java") to File("plugins/kapt3/kapt3-base/testData/runner/incremental/Address.java").absoluteFile
),
isolating.getGeneratedToSources()
)
}
@Test
fun testNoSourcesToProcess() {
val srcFiles = listOf("Observable.java").map { File(TEST_DATA_DIR, it) }
val isolating = SimpleProcessor().toIsolating()
runAnnotationProcessing(srcFiles, listOf(isolating), generatedSources)
assertEquals(RuntimeProcType.ISOLATING, isolating.getRuntimeType())
assertEquals(emptyMap<File, File>(), isolating.getGeneratedToSources())
}
@Test
fun testGeneratingSourcesClassesResources() {
val srcFiles = listOf("User.java", "Address.java", "Observable.java").map { File(TEST_DATA_DIR, it) }
val isolating = SimpleCreatingClassFilesAndResources().toIsolating()
runAnnotationProcessing(srcFiles, listOf(isolating), generatedSources)
assertEquals(RuntimeProcType.ISOLATING, isolating.getRuntimeType())
assertEquals(
mapOf(
generatedSources.resolve("test/UserGenerated.java") to TEST_DATA_DIR.resolve("User.java").absoluteFile,
generatedSources.resolve("test/UserGeneratedClass.class") to TEST_DATA_DIR.resolve("User.java").absoluteFile,
generatedSources.resolve("test/UserGeneratedResource") to TEST_DATA_DIR.resolve("User.java").absoluteFile,
generatedSources.resolve("test/AddressGenerated.java") to TEST_DATA_DIR.resolve("Address.java").absoluteFile,
generatedSources.resolve("test/AddressGeneratedClass.class") to TEST_DATA_DIR.resolve("Address.java").absoluteFile,
generatedSources.resolve("test/AddressGeneratedResource") to TEST_DATA_DIR.resolve("Address.java").absoluteFile
),
isolating.getGeneratedToSources()
)
}
@Test
fun testWrongOriginElement() {
val srcFiles = listOf("User.java", "Address.java", "Observable.java").map { File(TEST_DATA_DIR, it) }
val isolating = SimpleProcessor(wrongOrigin = true).toIsolating()
runAnnotationProcessing(srcFiles, listOf(isolating), generatedSources)
assertEquals(RuntimeProcType.NON_INCREMENTAL, isolating.getRuntimeType())
assertEquals(emptyMap<File, File>(), isolating.getGeneratedToSources())
}
@Test
fun testTwoIsolating() {
val srcFiles = listOf("User.java", "Address.java", "Observable.java").map { File(TEST_DATA_DIR, it) }
val isolating = listOf(
SimpleProcessor().toIsolating(),
SimpleProcessor(generatedSuffix = "Two").toIsolating()
)
runAnnotationProcessing(srcFiles, isolating, generatedSources)
isolating.forEach { assertEquals(RuntimeProcType.ISOLATING, it.getRuntimeType()) }
assertEquals(
mapOf(
generatedSources.resolve("test/UserGenerated.java") to File("plugins/kapt3/kapt3-base/testData/runner/incremental/User.java").absoluteFile,
generatedSources.resolve("test/AddressGenerated.java") to File("plugins/kapt3/kapt3-base/testData/runner/incremental/Address.java").absoluteFile
), isolating[0].getGeneratedToSources()
)
assertEquals(
mapOf(
generatedSources.resolve("test/UserGeneratedTwo.java") to File("plugins/kapt3/kapt3-base/testData/runner/incremental/User.java").absoluteFile,
generatedSources.resolve("test/AddressGeneratedTwo.java") to File("plugins/kapt3/kapt3-base/testData/runner/incremental/Address.java").absoluteFile
), isolating[1].getGeneratedToSources()
)
}
}
@@ -0,0 +1,223 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt3.base.incremental;
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.File
import java.io.ObjectOutputStream
class JavaClassCacheManagerTest {
@Rule
@JvmField
var tmp = TemporaryFolder()
private lateinit var cache: JavaClassCacheManager
private lateinit var cacheDir: File
private lateinit var classpathHistory: File
@Before
fun setUp() {
cacheDir = tmp.newFolder()
classpathHistory = tmp.newFolder()
cache = JavaClassCacheManager(cacheDir, classpathHistory)
}
@Test
fun testClosingCache() {
cache.close()
assertTrue(cacheDir.resolve("java-cache.bin").exists())
assertTrue(cacheDir.resolve("apt-cache.bin").exists())
assertTrue(cacheDir.resolve("last-build-ts.bin").exists())
}
@Test
fun testMentionedTypes() {
SourceFileStructure(File("Mentioned.java").toURI()).also {
it.addDeclaredType("test.Mentioned")
cache.javaCache.addSourceStructure(it)
}
SourceFileStructure(File("Src.java").toURI()).also {
it.addDeclaredType("test.Src")
it.addMentionedType("test.Mentioned")
cache.javaCache.addSourceStructure(it)
}
SourceFileStructure(File("ReferencesSrc.java").toURI()).also {
it.addDeclaredType("test.ReferencesSrc")
it.addPrivateType("test.Src")
cache.javaCache.addSourceStructure(it)
}
prepareForIncremental()
val dirtyFiles = cache.invalidateAndGetDirtyFiles(listOf(File("Mentioned.java"))) as SourcesToReprocess.Incremental
assertEquals(
listOf(
File("Mentioned.java").absoluteFile,
File("Src.java").absoluteFile,
File("ReferencesSrc.java").absoluteFile
), dirtyFiles.toReprocess
)
}
@Test
fun testPrivateTypes() {
SourceFileStructure(File("Mentioned.java").toURI()).also {
it.addDeclaredType("test.Mentioned")
cache.javaCache.addSourceStructure(it)
}
SourceFileStructure(File("Src.java").toURI()).also {
it.addDeclaredType("test.Src")
it.addPrivateType("test.Mentioned")
cache.javaCache.addSourceStructure(it)
}
SourceFileStructure(File("ReferencesSrc.java").toURI()).also {
it.addDeclaredType("test.ReferencesSrc")
it.addPrivateType("test.Src")
cache.javaCache.addSourceStructure(it)
}
prepareForIncremental()
val dirtyFiles = cache.invalidateAndGetDirtyFiles(listOf(File("Mentioned.java"))) as SourcesToReprocess.Incremental
assertEquals(
listOf(
File("Mentioned.java").absoluteFile,
File("Src.java").absoluteFile
), dirtyFiles.toReprocess
)
}
@Test
fun testMultipleDeclared() {
SourceFileStructure(File("TwoTypes.java").toURI()).also {
it.addDeclaredType("test.TwoTypes")
it.addDeclaredType("test.AnotherType")
cache.javaCache.addSourceStructure(it)
}
SourceFileStructure(File("ReferencesTwoTypes.java").toURI()).also {
it.addDeclaredType("test.ReferencesTwoTypes")
it.addPrivateType("test.TwoTypes")
cache.javaCache.addSourceStructure(it)
}
SourceFileStructure(File("ReferencesAnotherType.java").toURI()).also {
it.addDeclaredType("test.ReferencesAnotherType")
it.addPrivateType("test.AnotherType")
cache.javaCache.addSourceStructure(it)
}
prepareForIncremental()
val dirtyFiles = cache.invalidateAndGetDirtyFiles(listOf(File("TwoTypes.java"))) as SourcesToReprocess.Incremental
assertEquals(
listOf(
File("TwoTypes.java").absoluteFile,
File("ReferencesTwoTypes.java").absoluteFile,
File("ReferencesAnotherType.java").absoluteFile
), dirtyFiles.toReprocess
)
}
@Test
fun testNoClasspathHistory() {
SourceFileStructure(File("Src.java").toURI()).also {
it.addDeclaredType("test.Src")
cache.javaCache.addSourceStructure(it)
}
cache.close()
classpathHistory.resolve(Long.MAX_VALUE.toString()).createNewFile()
val dirtyFiles = cache.invalidateAndGetDirtyFiles(listOf())
assertEquals(SourcesToReprocess.FullRebuild, dirtyFiles)
}
@Test
fun testWithClasspathHistoryButNoNewChanges() {
SourceFileStructure(File("Src.java").toURI()).also {
it.addDeclaredType("test.Src")
it.addMentionedType("test.Mentioned")
cache.javaCache.addSourceStructure(it)
}
prepareForIncremental()
ObjectOutputStream(classpathHistory.resolve("0").outputStream()).use {
it.writeObject(listOf("test.Mentioned"))
}
val dirtyFiles = cache.invalidateAndGetDirtyFiles(listOf()) as SourcesToReprocess.Incremental
assertEquals(emptyList<File>(), dirtyFiles.toReprocess)
}
@Test
fun testWithClasspathHistoryWithChanges() {
SourceFileStructure(File("Src.java").toURI()).also {
it.addDeclaredType("test.Src")
it.addMentionedType("test.Mentioned")
cache.javaCache.addSourceStructure(it)
}
prepareForIncremental()
ObjectOutputStream(classpathHistory.resolve(Long.MAX_VALUE.toString()).outputStream()).use {
it.writeObject(listOf("test.Mentioned"))
}
val dirtyFiles = cache.invalidateAndGetDirtyFiles(listOf())
assertEquals(SourcesToReprocess.FullRebuild, dirtyFiles)
}
@Test
fun testDefinesConstant() {
SourceFileStructure(File("Constants.java").toURI()).also {
it.addDeclaredType("test.Constants")
it.addDefinedConstant("CONST", 123)
cache.javaCache.addSourceStructure(it)
}
SourceFileStructure(File("Unrelated1.java").toURI()).also {
it.addDeclaredType("test.Unrelated1")
cache.javaCache.addSourceStructure(it)
}
SourceFileStructure(File("Unrelated2.java").toURI()).also {
it.addDeclaredType("test.Unrelated2")
cache.javaCache.addSourceStructure(it)
}
prepareForIncremental()
val dirtyFiles = cache.invalidateAndGetDirtyFiles(listOf(File("Constants.java")))
assertEquals(SourcesToReprocess.FullRebuild, dirtyFiles)
}
@Test
fun testWithAnnotations() {
SourceFileStructure(File("Annotated1.java").toURI()).also {
it.addDeclaredType("test.Annotated1")
it.addMentionedAnnotations("test.Annotation")
cache.javaCache.addSourceStructure(it)
}
SourceFileStructure(File("Annotated2.java").toURI()).also {
it.addDeclaredType("test.Annotated2")
it.addMentionedAnnotations("com.test.MyAnnotation")
cache.javaCache.addSourceStructure(it)
}
SourceFileStructure(File("Annotated3.java").toURI()).also {
it.addDeclaredType("test.Annotated3")
it.addMentionedAnnotations("Runnable")
cache.javaCache.addSourceStructure(it)
}
prepareForIncremental()
assertEquals(setOf(File("Annotated1.java").absoluteFile), cache.javaCache.invalidateEntriesAnnotatedWith(setOf("test.Annotation")))
assertEquals(setOf(File("Annotated2.java").absoluteFile), cache.javaCache.invalidateEntriesAnnotatedWith(setOf("com.test.*")))
assertEquals(setOf(File("Annotated3.java").absoluteFile), cache.javaCache.invalidateEntriesAnnotatedWith(setOf("*")))
}
private fun prepareForIncremental() {
cache.close()
classpathHistory.resolve("0").createNewFile()
cache = JavaClassCacheManager(cacheDir, classpathHistory)
}
}
@@ -0,0 +1,210 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt.base.test.org.jetbrains.kotlin.kapt3.base.incremental
import org.jetbrains.kotlin.kapt3.base.incremental.JavaClassCacheManager
import org.jetbrains.kotlin.kapt3.base.incremental.MentionedTypesTaskListener
import org.junit.Assert.assertEquals
import org.junit.BeforeClass
import org.junit.ClassRule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.File
private val MY_TEST_DIR = File("plugins/kapt3/kapt3-base/testData/runner/incremental/complex")
class TestComplexIncrementalAptCache {
companion object {
@ClassRule
@JvmField
var tmp = TemporaryFolder()
private lateinit var cache: JavaClassCacheManager
private lateinit var generatedSources: File
@JvmStatic
@BeforeClass
fun setUp() {
val classpathHistory = tmp.newFolder()
cache = JavaClassCacheManager(tmp.newFolder(), classpathHistory)
generatedSources = tmp.newFolder()
cache.close()
classpathHistory.resolve("0").createNewFile()
val processor = SimpleProcessor().toAggregating()
val srcFiles = listOf(
"MyEnum.java",
"MyNumber.java",
"NumberAnnotation.java",
"NumberException.java",
"NumberHolder.java",
"NumberManager.java",
"GenericNumber.java"
).map { File(MY_TEST_DIR, it) }
runAnnotationProcessing(
srcFiles,
listOf(processor),
generatedSources
) { trees -> MentionedTypesTaskListener(cache.javaCache, trees) }
cache.updateCache(listOf(processor))
}
}
@Test
fun testEnum() {
val myEnum = cache.javaCache.getStructure(MY_TEST_DIR.resolve("MyEnum.java"))!!
assertEquals(setOf("test.MyEnum"), myEnum.getDeclaredTypes())
assertEquals(emptySet<String>(), myEnum.getMentionedAnnotations())
assertEquals(emptySet<String>(), myEnum.getPrivateTypes())
assertEquals(setOf("test.MyEnum", "test.TypeGeneratedByApt"), myEnum.getMentionedTypes())
assertEquals(emptyMap<String, Any>(), myEnum.getDefinedConstants())
}
@Test
fun testMyNumber() {
val myNumber = cache.javaCache.getStructure(MY_TEST_DIR.resolve("MyNumber.java"))!!
assertEquals(
setOf(
"test.MyNumber",
"test.FieldAnnotation",
"test.MethodAnnotation",
"test.ParameterAnnotation",
"test.TypeUseAnnotation",
"test.AnotherTypeUseAnnotation",
"test.ThrowTypeUseAnnotation"
), myNumber.getDeclaredTypes()
)
assertEquals(
setOf(
"java.lang.annotation.Target",
"test.FieldAnnotation",
"test.ParameterAnnotation",
"test.MethodAnnotation",
"test.TypeUseAnnotation",
"test.AnotherTypeUseAnnotation",
"test.ThrowTypeUseAnnotation"
), myNumber.getMentionedAnnotations()
)
assertEquals(
setOf(
"test.FieldAnnotation",
"java.lang.String",
"test.ParameterAnnotation",
"test.MethodAnnotation",
"test.AnotherTypeUseAnnotation",
"test.ThrowTypeUseAnnotation",
"java.lang.Number",
"java.lang.RuntimeException"
), myNumber.getPrivateTypes()
)
assertEquals(
setOf(
"test.MyNumber",
"java.lang.annotation.Target",
"test.FieldAnnotation",
"test.ParameterAnnotation",
"test.MethodAnnotation",
"test.TypeUseAnnotation",
"test.AnotherTypeUseAnnotation",
"test.ThrowTypeUseAnnotation",
"java.util.HashSet"
), myNumber.getMentionedTypes()
)
assertEquals(emptyMap<String, String>(), myNumber.getDefinedConstants())
}
@Test
fun testAnnotation() {
val numberAnnotation = cache.javaCache.getStructure(MY_TEST_DIR.resolve("NumberAnnotation.java"))!!
assertEquals(setOf("test.NumberAnnotation", "test.BaseAnnotation"), numberAnnotation.getDeclaredTypes())
assertEquals(setOf("test.BaseAnnotation"), numberAnnotation.getMentionedAnnotations())
assertEquals(emptySet<String>(), numberAnnotation.getPrivateTypes())
assertEquals(
setOf(
"test.BaseAnnotation",
"test.NumberAnnotation",
"java.lang.Class",
"test.MyEnum",
"test.NumberManager"
), numberAnnotation.getMentionedTypes()
)
assertEquals(emptyMap<String, String>(), numberAnnotation.getDefinedConstants())
}
@Test
fun testNumberException() {
val numberException = cache.javaCache.getStructure(MY_TEST_DIR.resolve("NumberException.java"))!!
assertEquals(setOf("test.NumberException"), numberException.getDeclaredTypes())
assertEquals(emptySet<String>(), numberException.getMentionedAnnotations())
assertEquals(emptySet<String>(), numberException.getPrivateTypes())
assertEquals(setOf("test.NumberException", "java.lang.RuntimeException"), numberException.getMentionedTypes())
assertEquals(emptyMap<String, String>(), numberException.getDefinedConstants())
}
@Test
fun testNumberHolder() {
val numberHolder = cache.javaCache.getStructure(MY_TEST_DIR.resolve("NumberHolder.java"))!!
assertEquals(setOf("test.NumberHolder", "test.NumberHolder.MyInnerClass"), numberHolder.getDeclaredTypes())
assertEquals(setOf("test.NumberAnnotation"), numberHolder.getMentionedAnnotations())
assertEquals(setOf("test.NumberManager"), numberHolder.getPrivateTypes())
assertEquals(
setOf(
"test.NumberHolder",
"test.NumberHolder.MyInnerClass",
"test.NumberAnnotation",
"test.NumberManager",
"test.MyNumber",
"java.util.HashSet",
"java.lang.Runnable",
"java.lang.String",
"test.NumberException"
), numberHolder.getMentionedTypes()
)
assertEquals(emptyMap<String, String>(), numberHolder.getDefinedConstants())
}
@Test
fun testNumberManager() {
val numberManager = cache.javaCache.getStructure(MY_TEST_DIR.resolve("NumberManager.java"))!!
assertEquals(setOf("test.NumberManager"), numberManager.getDeclaredTypes())
assertEquals(emptySet<String>(), numberManager.getMentionedAnnotations())
assertEquals(setOf("test.MyEnum"), numberManager.getPrivateTypes())
assertEquals(
setOf(
"test.NumberManager",
"java.lang.String",
"test.NumberHolder"
), numberManager.getMentionedTypes()
)
assertEquals(mapOf("CONST" to "STRING_CONST", "INT_CONST" to 246), numberManager.getDefinedConstants())
}
@Test
fun testGenericNumber() {
val genericNumber = cache.javaCache.getStructure(MY_TEST_DIR.resolve("GenericNumber.java"))!!
assertEquals(setOf("test.GenericNumber"), genericNumber.getDeclaredTypes())
assertEquals(emptySet<String>(), genericNumber.getMentionedAnnotations())
assertEquals(emptySet<String>(), genericNumber.getPrivateTypes())
assertEquals(
setOf(
"test.GenericNumber",
"java.util.HashSet",
"java.lang.Runnable",
"java.lang.Cloneable",
"java.lang.CharSequence",
"java.lang.Number"
), genericNumber.getMentionedTypes()
)
assertEquals(emptyMap<String, String>(), genericNumber.getDefinedConstants())
}
}
@@ -0,0 +1,79 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt.base.test.org.jetbrains.kotlin.kapt3.base.incremental
import org.jetbrains.kotlin.kapt3.base.incremental.IncrementalProcessor
import org.jetbrains.kotlin.kapt3.base.incremental.JavaClassCacheManager
import org.jetbrains.kotlin.kapt3.base.incremental.MentionedTypesTaskListener
import org.jetbrains.kotlin.kapt3.base.incremental.SourcesToReprocess
import org.junit.Assert.*
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.File
class TestSimpleIncrementalAptCache {
@Rule
@JvmField
var tmp = TemporaryFolder()
private lateinit var cache: JavaClassCacheManager
private lateinit var generatedSources: File
@Before
fun setUp() {
val classpathHistory = tmp.newFolder()
cache = JavaClassCacheManager(tmp.newFolder(), classpathHistory)
generatedSources = tmp.newFolder()
cache.close()
classpathHistory.resolve("0").createNewFile()
}
@Test
fun testAggregatingAnnotations() {
runProcessor(SimpleProcessor().toAggregating())
val dirtyFiles = cache.invalidateAndGetDirtyFiles(listOf(TEST_DATA_DIR.resolve("User.java"))) as SourcesToReprocess.Incremental
assertEquals(
listOf(TEST_DATA_DIR.resolve("User.java").absoluteFile, TEST_DATA_DIR.resolve("Address.java").absoluteFile),
dirtyFiles.toReprocess
)
assertFalse(generatedSources.resolve("test/UserGenerated.java").exists())
assertFalse(generatedSources.resolve("test/AddressGenerated.java").exists())
}
@Test
fun testIsolatingAnnotations() {
runProcessor(SimpleProcessor().toIsolating())
val dirtyFiles = cache.invalidateAndGetDirtyFiles(listOf(TEST_DATA_DIR.resolve("User.java"))) as SourcesToReprocess.Incremental
assertFalse(generatedSources.resolve("test/UserGenerated.java").exists())
assertEquals(
listOf(TEST_DATA_DIR.resolve("User.java").absoluteFile),
dirtyFiles.toReprocess
)
}
@Test
fun testNonIncremental() {
runProcessor(SimpleProcessor().toNonIncremental())
val dirtyFiles = cache.invalidateAndGetDirtyFiles(listOf(TEST_DATA_DIR.resolve("User.java")))
assertTrue(dirtyFiles is SourcesToReprocess.FullRebuild)
}
private fun runProcessor(processor: IncrementalProcessor) {
val srcFiles = listOf("User.java", "Address.java", "Observable.java").map { File(TEST_DATA_DIR, it) }
runAnnotationProcessing(
srcFiles,
listOf(processor),
generatedSources
) { trees -> MentionedTypesTaskListener(cache.javaCache, trees) }
cache.updateCache(listOf(processor))
}
}
@@ -0,0 +1,124 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt.base.test.org.jetbrains.kotlin.kapt3.base.incremental
import com.sun.source.util.TaskListener
import com.sun.source.util.Trees
import com.sun.tools.javac.api.JavacTaskImpl
import org.jetbrains.kotlin.kapt3.base.incremental.DeclaredProcType
import org.jetbrains.kotlin.kapt3.base.incremental.IncrementalProcessor
import org.jetbrains.kotlin.kapt3.base.incremental.RuntimeProcType
import java.io.File
import javax.annotation.processing.AbstractProcessor
import javax.annotation.processing.Filer
import javax.annotation.processing.ProcessingEnvironment
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.SourceVersion
import javax.lang.model.element.TypeElement
import javax.tools.StandardLocation
import javax.tools.ToolProvider
val TEST_DATA_DIR = File("plugins/kapt3/kapt3-base/testData/runner/incremental")
fun runAnnotationProcessing(
srcFiles: List<File>,
processor: List<IncrementalProcessor>,
generatedSources: File,
listener: (Trees) -> TaskListener? = { null }
) {
val compiler = ToolProvider.getSystemJavaCompiler()
compiler.getStandardFileManager(null, null, null).use { fileManager ->
val javaSrcs = fileManager.getJavaFileObjectsFromFiles(srcFiles)
val compilationTask =
compiler.getTask(
null,
fileManager,
null,
listOf("-proc:only", "-s", generatedSources.absolutePath, "-d", generatedSources.absolutePath),
null,
javaSrcs
) as JavacTaskImpl
val taskListener = listener(Trees.instance(compilationTask))
taskListener?.let { compilationTask.addTaskListener(it) }
compilationTask.setProcessors(processor)
compilationTask.call()
}
}
fun compileSources(srcFiles: Iterable<File>, outputDir: File) {
val compiler = ToolProvider.getSystemJavaCompiler()
compiler.getStandardFileManager(null, null, null).use { fileManager ->
val compilationTask =
compiler.getTask(
null,
fileManager,
null,
listOf("-d", outputDir.absolutePath),
null,
fileManager.getJavaFileObjectsFromFiles(srcFiles)
) as JavacTaskImpl
compilationTask.call()
}
}
fun SimpleProcessor.toAggregating() = IncrementalProcessor(this, DeclaredProcType.AGGREGATING)
fun SimpleProcessor.toIsolating() = IncrementalProcessor(this, DeclaredProcType.ISOLATING)
fun SimpleProcessor.toNonIncremental() = IncrementalProcessor(this, DeclaredProcType.NON_INCREMENTAL)
fun DynamicProcessor.toDynamic() = IncrementalProcessor(this, DeclaredProcType.DYNAMIC)
open class SimpleProcessor(private val wrongOrigin: Boolean = false, private val generatedSuffix: String = "") : AbstractProcessor() {
lateinit var filer: Filer
override fun init(processingEnv: ProcessingEnvironment?) {
super.init(processingEnv)
filer = processingEnv!!.filer
}
override fun getSupportedAnnotationTypes(): MutableSet<String> = mutableSetOf("test.Observable")
override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment): Boolean {
if (annotations.isEmpty()) return false
roundEnv.getElementsAnnotatedWith(annotations.single()).forEach {
it as TypeElement
val generatedName = "${it.qualifiedName}Generated$generatedSuffix"
filer.createSourceFile(generatedName, it.takeUnless { wrongOrigin }).openWriter().use { it.write("") }
}
return false
}
override fun getSupportedSourceVersion(): SourceVersion {
return SourceVersion.latest()
}
}
class DynamicProcessor(private val kind: RuntimeProcType) : SimpleProcessor() {
override fun getSupportedOptions(): MutableSet<String> {
return mutableSetOf("org.gradle.annotation.processing.${kind.name}")
}
}
class SimpleCreatingClassFilesAndResources : SimpleProcessor() {
override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment): Boolean {
super.process(annotations, roundEnv)
if (annotations.isEmpty()) return false
roundEnv.getElementsAnnotatedWith(annotations.single()).forEach {
it as TypeElement
val generatedName = "${it.qualifiedName}Generated"
filer.createClassFile("${generatedName}Class", it).openWriter().use { it.write("") }
filer.createResource(StandardLocation.SOURCE_OUTPUT, "test", "${it.simpleName}GeneratedResource", it).openWriter().use { it.write("") }
}
return false
}
}