Incremental KAPT - handle inherited annotations

Handle annotations with @Inherited. This is important for
the aggregating APs, as the current implementation passes all sources
annotated with claimed aggregating annotations.

Issue is https://youtrack.jetbrains.com/issue/KT-23880
This commit is contained in:
Ivan Gavrilovic
2019-03-15 17:38:31 +00:00
committed by Alexey Tsvetkov
parent 9f14daa682
commit 2f3d234516
12 changed files with 105 additions and 20 deletions
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.kapt3.base package org.jetbrains.kotlin.kapt3.base
import com.sun.source.util.Trees
import com.sun.tools.javac.comp.CompileStates.CompileState import com.sun.tools.javac.comp.CompileStates.CompileState
import com.sun.tools.javac.main.JavaCompiler import com.sun.tools.javac.main.JavaCompiler
import com.sun.tools.javac.processing.AnnotationProcessingError import com.sun.tools.javac.processing.AnnotationProcessingError
@@ -54,7 +53,7 @@ fun KaptContext.doAnnotationProcessing(
val listener = cacheManager?.let { val listener = cacheManager?.let {
if (processors.any { it.kind == DeclaredProcType.NON_INCREMENTAL}) return@let null if (processors.any { it.kind == DeclaredProcType.NON_INCREMENTAL}) return@let null
val recordTypesListener = MentionedTypesTaskListener(cacheManager.javaCache, Trees.instance(processingEnvironment)) val recordTypesListener = MentionedTypesTaskListener(cacheManager.javaCache, processingEnvironment.elementUtils)
compiler.getTaskListeners().add(recordTypesListener) compiler.getTaskListeners().add(recordTypesListener)
recordTypesListener recordTypesListener
} }
@@ -67,7 +66,6 @@ fun KaptContext.doAnnotationProcessing(
listener?.let { compiler.getTaskListeners().remove(it) } listener?.let { compiler.getTaskListeners().remove(it) }
val l = System.currentTimeMillis()
if (isJava9OrLater()) { if (isJava9OrLater()) {
val processAnnotationsMethod = compiler.javaClass.getMethod("processAnnotations", JavacList::class.java) val processAnnotationsMethod = compiler.javaClass.getMethod("processAnnotations", JavacList::class.java)
processAnnotationsMethod.invoke(compiler, analyzedFiles) processAnnotationsMethod.invoke(compiler, analyzedFiles)
@@ -32,7 +32,7 @@ class JavaClassCacheManager(private val file: File, private val classpathFqNames
} }
val dirtyFqNames = mutableSetOf<String>() val dirtyFqNames = mutableSetOf<String>()
after.forEach{ file -> after.forEach { file ->
ObjectInputStream(file.inputStream().buffered()).use { ObjectInputStream(file.inputStream().buffered()).use {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
dirtyFqNames.addAll(it.readObject() as Collection<String>) dirtyFqNames.addAll(it.readObject() as Collection<String>)
@@ -90,7 +90,7 @@ class JavaClassCacheManager(private val file: File, private val classpathFqNames
} }
} }
private fun maybeGetAptCacheFromFile(): IncrementalAptCache{ private fun maybeGetAptCacheFromFile(): IncrementalAptCache {
return if (aptCacheFile.exists()) { return if (aptCacheFile.exists()) {
try { try {
@@ -51,7 +51,7 @@ class JavaClassCache() : Serializable {
internal fun getStructure(sourceFile: File) = sourceCache[sourceFile.toURI()] internal fun getStructure(sourceFile: File) = sourceCache[sourceFile.toURI()]
/** /**
* Invalidate cache entires for the specified files, and any files that depend on the changed ones. It returns the set of files that * Invalidate cache entries for the specified files, and any files that depend on the changed ones. It returns the set of files that
* should be re-processed. * should be re-processed.
* */ * */
fun invalidateEntriesForChangedFiles(changes: Changes): SourcesToReprocess { fun invalidateEntriesForChangedFiles(changes: Changes): SourcesToReprocess {
@@ -17,7 +17,8 @@ private const val INCREMENTAL_ANNOTATION_FLAG = "META-INF/gradle/incremental.ann
/** Checks the incremental annotation processor information for the annotation processor classpath. */ /** Checks the incremental annotation processor information for the annotation processor classpath. */
fun getIncrementalProcessorsFromClasspath( fun getIncrementalProcessorsFromClasspath(
names: Set<String>, classpath: Iterable<File>): Map<String, DeclaredProcType> { names: Set<String>, classpath: Iterable<File>
): Map<String, DeclaredProcType> {
val finalValues = mutableMapOf<String, DeclaredProcType>() val finalValues = mutableMapOf<String, DeclaredProcType>()
classpath.forEach { entry -> classpath.forEach { entry ->
@@ -7,11 +7,7 @@ package org.jetbrains.kotlin.kapt3.base.incremental
import com.sun.tools.javac.code.Symbol import com.sun.tools.javac.code.Symbol
import java.io.File import java.io.File
import java.io.InputStream
import java.net.URI import java.net.URI
import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import javax.annotation.processing.Filer import javax.annotation.processing.Filer
import javax.annotation.processing.ProcessingEnvironment import javax.annotation.processing.ProcessingEnvironment
import javax.annotation.processing.Processor import javax.annotation.processing.Processor
@@ -118,7 +114,6 @@ internal class AnnotationProcessorDependencyCollector(private val runtimeProcTyp
} }
} }
internal fun getGeneratedToSources(): Map<File, File?> = if (isFullRebuild) emptyMap() else generatedToSource internal fun getGeneratedToSources(): Map<File, File?> = if (isFullRebuild) emptyMap() else generatedToSource
internal fun getRuntimeType(): RuntimeProcType { internal fun getRuntimeType(): RuntimeProcType {
return if (isFullRebuild) { return if (isFullRebuild) {
@@ -9,15 +9,17 @@ import com.sun.source.tree.*
import com.sun.source.util.SimpleTreeVisitor import com.sun.source.util.SimpleTreeVisitor
import com.sun.source.util.TaskEvent import com.sun.source.util.TaskEvent
import com.sun.source.util.TaskListener import com.sun.source.util.TaskListener
import com.sun.source.util.Trees
import com.sun.tools.javac.code.Symbol import com.sun.tools.javac.code.Symbol
import com.sun.tools.javac.tree.JCTree import com.sun.tools.javac.tree.JCTree
import java.io.File
import javax.lang.model.element.ElementKind import javax.lang.model.element.ElementKind
import javax.lang.model.element.Modifier import javax.lang.model.element.Modifier
import javax.lang.model.element.TypeElement
import javax.lang.model.util.Elements
class MentionedTypesTaskListener( class MentionedTypesTaskListener(
private val cache: JavaClassCache, private val cache: JavaClassCache,
private val trees: Trees private val elementUtils: Elements
) : TaskListener { ) : TaskListener {
var time = 0L var time = 0L
@@ -33,7 +35,7 @@ class MentionedTypesTaskListener(
val structure = SourceFileStructure(e.sourceFile.toUri()) val structure = SourceFileStructure(e.sourceFile.toUri())
val treeVisitor = TypeTreeVisitor(compilationUnit, trees, structure) val treeVisitor = TypeTreeVisitor(elementUtils, structure)
compilationUnit.typeDecls.forEach { compilationUnit.typeDecls.forEach {
it.accept(treeVisitor, Visibility.ABI) it.accept(treeVisitor, Visibility.ABI)
} }
@@ -46,14 +48,25 @@ private enum class Visibility {
ABI, NON_ABI ABI, NON_ABI
} }
private class TypeTreeVisitor(val unit: CompilationUnitTree, val trees: Trees, val sourceStructure: SourceFileStructure) : private class TypeTreeVisitor(val elementUtils: Elements, val sourceStructure: SourceFileStructure) :
SimpleTreeVisitor<Void, Visibility>() { SimpleTreeVisitor<Void, Visibility>() {
/** Handle annotations on this class, including the @Inherited ones as those are not visible using Tree APIs. */
private fun handleClassAnnotations(classSymbol: Symbol.ClassSymbol) {
elementUtils.getAllAnnotationMirrors(classSymbol).forEach {
(it.annotationType.asElement() as? TypeElement)?.let {
sourceStructure.addMentionedAnnotations(it.qualifiedName.toString())
}
}
}
override fun visitClass(node: ClassTree, visibility: Visibility): Void? { override fun visitClass(node: ClassTree, visibility: Visibility): Void? {
node as JCTree.JCClassDecl node as JCTree.JCClassDecl
sourceStructure.addDeclaredType(node.sym.fullname.toString()) sourceStructure.addDeclaredType(node.sym.fullname.toString())
sourceStructure.addMentionedType(node.sym.fullname.toString()) sourceStructure.addMentionedType(node.sym.fullname.toString())
handleClassAnnotations(node.sym)
node.modifiers.annotations.forEach { visit(it, visibility) } node.modifiers.annotations.forEach { visit(it, visibility) }
node.typeParameters.forEach { visit(it, visibility) } node.typeParameters.forEach { visit(it, visibility) }
visit(node.extendsClause, visibility) visit(node.extendsClause, visibility)
@@ -0,0 +1,67 @@
/*
* 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/inherited")
class TestInheritedAnnotation {
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(
"BaseClass.java",
"InheritableAnnotation.java",
"ExtendsBase.java"
).map { File(MY_TEST_DIR, it) }
runAnnotationProcessing(
srcFiles,
listOf(processor),
generatedSources
) { trees -> MentionedTypesTaskListener(cache.javaCache, trees) }
cache.updateCache(listOf(processor))
}
}
@Test
fun testAnnotationInherited() {
val shouldInheritAnnotation = cache.javaCache.getStructure(MY_TEST_DIR.resolve("ExtendsBase.java"))!!
assertEquals(setOf("test.ExtendsBase"), shouldInheritAnnotation.getDeclaredTypes())
assertEquals(setOf("test.InheritableAnnotation"), shouldInheritAnnotation.getMentionedAnnotations())
assertEquals(emptySet<String>(), shouldInheritAnnotation.getPrivateTypes())
assertEquals(
setOf(
"test.ExtendsBase",
"test.BaseClass"
), shouldInheritAnnotation.getMentionedTypes()
)
assertEquals(emptyMap<String, String>(), shouldInheritAnnotation.getDefinedConstants())
}
}
@@ -73,7 +73,7 @@ class TestSimpleIncrementalAptCache {
srcFiles, srcFiles,
listOf(processor), listOf(processor),
generatedSources generatedSources
) { trees -> MentionedTypesTaskListener(cache.javaCache, trees) } ) { elementUtils -> MentionedTypesTaskListener(cache.javaCache, elementUtils) }
cache.updateCache(listOf(processor)) cache.updateCache(listOf(processor))
} }
} }
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.kapt.base.test.org.jetbrains.kotlin.kapt3.base.incremental package org.jetbrains.kotlin.kapt.base.test.org.jetbrains.kotlin.kapt3.base.incremental
import com.sun.source.util.TaskListener import com.sun.source.util.TaskListener
import com.sun.source.util.Trees
import com.sun.tools.javac.api.JavacTaskImpl import com.sun.tools.javac.api.JavacTaskImpl
import org.jetbrains.kotlin.kapt3.base.incremental.DeclaredProcType import org.jetbrains.kotlin.kapt3.base.incremental.DeclaredProcType
import org.jetbrains.kotlin.kapt3.base.incremental.IncrementalProcessor import org.jetbrains.kotlin.kapt3.base.incremental.IncrementalProcessor
@@ -18,6 +17,7 @@ import javax.annotation.processing.ProcessingEnvironment
import javax.annotation.processing.RoundEnvironment import javax.annotation.processing.RoundEnvironment
import javax.lang.model.SourceVersion import javax.lang.model.SourceVersion
import javax.lang.model.element.TypeElement import javax.lang.model.element.TypeElement
import javax.lang.model.util.Elements
import javax.tools.StandardLocation import javax.tools.StandardLocation
import javax.tools.ToolProvider import javax.tools.ToolProvider
@@ -27,7 +27,7 @@ fun runAnnotationProcessing(
srcFiles: List<File>, srcFiles: List<File>,
processor: List<IncrementalProcessor>, processor: List<IncrementalProcessor>,
generatedSources: File, generatedSources: File,
listener: (Trees) -> TaskListener? = { null } listener: (Elements) -> TaskListener? = { null }
) { ) {
val compiler = ToolProvider.getSystemJavaCompiler() val compiler = ToolProvider.getSystemJavaCompiler()
compiler.getStandardFileManager(null, null, null).use { fileManager -> compiler.getStandardFileManager(null, null, null).use { fileManager ->
@@ -42,7 +42,7 @@ fun runAnnotationProcessing(
javaSrcs javaSrcs
) as JavacTaskImpl ) as JavacTaskImpl
val taskListener = listener(Trees.instance(compilationTask)) val taskListener = listener(compilationTask.elements)
taskListener?.let { compilationTask.addTaskListener(it) } taskListener?.let { compilationTask.addTaskListener(it) }
compilationTask.setProcessors(processor) compilationTask.setProcessors(processor)
@@ -0,0 +1,4 @@
package test;
@InheritableAnnotation
public class BaseClass {}
@@ -0,0 +1,3 @@
package test;
public class ExtendsBase extends BaseClass {}
@@ -0,0 +1,4 @@
package test;
@java.lang.annotation.Inherited
public @interface InheritableAnnotation {}