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(),