Use types are origins for incremental KAPT and track generated source

This change introduces tracking of generated sources structure in order
to e.g track classpath changes impacting generated sources. This fixes KT-42182.

Also, origin tracking for isolating processors is now using types, allowing
for origin elements from classpath. This fixes KT-34340. However, classpath
origin is used only to invalidate generated files when the type changes and
processing will not be requested for that type. This is in line with the
incap spec.
This commit is contained in:
Ivan Gavrilovic
2020-11-26 21:17:59 +00:00
committed by Mikhael Bogdanov
parent d512158c25
commit c7e5beece5
14 changed files with 475 additions and 274 deletions
@@ -0,0 +1,58 @@
/*
* 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 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 IncrementalProcessorReferencingClasspath extends AbstractProcessor {
// Type that all generated sources will extend.
public static final String CLASSPATH_TYPE = "com.example.FromClasspath";
@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("\npublic class ").append(name).append(" extends ").append(CLASSPATH_TYPE).append(" {}");
}
catch (IOException ignored) {
}
}
}
return false;
}
}
@@ -27,6 +27,7 @@ class KaptIncrementalWithAggregatingApt : KaptIncrementalIT() {
override fun defaultBuildOptions(): BuildOptions =
super.defaultBuildOptions().copy(
incremental = true,
debug=false,
kaptOptions = KaptOptions(
verbose = true,
useWorkers = true,
@@ -171,7 +172,7 @@ class KaptIncrementalWithAggregatingApt : KaptIncrementalIT() {
}
project.build("build") {
assertSuccessful()
assertTrue(getProcessedSources(output).isEmpty())
assertTrue(output.contains("Skipping annotation processing as all sources are up-to-date."))
}
}
@@ -275,8 +276,9 @@ class KaptIncrementalWithAggregatingApt : KaptIncrementalIT() {
assertEquals(
setOf(
fileInWorkingDir("build/tmp/kapt3/stubs/main/bar/NoAnnotationsKt.java").canonicalPath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/error/NonExistentClass.java").canonicalPath
), getProcessedSources(output)
fileInWorkingDir("build/tmp/kapt3/stubs/main/error/NonExistentClass.java").canonicalPath,
fileInWorkingDir("build/generated/source/kapt/main/bar/WithAnnotationGenerated.java").canonicalPath.takeUnless { isBinary },
).filterNotNull().toSet(), getProcessedSources(output)
)
checkAggregatingResource { lines ->
@@ -302,9 +304,9 @@ class KaptIncrementalWithAggregatingApt : KaptIncrementalIT() {
assertEquals(
setOf(
fileInWorkingDir("build/tmp/kapt3/stubs/main/baz/BazClass.java").canonicalPath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/error/NonExistentClass.java").canonicalPath
),
getProcessedSources(output)
fileInWorkingDir("build/tmp/kapt3/stubs/main/error/NonExistentClass.java").canonicalPath,
fileInWorkingDir("build/generated/source/kapt/main/bar/WithAnnotationGenerated.java").canonicalPath.takeUnless { isBinary },
).filterNotNull().toSet(), getProcessedSources(output)
)
checkAggregatingResource { lines ->
@@ -330,9 +332,9 @@ class KaptIncrementalWithAggregatingApt : KaptIncrementalIT() {
assertEquals(
setOf(
fileInWorkingDir("build/tmp/kapt3/stubs/main/baz/BazClass.java").canonicalPath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/error/NonExistentClass.java").canonicalPath
),
getProcessedSources(output)
fileInWorkingDir("build/tmp/kapt3/stubs/main/error/NonExistentClass.java").canonicalPath,
fileInWorkingDir("build/generated/source/kapt/main/bar/WithAnnotationGenerated.java").canonicalPath.takeUnless { isBinary },
).filterNotNull().toSet(), getProcessedSources(output)
)
checkAggregatingResource { lines ->
@@ -351,8 +353,30 @@ class KaptIncrementalWithAggregatingApt : KaptIncrementalIT() {
assertEquals(
setOf(
fileInWorkingDir("build/tmp/kapt3/stubs/main/bar/NoAnnotationsKt.java").canonicalPath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/error/NonExistentClass.java").canonicalPath
), getProcessedSources(output)
fileInWorkingDir("build/tmp/kapt3/stubs/main/error/NonExistentClass.java").canonicalPath,
fileInWorkingDir("build/generated/source/kapt/main/bar/WithAnnotationGenerated.java").canonicalPath.takeUnless { isBinary },
fileInWorkingDir("build/generated/source/kapt/main/BazClass/BazNestedGenerated.java").canonicalPath.takeUnless { isBinary },
).filterNotNull().toSet(), getProcessedSources(output)
)
checkAggregatingResource { lines ->
assertEquals(2, lines.size)
assertTrue(lines.contains("WithAnnotationGenerated"))
assertTrue(lines.contains("BazNestedGenerated"))
}
}
// make sure that changing the origin of isolating that produced
project.projectFile("withAnnotation.kt").modify { current -> current.substringBeforeLast("}") + "\nfun otherFunction() {} }" }
project.build("build", options = buildOptions) {
assertSuccessful()
assertEquals(
setOf(
fileInWorkingDir("build/tmp/kapt3/stubs/main/bar/WithAnnotation.java").canonicalPath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/error/NonExistentClass.java").canonicalPath,
fileInWorkingDir("build/generated/source/kapt/main/BazClass/BazNestedGenerated.java").canonicalPath.takeUnless { isBinary },
).filterNotNull().toSet(), getProcessedSources(output)
)
checkAggregatingResource { lines ->
@@ -6,10 +6,14 @@
package org.jetbrains.kotlin.gradle
import org.jetbrains.kotlin.gradle.incapt.IncrementalProcessor
import org.jetbrains.kotlin.gradle.incapt.IncrementalProcessorReferencingClasspath
import org.jetbrains.kotlin.gradle.util.modify
import org.junit.Assert.assertEquals
import org.junit.Assume
import org.junit.Test
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import test.kt33617.MyClass
import java.io.File
import java.util.zip.ZipEntry
@@ -192,6 +196,59 @@ class KaptIncrementalWithIsolatingApt : KaptIncrementalIT() {
assertSuccessful()
}
}
/** Regression test for https://youtrack.jetbrains.com/issue/KT-42182. */
@Test
fun testGeneratedSourcesImpactedByClasspathChanges() {
val project = Project(
"kaptIncrementalCompilationProject",
GradleVersionRequired.None
).apply {
setupIncrementalAptProject("ISOLATING", procClass = IncrementalProcessorReferencingClasspath::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/" + IncrementalProcessorReferencingClasspath.CLASSPATH_TYPE.replace(".", "/") + ".java")
source.parentFile.mkdirs()
source.writeText(
"""
package ${IncrementalProcessorReferencingClasspath.CLASSPATH_TYPE.substringBeforeLast(".")};
public class ${IncrementalProcessorReferencingClasspath.CLASSPATH_TYPE.substringAfterLast(".")} {}
""".trimIndent()
)
return@run source
}
project.gradleBuildScript().appendText(
"""
dependencies {
implementation project(':lib')
}
""".trimIndent()
)
project.build("clean", "kaptKotlin") {
assertSuccessful()
}
// change type that all generated sources reference
classpathTypeSource.writeText(classpathTypeSource.readText().replace("}", "int i = 10;\n}"))
project.build("build") {
assertSuccessful()
assertEquals(
setOf(
fileInWorkingDir("build/tmp/kapt3/stubs/main/foo/A.java").canonicalPath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/bar/B.java").canonicalPath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/bar/UseBKt.java").canonicalPath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/baz/UtilKt.java").canonicalPath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/error/NonExistentClass.java").canonicalPath
),
getProcessedSources(output)
)
}
}
}
private const val patternApt = "Processing java sources with annotation processors:"