Proper support of aggregated processors

This commit is contained in:
Mikhael Bogdanov
2020-11-12 13:43:46 +01:00
committed by max-kammerer
parent f382a55b17
commit 6748560184
16 changed files with 525 additions and 28 deletions
@@ -0,0 +1,57 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.gradle.incapt;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.tools.*;
import java.io.IOException;
import java.io.Writer;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
/**
* Simple processor that generates resource file that contains names of annotated elements.
*/
public class IncrementalAggregatingProcessor extends AbstractProcessor {
private Set<String> values = new TreeSet<String>();
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton("example.KotlinFilerGenerated");
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
if (element instanceof TypeElement || element instanceof ExecutableElement || element instanceof VariableElement) {
values.add(element.getSimpleName().toString());
}
}
}
if (roundEnv.processingOver() && !values.isEmpty()) {
try (Writer writer = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", "generated.txt").openWriter()) {
for (String value : values) {
writer.append(value).append("\n");
}
}
catch (IOException e) {
throw new RuntimeException(e);
}
values.clear();
}
return true;
}
}
@@ -0,0 +1,72 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.gradle.incapt;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collections;
import java.util.Set;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_SUPER;
/** Simple processor that generates a class for every annotated element (class, field, method). */
public class IncrementalBinaryIsolatingProcessor extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton("example.ExampleAnnotation");
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (annotations.isEmpty()) return true;
for (Element element : roundEnv.getElementsAnnotatedWith(annotations.iterator().next())) {
if (element instanceof TypeElement || element instanceof ExecutableElement || element instanceof VariableElement) {
String name = element.getSimpleName().toString();
name = name.substring(0, 1).toUpperCase() + name.substring(1) + "Generated";
System.out.println("kapt: IncrementalBinaryIsolatingProcessor " + name);
String packageName;
if (element instanceof TypeElement) {
packageName = element.getEnclosingElement().getSimpleName().toString();
}
else {
packageName = element.getEnclosingElement().getEnclosingElement().getSimpleName().toString();
}
String generatedClassName = packageName + "." + name;
try (OutputStream stream = processingEnv.getFiler().createClassFile(generatedClassName, element).openOutputStream()) {
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
writer.visit(Opcodes.V1_8,
ACC_PUBLIC | ACC_SUPER,
generatedClassName.replaceAll("\\.", "/"),
null,
"java/lang/Object",
null);
writer.visitAnnotation(Type.getObjectType("example/KotlinFilerGenerated").getDescriptor(), true);
writer.visitEnd();
stream.write(writer.toByteArray());
}
catch (IOException ignored) {
}
}
}
return false;
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.gradle.incapt;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import java.io.IOException;
import java.io.Writer;
import java.util.Collections;
import java.util.Set;
/** Simple processor that generates a class for every annotated element (class, field, method). */
public class IncrementalIsolatingProcessor extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton("example.ExampleAnnotation");
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (annotations.isEmpty()) return true;
for (Element element : roundEnv.getElementsAnnotatedWith(annotations.iterator().next())) {
if (element instanceof TypeElement || element instanceof ExecutableElement || element instanceof VariableElement) {
String name = element.getSimpleName().toString();
name = name.substring(0, 1).toUpperCase() + name.substring(1) + "Generated";
String packageName;
if (element instanceof TypeElement) {
packageName = element.getEnclosingElement().getSimpleName().toString();
}
else {
packageName = element.getEnclosingElement().getEnclosingElement().getSimpleName().toString();
}
try (Writer writer = processingEnv.getFiler().createSourceFile(packageName + "." + name, element).openWriter()) {
writer.append("package ").append(packageName).append(";");
writer.append("\n@example.KotlinFilerGenerated").append("\n");
writer.append("\npublic class ").append(name).append(" {}");
}
catch (IOException ignored) {
}
}
}
return false;
}
}
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.gradle
import org.jetbrains.kotlin.gradle.incapt.*
import org.jetbrains.kotlin.gradle.util.modify
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
@@ -34,6 +35,11 @@ class KaptIncrementalWithAggregatingApt : KaptIncrementalIT() {
)
)
private fun jdk9Options(javaHome: File): BuildOptions {
Assume.assumeTrue("JDK 9 isn't available", javaHome.isDirectory)
return defaultBuildOptions().copy(javaHome = javaHome)
}
@Test
fun testIncrementalChanges() {
val project = getProject()
@@ -80,8 +86,7 @@ class KaptIncrementalWithAggregatingApt : KaptIncrementalIT() {
@Test
fun testIncrementalChangesWithJdk9() {
val javaHome = File(System.getProperty("jdk9Home")!!)
Assume.assumeTrue("JDK 9 isn't available", javaHome.isDirectory)
val options = defaultBuildOptions().copy(javaHome = javaHome)
val options = jdk9Options(javaHome)
val project = getProject()
project.build("clean", "build", options = options) {
@@ -111,7 +116,7 @@ class KaptIncrementalWithAggregatingApt : KaptIncrementalIT() {
GradleVersionRequired.None
).apply {
setupWorkingDir()
val processorPath = generateProcessor("AGGREGATING")
val processorPath = generateProcessor("AGGREGATING" to IncrementalProcessor::class.java)
projectDir.resolve("app/build.gradle").appendText(
"""
@@ -202,4 +207,160 @@ class KaptIncrementalWithAggregatingApt : KaptIncrementalIT() {
)
}
}
@Test
fun testIncrementalAggregatingChanges() {
doIncrementalAggregatingChanges()
}
@Test
fun testIncrementalAggregatingChangesWithJdk9() {
val javaHome = File(System.getProperty("jdk9Home")!!)
doIncrementalAggregatingChanges(buildOptions = jdk9Options(javaHome))
}
@Test
fun testIncrementalBinaryAggregatingChanges() {
doIncrementalAggregatingChanges(true)
}
@Test
fun testIncrementalBinaryAggregatingChangesWithJdk9() {
val javaHome = File(System.getProperty("jdk9Home")!!)
val options = jdk9Options(javaHome)
doIncrementalAggregatingChanges(true, options)
}
private fun CompiledProject.checkAggregatingResource(check: (List<String>) -> Unit) {
val aggregatingResource = "build/tmp/kapt3/classes/main/generated.txt"
assertFileExists(aggregatingResource)
val lines = fileInWorkingDir(aggregatingResource).readLines()
check(lines)
}
fun doIncrementalAggregatingChanges(isBinary: Boolean = false, buildOptions: BuildOptions = defaultBuildOptions()) {
val project = Project(
"kaptIncrementalAggregatingProcessorProject",
GradleVersionRequired.None
).apply {
setupIncrementalAptProject(
"ISOLATING" to if (isBinary) IncrementalBinaryIsolatingProcessor::class.java else IncrementalIsolatingProcessor::class.java,
"AGGREGATING" to IncrementalAggregatingProcessor::class.java
)
}
project.build("clean", "build", options = buildOptions) {
assertSuccessful()
assertEquals(
setOf(
fileInWorkingDir("build/tmp/kapt3/stubs/main/bar/WithAnnotation.java").canonicalPath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/bar/noAnnotations.java").canonicalPath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/error/NonExistentClass.java").canonicalPath
), getProcessedSources(output)
)
checkAggregatingResource { lines ->
assertEquals(1, lines.size)
assertTrue(lines.contains("WithAnnotationGenerated"))
}
}
//change file without annotations
project.projectFile("noAnnotations.kt").modify { current -> "$current\nfun otherFunction() {}" }
project.build("build", options = buildOptions) {
assertSuccessful()
assertEquals(
setOf(
fileInWorkingDir("build/tmp/kapt3/stubs/main/bar/NoAnnotationsKt.java").canonicalPath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/error/NonExistentClass.java").canonicalPath
), getProcessedSources(output)
)
checkAggregatingResource { lines ->
assertEquals(1, lines.size)
assertTrue(lines.contains("WithAnnotationGenerated"))
}
}
//add new file with annotations
val newFile = File(project.projectDir, "src/main/java/baz/BazClass.kt")
newFile.parentFile.mkdirs()
newFile.createNewFile()
project.projectFile("BazClass.kt").modify {
"""
package baz
@example.ExampleAnnotation
class BazClass() {}
""".trimIndent()
}
project.build("build", options = buildOptions) {
assertSuccessful()
assertEquals(
setOf(
fileInWorkingDir("build/tmp/kapt3/stubs/main/baz/BazClass.java").canonicalPath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/error/NonExistentClass.java").canonicalPath
),
getProcessedSources(output)
)
checkAggregatingResource { lines ->
assertEquals(2, lines.size)
assertTrue(lines.contains("WithAnnotationGenerated"))
assertTrue(lines.contains("BazClassGenerated"))
}
}
//move annotation to nested class
project.projectFile("BazClass.kt").modify {
"""
package baz
class BazClass() {
@example.ExampleAnnotation
class BazNested {}
}
""".trimIndent()
}
project.build("build", options = buildOptions) {
assertSuccessful()
assertEquals(
setOf(
fileInWorkingDir("build/tmp/kapt3/stubs/main/baz/BazClass.java").canonicalPath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/error/NonExistentClass.java").canonicalPath
),
getProcessedSources(output)
)
checkAggregatingResource { lines ->
assertEquals(2, lines.size)
assertTrue(lines.contains("WithAnnotationGenerated"))
assertTrue(lines.contains("BazNestedGenerated"))
}
}
//change file without annotations to check that nested class is aggregated
project.projectFile("noAnnotations.kt").modify { current -> "$current\nfun otherFunction2() {}" }
project.build("build", options = buildOptions) {
assertSuccessful()
assertEquals(
setOf(
fileInWorkingDir("build/tmp/kapt3/stubs/main/bar/NoAnnotationsKt.java").canonicalPath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/error/NonExistentClass.java").canonicalPath
), getProcessedSources(output)
)
checkAggregatingResource { lines ->
assertEquals(2, lines.size)
assertTrue(lines.contains("WithAnnotationGenerated"))
assertTrue(lines.contains("BazNestedGenerated"))
}
}
}
}
@@ -201,10 +201,21 @@ fun getProcessedSources(output: String): Set<String> {
return logging.drop(indexOf).split(",").map { it.trim() }.filter { !it.isEmpty() }.toSet()
}
fun BaseGradleIT.Project.setupIncrementalAptProject(procType: String, buildFile: File = projectDir.resolve("build.gradle")) {
fun BaseGradleIT.Project.setupIncrementalAptProject(
procType: String,
buildFile: File = projectDir.resolve("build.gradle"),
procClass: Class<*> = IncrementalProcessor::class.java
) {
setupIncrementalAptProject(procType to procClass, buildFile = buildFile)
}
fun BaseGradleIT.Project.setupIncrementalAptProject(
vararg processors: Pair<String, Class<*>>,
buildFile: File = projectDir.resolve("build.gradle")
) {
setupWorkingDir()
val content = buildFile.readText()
val processorPath = generateProcessor(procType)
val processorPath = generateProcessor(*processors)
val updatedContent = content.replace(
Regex("^\\s*kapt\\s\"org\\.jetbrain.*$", RegexOption.MULTILINE),
@@ -213,20 +224,27 @@ fun BaseGradleIT.Project.setupIncrementalAptProject(procType: String, buildFile:
buildFile.writeText(updatedContent)
}
fun BaseGradleIT.Project.generateProcessor(procType: String): File {
fun BaseGradleIT.Project.generateProcessor(vararg processors: Pair<String, Class<*>>): File {
val processorPath = projectDir.resolve("incrementalProcessor.jar")
ZipOutputStream(processorPath.outputStream()).use {
val path = IncrementalProcessor::class.java.name.replace(".", "/") + ".class"
val inputStream = IncrementalProcessor::class.java.classLoader.getResourceAsStream(path)
it.putNextEntry(ZipEntry(path))
it.write(inputStream.readBytes())
it.closeEntry()
for ((_, procClass) in processors) {
val path = procClass.name.replace(".", "/") + ".class"
procClass.classLoader.getResourceAsStream(path).use { inputStream ->
it.putNextEntry(ZipEntry(path))
it.write(inputStream.readBytes())
it.closeEntry()
}
}
it.putNextEntry(ZipEntry("META-INF/gradle/incremental.annotation.processors"))
it.write("${IncrementalProcessor::class.java.name},$procType".toByteArray())
it.write(processors.joinToString("\n") { (procType, procClass) ->
"${procClass.name},$procType"
}.toByteArray())
it.closeEntry()
it.putNextEntry(ZipEntry("META-INF/services/javax.annotation.processing.Processor"))
it.write(IncrementalProcessor::class.java.name.toByteArray())
it.write(processors.joinToString("\n") { (_, procClass) ->
procClass.name
}.toByteArray())
it.closeEntry()
}
return processorPath
@@ -0,0 +1,26 @@
buildscript {
repositories {
mavenLocal()
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: "java"
apply plugin: "kotlin"
apply plugin: "kotlin-kapt"
repositories {
jcenter()
mavenLocal()
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlin:annotation-processor-example:$kotlin_version"
kapt "org.jetbrains.kotlin:annotation-processor-example:$kotlin_version"
kapt "org.ow2.asm:asm:9.0"
testImplementation 'junit:junit:4.12'
}
@@ -0,0 +1,7 @@
package bar
class noAnnotations {
val valB = "text"
fun funB() {}
}
@@ -42,7 +42,11 @@ object Kapt {
val processors = processorLoader.loadProcessors(findClassLoaderWithJavac())
val annotationProcessingTime = measureTimeMillis {
kaptContext.doAnnotationProcessing(javaSourceFiles, processors.processors)
kaptContext.doAnnotationProcessing(
javaSourceFiles,
processors.processors,
aggregatedTypes = collectAggregatedTypes(kaptContext.sourcesToReprocess)
)
}
logger.info { "Annotation processing took $annotationProcessingTime ms" }
@@ -111,7 +111,7 @@ open class KaptContext(val options: KaptOptions, val withJdk: Boolean, val logge
val compileClasspath = if (sourcesToReprocess is SourcesToReprocess.FullRebuild) {
options.compileClasspath
} else {
options.compileClasspath + options.compiledSources
options.compileClasspath + options.compiledSources + options.classesOutputDir
}
putJavacOption("CLASSPATH", "CLASS_PATH",
@@ -168,6 +168,15 @@ fun KaptOptions.collectJavaSourceFiles(sourcesToReprocess: SourcesToReprocess =
}
}
fun collectAggregatedTypes(sourcesToReprocess: SourcesToReprocess = SourcesToReprocess.FullRebuild): List<String> {
return when (sourcesToReprocess) {
is SourcesToReprocess.FullRebuild -> emptyList()
is SourcesToReprocess.Incremental -> {
sourcesToReprocess.unchangedAggregatedTypes
}
}
}
fun KaptOptions.logString(additionalInfo: String = "") = buildString {
val additionalInfoRendered = if (additionalInfo.isEmpty()) "" else " ($additionalInfo)"
appendLine("Kapt3 is enabled$additionalInfoRendered.")
@@ -31,7 +31,8 @@ import com.sun.tools.javac.util.List as JavacList
fun KaptContext.doAnnotationProcessing(
javaSourceFiles: List<File>,
processors: List<IncrementalProcessor>,
additionalSources: JavacList<JCTree.JCCompilationUnit> = JavacList.nil()
additionalSources: JavacList<JCTree.JCCompilationUnit> = JavacList.nil(),
aggregatedTypes: List<String> = emptyList()
) {
val processingEnvironment = JavacProcessingEnvironment.instance(context)
@@ -70,12 +71,14 @@ fun KaptContext.doAnnotationProcessing(
GeneratedTypesTaskListener(cacheManager!!.javaCache)
}?.also { compiler.getTaskListeners().add(it) }
val additionalClassNames = JavacList.from(aggregatedTypes)
if (isJava9OrLater()) {
val processAnnotationsMethod = compiler.javaClass.getMethod("processAnnotations", JavacList::class.java)
processAnnotationsMethod.invoke(compiler, analyzedFiles)
val processAnnotationsMethod =
compiler.javaClass.getMethod("processAnnotations", JavacList::class.java, java.util.Collection::class.java)
processAnnotationsMethod.invoke(compiler, analyzedFiles, additionalClassNames)
compiler
} else {
compiler.processAnnotations(analyzedFiles).also {
compiler.processAnnotations(analyzedFiles, additionalClassNames).also {
generatedSourcesListener?.let { compiler.getTaskListeners().remove(it) }
}
}
@@ -156,7 +159,7 @@ private class ProcessorWrapper(private val delegate: IncrementalProcessor) : Pro
private var initTime: Long = 0
private val roundTime = mutableListOf<Long>()
override fun process(annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment?): Boolean {
override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment): Boolean {
val (time, result) = measureTimeMillisWithResult {
delegate.process(annotations, roundEnv)
}
@@ -11,6 +11,7 @@ import java.io.Serializable
class IncrementalAptCache : Serializable {
private val aggregatingGenerated: MutableSet<File> = mutableSetOf()
private val aggregatedTypes: MutableSet<String> = linkedSetOf()
private val isolatingMapping: MutableMap<File, File> = mutableMapOf()
// Annotations claimed by aggregating annotation processors
private val aggregatingClaimedAnnotations: MutableSet<String> = mutableSetOf()
@@ -41,6 +42,9 @@ class IncrementalAptCache : Serializable {
aggregatingClaimedAnnotations.clear()
aggregatingClaimedAnnotations.addAll(aggregating.flatMap { it.supportedAnnotationTypes })
aggregatedTypes.clear()
aggregatedTypes.addAll(aggregating.flatMap { it.getAggregatedTypes() })
for (isolatingProcessor in isolating) {
isolatingProcessor.getGeneratedToSources().forEach {
isolatingMapping[it.key] = it.value!!
@@ -52,12 +56,15 @@ class IncrementalAptCache : Serializable {
fun getAggregatingClaimedAnnotations(): Set<String> = aggregatingClaimedAnnotations
/** Returns generated Java sources originating from aggregating APs. */
fun invalidateAggregating(): List<File> {
fun invalidateAggregating(): Pair<List<File>, List<String>> {
val dirtyAggregating = aggregatingGenerated.filter { it.extension == "java" }
aggregatingGenerated.forEach { it.delete() }
aggregatingGenerated.clear()
return dirtyAggregating
val dirtyAggregated = ArrayList(aggregatedTypes)
aggregatedTypes.clear()
return dirtyAggregating to dirtyAggregated
}
/** Returns generated Java sources originating from the specified sources, and generated by isloating APs. */
@@ -48,10 +48,11 @@ class JavaClassCacheManager(val file: File) : Closeable {
val isolatingGenerated = aptCache.invalidateIsolatingGenerated(toReprocess)
val generatedDirtyTypes = javaCache.invalidateGeneratedTypes(isolatingGenerated).toMutableSet()
val aggregatedTypes = mutableListOf<String>()
if (!toReprocess.isEmpty()) {
// only if there are some files to reprocess we should invalidate the aggregating ones
val aggregatingGenerated = aptCache.invalidateAggregating()
val (aggregatingGenerated, aggregatedTypes1) = aptCache.invalidateAggregating()
aggregatedTypes.addAll(aggregatedTypes1)
generatedDirtyTypes.addAll(javaCache.invalidateGeneratedTypes(aggregatingGenerated))
toReprocess.addAll(
@@ -59,7 +60,13 @@ class JavaClassCacheManager(val file: File) : Closeable {
)
}
SourcesToReprocess.Incremental(toReprocess.toList(), generatedDirtyTypes)
SourcesToReprocess.Incremental(
toReprocess.toList(),
generatedDirtyTypes,
aggregatedTypes.also {
it.removeAll(filesToReprocess.dirtyTypes)
it.removeAll(generatedDirtyTypes)
})
}
}
}
@@ -118,6 +125,11 @@ class JavaClassCacheManager(val file: File) : Closeable {
}
sealed class SourcesToReprocess {
class Incremental(val toReprocess: List<File>, val dirtyTypes: Set<String>) : SourcesToReprocess()
class Incremental(
val toReprocess: List<File>,
val dirtyTypes: Set<String>,
val unchangedAggregatedTypes: List<String>
) : SourcesToReprocess()
object FullRebuild : SourcesToReprocess()
}
@@ -139,7 +139,7 @@ class JavaClassCache() : Serializable {
currentDirtyFiles = nextRound.filter { !allDirtyFiles.contains(it) }.toMutableSet()
}
return SourcesToReprocess.Incremental(allDirtyFiles.map { File(it) }, allDirtyTypes)
return SourcesToReprocess.Incremental(allDirtyFiles.map { File(it) }, allDirtyTypes, emptyList())
}
/**
@@ -12,8 +12,10 @@ import java.net.URI
import javax.annotation.processing.Filer
import javax.annotation.processing.ProcessingEnvironment
import javax.annotation.processing.Processor
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.element.Element
import javax.lang.model.element.PackageElement
import javax.lang.model.element.TypeElement
import javax.tools.FileObject
import javax.tools.JavaFileManager
import javax.tools.JavaFileObject
@@ -69,7 +71,16 @@ class IncrementalProcessor(private val processor: Processor, private val kind: D
fun isUnableToRunIncrementally() = !kind.canRunIncrementally
fun getGeneratedToSources() = dependencyCollector.value.getGeneratedToSources()
fun getAggregatedTypes() = dependencyCollector.value.getAggregatedTypes()
fun getRuntimeType(): RuntimeProcType = dependencyCollector.value.getRuntimeType()
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
if (getRuntimeType() == RuntimeProcType.AGGREGATING) {
dependencyCollector.value.recordProcessingInputs(processor.supportedAnnotationTypes, annotations, roundEnv)
}
return processor.process(annotations, roundEnv)
}
}
internal class IncrementalProcessingEnvironment(private val processingEnv: ProcessingEnvironment, private val incFiler: IncrementalFiler) :
@@ -111,9 +122,29 @@ internal class AnnotationProcessorDependencyCollector(
private val warningCollector: (String) -> Unit
) {
private val generatedToSource = mutableMapOf<File, File?>()
private val aggregatedTypes = mutableSetOf<String>()
private var isFullRebuild = !runtimeProcType.isIncremental
internal fun add(createdFile: URI, originatingElements: Array<out Element?>) {
internal fun recordProcessingInputs(supportedAnnotationTypes: Set<String>, annotations: Set<TypeElement>, roundEnv: RoundEnvironment) {
if (isFullRebuild) return
if (supportedAnnotationTypes.contains("*")) {
aggregatedTypes.addAll(getTopLevelClassNames(roundEnv.rootElements?.filterNotNull() ?: emptyList()))
} else {
for (annotation in annotations) {
aggregatedTypes.addAll(
getTopLevelClassNames(
roundEnv.getElementsAnnotatedWith(
annotation
)?.filterNotNull() ?: emptyList()
)
)
}
}
}
internal fun add(createdFile: URI, originatingElements: Array<out Element?>, classId: String?) {
if (isFullRebuild) return
val generatedFile = File(createdFile)
@@ -134,6 +165,9 @@ internal class AnnotationProcessorDependencyCollector(
}
internal fun getGeneratedToSources(): Map<File, File?> = if (isFullRebuild) emptyMap() else generatedToSource
internal fun getAggregatedTypes(): Set<String> = if (isFullRebuild) emptySet() else aggregatedTypes
internal fun getRuntimeType(): RuntimeProcType {
return if (isFullRebuild) {
RuntimeProcType.NON_INCREMENTAL
@@ -154,6 +188,33 @@ private fun getSrcFiles(elements: Array<out Element?>): Set<File> {
}.toSet()
}
private const val PACKAGE_TYPE_NAME = "package-info"
fun getElementName(current: Element?): String? {
if (current is PackageElement) {
val packageName = current.qualifiedName.toString()
return if (packageName.isEmpty()) {
PACKAGE_TYPE_NAME
} else {
"$packageName.$PACKAGE_TYPE_NAME"
}
}
if (current is TypeElement) {
return current.qualifiedName.toString()
}
return null
}
private fun getTopLevelClassNames(elements: Collection<Element>): Collection<String> {
return elements.mapNotNull { elem ->
var origin = elem
while (origin.enclosingElement != null && origin.enclosingElement !is PackageElement) {
origin = origin.enclosingElement
}
getElementName(origin)
}
}
enum class DeclaredProcType(val canRunIncrementally: Boolean) {
AGGREGATING(true) {
override fun toRuntimeType() = RuntimeProcType.AGGREGATING