From b2ad82b7d2bba7983a1724dfda790de6e052ffce Mon Sep 17 00:00:00 2001 From: Ivan Gavrilovic Date: Fri, 15 Mar 2019 17:50:10 +0000 Subject: [PATCH] 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 --- .../gradle/KaptIncrementalWithIsolatingApt.kt | 2 +- .../org/jetbrains/kotlin/kapt3/base/Kapt.kt | 2 +- .../kotlin/kapt3/base/KaptContext.kt | 20 +++-- .../kotlin/kapt3/base/KaptOptions.kt | 15 +--- .../kotlin/kapt3/base/annotationProcessing.kt | 20 +++-- .../base/incremental/IncrementalAptCache.kt | 17 +++- .../kotlin/kapt3/base/incremental/cache.kt | 23 +++--- .../base/incremental/classStructureCache.kt | 29 ++++++- .../base/incremental/incrementalProcessors.kt | 7 +- .../kapt3/base/incremental/javacVisitors.kt | 24 ++++++ .../kapt3/base/javac/KaptJavaFileManager.kt | 56 +++++++++++++ .../base/incremental/IncrementalKaptTest.kt | 78 ++++++++++++++++++- .../kotlin/kapt3/base/incremental/fixtures.kt | 24 ++++++ .../jetbrains/kotlin/kapt3/Kapt3Extension.kt | 2 +- 14 files changed, 271 insertions(+), 48 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KaptIncrementalWithIsolatingApt.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KaptIncrementalWithIsolatingApt.kt index 06e01eadb15..5a2c29c2c06 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KaptIncrementalWithIsolatingApt.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KaptIncrementalWithIsolatingApt.kt @@ -107,7 +107,7 @@ fun BaseGradleIT.Project.setupIncrementalAptProject(procType: String) { val updatedContent = content.replace( Regex("^\\s*kapt\\s\"org\\.jetbrain.*$", RegexOption.MULTILINE), - " kapt files(\"$processorPath\")" + " kapt files(\"${processorPath.invariantSeparatorsPath}\")" ) buildFile.writeText(updatedContent) } diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/Kapt.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/Kapt.kt index c6a5b8134d3..e3a6368e2a9 100644 --- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/Kapt.kt +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/Kapt.kt @@ -34,7 +34,7 @@ object Kapt { logger.info { options.logString("stand-alone mode") } - val javaSourceFiles = options.collectJavaSourceFiles(kaptContext.cacheManager) + val javaSourceFiles = options.collectJavaSourceFiles(kaptContext.sourcesToReprocess) val processorLoader = ProcessorLoader(options, logger) diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/KaptContext.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/KaptContext.kt index 43329afc6b8..7d602713f86 100644 --- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/KaptContext.kt +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/KaptContext.kt @@ -14,6 +14,7 @@ import com.sun.tools.javac.util.Options import org.jetbrains.kotlin.base.kapt3.KaptFlag import org.jetbrains.kotlin.base.kapt3.KaptOptions import org.jetbrains.kotlin.kapt3.base.incremental.JavaClassCacheManager +import org.jetbrains.kotlin.kapt3.base.incremental.SourcesToReprocess import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaCompiler import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaFileManager import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaLog @@ -32,6 +33,8 @@ open class KaptContext(val options: KaptOptions, val withJdk: Boolean, val logge val javaLog: KaptJavaLog val cacheManager: JavaClassCacheManager? + val sourcesToReprocess: SourcesToReprocess + protected open fun preregisterTreeMaker(context: Context) {} private fun preregisterLog(context: Context) { @@ -53,6 +56,13 @@ open class KaptContext(val options: KaptOptions, val withJdk: Boolean, val logge KaptJavaCompiler.preRegister(context) + cacheManager = options.incrementalCache?.let { + JavaClassCacheManager(it, options.classpathFqNamesHistory!!) + } + sourcesToReprocess = cacheManager?.invalidateAndGetDirtyFiles( + options.changedFiles.filter { it.extension == "java" } + ) ?: SourcesToReprocess.FullRebuild + javacOptions = Options.instance(context).apply { for ((key, value) in options.processingOptions) { val option = if (value.isEmpty()) "-A$key" else "-A$key=$value" @@ -78,7 +88,7 @@ open class KaptContext(val options: KaptOptions, val withJdk: Boolean, val logge put("accessInternalAPI", "true") } - val compileClasspath = if (options.changedFiles.isEmpty()) { + val compileClasspath = if (sourcesToReprocess is SourcesToReprocess.FullRebuild || options.changedFiles.isEmpty()) { options.compileClasspath } else { options.compileClasspath + options.compiledSources @@ -101,6 +111,10 @@ open class KaptContext(val options: KaptOptions, val withJdk: Boolean, val logge } fileManager = context.get(JavaFileManager::class.java) as KaptJavaFileManager + if (sourcesToReprocess is SourcesToReprocess.Incremental) { + fileManager.typeToIgnore = sourcesToReprocess.dirtyTypes + fileManager.rootsToFilter = options.compiledSources.toSet() + } if (isJava9OrLater()) { for (option in Option.getJavacFileManagerOptions()) { @@ -114,10 +128,6 @@ open class KaptContext(val options: KaptOptions, val withJdk: Boolean, val logge ClassReader.instance(context).saveParameterNames = true - cacheManager = options.incrementalCache?.let { - JavaClassCacheManager(it, options.classpathFqNamesHistory!!) - } - javaLog = compiler.log as KaptJavaLog } diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/KaptOptions.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/KaptOptions.kt index f0bbaef8f7d..e12ce239527 100644 --- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/KaptOptions.kt +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/KaptOptions.kt @@ -5,7 +5,6 @@ package org.jetbrains.kotlin.base.kapt3 -import org.jetbrains.kotlin.kapt3.base.incremental.JavaClassCacheManager import org.jetbrains.kotlin.kapt3.base.incremental.SourcesToReprocess import java.io.File import java.nio.file.Files @@ -129,7 +128,7 @@ enum class AptMode(override val stringValue: String) : KaptSelector { get() = this != APT_ONLY } -fun KaptOptions.collectJavaSourceFiles(cacheManager: JavaClassCacheManager? = null): List { +fun KaptOptions.collectJavaSourceFiles(sourcesToReprocess: SourcesToReprocess = SourcesToReprocess.FullRebuild): List { fun allSources(): List { return (javaSourceRoots + stubsOutputDir) .sortedBy { Files.isSymbolicLink(it.toPath()) } // Get non-symbolic paths first @@ -138,15 +137,9 @@ fun KaptOptions.collectJavaSourceFiles(cacheManager: JavaClassCacheManager? = nu .distinctBy { it.canonicalPath } } - return if (cacheManager != null && changedFiles.isNotEmpty()) { - val toReprocess = cacheManager.invalidateAndGetDirtyFiles(changedFiles.filter { it.extension == "java" }) - - when (toReprocess) { - is SourcesToReprocess.FullRebuild -> allSources() - is SourcesToReprocess.Incremental -> toReprocess.toReprocess.filter { it.exists() } - } - } else { - allSources() + return when (sourcesToReprocess) { + is SourcesToReprocess.FullRebuild -> allSources() + is SourcesToReprocess.Incremental -> sourcesToReprocess.toReprocess.filter { it.exists() } } } diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/annotationProcessing.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/annotationProcessing.kt index 37e3cb8ddac..73b6086afde 100644 --- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/annotationProcessing.kt +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/annotationProcessing.kt @@ -12,8 +12,10 @@ import com.sun.tools.javac.processing.JavacFiler import com.sun.tools.javac.processing.JavacProcessingEnvironment import com.sun.tools.javac.tree.JCTree import org.jetbrains.kotlin.base.kapt3.KaptFlag -import org.jetbrains.kotlin.base.kapt3.collectJavaSourceFiles -import org.jetbrains.kotlin.kapt3.base.incremental.* +import org.jetbrains.kotlin.kapt3.base.incremental.DeclaredProcType +import org.jetbrains.kotlin.kapt3.base.incremental.GeneratedTypesTaskListener +import org.jetbrains.kotlin.kapt3.base.incremental.IncrementalProcessor +import org.jetbrains.kotlin.kapt3.base.incremental.MentionedTypesTaskListener import org.jetbrains.kotlin.kapt3.base.util.KaptBaseError import org.jetbrains.kotlin.kapt3.base.util.isJava9OrLater import org.jetbrains.kotlin.kapt3.base.util.measureTimeMillisWithResult @@ -50,8 +52,8 @@ fun KaptContext.doAnnotationProcessing( } val parsedJavaFiles = parseJavaFiles(javaSourceFiles) - val listener = cacheManager?.let { - if (processors.any { it.kind == DeclaredProcType.NON_INCREMENTAL}) return@let null + val sourcesStructureListener = cacheManager?.let { + if (processors.any { it.kind == DeclaredProcType.NON_INCREMENTAL }) return@let null val recordTypesListener = MentionedTypesTaskListener(cacheManager.javaCache, processingEnvironment.elementUtils) compiler.getTaskListeners().add(recordTypesListener) @@ -64,15 +66,19 @@ fun KaptContext.doAnnotationProcessing( CompileState.PARSE, compiler.enterTrees(parsedJavaFiles + additionalSources) ) - listener?.let { compiler.getTaskListeners().remove(it) } + val generatedSourcesListener = sourcesStructureListener?.let { + compiler.getTaskListeners().remove(it) + GeneratedTypesTaskListener(cacheManager!!.javaCache) + }?.also { compiler.getTaskListeners().add(it) } if (isJava9OrLater()) { val processAnnotationsMethod = compiler.javaClass.getMethod("processAnnotations", JavacList::class.java) processAnnotationsMethod.invoke(compiler, analyzedFiles) compiler } else { - val processAnnotations = compiler.processAnnotations(analyzedFiles) - processAnnotations + compiler.processAnnotations(analyzedFiles).also { + generatedSourcesListener?.let { compiler.getTaskListeners().remove(it) } + } } } catch (e: AnnotationProcessingError) { throw KaptBaseError(KaptBaseError.Kind.EXCEPTION, e.cause ?: e) diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/IncrementalAptCache.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/IncrementalAptCache.kt index 2a4dea1795c..f4f3f3095f1 100644 --- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/IncrementalAptCache.kt +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/IncrementalAptCache.kt @@ -49,25 +49,34 @@ class IncrementalAptCache : Serializable { return true } - fun invalidateAggregatingAndGetAnnotations(): Set { + fun getAggregatingClaimedAnnotations(): Set = aggregatingClaimedAnnotations + + /** Returns generated Java sources originating from aggregating APs. */ + fun invalidateAggregating(): List { + val dirtyAggregating = aggregatingGenerated.filter { it.extension == "java" } aggregatingGenerated.forEach { it.delete() } aggregatingGenerated.clear() - return aggregatingClaimedAnnotations + return dirtyAggregating } - fun invalidateIsolatingGenerated(fromSources: Set) { + /** Returns generated Java sources originating from the specified sources, and generated by isloating APs. */ + fun invalidateIsolatingGenerated(fromSources: Set): List { + val allInvalidated = mutableListOf() var changedSources = fromSources.toSet() // We need to do it in a loop because mapping could be: [AGenerated.java -> A.java, AGeneratedGenerated.java -> AGenerated.java] - while(changedSources.isNotEmpty()) { + while (changedSources.isNotEmpty()) { val generated = isolatingMapping.filter { changedSources.contains(it.value) }.keys generated.forEach { + if (it.extension == "java") allInvalidated.add(it) + it.delete() isolatingMapping.remove(it) } changedSources = generated } + return allInvalidated } private fun invalidateCache() { diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/cache.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/cache.kt index 4e866c0f12c..e427480e047 100644 --- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/cache.kt +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/cache.kt @@ -8,15 +8,15 @@ package org.jetbrains.kotlin.kapt3.base.incremental import java.io.* // TODO(gavra): switch away from Java serialization -class JavaClassCacheManager(private val file: File, private val classpathFqNamesHistory: File) : Closeable { +class JavaClassCacheManager(val file: File, private val classpathFqNamesHistory: File) : Closeable { - internal val javaCacheFile = file.resolve("java-cache.bin") + private val javaCacheFile = file.resolve("java-cache.bin") internal val javaCache = maybeGetJavaCacheFromFile() - internal val aptCacheFile = file.resolve("apt-cache.bin") - internal val aptCache = maybeGetAptCacheFromFile() + private val aptCacheFile = file.resolve("apt-cache.bin") + private val aptCache = maybeGetAptCacheFromFile() - internal val lastBuildTimestamp = file.resolve("last-build-ts.bin") + private val lastBuildTimestamp = file.resolve("last-build-ts.bin") private var closed = false @@ -75,15 +75,20 @@ class JavaClassCacheManager(private val file: File, private val classpathFqNames is SourcesToReprocess.Incremental -> { val toReprocess = filesToReprocess.toReprocess.toMutableSet() - aptCache.invalidateIsolatingGenerated(toReprocess) + val isolatingGenerated = aptCache.invalidateIsolatingGenerated(toReprocess) + val generatedDirtyTypes = javaCache.invalidateGeneratedTypes(isolatingGenerated).toMutableSet() + if (!toReprocess.isEmpty()) { // only if there are some files to reprocess we should invalidate the aggregating ones + val aggregatingGenerated = aptCache.invalidateAggregating() + generatedDirtyTypes.addAll(javaCache.invalidateGeneratedTypes(aggregatingGenerated)) + toReprocess.addAll( - javaCache.invalidateEntriesAnnotatedWith(aptCache.invalidateAggregatingAndGetAnnotations()) + javaCache.invalidateEntriesAnnotatedWith(aptCache.getAggregatingClaimedAnnotations()) ) } - SourcesToReprocess.Incremental(toReprocess.toList()) + SourcesToReprocess.Incremental(toReprocess.toList(), generatedDirtyTypes) } } } @@ -147,7 +152,7 @@ class JavaClassCacheManager(private val file: File, private val classpathFqNames } sealed class SourcesToReprocess { - class Incremental(val toReprocess: List): SourcesToReprocess() + class Incremental(val toReprocess: List, val dirtyTypes: Set) : SourcesToReprocess() object FullRebuild : SourcesToReprocess() } diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/classStructureCache.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/classStructureCache.kt index de8770186aa..cca53b0fe0c 100644 --- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/classStructureCache.kt +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/classStructureCache.kt @@ -7,12 +7,17 @@ package org.jetbrains.kotlin.kapt3.base.incremental import com.sun.tools.javac.processing.JavacProcessingEnvironment import java.io.File +import java.io.ObjectInputStream +import java.io.ObjectOutputStream import java.io.Serializable import java.net.URI class JavaClassCache() : Serializable { private var sourceCache = mutableMapOf() + /** Record these separately because we only need to know where each generated type is coming from. */ + private var generatedTypes = mutableMapOf>() + /** Map from types to files they are mentioned in. */ @Transient private var dependencyCache = mutableMapOf>() @@ -23,9 +28,21 @@ class JavaClassCache() : Serializable { sourceCache[sourceStructure.sourceFile] = sourceStructure } - private fun readObject(input: java.io.ObjectInputStream) { + fun addGeneratedType(type: String, generatedFile: File) { + val typesInFile = generatedTypes[generatedFile] ?: ArrayList(1) + typesInFile.add(type) + generatedTypes[generatedFile] = typesInFile + } + + fun invalidateGeneratedTypes(files: List): Set { + return files.mapNotNull { generatedTypes.remove(it) }.flatten().toSet() + } + + private fun readObject(input: ObjectInputStream) { @Suppress("UNCHECKED_CAST") sourceCache = input.readObject() as MutableMap + @Suppress("UNCHECKED_CAST") + generatedTypes = input.readObject() as MutableMap> dependencyCache = HashMap(sourceCache.size * 4) for (sourceInfo in sourceCache.values) { @@ -45,6 +62,11 @@ class JavaClassCache() : Serializable { } } + private fun writeObject(output: ObjectOutputStream) { + output.writeObject(sourceCache) + output.writeObject(generatedTypes) + } + fun isAlreadyProcessed(sourceFile: URI) = sourceCache.containsKey(sourceFile) /** Used for testing only. */ @@ -57,6 +79,7 @@ class JavaClassCache() : Serializable { fun invalidateEntriesForChangedFiles(changes: Changes): SourcesToReprocess { val allDirtyFiles = mutableSetOf() var currentDirtyFiles = changes.sourceChanges.map { it.toURI() } + val allDirtyTypes = mutableSetOf() // check for constants first because they cause full rebuilt for (sourceChange in currentDirtyFiles) { @@ -76,6 +99,7 @@ class JavaClassCache() : Serializable { val structure = sourceCache.remove(dirtyFile) ?: continue val dirtyTypes = structure.getDeclaredTypes() + allDirtyTypes.addAll(dirtyTypes) dirtyTypes.forEach { type -> nonTransitiveCache[type]?.let { @@ -91,7 +115,7 @@ class JavaClassCache() : Serializable { currentDirtyFiles = nextRound.filter { !allDirtyFiles.contains(it) } } - return SourcesToReprocess.Incremental(allDirtyFiles.map { File(it) }) + return SourcesToReprocess.Incremental(allDirtyFiles.map { File(it) }, allDirtyTypes) } /** @@ -119,6 +143,7 @@ class JavaClassCache() : Serializable { internal fun invalidateAll() { sourceCache.clear() + generatedTypes.clear() } } diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/incrementalProcessors.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/incrementalProcessors.kt index 88ba03ec61d..4aee664ea0a 100644 --- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/incrementalProcessors.kt +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/incrementalProcessors.kt @@ -125,12 +125,13 @@ internal class AnnotationProcessorDependencyCollector(private val runtimeProcTyp } private fun getSrcFiles(elements: Array): List { - return elements.filterNotNull().mapNotNull { - var origin = it + return elements.filterNotNull().mapNotNull { elem -> + var origin = elem while (origin.enclosingElement != null && origin.enclosingElement !is PackageElement) { origin = origin.enclosingElement } - (origin as? Symbol.ClassSymbol)?.sourcefile?.let { src -> File(src.toUri()) } + val uri = (origin as? Symbol.ClassSymbol)?.sourcefile?.toUri()?.takeIf { it.isAbsolute } + uri?.let { File(it) } } } diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/javacVisitors.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/javacVisitors.kt index 9d953b6c67e..19544cfb107 100644 --- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/javacVisitors.kt +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/javacVisitors.kt @@ -192,4 +192,28 @@ private class TypeTreeVisitor(val elementUtils: Elements, val sourceStructure: S } return true } +} + +class GeneratedTypesTaskListener(private val cache: JavaClassCache) : TaskListener { + + override fun started(e: TaskEvent) { + // do nothing, we just process on finish + } + + override fun finished(e: TaskEvent) { + if (e.kind != TaskEvent.Kind.ENTER || cache.isAlreadyProcessed(e.sourceFile.toUri())) return + + val treeVisitor = object : SimpleTreeVisitor() { + override fun visitClass(node: ClassTree, p: Void?): Void? { + node as JCTree.JCClassDecl + cache.addGeneratedType(node.sym.fullname.toString(), File(e.sourceFile.toUri())) + + node.members.forEach { visit(it, null) } + return null + } + } + e.compilationUnit.typeDecls.forEach { + it.accept(treeVisitor, null) + } + } } \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/javac/KaptJavaFileManager.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/javac/KaptJavaFileManager.kt index 3683e6cd87e..e18da197b33 100644 --- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/javac/KaptJavaFileManager.kt +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/javac/KaptJavaFileManager.kt @@ -5,10 +5,15 @@ package org.jetbrains.kotlin.kapt3.base.javac +import com.sun.tools.javac.file.BaseFileObject import com.sun.tools.javac.file.JavacFileManager import com.sun.tools.javac.main.Option import com.sun.tools.javac.util.Context +import java.io.File +import java.util.* import javax.tools.JavaFileManager +import javax.tools.JavaFileObject +import javax.tools.StandardLocation class KaptJavaFileManager(context: Context) : JavacFileManager(context, true, null) { fun handleOptionJavac9(option: Option, value: String) { @@ -18,6 +23,57 @@ class KaptJavaFileManager(context: Context) : JavacFileManager(context, true, nu handleOptionMethod.invoke(this, option, value) } + var rootsToFilter = emptySet() + // TODO (gavra): store these more efficiently by package (requires some changes to type collection). + var typeToIgnore = emptySet() + + override fun list( + location: JavaFileManager.Location?, + packageName: String?, + kinds: MutableSet?, + recurse: Boolean + ): MutableIterable { + val originalList = super.list(location, packageName, kinds, recurse) + + if (location == null + || location != StandardLocation.CLASS_PATH + || rootsToFilter.isEmpty() + || typeToIgnore.isEmpty() + || !filterThisPath(packageName) + ) { + return originalList + } + + val filteredList = LinkedList() + for (file in originalList) + if (!shouldBeFiltered(packageName, file)) { + filteredList.add(file) + } + + return filteredList + } + + + private fun filterThisPath(packageName: String?): Boolean { + packageName ?: return false + + val relativePath = packageName.replace('.', File.separatorChar) + return rootsToFilter.any { it.resolve(relativePath).isDirectory } + } + + private fun shouldBeFiltered(packageName: String?, fileObject: JavaFileObject): Boolean { + if (fileObject.kind != JavaFileObject.Kind.CLASS) return false + return when (fileObject.toUri().scheme) { + "jar", "zip" -> false + else -> { + if (fileObject !is BaseFileObject) return false + val typeName = packageName?.let { "$it." } + fileObject.shortName.dropLast(".class".length) + + return typeToIgnore.contains(typeName) + } + } + } + companion object { internal fun preRegister(context: Context) { context.put(JavaFileManager::class.java, Context.Factory { KaptJavaFileManager(it) }) diff --git a/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/IncrementalKaptTest.kt b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/IncrementalKaptTest.kt index 92f04c930b6..f9c4e44930d 100644 --- a/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/IncrementalKaptTest.kt +++ b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/IncrementalKaptTest.kt @@ -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()) + } } \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/fixtures.kt b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/fixtures.kt index 86e6194ecf6..934f37bf8f7 100644 --- a/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/fixtures.kt +++ b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/fixtures.kt @@ -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, 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 } } \ No newline at end of file diff --git a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/Kapt3Extension.kt b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/Kapt3Extension.kt index 31e083c3796..bc4729377c5 100644 --- a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/Kapt3Extension.kt +++ b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/Kapt3Extension.kt @@ -216,7 +216,7 @@ abstract class AbstractKapt3Extension( private fun runAnnotationProcessing(kaptContext: KaptContext, processors: LoadedProcessors) { if (!options.mode.runAnnotationProcessing) return - val javaSourceFiles = options.collectJavaSourceFiles(kaptContext.cacheManager) + val javaSourceFiles = options.collectJavaSourceFiles(kaptContext.sourcesToReprocess) logger.info { "Java source files: " + javaSourceFiles.joinToString { it.canonicalPath } } val (annotationProcessingTime) = measureTimeMillis {