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 package org.jetbrains.kotlin.gradle
import org.jetbrains.kotlin.gradle.incapt.*
import org.jetbrains.kotlin.gradle.util.modify import org.jetbrains.kotlin.gradle.util.modify
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue 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 @Test
fun testIncrementalChanges() { fun testIncrementalChanges() {
val project = getProject() val project = getProject()
@@ -80,8 +86,7 @@ class KaptIncrementalWithAggregatingApt : KaptIncrementalIT() {
@Test @Test
fun testIncrementalChangesWithJdk9() { fun testIncrementalChangesWithJdk9() {
val javaHome = File(System.getProperty("jdk9Home")!!) val javaHome = File(System.getProperty("jdk9Home")!!)
Assume.assumeTrue("JDK 9 isn't available", javaHome.isDirectory) val options = jdk9Options(javaHome)
val options = defaultBuildOptions().copy(javaHome = javaHome)
val project = getProject() val project = getProject()
project.build("clean", "build", options = options) { project.build("clean", "build", options = options) {
@@ -111,7 +116,7 @@ class KaptIncrementalWithAggregatingApt : KaptIncrementalIT() {
GradleVersionRequired.None GradleVersionRequired.None
).apply { ).apply {
setupWorkingDir() setupWorkingDir()
val processorPath = generateProcessor("AGGREGATING") val processorPath = generateProcessor("AGGREGATING" to IncrementalProcessor::class.java)
projectDir.resolve("app/build.gradle").appendText( 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() 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() setupWorkingDir()
val content = buildFile.readText() val content = buildFile.readText()
val processorPath = generateProcessor(procType) val processorPath = generateProcessor(*processors)
val updatedContent = content.replace( val updatedContent = content.replace(
Regex("^\\s*kapt\\s\"org\\.jetbrain.*$", RegexOption.MULTILINE), Regex("^\\s*kapt\\s\"org\\.jetbrain.*$", RegexOption.MULTILINE),
@@ -213,20 +224,27 @@ fun BaseGradleIT.Project.setupIncrementalAptProject(procType: String, buildFile:
buildFile.writeText(updatedContent) 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") val processorPath = projectDir.resolve("incrementalProcessor.jar")
ZipOutputStream(processorPath.outputStream()).use { ZipOutputStream(processorPath.outputStream()).use {
val path = IncrementalProcessor::class.java.name.replace(".", "/") + ".class" for ((_, procClass) in processors) {
val inputStream = IncrementalProcessor::class.java.classLoader.getResourceAsStream(path) val path = procClass.name.replace(".", "/") + ".class"
it.putNextEntry(ZipEntry(path)) procClass.classLoader.getResourceAsStream(path).use { inputStream ->
it.write(inputStream.readBytes()) it.putNextEntry(ZipEntry(path))
it.closeEntry() it.write(inputStream.readBytes())
it.closeEntry()
}
}
it.putNextEntry(ZipEntry("META-INF/gradle/incremental.annotation.processors")) 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.closeEntry()
it.putNextEntry(ZipEntry("META-INF/services/javax.annotation.processing.Processor")) 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() it.closeEntry()
} }
return processorPath 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 processors = processorLoader.loadProcessors(findClassLoaderWithJavac())
val annotationProcessingTime = measureTimeMillis { val annotationProcessingTime = measureTimeMillis {
kaptContext.doAnnotationProcessing(javaSourceFiles, processors.processors) kaptContext.doAnnotationProcessing(
javaSourceFiles,
processors.processors,
aggregatedTypes = collectAggregatedTypes(kaptContext.sourcesToReprocess)
)
} }
logger.info { "Annotation processing took $annotationProcessingTime ms" } 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) { val compileClasspath = if (sourcesToReprocess is SourcesToReprocess.FullRebuild) {
options.compileClasspath options.compileClasspath
} else { } else {
options.compileClasspath + options.compiledSources options.compileClasspath + options.compiledSources + options.classesOutputDir
} }
putJavacOption("CLASSPATH", "CLASS_PATH", 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 { fun KaptOptions.logString(additionalInfo: String = "") = buildString {
val additionalInfoRendered = if (additionalInfo.isEmpty()) "" else " ($additionalInfo)" val additionalInfoRendered = if (additionalInfo.isEmpty()) "" else " ($additionalInfo)"
appendLine("Kapt3 is enabled$additionalInfoRendered.") appendLine("Kapt3 is enabled$additionalInfoRendered.")
@@ -31,7 +31,8 @@ import com.sun.tools.javac.util.List as JavacList
fun KaptContext.doAnnotationProcessing( fun KaptContext.doAnnotationProcessing(
javaSourceFiles: List<File>, javaSourceFiles: List<File>,
processors: List<IncrementalProcessor>, processors: List<IncrementalProcessor>,
additionalSources: JavacList<JCTree.JCCompilationUnit> = JavacList.nil() additionalSources: JavacList<JCTree.JCCompilationUnit> = JavacList.nil(),
aggregatedTypes: List<String> = emptyList()
) { ) {
val processingEnvironment = JavacProcessingEnvironment.instance(context) val processingEnvironment = JavacProcessingEnvironment.instance(context)
@@ -70,12 +71,14 @@ fun KaptContext.doAnnotationProcessing(
GeneratedTypesTaskListener(cacheManager!!.javaCache) GeneratedTypesTaskListener(cacheManager!!.javaCache)
}?.also { compiler.getTaskListeners().add(it) } }?.also { compiler.getTaskListeners().add(it) }
val additionalClassNames = JavacList.from(aggregatedTypes)
if (isJava9OrLater()) { if (isJava9OrLater()) {
val processAnnotationsMethod = compiler.javaClass.getMethod("processAnnotations", JavacList::class.java) val processAnnotationsMethod =
processAnnotationsMethod.invoke(compiler, analyzedFiles) compiler.javaClass.getMethod("processAnnotations", JavacList::class.java, java.util.Collection::class.java)
processAnnotationsMethod.invoke(compiler, analyzedFiles, additionalClassNames)
compiler compiler
} else { } else {
compiler.processAnnotations(analyzedFiles).also { compiler.processAnnotations(analyzedFiles, additionalClassNames).also {
generatedSourcesListener?.let { compiler.getTaskListeners().remove(it) } generatedSourcesListener?.let { compiler.getTaskListeners().remove(it) }
} }
} }
@@ -156,7 +159,7 @@ private class ProcessorWrapper(private val delegate: IncrementalProcessor) : Pro
private var initTime: Long = 0 private var initTime: Long = 0
private val roundTime = mutableListOf<Long>() 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 { val (time, result) = measureTimeMillisWithResult {
delegate.process(annotations, roundEnv) delegate.process(annotations, roundEnv)
} }
@@ -11,6 +11,7 @@ import java.io.Serializable
class IncrementalAptCache : Serializable { class IncrementalAptCache : Serializable {
private val aggregatingGenerated: MutableSet<File> = mutableSetOf() private val aggregatingGenerated: MutableSet<File> = mutableSetOf()
private val aggregatedTypes: MutableSet<String> = linkedSetOf()
private val isolatingMapping: MutableMap<File, File> = mutableMapOf() private val isolatingMapping: MutableMap<File, File> = mutableMapOf()
// Annotations claimed by aggregating annotation processors // Annotations claimed by aggregating annotation processors
private val aggregatingClaimedAnnotations: MutableSet<String> = mutableSetOf() private val aggregatingClaimedAnnotations: MutableSet<String> = mutableSetOf()
@@ -41,6 +42,9 @@ class IncrementalAptCache : Serializable {
aggregatingClaimedAnnotations.clear() aggregatingClaimedAnnotations.clear()
aggregatingClaimedAnnotations.addAll(aggregating.flatMap { it.supportedAnnotationTypes }) aggregatingClaimedAnnotations.addAll(aggregating.flatMap { it.supportedAnnotationTypes })
aggregatedTypes.clear()
aggregatedTypes.addAll(aggregating.flatMap { it.getAggregatedTypes() })
for (isolatingProcessor in isolating) { for (isolatingProcessor in isolating) {
isolatingProcessor.getGeneratedToSources().forEach { isolatingProcessor.getGeneratedToSources().forEach {
isolatingMapping[it.key] = it.value!! isolatingMapping[it.key] = it.value!!
@@ -52,12 +56,15 @@ class IncrementalAptCache : Serializable {
fun getAggregatingClaimedAnnotations(): Set<String> = aggregatingClaimedAnnotations fun getAggregatingClaimedAnnotations(): Set<String> = aggregatingClaimedAnnotations
/** Returns generated Java sources originating from aggregating APs. */ /** 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" } val dirtyAggregating = aggregatingGenerated.filter { it.extension == "java" }
aggregatingGenerated.forEach { it.delete() } aggregatingGenerated.forEach { it.delete() }
aggregatingGenerated.clear() 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. */ /** 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 isolatingGenerated = aptCache.invalidateIsolatingGenerated(toReprocess)
val generatedDirtyTypes = javaCache.invalidateGeneratedTypes(isolatingGenerated).toMutableSet() val generatedDirtyTypes = javaCache.invalidateGeneratedTypes(isolatingGenerated).toMutableSet()
val aggregatedTypes = mutableListOf<String>()
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() val (aggregatingGenerated, aggregatedTypes1) = aptCache.invalidateAggregating()
aggregatedTypes.addAll(aggregatedTypes1)
generatedDirtyTypes.addAll(javaCache.invalidateGeneratedTypes(aggregatingGenerated)) generatedDirtyTypes.addAll(javaCache.invalidateGeneratedTypes(aggregatingGenerated))
toReprocess.addAll( 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 { 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() object FullRebuild : SourcesToReprocess()
} }
@@ -139,7 +139,7 @@ class JavaClassCache() : Serializable {
currentDirtyFiles = nextRound.filter { !allDirtyFiles.contains(it) }.toMutableSet() 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.Filer
import javax.annotation.processing.ProcessingEnvironment import javax.annotation.processing.ProcessingEnvironment
import javax.annotation.processing.Processor import javax.annotation.processing.Processor
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.element.Element import javax.lang.model.element.Element
import javax.lang.model.element.PackageElement import javax.lang.model.element.PackageElement
import javax.lang.model.element.TypeElement
import javax.tools.FileObject import javax.tools.FileObject
import javax.tools.JavaFileManager import javax.tools.JavaFileManager
import javax.tools.JavaFileObject import javax.tools.JavaFileObject
@@ -69,7 +71,16 @@ class IncrementalProcessor(private val processor: Processor, private val kind: D
fun isUnableToRunIncrementally() = !kind.canRunIncrementally fun isUnableToRunIncrementally() = !kind.canRunIncrementally
fun getGeneratedToSources() = dependencyCollector.value.getGeneratedToSources() fun getGeneratedToSources() = dependencyCollector.value.getGeneratedToSources()
fun getAggregatedTypes() = dependencyCollector.value.getAggregatedTypes()
fun getRuntimeType(): RuntimeProcType = dependencyCollector.value.getRuntimeType() 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) : 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 warningCollector: (String) -> Unit
) { ) {
private val generatedToSource = mutableMapOf<File, File?>() private val generatedToSource = mutableMapOf<File, File?>()
private val aggregatedTypes = mutableSetOf<String>()
private var isFullRebuild = !runtimeProcType.isIncremental 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 if (isFullRebuild) return
val generatedFile = File(createdFile) val generatedFile = File(createdFile)
@@ -134,6 +165,9 @@ internal class AnnotationProcessorDependencyCollector(
} }
internal fun getGeneratedToSources(): Map<File, File?> = if (isFullRebuild) emptyMap() else generatedToSource 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 { internal fun getRuntimeType(): RuntimeProcType {
return if (isFullRebuild) { return if (isFullRebuild) {
RuntimeProcType.NON_INCREMENTAL RuntimeProcType.NON_INCREMENTAL
@@ -154,6 +188,33 @@ private fun getSrcFiles(elements: Array<out Element?>): Set<File> {
}.toSet() }.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) { enum class DeclaredProcType(val canRunIncrementally: Boolean) {
AGGREGATING(true) { AGGREGATING(true) {
override fun toRuntimeType() = RuntimeProcType.AGGREGATING override fun toRuntimeType() = RuntimeProcType.AGGREGATING