From 65a9d9e8d2876f4b31a24127ba5d94fce7801542 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Fri, 21 Oct 2016 03:30:10 +0300 Subject: [PATCH] Kapt3: Handle exceptions while annotation processing gracefully --- .../jetbrains/kotlin/kapt3}/KaptRunner.kt | 69 ++++++++++++++++--- plugins/kapt3/test/KaptRunnerTest.kt | 51 ++++++++++++-- plugins/kapt3/testData/runner/ParseError.java | 1 + 3 files changed, 108 insertions(+), 13 deletions(-) rename plugins/kapt3/src/{ => org/jetbrains/kotlin/kapt3}/KaptRunner.kt (50%) create mode 100644 plugins/kapt3/testData/runner/ParseError.java diff --git a/plugins/kapt3/src/KaptRunner.kt b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/KaptRunner.kt similarity index 50% rename from plugins/kapt3/src/KaptRunner.kt rename to plugins/kapt3/src/org/jetbrains/kotlin/kapt3/KaptRunner.kt index a3c93ba6ef0..78e79788f71 100644 --- a/plugins/kapt3/src/KaptRunner.kt +++ b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/KaptRunner.kt @@ -20,7 +20,9 @@ import com.sun.tools.javac.comp.CompileStates import com.sun.tools.javac.file.JavacFileManager import com.sun.tools.javac.main.JavaCompiler import com.sun.tools.javac.main.Option +import com.sun.tools.javac.processing.AnnotationProcessingError import com.sun.tools.javac.processing.JavacProcessingEnvironment +import com.sun.tools.javac.tree.JCTree import com.sun.tools.javac.util.Context import com.sun.tools.javac.util.Log import com.sun.tools.javac.util.Options @@ -28,21 +30,65 @@ import java.io.File import java.io.PrintWriter import javax.annotation.processing.Processor import javax.tools.JavaFileManager +import com.sun.tools.javac.util.List as JavacList class KaptError : RuntimeException { - constructor(message: String) : super(message) - constructor(message: String, cause: Throwable) : super(message, cause) + val kind: Kind + + enum class Kind(val message: String) { + JAVA_FILE_PARSING_ERROR("Java file parsing error"), + EXCEPTION("Exception while annotation processing"), + UNKNOWN("Unknown error while annotation processing") + } + + constructor(kind: Kind) : super(kind.message) { + this.kind = kind + } + + constructor(kind: Kind, cause: Throwable) : super(kind.message, cause) { + this.kind = kind + } } class KaptRunner { - fun doAnnotationProcessing(javaSourceFiles: List, processors: List, sourceOutputDir: File, classOutputDir: File) { - val context = Context() + private val context = Context() + private val options: Options + + init { JavacFileManager.preRegister(context) KaptJavaCompiler.preRegister(context) - val options = Options.instance(context) + options = Options.instance(context) context.put(Log.outKey, PrintWriter(System.err, true)) + } + + fun parseJavaFiles( + javaSourceFiles: List, + classpath: List + ): JavacList { + classpath.forEach { options.put(Option.CLASSPATH, it.canonicalPath) } + + val fileManager = context.get(JavaFileManager::class.java) as JavacFileManager + val compiler = JavaCompiler.instance(context) as KaptJavaCompiler + + try { + val javaFileObjects = fileManager.getJavaFileObjectsFromFiles(javaSourceFiles) + return compiler.parseFiles(javaFileObjects) + } finally { + fileManager.close() + compiler.close() + } + } + + fun doAnnotationProcessing( + javaSourceFiles: List, + processors: List, + classpath: List, + sourceOutputDir: File, + classOutputDir: File + ) { options.put(Option.PROC, "only") // Only process annotations + classpath.forEach { options.put(Option.CLASSPATH, it.canonicalPath) } options.put(Option.S, sourceOutputDir.canonicalPath) options.put(Option.D, classOutputDir.canonicalPath) @@ -56,13 +102,20 @@ class KaptRunner { val javaFileObjects = fileManager.getJavaFileObjectsFromFiles(javaSourceFiles) val parsedJavaFiles = compiler.parseFiles(javaFileObjects) if (compiler.shouldStop(CompileStates.CompileState.PARSE)) { - throw KaptError("Error while parsing Java files.") + throw KaptError(KaptError.Kind.JAVA_FILE_PARSING_ERROR) } - val compilerAfterAnnotationProcessing = compiler.processAnnotations(compiler.enterTrees(parsedJavaFiles)) - compilerAfterAnnotationProcessing.close() + val compilerAfterAnnotationProcessing: JavaCompiler? = null + try { + compiler.processAnnotations(compiler.enterTrees(parsedJavaFiles)) + } catch (e: AnnotationProcessingError) { + throw KaptError(KaptError.Kind.EXCEPTION, e.cause ?: e) + } + + compilerAfterAnnotationProcessing?.close() } finally { processingEnvironment.close() + compiler.close() fileManager.close() } } diff --git a/plugins/kapt3/test/KaptRunnerTest.kt b/plugins/kapt3/test/KaptRunnerTest.kt index 959fa9f33f9..c90c9e9ce78 100644 --- a/plugins/kapt3/test/KaptRunnerTest.kt +++ b/plugins/kapt3/test/KaptRunnerTest.kt @@ -1,3 +1,4 @@ +import org.jetbrains.kotlin.kapt3.KaptError import org.jetbrains.kotlin.kapt3.KaptRunner import org.junit.Assert import org.junit.Assert.* @@ -27,11 +28,8 @@ import javax.lang.model.element.TypeElement class KaptRunnerTest { private companion object { val TEST_DATA_DIR = File("plugins/kapt3/testData/runner") - } - @Test - fun testSimple() { - val processor = object : AbstractProcessor() { + val SIMPLE_PROCESSOR = object : AbstractProcessor() { override fun process(annotations: Set, roundEnv: RoundEnvironment): Boolean { for (annotation in annotations) { val annotationName = annotation.simpleName.toString() @@ -54,12 +52,16 @@ class KaptRunnerTest { override fun getSupportedAnnotationTypes() = setOf("test.MyAnnotation") } + } + @Test + fun testSimple() { val sourceOutputDir = Files.createTempDirectory("kaptRunner").toFile() try { KaptRunner().doAnnotationProcessing( listOf(File(TEST_DATA_DIR, "Simple.java")), - listOf(processor), + listOf(SIMPLE_PROCESSOR), + emptyList(), // classpath sourceOutputDir, sourceOutputDir) val myMethodFile = File(sourceOutputDir, "generated/MyMethodMyAnnotation.java") @@ -68,4 +70,43 @@ class KaptRunnerTest { sourceOutputDir.deleteRecursively() } } + + @Test + fun testException() { + val exceptionMessage = "Here we are!" + + val processor = object : AbstractProcessor() { + override fun process(annotations: Set, roundEnv: RoundEnvironment): Boolean { + throw RuntimeException(exceptionMessage) + } + + override fun getSupportedAnnotationTypes() = setOf("test.MyAnnotation") + } + + try { + KaptRunner().doAnnotationProcessing( + listOf(File(TEST_DATA_DIR, "Simple.java")), + listOf(processor), + emptyList(), + TEST_DATA_DIR, + TEST_DATA_DIR) + } catch (e: KaptError) { + assertEquals(KaptError.Kind.EXCEPTION, e.kind) + assertEquals("Here we are!", e.cause!!.message) + } + } + + @Test + fun testParsingError() { + try { + KaptRunner().doAnnotationProcessing( + listOf(File(TEST_DATA_DIR, "ParseError.java")), + listOf(SIMPLE_PROCESSOR), + emptyList(), + TEST_DATA_DIR, + TEST_DATA_DIR) + } catch (e: KaptError) { + assertEquals(KaptError.Kind.JAVA_FILE_PARSING_ERROR, e.kind) + } + } } \ No newline at end of file diff --git a/plugins/kapt3/testData/runner/ParseError.java b/plugins/kapt3/testData/runner/ParseError.java new file mode 100644 index 00000000000..88051f4a270 --- /dev/null +++ b/plugins/kapt3/testData/runner/ParseError.java @@ -0,0 +1 @@ +BLAHBLAH \ No newline at end of file