diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/incapt/IncrementalAggregatingReferencingClasspathProcessor.java b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/incapt/IncrementalAggregatingReferencingClasspathProcessor.java new file mode 100644 index 00000000000..74c7286ac6f --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/incapt/IncrementalAggregatingReferencingClasspathProcessor.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 java.io.IOException; +import java.io.Writer; +import java.util.Collections; +import java.util.Set; +import java.util.TreeSet; + +public class IncrementalAggregatingReferencingClasspathProcessor extends AbstractProcessor { + + // Type that the generated source will extend. + public static final String CLASSPATH_TYPE = "com.example.FromClasspath"; + + 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().createSourceFile("com.example.AggGenerated").openWriter()) { + writer.append("package ").append("com.example").append(";"); + writer.append("\npublic class ").append("AggGenerated").append(" extends ").append(CLASSPATH_TYPE).append(" {}"); + } + 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/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 4ce515d8f13..345ead8fd05 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 @@ -5,11 +5,14 @@ package org.jetbrains.kotlin.gradle +import org.jetbrains.kotlin.gradle.incapt.IncrementalAggregatingReferencingClasspathProcessor +import org.jetbrains.kotlin.gradle.incapt.IncrementalBinaryIsolatingProcessor import org.jetbrains.kotlin.gradle.incapt.IncrementalProcessor import org.jetbrains.kotlin.gradle.incapt.IncrementalProcessorReferencingClasspath import org.jetbrains.kotlin.gradle.util.AGPVersion import org.jetbrains.kotlin.gradle.util.modify import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Assume import org.junit.Test import test.kt33617.MyClass @@ -275,6 +278,92 @@ class KaptIncrementalWithIsolatingApt : KaptIncrementalIT() { ) } } + + /** + * Make sure that changes to classpath can cause types to be reprocessed (i.e types in generated .class files that contain annotations + * claimed by annotation processors). + */ + @Test + fun testClasspathChangesCauseTypesToBeReprocessed() { + val project = Project( + "kaptIncrementalCompilationProject", + GradleVersionRequired.None + ).apply { + setupIncrementalAptProject( + Pair("ISOLATING", IncrementalBinaryIsolatingProcessor::class.java), + Pair("AGGREGATING", IncrementalAggregatingReferencingClasspathProcessor::class.java), + ) + } + project.gradleSettingsScript().writeText("include ':', ':lib'") + val classpathTypeSource = project.projectDir.resolve("lib").run { + mkdirs() + resolve("build.gradle").writeText("apply plugin: 'java'") + val source = + resolve("src/main/java/" + IncrementalAggregatingReferencingClasspathProcessor.CLASSPATH_TYPE.replace(".", "/") + ".java") + source.parentFile.mkdirs() + + source.writeText( + """ + package ${IncrementalAggregatingReferencingClasspathProcessor.CLASSPATH_TYPE.substringBeforeLast(".")}; + public class ${IncrementalAggregatingReferencingClasspathProcessor.CLASSPATH_TYPE.substringAfterLast(".")} {} + """.trimIndent() + ) + return@run source + } + project.gradleBuildScript().appendText( + """ + + dependencies { + implementation project(':lib') + } + """.trimIndent() + ) + + // Remove all sources, and add only 1 source file + project.projectDir.resolve("src").let { + it.deleteRecursively() + with(it.resolve("main/java/example/A.kt")) { + parentFile.mkdirs() + writeText( + """ + package example + + annotation class ExampleAnnotation + @ExampleAnnotation + class A + """.trimIndent() + ) + } + } + + val allKotlinStubs = setOf( + project.projectDir.resolve("build/tmp/kapt3/stubs/main/example/ExampleAnnotation.java").canonicalPath, + project.projectDir.resolve("build/tmp/kapt3/stubs/main/example/A.java").canonicalPath, + project.projectDir.resolve("build/tmp/kapt3/stubs/main/error/NonExistentClass.java").canonicalPath + ) + + project.build("clean", "build") { + assertSuccessful() + assertEquals(allKotlinStubs, getProcessedSources(output)) + + assertTrue( + "Aggregating sources exists", + fileInWorkingDir("build/generated/source/kapt/main/com/example/AggGenerated.java").exists() + ) + } + + // change type that the aggregated generated source reference + classpathTypeSource.writeText(classpathTypeSource.readText().replace("}", "int i = 10;\n}")) + project.build("build") { + assertSuccessful() + assertEquals(emptySet(), getProcessedSources(output)) + assertEquals(setOf("example.AGenerated"), getProcessedTypes(output)) + assertTrue( + "Aggregating sources exists", + fileInWorkingDir("build/generated/source/kapt/main/com/example/AggGenerated.java").exists() + ) + } + } } private const val patternApt = "Processing java sources with annotation processors:" @@ -285,6 +374,14 @@ fun getProcessedSources(output: String): Set { } } +private const val patternClassesApt = "Processing types with annotation processors: " +fun getProcessedTypes(output: String): Set { + return output.lines().filter { it.contains(patternClassesApt) }.flatMapTo(HashSet()) { logging -> + val indexOf = logging.indexOf(patternClassesApt) + patternClassesApt.length + logging.drop(indexOf).split(",").map { it.trim() }.filter { !it.isEmpty() }.toSet() + } +} + fun BaseGradleIT.Project.setupIncrementalAptProject( procType: String, buildFile: File = projectDir.resolve("build.gradle"), diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kaptIncrementalCompilationProject/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kaptIncrementalCompilationProject/build.gradle index b347cdb6bbc..f1f80a4ca42 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kaptIncrementalCompilationProject/build.gradle +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kaptIncrementalCompilationProject/build.gradle @@ -15,11 +15,15 @@ apply plugin: "kotlin-kapt" repositories { jcenter() mavenLocal() + maven { + url "https://jetbrains.bintray.com/intellij-third-party-dependencies/" + } } 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.jetbrains.intellij.deps:asm-all:9.0" testImplementation 'junit:junit:4.12' } \ 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 35732bf20a2..140e3818f06 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 @@ -45,7 +45,7 @@ object Kapt { kaptContext.doAnnotationProcessing( javaSourceFiles, processors.processors, - aggregatedTypes = collectAggregatedTypes(kaptContext.sourcesToReprocess) + binaryTypesToReprocess = collectAggregatedTypes(kaptContext.sourcesToReprocess) ) } 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 3ce96ff3de6..7475a7f3129 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 @@ -14,7 +14,6 @@ import com.sun.tools.javac.processing.JavacProcessingEnvironment import com.sun.tools.javac.tree.JCTree import org.jetbrains.kotlin.base.kapt3.KaptFlag import org.jetbrains.kotlin.kapt3.base.incremental.* -import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaCompiler import org.jetbrains.kotlin.kapt3.base.util.KaptBaseError import org.jetbrains.kotlin.kapt3.base.util.KaptLogger import org.jetbrains.kotlin.kapt3.base.util.isJava9OrLater @@ -33,7 +32,7 @@ fun KaptContext.doAnnotationProcessing( javaSourceFiles: List, processors: List, additionalSources: JavacList = JavacList.nil(), - aggregatedTypes: List = emptyList() + binaryTypesToReprocess: List = emptyList() ) { val processingEnvironment = JavacProcessingEnvironment.instance(context) @@ -41,7 +40,7 @@ fun KaptContext.doAnnotationProcessing( val compilerAfterAP: JavaCompiler try { - if (javaSourceFiles.isEmpty() && aggregatedTypes.isEmpty() && additionalSources.isEmpty()) { + if (javaSourceFiles.isEmpty() && binaryTypesToReprocess.isEmpty() && additionalSources.isEmpty()) { if (logger.isVerbose) { logger.info("Skipping annotation processing as all sources are up-to-date.") } @@ -57,6 +56,7 @@ fun KaptContext.doAnnotationProcessing( if (logger.isVerbose) { logger.info("Processing java sources with annotation processors: ${javaSourceFiles.joinToString()}") + logger.info("Processing types with annotation processors: ${binaryTypesToReprocess.joinToString()}") } val parsedJavaFiles = parseJavaFiles(javaSourceFiles) @@ -74,7 +74,7 @@ fun KaptContext.doAnnotationProcessing( CompileState.PARSE, compiler.enterTrees(parsedJavaFiles + additionalSources) ) - val additionalClassNames = JavacList.from(aggregatedTypes) + val additionalClassNames = JavacList.from(binaryTypesToReprocess) if (isJava9OrLater()) { val processAnnotationsMethod = compiler.javaClass.getMethod("processAnnotations", JavacList::class.java, java.util.Collection::class.java) 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 513321ecfff..90a17d0be90 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 @@ -93,8 +93,7 @@ class JavaClassCacheManager(val file: File) : Closeable { javaCache.invalidateDataForTypes(impactedTypes) aptCache.invalidateAggregating() // for isolating, invalidate both own types and classpath types - aptCache.invalidateIsolatingForOriginTypes(impactedTypes) - aptCache.invalidateIsolatingForOriginTypes(dirtyClasspathFqNames) + aptCache.invalidateIsolatingForOriginTypes(impactedTypes + dirtyClasspathFqNames) } return SourcesToReprocess.Incremental(sourcesToReprocess.toList(), impactedTypes, classNamesToReprocess) 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 91e53ebe20b..2f64043510a 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 @@ -31,17 +31,6 @@ class JavaClassCache() : Serializable { sourceCache[sourceStructure.sourceFile] = sourceStructure } - /** Invalidates types for these files, and return the list of invalidated types.*/ - fun invalidateTypesForFiles(files: List): Set { - val typesFromFiles = HashSet() - for (file in files) { - sourceCache.remove(file.toURI())?.getDeclaredTypes()?.let { - typesFromFiles.addAll(it) - } - } - return typesFromFiles - } - /** Returns all types defined in these files. */ fun getTypesForFiles(files: Collection): Set { val typesFromFiles = HashSet(files.size)