Proper support of aggregated processors
This commit is contained in:
committed by
max-kammerer
parent
f382a55b17
commit
6748560184
+57
@@ -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<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().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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+72
@@ -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<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";
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+56
@@ -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<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("\n@example.KotlinFilerGenerated").append("\n");
|
||||||
|
writer.append("\npublic class ").append(name).append(" {}");
|
||||||
|
}
|
||||||
|
catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
+164
-3
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.gradle
|
package org.jetbrains.kotlin.gradle
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.gradle.incapt.*
|
||||||
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.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
|
@Test
|
||||||
fun testIncrementalChanges() {
|
fun testIncrementalChanges() {
|
||||||
val project = getProject()
|
val project = getProject()
|
||||||
@@ -80,8 +86,7 @@ class KaptIncrementalWithAggregatingApt : KaptIncrementalIT() {
|
|||||||
@Test
|
@Test
|
||||||
fun testIncrementalChangesWithJdk9() {
|
fun testIncrementalChangesWithJdk9() {
|
||||||
val javaHome = File(System.getProperty("jdk9Home")!!)
|
val javaHome = File(System.getProperty("jdk9Home")!!)
|
||||||
Assume.assumeTrue("JDK 9 isn't available", javaHome.isDirectory)
|
val options = jdk9Options(javaHome)
|
||||||
val options = defaultBuildOptions().copy(javaHome = javaHome)
|
|
||||||
val project = getProject()
|
val project = getProject()
|
||||||
|
|
||||||
project.build("clean", "build", options = options) {
|
project.build("clean", "build", options = options) {
|
||||||
@@ -111,7 +116,7 @@ class KaptIncrementalWithAggregatingApt : KaptIncrementalIT() {
|
|||||||
GradleVersionRequired.None
|
GradleVersionRequired.None
|
||||||
).apply {
|
).apply {
|
||||||
setupWorkingDir()
|
setupWorkingDir()
|
||||||
val processorPath = generateProcessor("AGGREGATING")
|
val processorPath = generateProcessor("AGGREGATING" to IncrementalProcessor::class.java)
|
||||||
|
|
||||||
projectDir.resolve("app/build.gradle").appendText(
|
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<String>) -> 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"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+28
-10
@@ -201,10 +201,21 @@ fun getProcessedSources(output: String): Set<String> {
|
|||||||
return logging.drop(indexOf).split(",").map { it.trim() }.filter { !it.isEmpty() }.toSet()
|
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<String, Class<*>>,
|
||||||
|
buildFile: File = projectDir.resolve("build.gradle")
|
||||||
|
) {
|
||||||
setupWorkingDir()
|
setupWorkingDir()
|
||||||
val content = buildFile.readText()
|
val content = buildFile.readText()
|
||||||
val processorPath = generateProcessor(procType)
|
val processorPath = generateProcessor(*processors)
|
||||||
|
|
||||||
val updatedContent = content.replace(
|
val updatedContent = content.replace(
|
||||||
Regex("^\\s*kapt\\s\"org\\.jetbrain.*$", RegexOption.MULTILINE),
|
Regex("^\\s*kapt\\s\"org\\.jetbrain.*$", RegexOption.MULTILINE),
|
||||||
@@ -213,20 +224,27 @@ fun BaseGradleIT.Project.setupIncrementalAptProject(procType: String, buildFile:
|
|||||||
buildFile.writeText(updatedContent)
|
buildFile.writeText(updatedContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun BaseGradleIT.Project.generateProcessor(procType: String): File {
|
fun BaseGradleIT.Project.generateProcessor(vararg processors: Pair<String, Class<*>>): File {
|
||||||
val processorPath = projectDir.resolve("incrementalProcessor.jar")
|
val processorPath = projectDir.resolve("incrementalProcessor.jar")
|
||||||
|
|
||||||
ZipOutputStream(processorPath.outputStream()).use {
|
ZipOutputStream(processorPath.outputStream()).use {
|
||||||
val path = IncrementalProcessor::class.java.name.replace(".", "/") + ".class"
|
for ((_, procClass) in processors) {
|
||||||
val inputStream = IncrementalProcessor::class.java.classLoader.getResourceAsStream(path)
|
val path = procClass.name.replace(".", "/") + ".class"
|
||||||
it.putNextEntry(ZipEntry(path))
|
procClass.classLoader.getResourceAsStream(path).use { inputStream ->
|
||||||
it.write(inputStream.readBytes())
|
it.putNextEntry(ZipEntry(path))
|
||||||
it.closeEntry()
|
it.write(inputStream.readBytes())
|
||||||
|
it.closeEntry()
|
||||||
|
}
|
||||||
|
}
|
||||||
it.putNextEntry(ZipEntry("META-INF/gradle/incremental.annotation.processors"))
|
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.closeEntry()
|
||||||
it.putNextEntry(ZipEntry("META-INF/services/javax.annotation.processing.Processor"))
|
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()
|
it.closeEntry()
|
||||||
}
|
}
|
||||||
return processorPath
|
return processorPath
|
||||||
|
|||||||
+26
@@ -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'
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
package bar
|
||||||
|
|
||||||
|
class noAnnotations {
|
||||||
|
val valB = "text"
|
||||||
|
|
||||||
|
fun funB() {}
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
package bar
|
||||||
|
|
||||||
|
@example.ExampleAnnotation
|
||||||
|
class WithAnnotation() {}
|
||||||
@@ -42,7 +42,11 @@ object Kapt {
|
|||||||
val processors = processorLoader.loadProcessors(findClassLoaderWithJavac())
|
val processors = processorLoader.loadProcessors(findClassLoaderWithJavac())
|
||||||
|
|
||||||
val annotationProcessingTime = measureTimeMillis {
|
val annotationProcessingTime = measureTimeMillis {
|
||||||
kaptContext.doAnnotationProcessing(javaSourceFiles, processors.processors)
|
kaptContext.doAnnotationProcessing(
|
||||||
|
javaSourceFiles,
|
||||||
|
processors.processors,
|
||||||
|
aggregatedTypes = collectAggregatedTypes(kaptContext.sourcesToReprocess)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info { "Annotation processing took $annotationProcessingTime ms" }
|
logger.info { "Annotation processing took $annotationProcessingTime ms" }
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ open class KaptContext(val options: KaptOptions, val withJdk: Boolean, val logge
|
|||||||
val compileClasspath = if (sourcesToReprocess is SourcesToReprocess.FullRebuild) {
|
val compileClasspath = if (sourcesToReprocess is SourcesToReprocess.FullRebuild) {
|
||||||
options.compileClasspath
|
options.compileClasspath
|
||||||
} else {
|
} else {
|
||||||
options.compileClasspath + options.compiledSources
|
options.compileClasspath + options.compiledSources + options.classesOutputDir
|
||||||
}
|
}
|
||||||
|
|
||||||
putJavacOption("CLASSPATH", "CLASS_PATH",
|
putJavacOption("CLASSPATH", "CLASS_PATH",
|
||||||
|
|||||||
@@ -168,6 +168,15 @@ fun KaptOptions.collectJavaSourceFiles(sourcesToReprocess: SourcesToReprocess =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun collectAggregatedTypes(sourcesToReprocess: SourcesToReprocess = SourcesToReprocess.FullRebuild): List<String> {
|
||||||
|
return when (sourcesToReprocess) {
|
||||||
|
is SourcesToReprocess.FullRebuild -> emptyList()
|
||||||
|
is SourcesToReprocess.Incremental -> {
|
||||||
|
sourcesToReprocess.unchangedAggregatedTypes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun KaptOptions.logString(additionalInfo: String = "") = buildString {
|
fun KaptOptions.logString(additionalInfo: String = "") = buildString {
|
||||||
val additionalInfoRendered = if (additionalInfo.isEmpty()) "" else " ($additionalInfo)"
|
val additionalInfoRendered = if (additionalInfo.isEmpty()) "" else " ($additionalInfo)"
|
||||||
appendLine("Kapt3 is enabled$additionalInfoRendered.")
|
appendLine("Kapt3 is enabled$additionalInfoRendered.")
|
||||||
|
|||||||
+8
-5
@@ -31,7 +31,8 @@ import com.sun.tools.javac.util.List as JavacList
|
|||||||
fun KaptContext.doAnnotationProcessing(
|
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()
|
||||||
) {
|
) {
|
||||||
val processingEnvironment = JavacProcessingEnvironment.instance(context)
|
val processingEnvironment = JavacProcessingEnvironment.instance(context)
|
||||||
|
|
||||||
@@ -70,12 +71,14 @@ fun KaptContext.doAnnotationProcessing(
|
|||||||
GeneratedTypesTaskListener(cacheManager!!.javaCache)
|
GeneratedTypesTaskListener(cacheManager!!.javaCache)
|
||||||
}?.also { compiler.getTaskListeners().add(it) }
|
}?.also { compiler.getTaskListeners().add(it) }
|
||||||
|
|
||||||
|
val additionalClassNames = JavacList.from(aggregatedTypes)
|
||||||
if (isJava9OrLater()) {
|
if (isJava9OrLater()) {
|
||||||
val processAnnotationsMethod = compiler.javaClass.getMethod("processAnnotations", JavacList::class.java)
|
val processAnnotationsMethod =
|
||||||
processAnnotationsMethod.invoke(compiler, analyzedFiles)
|
compiler.javaClass.getMethod("processAnnotations", JavacList::class.java, java.util.Collection::class.java)
|
||||||
|
processAnnotationsMethod.invoke(compiler, analyzedFiles, additionalClassNames)
|
||||||
compiler
|
compiler
|
||||||
} else {
|
} else {
|
||||||
compiler.processAnnotations(analyzedFiles).also {
|
compiler.processAnnotations(analyzedFiles, additionalClassNames).also {
|
||||||
generatedSourcesListener?.let { compiler.getTaskListeners().remove(it) }
|
generatedSourcesListener?.let { compiler.getTaskListeners().remove(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +159,7 @@ private class ProcessorWrapper(private val delegate: IncrementalProcessor) : Pro
|
|||||||
private var initTime: Long = 0
|
private var initTime: Long = 0
|
||||||
private val roundTime = mutableListOf<Long>()
|
private val roundTime = mutableListOf<Long>()
|
||||||
|
|
||||||
override fun process(annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment?): Boolean {
|
override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment): Boolean {
|
||||||
val (time, result) = measureTimeMillisWithResult {
|
val (time, result) = measureTimeMillisWithResult {
|
||||||
delegate.process(annotations, roundEnv)
|
delegate.process(annotations, roundEnv)
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-2
@@ -11,6 +11,7 @@ import java.io.Serializable
|
|||||||
class IncrementalAptCache : Serializable {
|
class IncrementalAptCache : Serializable {
|
||||||
|
|
||||||
private val aggregatingGenerated: MutableSet<File> = mutableSetOf()
|
private val aggregatingGenerated: MutableSet<File> = mutableSetOf()
|
||||||
|
private val aggregatedTypes: MutableSet<String> = linkedSetOf()
|
||||||
private val isolatingMapping: MutableMap<File, File> = mutableMapOf()
|
private val isolatingMapping: MutableMap<File, File> = mutableMapOf()
|
||||||
// Annotations claimed by aggregating annotation processors
|
// Annotations claimed by aggregating annotation processors
|
||||||
private val aggregatingClaimedAnnotations: MutableSet<String> = mutableSetOf()
|
private val aggregatingClaimedAnnotations: MutableSet<String> = mutableSetOf()
|
||||||
@@ -41,6 +42,9 @@ class IncrementalAptCache : Serializable {
|
|||||||
aggregatingClaimedAnnotations.clear()
|
aggregatingClaimedAnnotations.clear()
|
||||||
aggregatingClaimedAnnotations.addAll(aggregating.flatMap { it.supportedAnnotationTypes })
|
aggregatingClaimedAnnotations.addAll(aggregating.flatMap { it.supportedAnnotationTypes })
|
||||||
|
|
||||||
|
aggregatedTypes.clear()
|
||||||
|
aggregatedTypes.addAll(aggregating.flatMap { it.getAggregatedTypes() })
|
||||||
|
|
||||||
for (isolatingProcessor in isolating) {
|
for (isolatingProcessor in isolating) {
|
||||||
isolatingProcessor.getGeneratedToSources().forEach {
|
isolatingProcessor.getGeneratedToSources().forEach {
|
||||||
isolatingMapping[it.key] = it.value!!
|
isolatingMapping[it.key] = it.value!!
|
||||||
@@ -52,12 +56,15 @@ class IncrementalAptCache : Serializable {
|
|||||||
fun getAggregatingClaimedAnnotations(): Set<String> = aggregatingClaimedAnnotations
|
fun getAggregatingClaimedAnnotations(): Set<String> = aggregatingClaimedAnnotations
|
||||||
|
|
||||||
/** Returns generated Java sources originating from aggregating APs. */
|
/** Returns generated Java sources originating from aggregating APs. */
|
||||||
fun invalidateAggregating(): List<File> {
|
fun invalidateAggregating(): Pair<List<File>, List<String>> {
|
||||||
val dirtyAggregating = aggregatingGenerated.filter { it.extension == "java" }
|
val dirtyAggregating = aggregatingGenerated.filter { it.extension == "java" }
|
||||||
aggregatingGenerated.forEach { it.delete() }
|
aggregatingGenerated.forEach { it.delete() }
|
||||||
aggregatingGenerated.clear()
|
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. */
|
/** Returns generated Java sources originating from the specified sources, and generated by isloating APs. */
|
||||||
|
|||||||
@@ -48,10 +48,11 @@ class JavaClassCacheManager(val file: File) : Closeable {
|
|||||||
|
|
||||||
val isolatingGenerated = aptCache.invalidateIsolatingGenerated(toReprocess)
|
val isolatingGenerated = aptCache.invalidateIsolatingGenerated(toReprocess)
|
||||||
val generatedDirtyTypes = javaCache.invalidateGeneratedTypes(isolatingGenerated).toMutableSet()
|
val generatedDirtyTypes = javaCache.invalidateGeneratedTypes(isolatingGenerated).toMutableSet()
|
||||||
|
val aggregatedTypes = mutableListOf<String>()
|
||||||
if (!toReprocess.isEmpty()) {
|
if (!toReprocess.isEmpty()) {
|
||||||
// only if there are some files to reprocess we should invalidate the aggregating ones
|
// 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))
|
generatedDirtyTypes.addAll(javaCache.invalidateGeneratedTypes(aggregatingGenerated))
|
||||||
|
|
||||||
toReprocess.addAll(
|
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 {
|
sealed class SourcesToReprocess {
|
||||||
class Incremental(val toReprocess: List<File>, val dirtyTypes: Set<String>) : SourcesToReprocess()
|
class Incremental(
|
||||||
|
val toReprocess: List<File>,
|
||||||
|
val dirtyTypes: Set<String>,
|
||||||
|
val unchangedAggregatedTypes: List<String>
|
||||||
|
) : SourcesToReprocess()
|
||||||
|
|
||||||
object FullRebuild : SourcesToReprocess()
|
object FullRebuild : SourcesToReprocess()
|
||||||
}
|
}
|
||||||
+1
-1
@@ -139,7 +139,7 @@ class JavaClassCache() : Serializable {
|
|||||||
currentDirtyFiles = nextRound.filter { !allDirtyFiles.contains(it) }.toMutableSet()
|
currentDirtyFiles = nextRound.filter { !allDirtyFiles.contains(it) }.toMutableSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
return SourcesToReprocess.Incremental(allDirtyFiles.map { File(it) }, allDirtyTypes)
|
return SourcesToReprocess.Incremental(allDirtyFiles.map { File(it) }, allDirtyTypes, emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+62
-1
@@ -12,8 +12,10 @@ import java.net.URI
|
|||||||
import javax.annotation.processing.Filer
|
import javax.annotation.processing.Filer
|
||||||
import javax.annotation.processing.ProcessingEnvironment
|
import javax.annotation.processing.ProcessingEnvironment
|
||||||
import javax.annotation.processing.Processor
|
import javax.annotation.processing.Processor
|
||||||
|
import javax.annotation.processing.RoundEnvironment
|
||||||
import javax.lang.model.element.Element
|
import javax.lang.model.element.Element
|
||||||
import javax.lang.model.element.PackageElement
|
import javax.lang.model.element.PackageElement
|
||||||
|
import javax.lang.model.element.TypeElement
|
||||||
import javax.tools.FileObject
|
import javax.tools.FileObject
|
||||||
import javax.tools.JavaFileManager
|
import javax.tools.JavaFileManager
|
||||||
import javax.tools.JavaFileObject
|
import javax.tools.JavaFileObject
|
||||||
@@ -69,7 +71,16 @@ class IncrementalProcessor(private val processor: Processor, private val kind: D
|
|||||||
|
|
||||||
fun isUnableToRunIncrementally() = !kind.canRunIncrementally
|
fun isUnableToRunIncrementally() = !kind.canRunIncrementally
|
||||||
fun getGeneratedToSources() = dependencyCollector.value.getGeneratedToSources()
|
fun getGeneratedToSources() = dependencyCollector.value.getGeneratedToSources()
|
||||||
|
fun getAggregatedTypes() = dependencyCollector.value.getAggregatedTypes()
|
||||||
fun getRuntimeType(): RuntimeProcType = dependencyCollector.value.getRuntimeType()
|
fun getRuntimeType(): RuntimeProcType = dependencyCollector.value.getRuntimeType()
|
||||||
|
|
||||||
|
|
||||||
|
override fun process(annotations: Set<TypeElement>, 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) :
|
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 warningCollector: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
private val generatedToSource = mutableMapOf<File, File?>()
|
private val generatedToSource = mutableMapOf<File, File?>()
|
||||||
|
private val aggregatedTypes = mutableSetOf<String>()
|
||||||
|
|
||||||
private var isFullRebuild = !runtimeProcType.isIncremental
|
private var isFullRebuild = !runtimeProcType.isIncremental
|
||||||
|
|
||||||
internal fun add(createdFile: URI, originatingElements: Array<out Element?>) {
|
internal fun recordProcessingInputs(supportedAnnotationTypes: Set<String>, annotations: Set<TypeElement>, 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<out Element?>, classId: String?) {
|
||||||
if (isFullRebuild) return
|
if (isFullRebuild) return
|
||||||
|
|
||||||
val generatedFile = File(createdFile)
|
val generatedFile = File(createdFile)
|
||||||
@@ -134,6 +165,9 @@ internal class AnnotationProcessorDependencyCollector(
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun getGeneratedToSources(): Map<File, File?> = if (isFullRebuild) emptyMap() else generatedToSource
|
internal fun getGeneratedToSources(): Map<File, File?> = if (isFullRebuild) emptyMap() else generatedToSource
|
||||||
|
|
||||||
|
internal fun getAggregatedTypes(): Set<String> = if (isFullRebuild) emptySet() else aggregatedTypes
|
||||||
|
|
||||||
internal fun getRuntimeType(): RuntimeProcType {
|
internal fun getRuntimeType(): RuntimeProcType {
|
||||||
return if (isFullRebuild) {
|
return if (isFullRebuild) {
|
||||||
RuntimeProcType.NON_INCREMENTAL
|
RuntimeProcType.NON_INCREMENTAL
|
||||||
@@ -154,6 +188,33 @@ private fun getSrcFiles(elements: Array<out Element?>): Set<File> {
|
|||||||
}.toSet()
|
}.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<Element>): Collection<String> {
|
||||||
|
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) {
|
enum class DeclaredProcType(val canRunIncrementally: Boolean) {
|
||||||
AGGREGATING(true) {
|
AGGREGATING(true) {
|
||||||
override fun toRuntimeType() = RuntimeProcType.AGGREGATING
|
override fun toRuntimeType() = RuntimeProcType.AGGREGATING
|
||||||
|
|||||||
Reference in New Issue
Block a user