KAPT: add tests for processed types, remove dead code, simplify logic

Add integration test which checks if only types can be
reprocessed in an incremental round. Also, remove unused
`invalidateTypesForFiles` method.

Furthermore, clarify that types that are reprocessed
(i.e types from .class files) are not necessarily
aggregating types, but simply types that should be reprocessed.

Test: KaptIncrementalWithIsolatingApt.testClasspathChangesCauseTypesToBeReprocessed
This commit is contained in:
Ivan Gavrilovic
2020-12-02 12:19:34 +00:00
committed by Mikhael Bogdanov
parent 08a2b47c77
commit 11673bd09c
7 changed files with 164 additions and 18 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 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<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().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;
}
}
@@ -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<String>(), 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<String> {
}
}
private const val patternClassesApt = "Processing types with annotation processors: "
fun getProcessedTypes(output: String): Set<String> {
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"),
@@ -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'
}
@@ -45,7 +45,7 @@ object Kapt {
kaptContext.doAnnotationProcessing(
javaSourceFiles,
processors.processors,
aggregatedTypes = collectAggregatedTypes(kaptContext.sourcesToReprocess)
binaryTypesToReprocess = collectAggregatedTypes(kaptContext.sourcesToReprocess)
)
}
@@ -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<File>,
processors: List<IncrementalProcessor>,
additionalSources: JavacList<JCTree.JCCompilationUnit> = JavacList.nil(),
aggregatedTypes: List<String> = emptyList()
binaryTypesToReprocess: List<String> = 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)
@@ -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)
@@ -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<File>): Set<String> {
val typesFromFiles = HashSet<String>()
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<File>): Set<String> {
val typesFromFiles = HashSet<String>(files.size)