diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/incapt/IncrementalAggregatingProcessor.java b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/incapt/IncrementalAggregatingProcessor.java new file mode 100644 index 00000000000..a59019a2ffd --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/incapt/IncrementalAggregatingProcessor.java @@ -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 values = new TreeSet(); + + @Override + public Set getSupportedAnnotationTypes() { + return Collections.singleton("example.KotlinFilerGenerated"); + } + + @Override + public boolean process(Set 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; + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/incapt/IncrementalBinaryIsolatingProcessor.java b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/incapt/IncrementalBinaryIsolatingProcessor.java new file mode 100644 index 00000000000..3d80ceb23ef --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/incapt/IncrementalBinaryIsolatingProcessor.java @@ -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 getSupportedAnnotationTypes() { + return Collections.singleton("example.ExampleAnnotation"); + } + + @Override + public boolean process(Set 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; + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/incapt/IncrementalIsolatingProcessor.java b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/incapt/IncrementalIsolatingProcessor.java new file mode 100644 index 00000000000..bee92859a50 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/incapt/IncrementalIsolatingProcessor.java @@ -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 getSupportedAnnotationTypes() { + return Collections.singleton("example.ExampleAnnotation"); + } + + @Override + public boolean process(Set 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; + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KaptIncrementalWithAggregatingApt.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KaptIncrementalWithAggregatingApt.kt index ca60e4001ca..e7e629f65e9 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KaptIncrementalWithAggregatingApt.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KaptIncrementalWithAggregatingApt.kt @@ -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) -> 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")) + } + } + } + } \ No newline at end of file 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 386c15a1fde..71e9ec7c981 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 @@ -201,10 +201,21 @@ fun getProcessedSources(output: String): Set { 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>, + 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>): 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 diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kaptIncrementalAggregatingProcessorProject/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kaptIncrementalAggregatingProcessorProject/build.gradle new file mode 100644 index 00000000000..dd8fa25bfdc --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kaptIncrementalAggregatingProcessorProject/build.gradle @@ -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' +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kaptIncrementalAggregatingProcessorProject/src/main/java/bar/noAnnotations.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kaptIncrementalAggregatingProcessorProject/src/main/java/bar/noAnnotations.kt new file mode 100644 index 00000000000..b3801111e6d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kaptIncrementalAggregatingProcessorProject/src/main/java/bar/noAnnotations.kt @@ -0,0 +1,7 @@ +package bar + +class noAnnotations { + val valB = "text" + + fun funB() {} +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kaptIncrementalAggregatingProcessorProject/src/main/java/bar/withAnnotation.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kaptIncrementalAggregatingProcessorProject/src/main/java/bar/withAnnotation.kt new file mode 100644 index 00000000000..b42ba0f4244 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kaptIncrementalAggregatingProcessorProject/src/main/java/bar/withAnnotation.kt @@ -0,0 +1,4 @@ +package bar + +@example.ExampleAnnotation +class WithAnnotation() {} \ No newline at end of file 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 5e407f2ae3f..35732bf20a2 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 @@ -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" } 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 4a40c2a4741..5f06b01a622 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 @@ -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", 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 5f3d8914e02..c8cf29e70c5 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 @@ -168,6 +168,15 @@ fun KaptOptions.collectJavaSourceFiles(sourcesToReprocess: SourcesToReprocess = } } +fun collectAggregatedTypes(sourcesToReprocess: SourcesToReprocess = SourcesToReprocess.FullRebuild): List { + 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.") 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 9f8b4c35dd3..1919bb94ca1 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 @@ -31,7 +31,8 @@ import com.sun.tools.javac.util.List as JavacList fun KaptContext.doAnnotationProcessing( javaSourceFiles: List, processors: List, - additionalSources: JavacList = JavacList.nil() + additionalSources: JavacList = JavacList.nil(), + aggregatedTypes: List = 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() - override fun process(annotations: MutableSet?, roundEnv: RoundEnvironment?): Boolean { + override fun process(annotations: MutableSet, roundEnv: RoundEnvironment): Boolean { val (time, result) = measureTimeMillisWithResult { delegate.process(annotations, roundEnv) } 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 ae2cf1dab15..30d20bb0242 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 @@ -11,6 +11,7 @@ import java.io.Serializable class IncrementalAptCache : Serializable { private val aggregatingGenerated: MutableSet = mutableSetOf() + private val aggregatedTypes: MutableSet = linkedSetOf() private val isolatingMapping: MutableMap = mutableMapOf() // Annotations claimed by aggregating annotation processors private val aggregatingClaimedAnnotations: MutableSet = 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 = aggregatingClaimedAnnotations /** Returns generated Java sources originating from aggregating APs. */ - fun invalidateAggregating(): List { + fun invalidateAggregating(): Pair, List> { 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. */ 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 ce2d1e999b8..29ede6d3ede 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 @@ -48,10 +48,11 @@ class JavaClassCacheManager(val file: File) : Closeable { val isolatingGenerated = aptCache.invalidateIsolatingGenerated(toReprocess) val generatedDirtyTypes = javaCache.invalidateGeneratedTypes(isolatingGenerated).toMutableSet() - + val aggregatedTypes = mutableListOf() 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, val dirtyTypes: Set) : SourcesToReprocess() + class Incremental( + val toReprocess: List, + val dirtyTypes: Set, + val unchangedAggregatedTypes: List + ) : SourcesToReprocess() + object FullRebuild : SourcesToReprocess() } \ No newline at end of file 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 374db2302af..997fc5398bf 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 @@ -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()) } /** 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 d65ca61aa7d..9cba352db3a 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 @@ -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, 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() + private val aggregatedTypes = mutableSetOf() + private var isFullRebuild = !runtimeProcType.isIncremental - internal fun add(createdFile: URI, originatingElements: Array) { + internal fun recordProcessingInputs(supportedAnnotationTypes: Set, annotations: Set, 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, classId: String?) { if (isFullRebuild) return val generatedFile = File(createdFile) @@ -134,6 +165,9 @@ internal class AnnotationProcessorDependencyCollector( } internal fun getGeneratedToSources(): Map = if (isFullRebuild) emptyMap() else generatedToSource + + internal fun getAggregatedTypes(): Set = if (isFullRebuild) emptySet() else aggregatedTypes + internal fun getRuntimeType(): RuntimeProcType { return if (isFullRebuild) { RuntimeProcType.NON_INCREMENTAL @@ -154,6 +188,33 @@ private fun getSrcFiles(elements: Array): Set { }.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): Collection { + 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