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 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.IncrementalProcessor
import org.jetbrains.kotlin.gradle.incapt.IncrementalProcessorReferencingClasspath import org.jetbrains.kotlin.gradle.incapt.IncrementalProcessorReferencingClasspath
import org.jetbrains.kotlin.gradle.util.AGPVersion import org.jetbrains.kotlin.gradle.util.AGPVersion
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.Assume import org.junit.Assume
import org.junit.Test import org.junit.Test
import test.kt33617.MyClass 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:" 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( fun BaseGradleIT.Project.setupIncrementalAptProject(
procType: String, procType: String,
buildFile: File = projectDir.resolve("build.gradle"), buildFile: File = projectDir.resolve("build.gradle"),
@@ -15,11 +15,15 @@ apply plugin: "kotlin-kapt"
repositories { repositories {
jcenter() jcenter()
mavenLocal() mavenLocal()
maven {
url "https://jetbrains.bintray.com/intellij-third-party-dependencies/"
}
} }
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlin:annotation-processor-example:$kotlin_version" implementation "org.jetbrains.kotlin:annotation-processor-example:$kotlin_version"
kapt "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' testImplementation 'junit:junit:4.12'
} }
@@ -45,7 +45,7 @@ object Kapt {
kaptContext.doAnnotationProcessing( kaptContext.doAnnotationProcessing(
javaSourceFiles, javaSourceFiles,
processors.processors, 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 com.sun.tools.javac.tree.JCTree
import org.jetbrains.kotlin.base.kapt3.KaptFlag import org.jetbrains.kotlin.base.kapt3.KaptFlag
import org.jetbrains.kotlin.kapt3.base.incremental.* 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.KaptBaseError
import org.jetbrains.kotlin.kapt3.base.util.KaptLogger import org.jetbrains.kotlin.kapt3.base.util.KaptLogger
import org.jetbrains.kotlin.kapt3.base.util.isJava9OrLater import org.jetbrains.kotlin.kapt3.base.util.isJava9OrLater
@@ -33,7 +32,7 @@ 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() binaryTypesToReprocess: List<String> = emptyList()
) { ) {
val processingEnvironment = JavacProcessingEnvironment.instance(context) val processingEnvironment = JavacProcessingEnvironment.instance(context)
@@ -41,7 +40,7 @@ fun KaptContext.doAnnotationProcessing(
val compilerAfterAP: JavaCompiler val compilerAfterAP: JavaCompiler
try { try {
if (javaSourceFiles.isEmpty() && aggregatedTypes.isEmpty() && additionalSources.isEmpty()) { if (javaSourceFiles.isEmpty() && binaryTypesToReprocess.isEmpty() && additionalSources.isEmpty()) {
if (logger.isVerbose) { if (logger.isVerbose) {
logger.info("Skipping annotation processing as all sources are up-to-date.") logger.info("Skipping annotation processing as all sources are up-to-date.")
} }
@@ -57,6 +56,7 @@ fun KaptContext.doAnnotationProcessing(
if (logger.isVerbose) { if (logger.isVerbose) {
logger.info("Processing java sources with annotation processors: ${javaSourceFiles.joinToString()}") logger.info("Processing java sources with annotation processors: ${javaSourceFiles.joinToString()}")
logger.info("Processing types with annotation processors: ${binaryTypesToReprocess.joinToString()}")
} }
val parsedJavaFiles = parseJavaFiles(javaSourceFiles) val parsedJavaFiles = parseJavaFiles(javaSourceFiles)
@@ -74,7 +74,7 @@ fun KaptContext.doAnnotationProcessing(
CompileState.PARSE, compiler.enterTrees(parsedJavaFiles + additionalSources) CompileState.PARSE, compiler.enterTrees(parsedJavaFiles + additionalSources)
) )
val additionalClassNames = JavacList.from(aggregatedTypes) val additionalClassNames = JavacList.from(binaryTypesToReprocess)
if (isJava9OrLater()) { if (isJava9OrLater()) {
val processAnnotationsMethod = val processAnnotationsMethod =
compiler.javaClass.getMethod("processAnnotations", JavacList::class.java, java.util.Collection::class.java) 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) javaCache.invalidateDataForTypes(impactedTypes)
aptCache.invalidateAggregating() aptCache.invalidateAggregating()
// for isolating, invalidate both own types and classpath types // for isolating, invalidate both own types and classpath types
aptCache.invalidateIsolatingForOriginTypes(impactedTypes) aptCache.invalidateIsolatingForOriginTypes(impactedTypes + dirtyClasspathFqNames)
aptCache.invalidateIsolatingForOriginTypes(dirtyClasspathFqNames)
} }
return SourcesToReprocess.Incremental(sourcesToReprocess.toList(), impactedTypes, classNamesToReprocess) return SourcesToReprocess.Incremental(sourcesToReprocess.toList(), impactedTypes, classNamesToReprocess)
@@ -31,17 +31,6 @@ class JavaClassCache() : Serializable {
sourceCache[sourceStructure.sourceFile] = sourceStructure 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. */ /** Returns all types defined in these files. */
fun getTypesForFiles(files: Collection<File>): Set<String> { fun getTypesForFiles(files: Collection<File>): Set<String> {
val typesFromFiles = HashSet<String>(files.size) val typesFromFiles = HashSet<String>(files.size)