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
|
||||
|
||||
import org.jetbrains.kotlin.gradle.incapt.*
|
||||
import org.jetbrains.kotlin.gradle.util.modify
|
||||
import org.junit.Assert.assertEquals
|
||||
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
|
||||
fun testIncrementalChanges() {
|
||||
val project = getProject()
|
||||
@@ -80,8 +86,7 @@ class KaptIncrementalWithAggregatingApt : KaptIncrementalIT() {
|
||||
@Test
|
||||
fun testIncrementalChangesWithJdk9() {
|
||||
val javaHome = File(System.getProperty("jdk9Home")!!)
|
||||
Assume.assumeTrue("JDK 9 isn't available", javaHome.isDirectory)
|
||||
val options = defaultBuildOptions().copy(javaHome = javaHome)
|
||||
val options = jdk9Options(javaHome)
|
||||
val project = getProject()
|
||||
|
||||
project.build("clean", "build", options = options) {
|
||||
@@ -111,7 +116,7 @@ class KaptIncrementalWithAggregatingApt : KaptIncrementalIT() {
|
||||
GradleVersionRequired.None
|
||||
).apply {
|
||||
setupWorkingDir()
|
||||
val processorPath = generateProcessor("AGGREGATING")
|
||||
val processorPath = generateProcessor("AGGREGATING" to IncrementalProcessor::class.java)
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
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()
|
||||
val content = buildFile.readText()
|
||||
val processorPath = generateProcessor(procType)
|
||||
val processorPath = generateProcessor(*processors)
|
||||
|
||||
val updatedContent = content.replace(
|
||||
Regex("^\\s*kapt\\s\"org\\.jetbrain.*$", RegexOption.MULTILINE),
|
||||
@@ -213,20 +224,27 @@ fun BaseGradleIT.Project.setupIncrementalAptProject(procType: String, buildFile:
|
||||
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")
|
||||
|
||||
ZipOutputStream(processorPath.outputStream()).use {
|
||||
val path = IncrementalProcessor::class.java.name.replace(".", "/") + ".class"
|
||||
val inputStream = IncrementalProcessor::class.java.classLoader.getResourceAsStream(path)
|
||||
it.putNextEntry(ZipEntry(path))
|
||||
it.write(inputStream.readBytes())
|
||||
it.closeEntry()
|
||||
for ((_, procClass) in processors) {
|
||||
val path = procClass.name.replace(".", "/") + ".class"
|
||||
procClass.classLoader.getResourceAsStream(path).use { inputStream ->
|
||||
it.putNextEntry(ZipEntry(path))
|
||||
it.write(inputStream.readBytes())
|
||||
it.closeEntry()
|
||||
}
|
||||
}
|
||||
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.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()
|
||||
}
|
||||
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() {}
|
||||
Reference in New Issue
Block a user