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.
This commit is contained in:
Yan Zhulanow
2016-10-29 03:32:13 +03:00
committed by Yan Zhulanow
parent bd9d33fe44
commit aa15e0ad67
6 changed files with 158 additions and 19 deletions
@@ -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<File>,
val annotationProcessingClasspath: List<File>,
val javaSourceRoots: List<File>,
val sourcesOutputDir: File,
val classesOutputDir: File,
val options: Map<String, String>,
val isVerbose: Boolean
val options: Map<String, String>, //TODO
val aptOnly: Boolean,
val logger: Logger
) : AnalysisCompletedHandlerExtension {
private var annotationProcessingComplete = false
override fun analysisCompleted(
project: Project,
module: ModuleDescriptor,
bindingTrace: BindingTrace,
files: Collection<KtFile>
): 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 ?: "<in memory ${it.hashCode()}>" } }
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<File>): List<Processor> {
val classLoader = URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray())
return ServiceLoader.load(Processor::class.java, classLoader).toList()
}
}
@@ -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<String> =
CompilerConfigurationKey.create<String>("verbose mode")
val APT_ONLY: CompilerConfigurationKey<String> =
CompilerConfigurationKey.create<String>("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)
}
}
@@ -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<File>,
processors: List<Processor>,
classpath: List<File>,
sourceOutputDir: File,
classOutputDir: File,
sourcesOutputDir: File,
classesOutputDir: File,
additionalSources: JavacList<JCTree.JCCompilationUnit> = 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()
@@ -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)
}
}
@@ -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" }
@@ -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(),