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
@@ -107,7 +107,7 @@ fun BaseGradleIT.Project.setupIncrementalAptProject(procType: String) {
val updatedContent = content.replace( val updatedContent = content.replace(
Regex("^\\s*kapt\\s\"org\\.jetbrain.*$", RegexOption.MULTILINE), Regex("^\\s*kapt\\s\"org\\.jetbrain.*$", RegexOption.MULTILINE),
" kapt files(\"$processorPath\")" " kapt files(\"${processorPath.invariantSeparatorsPath}\")"
) )
buildFile.writeText(updatedContent) buildFile.writeText(updatedContent)
} }
@@ -34,7 +34,7 @@ object Kapt {
logger.info { options.logString("stand-alone mode") } logger.info { options.logString("stand-alone mode") }
val javaSourceFiles = options.collectJavaSourceFiles(kaptContext.cacheManager) val javaSourceFiles = options.collectJavaSourceFiles(kaptContext.sourcesToReprocess)
val processorLoader = ProcessorLoader(options, logger) val processorLoader = ProcessorLoader(options, logger)
@@ -14,6 +14,7 @@ import com.sun.tools.javac.util.Options
import org.jetbrains.kotlin.base.kapt3.KaptFlag import org.jetbrains.kotlin.base.kapt3.KaptFlag
import org.jetbrains.kotlin.base.kapt3.KaptOptions import org.jetbrains.kotlin.base.kapt3.KaptOptions
import org.jetbrains.kotlin.kapt3.base.incremental.JavaClassCacheManager 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.KaptJavaCompiler
import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaFileManager import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaFileManager
import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaLog 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 javaLog: KaptJavaLog
val cacheManager: JavaClassCacheManager? val cacheManager: JavaClassCacheManager?
val sourcesToReprocess: SourcesToReprocess
protected open fun preregisterTreeMaker(context: Context) {} protected open fun preregisterTreeMaker(context: Context) {}
private fun preregisterLog(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) 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 { javacOptions = Options.instance(context).apply {
for ((key, value) in options.processingOptions) { for ((key, value) in options.processingOptions) {
val option = if (value.isEmpty()) "-A$key" else "-A$key=$value" 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") put("accessInternalAPI", "true")
} }
val compileClasspath = if (options.changedFiles.isEmpty()) { val compileClasspath = if (sourcesToReprocess is SourcesToReprocess.FullRebuild || options.changedFiles.isEmpty()) {
options.compileClasspath options.compileClasspath
} else { } else {
options.compileClasspath + options.compiledSources 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 fileManager = context.get(JavaFileManager::class.java) as KaptJavaFileManager
if (sourcesToReprocess is SourcesToReprocess.Incremental) {
fileManager.typeToIgnore = sourcesToReprocess.dirtyTypes
fileManager.rootsToFilter = options.compiledSources.toSet()
}
if (isJava9OrLater()) { if (isJava9OrLater()) {
for (option in Option.getJavacFileManagerOptions()) { 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 ClassReader.instance(context).saveParameterNames = true
cacheManager = options.incrementalCache?.let {
JavaClassCacheManager(it, options.classpathFqNamesHistory!!)
}
javaLog = compiler.log as KaptJavaLog javaLog = compiler.log as KaptJavaLog
} }
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.base.kapt3 package org.jetbrains.kotlin.base.kapt3
import org.jetbrains.kotlin.kapt3.base.incremental.JavaClassCacheManager
import org.jetbrains.kotlin.kapt3.base.incremental.SourcesToReprocess import org.jetbrains.kotlin.kapt3.base.incremental.SourcesToReprocess
import java.io.File import java.io.File
import java.nio.file.Files import java.nio.file.Files
@@ -129,7 +128,7 @@ enum class AptMode(override val stringValue: String) : KaptSelector {
get() = this != APT_ONLY get() = this != APT_ONLY
} }
fun KaptOptions.collectJavaSourceFiles(cacheManager: JavaClassCacheManager? = null): List<File> { fun KaptOptions.collectJavaSourceFiles(sourcesToReprocess: SourcesToReprocess = SourcesToReprocess.FullRebuild): List<File> {
fun allSources(): List<File> { fun allSources(): List<File> {
return (javaSourceRoots + stubsOutputDir) return (javaSourceRoots + stubsOutputDir)
.sortedBy { Files.isSymbolicLink(it.toPath()) } // Get non-symbolic paths first .sortedBy { Files.isSymbolicLink(it.toPath()) } // Get non-symbolic paths first
@@ -138,15 +137,9 @@ fun KaptOptions.collectJavaSourceFiles(cacheManager: JavaClassCacheManager? = nu
.distinctBy { it.canonicalPath } .distinctBy { it.canonicalPath }
} }
return if (cacheManager != null && changedFiles.isNotEmpty()) { return when (sourcesToReprocess) {
val toReprocess = cacheManager.invalidateAndGetDirtyFiles(changedFiles.filter { it.extension == "java" }) is SourcesToReprocess.FullRebuild -> allSources()
is SourcesToReprocess.Incremental -> sourcesToReprocess.toReprocess.filter { it.exists() }
when (toReprocess) {
is SourcesToReprocess.FullRebuild -> allSources()
is SourcesToReprocess.Incremental -> toReprocess.toReprocess.filter { it.exists() }
}
} else {
allSources()
} }
} }
@@ -12,8 +12,10 @@ import com.sun.tools.javac.processing.JavacFiler
import com.sun.tools.javac.processing.JavacProcessingEnvironment import com.sun.tools.javac.processing.JavacProcessingEnvironment
import com.sun.tools.javac.tree.JCTree import com.sun.tools.javac.tree.JCTree
import org.jetbrains.kotlin.base.kapt3.KaptFlag import org.jetbrains.kotlin.base.kapt3.KaptFlag
import org.jetbrains.kotlin.base.kapt3.collectJavaSourceFiles import org.jetbrains.kotlin.kapt3.base.incremental.DeclaredProcType
import org.jetbrains.kotlin.kapt3.base.incremental.* 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.KaptBaseError
import org.jetbrains.kotlin.kapt3.base.util.isJava9OrLater import org.jetbrains.kotlin.kapt3.base.util.isJava9OrLater
import org.jetbrains.kotlin.kapt3.base.util.measureTimeMillisWithResult import org.jetbrains.kotlin.kapt3.base.util.measureTimeMillisWithResult
@@ -50,8 +52,8 @@ fun KaptContext.doAnnotationProcessing(
} }
val parsedJavaFiles = parseJavaFiles(javaSourceFiles) val parsedJavaFiles = parseJavaFiles(javaSourceFiles)
val listener = cacheManager?.let { val sourcesStructureListener = 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, processingEnvironment.elementUtils) val recordTypesListener = MentionedTypesTaskListener(cacheManager.javaCache, processingEnvironment.elementUtils)
compiler.getTaskListeners().add(recordTypesListener) compiler.getTaskListeners().add(recordTypesListener)
@@ -64,15 +66,19 @@ fun KaptContext.doAnnotationProcessing(
CompileState.PARSE, compiler.enterTrees(parsedJavaFiles + additionalSources) 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()) { 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)
compiler compiler
} else { } else {
val processAnnotations = compiler.processAnnotations(analyzedFiles) compiler.processAnnotations(analyzedFiles).also {
processAnnotations generatedSourcesListener?.let { compiler.getTaskListeners().remove(it) }
}
} }
} catch (e: AnnotationProcessingError) { } catch (e: AnnotationProcessingError) {
throw KaptBaseError(KaptBaseError.Kind.EXCEPTION, e.cause ?: e) throw KaptBaseError(KaptBaseError.Kind.EXCEPTION, e.cause ?: e)
@@ -49,25 +49,34 @@ class IncrementalAptCache : Serializable {
return true return true
} }
fun invalidateAggregatingAndGetAnnotations(): Set<String> { fun getAggregatingClaimedAnnotations(): Set<String> = aggregatingClaimedAnnotations
/** Returns generated Java sources originating from aggregating APs. */
fun invalidateAggregating(): List<File> {
val dirtyAggregating = aggregatingGenerated.filter { it.extension == "java" }
aggregatingGenerated.forEach { it.delete() } aggregatingGenerated.forEach { it.delete() }
aggregatingGenerated.clear() aggregatingGenerated.clear()
return aggregatingClaimedAnnotations return dirtyAggregating
} }
fun invalidateIsolatingGenerated(fromSources: Set<File>) { /** Returns generated Java sources originating from the specified sources, and generated by isloating APs. */
fun invalidateIsolatingGenerated(fromSources: Set<File>): List<File> {
val allInvalidated = mutableListOf<File>()
var changedSources = fromSources.toSet() var changedSources = fromSources.toSet()
// We need to do it in a loop because mapping could be: [AGenerated.java -> A.java, AGeneratedGenerated.java -> AGenerated.java] // 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 val generated = isolatingMapping.filter { changedSources.contains(it.value) }.keys
generated.forEach { generated.forEach {
if (it.extension == "java") allInvalidated.add(it)
it.delete() it.delete()
isolatingMapping.remove(it) isolatingMapping.remove(it)
} }
changedSources = generated changedSources = generated
} }
return allInvalidated
} }
private fun invalidateCache() { private fun invalidateCache() {
@@ -8,15 +8,15 @@ package org.jetbrains.kotlin.kapt3.base.incremental
import java.io.* import java.io.*
// TODO(gavra): switch away from Java serialization // 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 javaCache = maybeGetJavaCacheFromFile()
internal val aptCacheFile = file.resolve("apt-cache.bin") private val aptCacheFile = file.resolve("apt-cache.bin")
internal val aptCache = maybeGetAptCacheFromFile() 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 private var closed = false
@@ -75,15 +75,20 @@ class JavaClassCacheManager(private val file: File, private val classpathFqNames
is SourcesToReprocess.Incremental -> { is SourcesToReprocess.Incremental -> {
val toReprocess = filesToReprocess.toReprocess.toMutableSet() val toReprocess = filesToReprocess.toReprocess.toMutableSet()
aptCache.invalidateIsolatingGenerated(toReprocess) val isolatingGenerated = aptCache.invalidateIsolatingGenerated(toReprocess)
val generatedDirtyTypes = javaCache.invalidateGeneratedTypes(isolatingGenerated).toMutableSet()
if (!toReprocess.isEmpty()) { if (!toReprocess.isEmpty()) {
// only if there are some files to reprocess we should invalidate the aggregating ones // 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( 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 { sealed class SourcesToReprocess {
class Incremental(val toReprocess: List<File>): SourcesToReprocess() class Incremental(val toReprocess: List<File>, val dirtyTypes: Set<String>) : SourcesToReprocess()
object FullRebuild : SourcesToReprocess() object FullRebuild : SourcesToReprocess()
} }
@@ -7,12 +7,17 @@ package org.jetbrains.kotlin.kapt3.base.incremental
import com.sun.tools.javac.processing.JavacProcessingEnvironment import com.sun.tools.javac.processing.JavacProcessingEnvironment
import java.io.File import java.io.File
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.Serializable import java.io.Serializable
import java.net.URI import java.net.URI
class JavaClassCache() : Serializable { class JavaClassCache() : Serializable {
private var sourceCache = mutableMapOf<URI, SourceFileStructure>() private var sourceCache = mutableMapOf<URI, SourceFileStructure>()
/** Record these separately because we only need to know where each generated type is coming from. */
private var generatedTypes = mutableMapOf<File, MutableList<String>>()
/** Map from types to files they are mentioned in. */ /** Map from types to files they are mentioned in. */
@Transient @Transient
private var dependencyCache = mutableMapOf<String, MutableSet<URI>>() private var dependencyCache = mutableMapOf<String, MutableSet<URI>>()
@@ -23,9 +28,21 @@ class JavaClassCache() : Serializable {
sourceCache[sourceStructure.sourceFile] = sourceStructure 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<File>): Set<String> {
return files.mapNotNull { generatedTypes.remove(it) }.flatten().toSet()
}
private fun readObject(input: ObjectInputStream) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
sourceCache = input.readObject() as MutableMap<URI, SourceFileStructure> sourceCache = input.readObject() as MutableMap<URI, SourceFileStructure>
@Suppress("UNCHECKED_CAST")
generatedTypes = input.readObject() as MutableMap<File, MutableList<String>>
dependencyCache = HashMap(sourceCache.size * 4) dependencyCache = HashMap(sourceCache.size * 4)
for (sourceInfo in sourceCache.values) { 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) fun isAlreadyProcessed(sourceFile: URI) = sourceCache.containsKey(sourceFile)
/** Used for testing only. */ /** Used for testing only. */
@@ -57,6 +79,7 @@ class JavaClassCache() : Serializable {
fun invalidateEntriesForChangedFiles(changes: Changes): SourcesToReprocess { fun invalidateEntriesForChangedFiles(changes: Changes): SourcesToReprocess {
val allDirtyFiles = mutableSetOf<URI>() val allDirtyFiles = mutableSetOf<URI>()
var currentDirtyFiles = changes.sourceChanges.map { it.toURI() } var currentDirtyFiles = changes.sourceChanges.map { it.toURI() }
val allDirtyTypes = mutableSetOf<String>()
// check for constants first because they cause full rebuilt // check for constants first because they cause full rebuilt
for (sourceChange in currentDirtyFiles) { for (sourceChange in currentDirtyFiles) {
@@ -76,6 +99,7 @@ class JavaClassCache() : Serializable {
val structure = sourceCache.remove(dirtyFile) ?: continue val structure = sourceCache.remove(dirtyFile) ?: continue
val dirtyTypes = structure.getDeclaredTypes() val dirtyTypes = structure.getDeclaredTypes()
allDirtyTypes.addAll(dirtyTypes)
dirtyTypes.forEach { type -> dirtyTypes.forEach { type ->
nonTransitiveCache[type]?.let { nonTransitiveCache[type]?.let {
@@ -91,7 +115,7 @@ class JavaClassCache() : Serializable {
currentDirtyFiles = nextRound.filter { !allDirtyFiles.contains(it) } 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() { internal fun invalidateAll() {
sourceCache.clear() sourceCache.clear()
generatedTypes.clear()
} }
} }
@@ -125,12 +125,13 @@ internal class AnnotationProcessorDependencyCollector(private val runtimeProcTyp
} }
private fun getSrcFiles(elements: Array<out Element?>): List<File> { private fun getSrcFiles(elements: Array<out Element?>): List<File> {
return elements.filterNotNull().mapNotNull { return elements.filterNotNull().mapNotNull { elem ->
var origin = it var origin = elem
while (origin.enclosingElement != null && origin.enclosingElement !is PackageElement) { while (origin.enclosingElement != null && origin.enclosingElement !is PackageElement) {
origin = origin.enclosingElement 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) }
} }
} }
@@ -192,4 +192,28 @@ private class TypeTreeVisitor(val elementUtils: Elements, val sourceStructure: S
} }
return true 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<Void, Void>() {
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)
}
}
} }
@@ -5,10 +5,15 @@
package org.jetbrains.kotlin.kapt3.base.javac 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.file.JavacFileManager
import com.sun.tools.javac.main.Option import com.sun.tools.javac.main.Option
import com.sun.tools.javac.util.Context import com.sun.tools.javac.util.Context
import java.io.File
import java.util.*
import javax.tools.JavaFileManager import javax.tools.JavaFileManager
import javax.tools.JavaFileObject
import javax.tools.StandardLocation
class KaptJavaFileManager(context: Context) : JavacFileManager(context, true, null) { class KaptJavaFileManager(context: Context) : JavacFileManager(context, true, null) {
fun handleOptionJavac9(option: Option, value: String) { fun handleOptionJavac9(option: Option, value: String) {
@@ -18,6 +23,57 @@ class KaptJavaFileManager(context: Context) : JavacFileManager(context, true, nu
handleOptionMethod.invoke(this, option, value) handleOptionMethod.invoke(this, option, value)
} }
var rootsToFilter = emptySet<File>()
// TODO (gavra): store these more efficiently by package (requires some changes to type collection).
var typeToIgnore = emptySet<String>()
override fun list(
location: JavaFileManager.Location?,
packageName: String?,
kinds: MutableSet<JavaFileObject.Kind>?,
recurse: Boolean
): MutableIterable<JavaFileObject> {
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<JavaFileObject>()
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 { companion object {
internal fun preRegister(context: Context) { internal fun preRegister(context: Context) {
context.put(JavaFileManager::class.java, Context.Factory<JavaFileManager> { KaptJavaFileManager(it) }) context.put(JavaFileManager::class.java, Context.Factory<JavaFileManager> { KaptJavaFileManager(it) })
@@ -50,8 +50,9 @@ class IncrementalKaptTest {
val logger = WriterBackedKaptLogger(isVerbose = true) val logger = WriterBackedKaptLogger(isVerbose = true)
KaptContext(options, true, logger).use { KaptContext(options, true, logger).use {
val toReprocess = it.cacheManager!!.invalidateAndGetDirtyFiles(options.changedFiles)
it.doAnnotationProcessing( it.doAnnotationProcessing(
options.collectJavaSourceFiles(it.cacheManager), listOf(SimpleProcessor().toIsolating()) options.collectJavaSourceFiles(toReprocess), listOf(SimpleProcessor().toIsolating())
) )
} }
@@ -77,11 +78,12 @@ class IncrementalKaptTest {
}.build() }.build()
KaptContext(optionsForSecondRun, true, logger).use { 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()) assertFalse(outputDir.resolve("test/UserGenerated.java").exists())
it.doAnnotationProcessing( it.doAnnotationProcessing(
sourcesToReprocess, listOf(SimpleProcessor().toIsolating()) optionsForSecondRun.collectJavaSourceFiles(sourcesToReprocess), listOf(SimpleProcessor().toIsolating())
) )
} }
@@ -90,12 +92,80 @@ class IncrementalKaptTest {
sourcesDir.resolve("User.java").delete() sourcesDir.resolve("User.java").delete()
KaptContext(optionsForSecondRun, true, logger).use { KaptContext(optionsForSecondRun, true, logger).use {
val sourcesToReprocess = it.cacheManager!!.invalidateAndGetDirtyFiles(optionsForSecondRun.changedFiles)
it.doAnnotationProcessing( it.doAnnotationProcessing(
optionsForSecondRun.collectJavaSourceFiles(it.cacheManager), listOf(SimpleProcessor().toIsolating()) optionsForSecondRun.collectJavaSourceFiles(sourcesToReprocess), listOf(SimpleProcessor().toIsolating())
) )
} }
assertEquals(addressTimestamp, outputDir.resolve("test/AddressGenerated.java").lastModified()) assertEquals(addressTimestamp, outputDir.resolve("test/AddressGenerated.java").lastModified())
assertFalse(outputDir.resolve("test/UserGenerated.java").exists()) 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("") } 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 return false
} }
} }
@@ -216,7 +216,7 @@ abstract class AbstractKapt3Extension(
private fun runAnnotationProcessing(kaptContext: KaptContext, processors: LoadedProcessors) { private fun runAnnotationProcessing(kaptContext: KaptContext, processors: LoadedProcessors) {
if (!options.mode.runAnnotationProcessing) return 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 } } logger.info { "Java source files: " + javaSourceFiles.joinToString { it.canonicalPath } }
val (annotationProcessingTime) = measureTimeMillis { val (annotationProcessingTime) = measureTimeMillis {