From aa15e0ad67aa8c7869cbd231c15400ba5aaeb602 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Sat, 29 Oct 2016 03:32:13 +0300 Subject: [PATCH] Kapt3: Run Javac annotation processing in the kapt3 plugin. There are two modes: 1. Run only annotation processing (like kapt1 with stubs + javac). Seems that it may be faster to process annotations once before Kotlin compilation than launching AP on each IC round. 2. Run AP, repeat analysis and compile Kotlin classes (like kapt2). This mode doesn't support IC for now. --- .../Kapt3AnalysisCompletedHandlerExtension.kt | 67 +++++++++++++++++-- .../org/jetbrains/kotlin/kapt3/Kapt3Plugin.kt | 22 +++++- .../org/jetbrains/kotlin/kapt3/KaptRunner.kt | 34 ++++++++-- .../src/org/jetbrains/kotlin/kapt3/Logger.kt | 41 ++++++++++++ .../kapt3/test/AbstractKotlinKapt3Test.kt | 6 +- .../kotlin/kapt3/test/KaptRunnerTest.kt | 7 +- 6 files changed, 158 insertions(+), 19 deletions(-) create mode 100644 plugins/kapt3/src/org/jetbrains/kotlin/kapt3/Logger.kt diff --git a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/Kapt3AnalysisCompletedHandlerExtension.kt b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/Kapt3AnalysisCompletedHandlerExtension.kt index 2f314a11877..c2fd59d317a 100644 --- a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/Kapt3AnalysisCompletedHandlerExtension.kt +++ b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/Kapt3AnalysisCompletedHandlerExtension.kt @@ -33,25 +33,48 @@ import org.jetbrains.org.objectweb.asm.tree.ClassNode import org.jetbrains.org.objectweb.asm.tree.FieldNode import org.jetbrains.org.objectweb.asm.tree.MethodNode import java.io.File +import java.net.URLClassLoader +import java.util.* +import javax.annotation.processing.Processor class Kapt3AnalysisCompletedHandlerExtension( - val classpath: List, + val annotationProcessingClasspath: List, val javaSourceRoots: List, val sourcesOutputDir: File, val classesOutputDir: File, - val options: Map, - val isVerbose: Boolean + val options: Map, //TODO + val aptOnly: Boolean, + val logger: Logger ) : AnalysisCompletedHandlerExtension { + private var annotationProcessingComplete = false + override fun analysisCompleted( project: Project, module: ModuleDescriptor, bindingTrace: BindingTrace, files: Collection ): AnalysisResult? { - if (files.isEmpty()) { - return AnalysisResult.Companion.success(BindingContext.EMPTY, module, shouldGenerateCode = false) + if (annotationProcessingComplete) { + return null } + fun doNotGenerateCode() = AnalysisResult.Companion.success(BindingContext.EMPTY, module, shouldGenerateCode = false) + + if (files.isEmpty()) { + logger.info("No Kotlin source files, aborting") + return if (aptOnly) doNotGenerateCode() else null + } + + logger.info { "Kotlin files: " + files.map { it.virtualFile?.name ?: "" } } + + val processors = loadProcessors(annotationProcessingClasspath) + if (processors.isEmpty()) { + logger.info("No annotation processors available, aborting") + return if (aptOnly) doNotGenerateCode() else null + } + + logger.info { "Annotation processors: " + processors.joinToString { it.javaClass.canonicalName } } + val builderFactory = Kapt3BuilderFactory() val generationState = GenerationState( @@ -67,13 +90,45 @@ class Kapt3AnalysisCompletedHandlerExtension( KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION) val compiledClasses = builderFactory.compiledClasses val origins = builderFactory.origins + logger.info { "Compiled classes: " + compiledClasses.joinToString { it.name } } + + val javaFilesFromJavaSourceRoots = javaSourceRoots.flatMap { + root -> root.walk().filter { it.isFile && it.extension == "java" }.toList() + } + logger.info { "Java source files: " + javaFilesFromJavaSourceRoots.joinToString { it.canonicalPath } } + + val kaptRunner = KaptRunner(logger) + val treeConverter = JCTreeConverter(kaptRunner.context, generationState.typeMapper, compiledClasses, origins) + val jcCompilationUnitsForKotlinClasses = treeConverter.convert() + logger.info { "Stubs for Kotlin classes: " + jcCompilationUnitsForKotlinClasses.joinToString { it.sourcefile.name } } + + kaptRunner.doAnnotationProcessing( + javaFilesFromJavaSourceRoots, processors, + annotationProcessingClasspath, sourcesOutputDir, classesOutputDir, jcCompilationUnitsForKotlinClasses) } catch (thr: Throwable) { + if (thr !is KaptError || thr.kind != KaptError.Kind.ERROR_RAISED) { + logger.exception(thr) + } bindingTrace.report(ErrorsKapt3.KAPT3_PROCESSING_ERROR.on(files.first())) + return null // Compilation will be aborted anyway because of the error above } finally { generationState.destroy() } - return AnalysisResult.success(BindingContext.EMPTY, module, shouldGenerateCode = false) + return if (aptOnly) { + doNotGenerateCode() + } else { + AnalysisResult.RetryWithAdditionalJavaRoots( + bindingTrace.bindingContext, + module, + listOf(sourcesOutputDir), + addToEnvironment = true) + } + } + + private fun loadProcessors(classpath: List): List { + val classLoader = URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray()) + return ServiceLoader.load(Processor::class.java, classLoader).toList() } } diff --git a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/Kapt3Plugin.kt b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/Kapt3Plugin.kt index 6a75b419af6..67e55d6b48d 100644 --- a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/Kapt3Plugin.kt +++ b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/Kapt3Plugin.kt @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfigurationKey import org.jetbrains.kotlin.config.JVMConfigurationKeys import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages +import org.jetbrains.kotlin.kapt3.Kapt3ConfigurationKeys.APT_ONLY import org.jetbrains.kotlin.kapt3.diagnostic.DefaultErrorMessagesKapt3 import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisCompletedHandlerExtension import java.io.File @@ -49,6 +50,9 @@ object Kapt3ConfigurationKeys { val VERBOSE_MODE: CompilerConfigurationKey = CompilerConfigurationKey.create("verbose mode") + + val APT_ONLY: CompilerConfigurationKey = + CompilerConfigurationKey.create("do only annotation processing") } class Kapt3CommandLineProcessor : CommandLineProcessor { @@ -71,6 +75,9 @@ class Kapt3CommandLineProcessor : CommandLineProcessor { val VERBOSE_MODE_OPTION: CliOption = CliOption("verbose", "true | false", "Enable verbose output", required = false) + + val APT_ONLY_OPTION: CliOption = + CliOption("aptOnly", "true | false", "Run only annotation processing, do not compile Kotlin files", required = false) } override val pluginId: String = ANNOTATION_PROCESSING_COMPILER_PLUGIN_ID @@ -92,6 +99,7 @@ class Kapt3CommandLineProcessor : CommandLineProcessor { GENERATED_OUTPUT_DIR_OPTION -> configuration.put(Kapt3ConfigurationKeys.GENERATED_OUTPUT_DIR, value) CLASS_FILES_OUTPUT_DIR_OPTION -> configuration.put(Kapt3ConfigurationKeys.CLASS_FILES_OUTPUT_DIR, value) VERBOSE_MODE_OPTION -> configuration.put(Kapt3ConfigurationKeys.VERBOSE_MODE, value) + APT_ONLY_OPTION -> configuration.put(Kapt3ConfigurationKeys.APT_ONLY, value) else -> throw CliOptionProcessingException("Unknown option: ${option.name}") } } @@ -122,11 +130,23 @@ class Kapt3ComponentRegistrar : ComponentRegistrar { ?: configuration[JVMConfigurationKeys.MODULES]!!.first().getOutputDirectory()) val isVerbose = configuration.get(Kapt3ConfigurationKeys.VERBOSE_MODE) == "true" + val isAptOnly = configuration.get(Kapt3ConfigurationKeys.APT_ONLY) == "true" Extensions.getRootArea().getExtensionPoint(DefaultErrorMessages.Extension.EP_NAME).registerExtension(DefaultErrorMessagesKapt3()) + val logger = Logger(isVerbose) + if (isVerbose) { + logger.info("Kapt3 is enabled.") + logger.info("Do annotation processing only: $isAptOnly") + logger.info("Source output directory: $sourcesOutputDir") + logger.info("Classes output directory: $classesOutputDir") + logger.info("Annotation processing classpath: " + classpath.joinToString()) + logger.info("Java source roots: " + javaSourceRoots.joinToString()) + logger.info("Options: $apOptions") + } + val kapt3AnalysisCompletedHandlerExtension = Kapt3AnalysisCompletedHandlerExtension( - classpath, javaSourceRoots, sourcesOutputDir, classesOutputDir, apOptions, isVerbose) + classpath, javaSourceRoots, sourcesOutputDir, classesOutputDir, apOptions, isAptOnly, logger) AnalysisCompletedHandlerExtension.registerExtension(project, kapt3AnalysisCompletedHandlerExtension) } } \ No newline at end of file diff --git a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/KaptRunner.kt b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/KaptRunner.kt index a2c4e55ffc7..797b577112f 100644 --- a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/KaptRunner.kt +++ b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/KaptRunner.kt @@ -21,10 +21,12 @@ 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.JavacFiler +import com.sun.tools.javac.processing.JavacMessager import com.sun.tools.javac.processing.JavacProcessingEnvironment import com.sun.tools.javac.tree.JCTree -import com.sun.tools.javac.tree.TreeMaker import com.sun.tools.javac.util.Context +import com.sun.tools.javac.util.Log import com.sun.tools.javac.util.Options import java.io.File import javax.annotation.processing.Processor @@ -37,6 +39,7 @@ class KaptError : RuntimeException { enum class Kind(val message: String) { JAVA_FILE_PARSING_ERROR("Java file parsing error"), EXCEPTION("Exception while annotation processing"), + ERROR_RAISED("Error while annotation processing"), UNKNOWN("Unknown error while annotation processing") } @@ -49,7 +52,7 @@ class KaptError : RuntimeException { } } -class KaptRunner { +class KaptRunner(val logger: Logger) { val context = Context() val compiler: KaptJavaCompiler val fileManager: JavacFileManager @@ -92,14 +95,14 @@ class KaptRunner { javaSourceFiles: List, processors: List, classpath: List, - sourceOutputDir: File, - classOutputDir: File, + sourcesOutputDir: File, + classesOutputDir: File, additionalSources: JavacList = JavacList.nil() ) { 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) + options.put(Option.CLASSPATH, classpath.joinToString(File.pathSeparator) { it.canonicalPath }) + options.put(Option.S, sourcesOutputDir.canonicalPath) + options.put(Option.D, classesOutputDir.canonicalPath) val fileManager = context.get(JavaFileManager::class.java) as JavacFileManager val processingEnvironment = JavacProcessingEnvironment.instance(context) @@ -113,6 +116,10 @@ class KaptRunner { throw KaptError(KaptError.Kind.JAVA_FILE_PARSING_ERROR) } + val log = Log.instance(context) + val errorCountBeforeAp = log.nerrors + val warningsBeforeAp = log.nwarnings + val compilerAfterAnnotationProcessing: JavaCompiler? = null try { compiler.processAnnotations(compiler.enterTrees(parsedJavaFiles + additionalSources)) @@ -120,6 +127,19 @@ class KaptRunner { throw KaptError(KaptError.Kind.EXCEPTION, e.cause ?: e) } + val filer = processingEnvironment.filer as JavacFiler + val errorCount = Math.max(log.nerrors, errorCountBeforeAp) + val warningCount = log.nwarnings - warningsBeforeAp + + logger.info { "Annotation processing complete, errors: $errorCount, warnings: $warningCount" } + if (logger.isVerbose) { + filer.displayState() + } + + if (log.nerrors > 0) { + throw KaptError(KaptError.Kind.ERROR_RAISED) + } + compilerAfterAnnotationProcessing?.close() } finally { processingEnvironment.close() diff --git a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/Logger.kt b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/Logger.kt new file mode 100644 index 00000000000..c4b822ec9a7 --- /dev/null +++ b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/Logger.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.kapt3 + +class Logger(val isVerbose: Boolean) { + private companion object { + val PREFIX = "[kapt] " + } + + fun info(message: String) { + if (isVerbose) println(PREFIX + message) + } + + inline fun info(message: () -> String) { + if (isVerbose) { + info(message()) + } + } + fun error(message: String) { + System.err.println(PREFIX + message) + } + + fun exception(e: Throwable) { + System.err.println(PREFIX + "An exception occurred:") + e.printStackTrace(System.err) + } +} \ No newline at end of file diff --git a/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/AbstractKotlinKapt3Test.kt b/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/AbstractKotlinKapt3Test.kt index 4a34de64d9c..014c564bca2 100644 --- a/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/AbstractKotlinKapt3Test.kt +++ b/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/AbstractKotlinKapt3Test.kt @@ -25,6 +25,7 @@ import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper import org.jetbrains.kotlin.kapt3.JCTreeConverter import org.jetbrains.kotlin.kapt3.Kapt3BuilderFactory import org.jetbrains.kotlin.kapt3.KaptRunner +import org.jetbrains.kotlin.kapt3.Logger import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin import org.jetbrains.kotlin.test.ConfigurationKind import org.jetbrains.kotlin.test.KotlinTestUtils @@ -50,7 +51,8 @@ abstract class AbstractKotlinKapt3Test : CodegenTestCase() { val factory = CodegenTestUtil.generateFiles(myEnvironment, myFiles, classBuilderFactory) val typeMapper = factory.generationState.typeMapper - val kaptRunner = KaptRunner() + val logger = Logger(isVerbose = true) + val kaptRunner = KaptRunner(logger) try { check(kaptRunner, typeMapper, classBuilderFactory, txtFile) } finally { @@ -96,7 +98,7 @@ abstract class AbstractKotlinKaptRunnerTest : AbstractKotlinKapt3Test() { val sourceOutputDir = Files.createTempDirectory("kaptRunner").toFile() try { kaptRunner.doAnnotationProcessing(emptyList(), listOf(KaptRunnerTest.SIMPLE_PROCESSOR), - classpath = listOf(), sourceOutputDir = sourceOutputDir, classOutputDir = sourceOutputDir, + classpath = listOf(), sourcesOutputDir = sourceOutputDir, classesOutputDir = sourceOutputDir, additionalSources = compilationUnits) val javaFiles = sourceOutputDir.walkTopDown().filter { it.isFile && it.extension == "java" } diff --git a/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/KaptRunnerTest.kt b/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/KaptRunnerTest.kt index 4490ca05714..4f78c78553a 100644 --- a/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/KaptRunnerTest.kt +++ b/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/KaptRunnerTest.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.kapt3.test import org.jetbrains.kotlin.kapt3.KaptError import org.jetbrains.kotlin.kapt3.KaptRunner +import org.jetbrains.kotlin.kapt3.Logger import org.junit.Assert.* import org.junit.Test import java.io.File @@ -75,7 +76,7 @@ class KaptRunnerTest { fun testSimple() { val sourceOutputDir = Files.createTempDirectory("kaptRunner").toFile() try { - KaptRunner().doAnnotationProcessing( + KaptRunner(Logger(isVerbose = true)).doAnnotationProcessing( listOf(File(TEST_DATA_DIR, "Simple.java")), listOf(SIMPLE_PROCESSOR), emptyList(), // classpath @@ -101,7 +102,7 @@ class KaptRunnerTest { } try { - KaptRunner().doAnnotationProcessing( + KaptRunner(Logger(isVerbose = true)).doAnnotationProcessing( listOf(File(TEST_DATA_DIR, "Simple.java")), listOf(processor), emptyList(), @@ -116,7 +117,7 @@ class KaptRunnerTest { @Test fun testParsingError() { try { - KaptRunner().doAnnotationProcessing( + KaptRunner(Logger(isVerbose = true)).doAnnotationProcessing( listOf(File(TEST_DATA_DIR, "ParseError.java")), listOf(SIMPLE_PROCESSOR), emptyList(),