Kapt: Extract annotation processing running logic from the compiler plugin
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("jps-compatible")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile(projectDist(":kotlin-stdlib"))
|
||||
compile(files("${System.getProperty("java.home")}/../lib/tools.jar"))
|
||||
testCompile(commonDep("junit:junit"))
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
"main" { projectDefault() }
|
||||
"test" { projectDefault() }
|
||||
}
|
||||
|
||||
testsJar {}
|
||||
|
||||
projectTest {
|
||||
workingDir = rootDir
|
||||
dependsOn(":dist")
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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.kapt3.base
|
||||
|
||||
import com.sun.tools.javac.util.Context
|
||||
import org.jetbrains.kotlin.kapt3.base.util.KaptLogger
|
||||
import org.jetbrains.kotlin.kapt3.base.util.WriterBackedKaptLogger
|
||||
import org.jetbrains.kotlin.kapt3.base.util.info
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
object Kapt {
|
||||
private const val JAVAC_CONTEXT_CLASS = "com.sun.tools.javac.util.Context"
|
||||
|
||||
@JvmStatic
|
||||
@Suppress("unused")
|
||||
fun kapt(
|
||||
paths: KaptPaths,
|
||||
isVerbose: Boolean,
|
||||
mapDiagnosticLocations: Boolean,
|
||||
annotationProcessorFqNames: List<String>,
|
||||
processorOptions: Map<String, String>,
|
||||
javacOptions: Map<String, String>
|
||||
): Boolean {
|
||||
val logger = WriterBackedKaptLogger(isVerbose)
|
||||
|
||||
if (!Kapt.checkJavacComponentsAccess(logger)) {
|
||||
return false
|
||||
}
|
||||
|
||||
val kaptContext = KaptContext(paths, false, logger, mapDiagnosticLocations, processorOptions, javacOptions)
|
||||
|
||||
logger.info { "Kapt3 is enabled (stand-alone mode)." }
|
||||
logger.info { "Map diagnostic locations: $mapDiagnosticLocations" }
|
||||
paths.log(logger)
|
||||
logger.info { "Javac options: $javacOptions" }
|
||||
logger.info { "AP options: $processorOptions" }
|
||||
|
||||
val javaSourceFiles = paths.collectJavaSourceFiles()
|
||||
logger.info { "Java source files: " + javaSourceFiles.joinToString { it.canonicalPath } }
|
||||
|
||||
val processorLoader = ProcessorLoader(paths, annotationProcessorFqNames, logger)
|
||||
|
||||
processorLoader.use {
|
||||
val processors = processorLoader.loadProcessors(findClassLoaderWithJavac())
|
||||
|
||||
val annotationProcessingTime = measureTimeMillis {
|
||||
kaptContext.doAnnotationProcessing(javaSourceFiles, processors)
|
||||
}
|
||||
|
||||
logger.info { "Annotation processing took $annotationProcessingTime ms" }
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun checkJavacComponentsAccess(logger: KaptLogger): Boolean {
|
||||
try {
|
||||
Class.forName(JAVAC_CONTEXT_CLASS)
|
||||
return true
|
||||
} catch (e: ClassNotFoundException) {
|
||||
logger.error("'$JAVAC_CONTEXT_CLASS' class can't be found ('tools.jar' is absent in the plugin classpath). Kapt won't work.")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun findClassLoaderWithJavac(): ClassLoader {
|
||||
fun Class<*>.toClassFilePath() = name.replace('.', '/') + ".class"
|
||||
|
||||
// find topmost class loader with javac
|
||||
val javacContextPath = Context::class.java.toClassFilePath()
|
||||
val kaptPath = Kapt::class.java.toClassFilePath()
|
||||
|
||||
fun findRightClassLoader(current: ClassLoader): ClassLoader? {
|
||||
if (current.getResource(javacContextPath) != null && current.getResource(kaptPath) == null) {
|
||||
return current
|
||||
}
|
||||
|
||||
val parent = current.parent ?: return null
|
||||
return findRightClassLoader(parent)
|
||||
}
|
||||
|
||||
val kaptClassLoader = Kapt::class.java.classLoader
|
||||
return findRightClassLoader(kaptClassLoader) ?: kaptClassLoader
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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.kapt3.base
|
||||
|
||||
import com.sun.tools.javac.jvm.ClassReader
|
||||
import com.sun.tools.javac.main.JavaCompiler
|
||||
import com.sun.tools.javac.main.Option
|
||||
import com.sun.tools.javac.util.Context
|
||||
import com.sun.tools.javac.util.Log
|
||||
import com.sun.tools.javac.util.Options
|
||||
import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaCompiler
|
||||
import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaFileManager
|
||||
import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaLog
|
||||
import org.jetbrains.kotlin.kapt3.base.util.KaptLogger
|
||||
import org.jetbrains.kotlin.kapt3.base.util.isJava9OrLater
|
||||
import org.jetbrains.kotlin.kapt3.base.util.putJavacOption
|
||||
import java.io.File
|
||||
import javax.tools.JavaFileManager
|
||||
|
||||
open class KaptContext(
|
||||
val paths: KaptPaths,
|
||||
val withJdk: Boolean,
|
||||
val logger: KaptLogger,
|
||||
private val mapDiagnosticLocations: Boolean,
|
||||
processorOptions: Map<String, String>,
|
||||
javacOptions: Map<String, String> = emptyMap()
|
||||
) : AutoCloseable {
|
||||
val context = Context()
|
||||
val compiler: KaptJavaCompiler
|
||||
val fileManager: KaptJavaFileManager
|
||||
val options: Options
|
||||
val javaLog: KaptJavaLog
|
||||
|
||||
protected open fun preregisterTreeMaker(context: Context) {}
|
||||
|
||||
private fun preregisterLog(context: Context) {
|
||||
val interceptorData = KaptJavaLog.DiagnosticInterceptorData()
|
||||
context.put(Log.logKey, Context.Factory<Log> { newContext ->
|
||||
KaptJavaLog(
|
||||
paths.projectBaseDir, newContext, logger.errorWriter, logger.warnWriter, logger.infoWriter,
|
||||
interceptorData, mapDiagnosticLocations
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
init {
|
||||
preregisterLog(context)
|
||||
KaptJavaFileManager.preRegister(context)
|
||||
|
||||
@Suppress("LeakingThis")
|
||||
preregisterTreeMaker(context)
|
||||
|
||||
KaptJavaCompiler.preRegister(context)
|
||||
|
||||
options = Options.instance(context).apply {
|
||||
for ((key, value) in processorOptions) {
|
||||
val option = if (value.isEmpty()) "-A$key" else "-A$key=$value"
|
||||
put(option, option) // key == value: it's intentional
|
||||
}
|
||||
|
||||
for ((key, value) in javacOptions) {
|
||||
if (value.isNotEmpty()) {
|
||||
put(key, value)
|
||||
} else {
|
||||
put(key, key)
|
||||
}
|
||||
}
|
||||
|
||||
put(Option.PROC, "only") // Only process annotations
|
||||
|
||||
if (!withJdk) {
|
||||
putJavacOption("BOOTCLASSPATH", "BOOT_CLASS_PATH", "") // No boot classpath
|
||||
}
|
||||
|
||||
if (isJava9OrLater()) {
|
||||
put("accessInternalAPI", "true")
|
||||
}
|
||||
|
||||
putJavacOption("CLASSPATH", "CLASS_PATH",
|
||||
paths.compileClasspath.joinToString(File.pathSeparator) { it.canonicalPath })
|
||||
putJavacOption("PROCESSORPATH", "PROCESSOR_PATH",
|
||||
paths.annotationProcessingClasspath.joinToString(File.pathSeparator) { it.canonicalPath })
|
||||
|
||||
put(Option.S, paths.sourcesOutputDir.canonicalPath)
|
||||
put(Option.D, paths.classFilesOutputDir.canonicalPath)
|
||||
put(Option.ENCODING, "UTF-8")
|
||||
}
|
||||
|
||||
if (logger.isVerbose) {
|
||||
logger.info("All Javac options: " + options.keySet().associateBy({ it }) { key -> options[key] ?: "" })
|
||||
}
|
||||
|
||||
fileManager = context.get(JavaFileManager::class.java) as KaptJavaFileManager
|
||||
|
||||
if (isJava9OrLater()) {
|
||||
for (option in Option.getJavacFileManagerOptions()) {
|
||||
val value = options.get(option) ?: continue
|
||||
fileManager.handleOptionJavac9(option, value)
|
||||
}
|
||||
}
|
||||
|
||||
compiler = JavaCompiler.instance(context) as KaptJavaCompiler
|
||||
compiler.keepComments = true
|
||||
|
||||
ClassReader.instance(context).saveParameterNames = true
|
||||
|
||||
javaLog = compiler.log as KaptJavaLog
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
compiler.close()
|
||||
fileManager.close()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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.kapt3.base
|
||||
|
||||
import org.jetbrains.kotlin.kapt3.base.util.KaptLogger
|
||||
import java.io.File
|
||||
|
||||
class KaptPaths(
|
||||
val projectBaseDir: File?,
|
||||
compileClasspath: List<File>,
|
||||
annotationProcessingClasspath: List<File>,
|
||||
val javaSourceRoots: List<File>,
|
||||
val sourcesOutputDir: File,
|
||||
val classFilesOutputDir: File,
|
||||
val stubsOutputDir: File,
|
||||
val incrementalDataOutputDir: File?
|
||||
) {
|
||||
val compileClasspath = compileClasspath.distinct()
|
||||
val annotationProcessingClasspath = annotationProcessingClasspath.distinct()
|
||||
|
||||
fun collectJavaSourceFiles(): List<File> {
|
||||
return (javaSourceRoots + stubsOutputDir).flatMap { root ->
|
||||
root.walk().filter { it.isFile && it.extension == "java" }.toList()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun KaptPaths.log(logger: KaptLogger) {
|
||||
if (!logger.isVerbose) return
|
||||
|
||||
logger.info("Project base dir: $projectBaseDir")
|
||||
logger.info("Compile classpath: ${compileClasspath.joinToString()}")
|
||||
logger.info("Annotation processing classpath: ${annotationProcessingClasspath.joinToString()}")
|
||||
logger.info("Java source roots: ${javaSourceRoots.joinToString()}")
|
||||
logger.info("Sources output directory: $sourcesOutputDir")
|
||||
logger.info("Class files output directory: $classFilesOutputDir")
|
||||
logger.info("Stubs output directory: $stubsOutputDir")
|
||||
logger.info("Incremental data output directory: $incrementalDataOutputDir")
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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.kapt3.base
|
||||
|
||||
import org.jetbrains.kotlin.kapt3.base.util.KaptLogger
|
||||
import org.jetbrains.kotlin.kapt3.base.util.info
|
||||
import java.io.Closeable
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Modifier
|
||||
import java.net.URLClassLoader
|
||||
import java.util.*
|
||||
import javax.annotation.processing.Processor
|
||||
|
||||
class ProcessorLoader(
|
||||
private val paths: KaptPaths,
|
||||
private val annotationProcessorFqNames: List<String>,
|
||||
private val logger: KaptLogger
|
||||
) : Closeable {
|
||||
private var annotationProcessingClassLoader: URLClassLoader? = null
|
||||
|
||||
fun loadProcessors(parentClassLoader: ClassLoader = ClassLoader.getSystemClassLoader()): List<Processor> {
|
||||
clearJarURLCache()
|
||||
|
||||
val classpath = (paths.annotationProcessingClasspath + paths.compileClasspath).distinct()
|
||||
val classLoader = URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), parentClassLoader)
|
||||
this.annotationProcessingClassLoader = classLoader
|
||||
|
||||
val processors = if (annotationProcessorFqNames.isNotEmpty()) {
|
||||
logger.info("Annotation processor class names are set, skip AP discovery")
|
||||
annotationProcessorFqNames.mapNotNull { tryLoadProcessor(it, classLoader) }
|
||||
} else {
|
||||
logger.info("Need to discovery annotation processors in the AP classpath")
|
||||
ServiceLoader.load(Processor::class.java, classLoader).toList()
|
||||
}
|
||||
|
||||
if (processors.isEmpty()) {
|
||||
logger.info("No annotation processors available, aborting")
|
||||
} else {
|
||||
logger.info { "Annotation processors: " + processors.joinToString { it::class.java.canonicalName } }
|
||||
}
|
||||
|
||||
return processors
|
||||
}
|
||||
|
||||
|
||||
private fun tryLoadProcessor(fqName: String, classLoader: ClassLoader): Processor? {
|
||||
val annotationProcessorClass = try {
|
||||
Class.forName(fqName, true, classLoader)
|
||||
} catch (e: Throwable) {
|
||||
logger.warn("Can't find annotation processor class $fqName: ${e.message}")
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
val annotationProcessorInstance = annotationProcessorClass.newInstance()
|
||||
if (annotationProcessorInstance !is Processor) {
|
||||
logger.warn("$fqName is not an instance of 'Processor'")
|
||||
return null
|
||||
}
|
||||
|
||||
return annotationProcessorInstance
|
||||
} catch (e: Throwable) {
|
||||
logger.warn("Can't load annotation processor class $fqName: ${e.message}")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
annotationProcessingClassLoader?.close()
|
||||
clearJarURLCache()
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from com.intellij.ide.ClassUtilCore
|
||||
private fun clearJarURLCache() {
|
||||
fun clearMap(cache: Field) {
|
||||
cache.isAccessible = true
|
||||
|
||||
if (!Modifier.isFinal(cache.modifiers)) {
|
||||
cache.set(null, hashMapOf<Any, Any>())
|
||||
} else {
|
||||
val map = cache.get(null) as MutableMap<*, *>
|
||||
map.clear()
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
val jarFileFactory = Class.forName("sun.net.www.protocol.jar.JarFileFactory")
|
||||
|
||||
clearMap(jarFileFactory.getDeclaredField("fileCache"))
|
||||
clearMap(jarFileFactory.getDeclaredField("urlCache"))
|
||||
} catch (ignore: Exception) {}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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.kapt3.base
|
||||
|
||||
import com.sun.tools.javac.comp.CompileStates.*
|
||||
import com.sun.tools.javac.main.JavaCompiler
|
||||
import com.sun.tools.javac.processing.AnnotationProcessingError
|
||||
import com.sun.tools.javac.processing.JavacFiler
|
||||
import com.sun.tools.javac.processing.JavacProcessingEnvironment
|
||||
import com.sun.tools.javac.tree.JCTree
|
||||
import org.jetbrains.kotlin.kapt3.base.util.KaptBaseError
|
||||
import org.jetbrains.kotlin.kapt3.base.util.isJava9OrLater
|
||||
import org.jetbrains.kotlin.kapt3.base.util.measureTimeMillisWithResult
|
||||
import java.io.File
|
||||
import javax.annotation.processing.Processor
|
||||
import javax.annotation.processing.RoundEnvironment
|
||||
import javax.lang.model.element.TypeElement
|
||||
import javax.tools.JavaFileObject
|
||||
import com.sun.tools.javac.util.List as JavacList
|
||||
|
||||
fun KaptContext.doAnnotationProcessing(
|
||||
javaSourceFiles: List<File>,
|
||||
processors: List<Processor>,
|
||||
additionalSources: JavacList<JCTree.JCCompilationUnit> = JavacList.nil()
|
||||
) {
|
||||
val processingEnvironment = JavacProcessingEnvironment.instance(context)
|
||||
val wrappedProcessors = processors.map { ProcessorWrapper(it) }
|
||||
|
||||
val compilerAfterAP: JavaCompiler
|
||||
try {
|
||||
if (isJava9OrLater()) {
|
||||
val initProcessAnnotationsMethod = JavaCompiler::class.java.declaredMethods.single { it.name == "initProcessAnnotations" }
|
||||
initProcessAnnotationsMethod.invoke(compiler, wrappedProcessors, emptyList<JavaFileObject>(), emptyList<String>())
|
||||
}
|
||||
else {
|
||||
compiler.initProcessAnnotations(wrappedProcessors)
|
||||
}
|
||||
|
||||
val parsedJavaFiles = parseJavaFiles(javaSourceFiles)
|
||||
|
||||
compilerAfterAP = try {
|
||||
javaLog.interceptorData.files = parsedJavaFiles.map { it.sourceFile to it }.toMap()
|
||||
val analyzedFiles = compiler.stopIfErrorOccurred(
|
||||
CompileState.PARSE, compiler.enterTrees(parsedJavaFiles + additionalSources))
|
||||
|
||||
if (isJava9OrLater()) {
|
||||
val processAnnotationsMethod = compiler.javaClass.getMethod("processAnnotations", JavacList::class.java)
|
||||
processAnnotationsMethod.invoke(compiler, analyzedFiles)
|
||||
compiler
|
||||
}
|
||||
else {
|
||||
compiler.processAnnotations(analyzedFiles)
|
||||
}
|
||||
} catch (e: AnnotationProcessingError) {
|
||||
throw KaptBaseError(KaptBaseError.Kind.EXCEPTION, e.cause ?: e)
|
||||
}
|
||||
|
||||
val log = compilerAfterAP.log
|
||||
|
||||
val filer = processingEnvironment.filer as JavacFiler
|
||||
val errorCount = log.nerrors
|
||||
val warningCount = log.nwarnings
|
||||
|
||||
if (logger.isVerbose) {
|
||||
logger.info("Annotation processing complete, errors: $errorCount, warnings: $warningCount")
|
||||
|
||||
logger.info("Annotation processor stats:")
|
||||
wrappedProcessors.forEach { processor ->
|
||||
val rounds = processor.rounds
|
||||
val roundMs = rounds.joinToString { "$it ms" }
|
||||
val totalMs = rounds.sum()
|
||||
|
||||
logger.info("${processor.name}: ${rounds.size} rounds ($roundMs), $totalMs ms in total")
|
||||
}
|
||||
|
||||
filer.displayState()
|
||||
}
|
||||
|
||||
if (log.nerrors > 0) {
|
||||
throw KaptBaseError(KaptBaseError.Kind.ERROR_RAISED)
|
||||
}
|
||||
} finally {
|
||||
processingEnvironment.close()
|
||||
this@doAnnotationProcessing.close()
|
||||
}
|
||||
}
|
||||
|
||||
private class ProcessorWrapper(private val delegate: Processor) : Processor by delegate {
|
||||
val rounds = mutableListOf<Long>()
|
||||
|
||||
val name: String
|
||||
get() = delegate.javaClass.simpleName
|
||||
|
||||
override fun process(annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment?): Boolean {
|
||||
val (time, result) = measureTimeMillisWithResult {
|
||||
delegate.process(annotations, roundEnv)
|
||||
}
|
||||
|
||||
rounds += time
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
fun KaptContext.parseJavaFiles(javaSourceFiles: List<File>): JavacList<JCTree.JCCompilationUnit> {
|
||||
val javaFileObjects = fileManager.getJavaFileObjectsFromFiles(javaSourceFiles)
|
||||
|
||||
return compiler.stopIfErrorOccurred(CompileState.PARSE,
|
||||
initModulesIfNeeded(
|
||||
compiler.stopIfErrorOccurred(CompileState.PARSE,
|
||||
compiler.parseFiles(javaFileObjects))))
|
||||
}
|
||||
|
||||
private fun KaptContext.initModulesIfNeeded(files: JavacList<JCTree.JCCompilationUnit>): JavacList<JCTree.JCCompilationUnit> {
|
||||
if (isJava9OrLater()) {
|
||||
val initModulesMethod = compiler.javaClass.getMethod("initModules", JavacList::class.java)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return compiler.stopIfErrorOccurred(
|
||||
CompileState.PARSE,
|
||||
initModulesMethod.invoke(compiler, files) as JavacList<JCTree.JCCompilationUnit>)
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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.kapt3.base.javac
|
||||
|
||||
import com.sun.tools.javac.comp.CompileStates
|
||||
import com.sun.tools.javac.main.JavaCompiler
|
||||
import com.sun.tools.javac.util.Context
|
||||
import com.sun.tools.javac.util.List as JavacList
|
||||
|
||||
class KaptJavaCompiler(context: Context) : JavaCompiler(context) {
|
||||
public override fun shouldStop(cs: CompileStates.CompileState) = super.shouldStop(cs)
|
||||
|
||||
fun <T> stopIfErrorOccurred(cs: CompileStates.CompileState, list: JavacList<T>): JavacList<T> {
|
||||
return if (shouldStop(cs)) JavacList.nil<T>() else list
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal fun preRegister(context: Context) {
|
||||
context.put(compilerKey, Context.Factory<JavaCompiler>(::KaptJavaCompiler))
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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.kapt3.base.javac
|
||||
|
||||
import com.sun.tools.javac.file.JavacFileManager
|
||||
import com.sun.tools.javac.main.Option
|
||||
import com.sun.tools.javac.util.Context
|
||||
import javax.tools.JavaFileManager
|
||||
|
||||
class KaptJavaFileManager(context: Context) : JavacFileManager(context, true, null) {
|
||||
fun handleOptionJavac9(option: Option, value: String) {
|
||||
val handleOptionMethod = JavacFileManager::class.java
|
||||
.getMethod("handleOption", Option::class.java, String::class.java)
|
||||
|
||||
handleOptionMethod.invoke(this, option, value)
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal fun preRegister(context: Context) {
|
||||
context.put(JavaFileManager::class.java, Context.Factory<JavaFileManager> { KaptJavaFileManager(it) })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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.kapt3.base.javac
|
||||
|
||||
import com.sun.tools.javac.tree.JCTree
|
||||
import com.sun.tools.javac.util.*
|
||||
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticType
|
||||
import org.jetbrains.kotlin.kapt3.base.KaptContext
|
||||
import org.jetbrains.kotlin.kapt3.base.stubs.KaptStubLineInformation
|
||||
import org.jetbrains.kotlin.kapt3.base.stubs.KotlinPosition
|
||||
import org.jetbrains.kotlin.kapt3.base.util.isJava9OrLater
|
||||
import java.io.*
|
||||
import javax.tools.Diagnostic
|
||||
import javax.tools.JavaFileObject
|
||||
import javax.tools.JavaFileObject.Kind
|
||||
import javax.tools.SimpleJavaFileObject
|
||||
import com.sun.tools.javac.util.List as JavacList
|
||||
|
||||
class KaptJavaLog(
|
||||
private val projectBaseDir: File?,
|
||||
context: Context,
|
||||
errWriter: PrintWriter,
|
||||
warnWriter: PrintWriter,
|
||||
noticeWriter: PrintWriter,
|
||||
val interceptorData: DiagnosticInterceptorData,
|
||||
private val mapDiagnosticLocations: Boolean
|
||||
) : Log(context, errWriter, warnWriter, noticeWriter) {
|
||||
private val stubLineInfo = KaptStubLineInformation()
|
||||
private val javacMessages = JavacMessages.instance(context)
|
||||
|
||||
init {
|
||||
context.put(Log.outKey, noticeWriter)
|
||||
}
|
||||
|
||||
val reportedDiagnostics: List<JCDiagnostic>
|
||||
get() = _reportedDiagnostics
|
||||
|
||||
private val _reportedDiagnostics = mutableListOf<JCDiagnostic>()
|
||||
|
||||
override fun flush(kind: WriterKind?) {
|
||||
super.flush(kind)
|
||||
|
||||
val diagnosticKind = when (kind) {
|
||||
WriterKind.ERROR -> JCDiagnostic.DiagnosticType.ERROR
|
||||
WriterKind.WARNING -> JCDiagnostic.DiagnosticType.WARNING
|
||||
WriterKind.NOTICE -> JCDiagnostic.DiagnosticType.NOTE
|
||||
else -> return
|
||||
}
|
||||
|
||||
_reportedDiagnostics.removeAll { it.type == diagnosticKind }
|
||||
}
|
||||
|
||||
override fun flush() {
|
||||
super.flush()
|
||||
_reportedDiagnostics.clear()
|
||||
}
|
||||
|
||||
override fun report(diagnostic: JCDiagnostic) {
|
||||
if (diagnostic.type == JCDiagnostic.DiagnosticType.ERROR && diagnostic.code in IGNORED_DIAGNOSTICS) {
|
||||
return
|
||||
}
|
||||
|
||||
if (diagnostic.type == JCDiagnostic.DiagnosticType.WARNING
|
||||
&& diagnostic.code == "compiler.warn.proc.unmatched.processor.options"
|
||||
&& diagnostic.args.singleOrNull() == "[kapt.kotlin.generated]"
|
||||
) {
|
||||
// Do not report the warning about "kapt.kotlin.generated" option being ignored if it's the only ignored option
|
||||
return
|
||||
}
|
||||
|
||||
val targetElement = diagnostic.diagnosticPosition
|
||||
val sourceFile = interceptorData.files[diagnostic.source]
|
||||
|
||||
if (diagnostic.code.contains("err.cant.resolve") && targetElement != null) {
|
||||
if (sourceFile != null) {
|
||||
val insideImports = targetElement.tree in sourceFile.imports
|
||||
// Ignore resolve errors in import statements
|
||||
if (insideImports) return
|
||||
}
|
||||
}
|
||||
|
||||
if (mapDiagnosticLocations && sourceFile != null && targetElement.tree != null) {
|
||||
val kotlinPosition = stubLineInfo.getPositionInKotlinFile(sourceFile, targetElement.tree)
|
||||
val kotlinFile = kotlinPosition?.let { getKotlinSourceFile(it) }
|
||||
if (kotlinPosition != null && kotlinFile != null) {
|
||||
val flags = JCDiagnostic.DiagnosticFlag.values().filterTo(mutableSetOf(), diagnostic::isFlagSet)
|
||||
|
||||
val kotlinDiagnostic = diags.create(
|
||||
diagnostic.type,
|
||||
diagnostic.lintCategory,
|
||||
flags,
|
||||
DiagnosticSource(KotlinFileObject(kotlinFile), this),
|
||||
JCDiagnostic.SimpleDiagnosticPosition(kotlinPosition.pos),
|
||||
diagnostic.code.stripCompilerKeyPrefix(),
|
||||
*diagnostic.args
|
||||
)
|
||||
|
||||
reportDiagnostic(kotlinDiagnostic)
|
||||
|
||||
// Avoid reporting the diagnostic twice
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
reportDiagnostic(diagnostic)
|
||||
}
|
||||
|
||||
private fun String.stripCompilerKeyPrefix(): String {
|
||||
for (kind in listOf("err", "warn", "misc", "note")) {
|
||||
val prefix = "compiler.$kind."
|
||||
if (startsWith(prefix)) {
|
||||
return drop(prefix.length)
|
||||
}
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
private fun reportDiagnostic(diagnostic: JCDiagnostic) {
|
||||
if (diagnostic.kind == Diagnostic.Kind.ERROR) {
|
||||
val oldErrors = nerrors
|
||||
super.report(diagnostic)
|
||||
if (nerrors > oldErrors) {
|
||||
_reportedDiagnostics += diagnostic
|
||||
}
|
||||
}
|
||||
else if (diagnostic.kind == Diagnostic.Kind.WARNING) {
|
||||
val oldWarnings = nwarnings
|
||||
super.report(diagnostic)
|
||||
if (nwarnings > oldWarnings) {
|
||||
_reportedDiagnostics += diagnostic
|
||||
}
|
||||
}
|
||||
else {
|
||||
super.report(diagnostic)
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeDiagnostic(diagnostic: JCDiagnostic) {
|
||||
if (hasDiagnosticListener()) {
|
||||
diagListener.report(diagnostic)
|
||||
return
|
||||
}
|
||||
|
||||
val writer = when (diagnostic.type) {
|
||||
DiagnosticType.FRAGMENT, null -> kotlin.error("Invalid root diagnostic type: ${diagnostic.type}")
|
||||
DiagnosticType.NOTE -> super.getWriter(WriterKind.NOTICE)
|
||||
DiagnosticType.WARNING -> super.getWriter(WriterKind.WARNING)
|
||||
DiagnosticType.ERROR -> super.getWriter(WriterKind.ERROR)
|
||||
}
|
||||
|
||||
val formattedMessage = diagnosticFormatter.format(diagnostic, javacMessages.currentLocale)
|
||||
.lines()
|
||||
.joinToString(LINE_SEPARATOR) { original ->
|
||||
// Kotlin location is put as a sub-diagnostic, so the formatter indents it with four additional spaces (6 in total).
|
||||
// It looks weird, especially in the build log inside IntelliJ, so let's make things a bit better.
|
||||
val trimmed = original.trimStart()
|
||||
// Typically, javac places additional details about the diagnostics indented by two spaces
|
||||
if (trimmed.startsWith(KOTLIN_LOCATION_PREFIX)) " " + trimmed else original
|
||||
}
|
||||
|
||||
writer.print(formattedMessage)
|
||||
writer.flush()
|
||||
}
|
||||
|
||||
private fun getKotlinSourceFile(pos: KotlinPosition): File? {
|
||||
return if (pos.isRelativePath) {
|
||||
val basePath = this.projectBaseDir
|
||||
if (basePath != null) File(basePath, pos.path) else null
|
||||
}
|
||||
else {
|
||||
File(pos.path)
|
||||
}
|
||||
}
|
||||
|
||||
private operator fun <T : JCTree> Iterable<T>.contains(element: JCTree?): Boolean {
|
||||
if (element == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
var found = false
|
||||
val visitor = object : JCTree.Visitor() {
|
||||
override fun visitImport(that: JCTree.JCImport) {
|
||||
super.visitImport(that)
|
||||
if (!found) that.qualid.accept(this)
|
||||
}
|
||||
|
||||
override fun visitSelect(that: JCTree.JCFieldAccess) {
|
||||
super.visitSelect(that)
|
||||
if (!found) that.selected.accept(this)
|
||||
}
|
||||
|
||||
override fun visitTree(that: JCTree) {
|
||||
if (!found && element == that) found = true
|
||||
}
|
||||
}
|
||||
this.forEach { if (!found) it.accept(visitor) }
|
||||
return found
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LINE_SEPARATOR: String = System.getProperty("line.separator")
|
||||
private val KOTLIN_LOCATION_PREFIX = "Kotlin location: "
|
||||
|
||||
private val IGNORED_DIAGNOSTICS = setOf(
|
||||
"compiler.err.name.clash.same.erasure",
|
||||
"compiler.err.name.clash.same.erasure.no.override",
|
||||
"compiler.err.name.clash.same.erasure.no.override.1",
|
||||
"compiler.err.name.clash.same.erasure.no.hide",
|
||||
"compiler.err.already.defined",
|
||||
"compiler.err.annotation.type.not.applicable",
|
||||
"compiler.err.doesnt.exist",
|
||||
"compiler.err.duplicate.annotation.missing.container",
|
||||
"compiler.err.not.def.access.package.cant.access",
|
||||
"compiler.err.package.not.visible"
|
||||
)
|
||||
}
|
||||
|
||||
class DiagnosticInterceptorData {
|
||||
var files: Map<JavaFileObject, JCTree.JCCompilationUnit> = emptyMap()
|
||||
}
|
||||
}
|
||||
|
||||
fun KaptContext.kaptError(text: String): JCDiagnostic {
|
||||
return JCDiagnostic.Factory.instance(context).errorJava9Aware(null, null, "proc.messager", text)
|
||||
}
|
||||
|
||||
private fun JCDiagnostic.Factory.errorJava9Aware(
|
||||
source: DiagnosticSource?,
|
||||
pos: JCDiagnostic.DiagnosticPosition?,
|
||||
key: String,
|
||||
vararg args: String
|
||||
): JCDiagnostic {
|
||||
return if (isJava9OrLater()) {
|
||||
val errorMethod = this::class.java.getDeclaredMethod(
|
||||
"error",
|
||||
JCDiagnostic.DiagnosticFlag::class.java,
|
||||
DiagnosticSource::class.java,
|
||||
JCDiagnostic.DiagnosticPosition::class.java,
|
||||
String::class.java,
|
||||
Array<Any>::class.java)
|
||||
|
||||
errorMethod.invoke(this, JCDiagnostic.DiagnosticFlag.MANDATORY, source, pos, key, args) as JCDiagnostic
|
||||
}
|
||||
else {
|
||||
this.error(source, pos, key, *args)
|
||||
}
|
||||
}
|
||||
|
||||
private data class KotlinFileObject(val file: File) : SimpleJavaFileObject(file.toURI(), Kind.SOURCE) {
|
||||
override fun openOutputStream() = file.outputStream()
|
||||
override fun openWriter() = file.writer()
|
||||
override fun openInputStream() = file.inputStream()
|
||||
override fun getCharContent(ignoreEncodingErrors: Boolean) = file.readText()
|
||||
override fun getLastModified() = file.lastModified()
|
||||
override fun openReader(ignoreEncodingErrors: Boolean) = file.reader()
|
||||
override fun delete() = file.delete()
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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.kapt3.base.stubs
|
||||
|
||||
import com.sun.tools.javac.tree.JCTree
|
||||
|
||||
fun JCTree.JCMethodDecl.getJavacSignature(): String {
|
||||
val name = name.toString()
|
||||
val params = parameters.joinToString { it.getType().toString() }
|
||||
return "$name($params)"
|
||||
}
|
||||
|
||||
typealias LineInfoMap = MutableMap<String, KotlinPosition>
|
||||
|
||||
class FileInfo(private val lineInfo: LineInfoMap, private val signatureInfo: Map<String, String>) {
|
||||
companion object {
|
||||
val EMPTY = FileInfo(mutableMapOf(), emptyMap())
|
||||
}
|
||||
|
||||
fun getPositionFor(fqName: String) = lineInfo[fqName]
|
||||
fun getMethodDescriptor(decl: JCTree.JCMethodDecl) = signatureInfo[decl.getJavacSignature()]
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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.kapt3.base.stubs
|
||||
|
||||
import com.sun.tools.javac.tree.JCTree
|
||||
import com.sun.tools.javac.tree.TreeScanner
|
||||
import org.jetbrains.kotlin.kapt3.base.util.getPackageNameJava9Aware
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
import java.io.ObjectInputStream
|
||||
|
||||
class KaptStubLineInformation {
|
||||
private val offsets = mutableMapOf<JCTree.JCCompilationUnit, FileInfo>()
|
||||
private val declarations = mutableMapOf<JCTree.JCCompilationUnit, List<JCTree>>()
|
||||
|
||||
companion object {
|
||||
const val KAPT_METADATA_EXTENSION = ".kapt_metadata"
|
||||
const val METADATA_VERSION = 1
|
||||
|
||||
fun parseFileInfo(file: JCTree.JCCompilationUnit): FileInfo {
|
||||
val sourceUri = file.sourcefile
|
||||
?.toUri()
|
||||
?.takeIf { it.isAbsolute && !it.isOpaque && it.path != null && it.scheme?.toLowerCase() == "file" } ?: return FileInfo.EMPTY
|
||||
|
||||
val sourceFile = File(sourceUri).takeIf { it.exists() } ?: return FileInfo.EMPTY
|
||||
val kaptMetadataFile = File(sourceFile.parentFile, sourceFile.nameWithoutExtension + KAPT_METADATA_EXTENSION)
|
||||
|
||||
if (!kaptMetadataFile.isFile) {
|
||||
return FileInfo.EMPTY
|
||||
}
|
||||
|
||||
return deserialize(kaptMetadataFile.readBytes())
|
||||
}
|
||||
|
||||
private fun deserialize(data: ByteArray): FileInfo {
|
||||
val lineInfo: LineInfoMap = mutableMapOf()
|
||||
val signatureInfo = mutableMapOf<String, String>()
|
||||
|
||||
val ois = ObjectInputStream(ByteArrayInputStream(data))
|
||||
|
||||
val version = ois.readInt()
|
||||
if (version != METADATA_VERSION) {
|
||||
return FileInfo.EMPTY
|
||||
}
|
||||
|
||||
val lineInfoCount = ois.readInt()
|
||||
repeat(lineInfoCount) {
|
||||
val fqName = ois.readUTF()
|
||||
val path = ois.readUTF()
|
||||
val isRelative = ois.readBoolean()
|
||||
val pos = ois.readInt()
|
||||
|
||||
lineInfo[fqName] = KotlinPosition(path, isRelative, pos)
|
||||
}
|
||||
|
||||
val signatureCount = ois.readInt()
|
||||
repeat(signatureCount) {
|
||||
val javacSignature = ois.readUTF()
|
||||
val methodDesc = ois.readUTF()
|
||||
|
||||
signatureInfo[javacSignature] = methodDesc
|
||||
}
|
||||
|
||||
return FileInfo(lineInfo, signatureInfo)
|
||||
}
|
||||
}
|
||||
|
||||
fun getPositionInKotlinFile(file: JCTree.JCCompilationUnit, element: JCTree): KotlinPosition? {
|
||||
val declaration = findDeclarationFor(element, file) ?: return null
|
||||
|
||||
val fileInfo = offsets.getOrPut(file) { parseFileInfo(file) }
|
||||
val elementDescriptor = getKaptDescriptor(declaration, file, fileInfo) ?: return null
|
||||
|
||||
return fileInfo.getPositionFor(elementDescriptor)
|
||||
}
|
||||
|
||||
private fun findDeclarationFor(element: JCTree, file: JCTree.JCCompilationUnit): JCTree? {
|
||||
val fileDeclarations = declarations.getOrPut(file) { collectDeclarations(file) }
|
||||
return fileDeclarations.firstOrNull { element.isLocatedInside(it) }
|
||||
}
|
||||
|
||||
private fun getKaptDescriptor(declaration: JCTree, file: JCTree.JCCompilationUnit, fileInfo: FileInfo): String? {
|
||||
fun getFqName(declaration: JCTree, parent: JCTree, currentName: String): String? {
|
||||
return when (parent) {
|
||||
is JCTree.JCCompilationUnit -> {
|
||||
for (definition in parent.defs) {
|
||||
// There could be only class definitions on the top level
|
||||
definition as? JCTree.JCClassDecl ?: continue
|
||||
getFqName(declaration, definition, "")?.let { return it }
|
||||
}
|
||||
return null
|
||||
}
|
||||
is JCTree.JCClassDecl -> {
|
||||
val className = parent.simpleName.toString()
|
||||
val newName = if (currentName.isEmpty()) className else currentName + "#" + className
|
||||
if (declaration === parent) {
|
||||
return newName
|
||||
}
|
||||
|
||||
for (definition in parent.defs) {
|
||||
getFqName(declaration, definition, className)?.let { return it }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
is JCTree.JCVariableDecl -> {
|
||||
if (declaration === parent) {
|
||||
return currentName + "#" + parent.name.toString()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
is JCTree.JCMethodDecl -> {
|
||||
// We don't need to process local declarations here as kapt does not support locals entirely.
|
||||
if (declaration === parent) {
|
||||
val nameAndSignature = fileInfo.getMethodDescriptor(parent) ?: return null
|
||||
return currentName + "#" + nameAndSignature
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
// Unfortunately, we have to do this the hard way, as symbols may be not available yet
|
||||
// (for instance, if this code is called inside the "enterTrees()")
|
||||
val simpleDescriptor = getFqName(declaration, file, "")
|
||||
val packageName = file.getPackageNameJava9Aware()?.toString()?.replace('.', '/')
|
||||
return if (packageName == null) simpleDescriptor else "$packageName/$simpleDescriptor"
|
||||
}
|
||||
|
||||
private fun collectDeclarations(file: JCTree.JCCompilationUnit): List<JCTree> {
|
||||
val declarations = mutableListOf<JCTree>()
|
||||
|
||||
// Note that super.visit...() is above the declarations saving.
|
||||
// This allows us to get the deepest declarations in the beginning of the list.
|
||||
file.accept(object : TreeScanner() {
|
||||
override fun visitClassDef(tree: JCTree.JCClassDecl) {
|
||||
super.visitClassDef(tree)
|
||||
declarations += tree
|
||||
}
|
||||
|
||||
override fun visitVarDef(tree: JCTree.JCVariableDecl) {
|
||||
// Do not visit variable contents, there can be nothing but local declarations which we don't support
|
||||
declarations += tree
|
||||
}
|
||||
|
||||
override fun visitMethodDef(tree: JCTree.JCMethodDecl) {
|
||||
// Do not visit methods contents, there can be nothing but local declarations which we don't support
|
||||
declarations += tree
|
||||
}
|
||||
|
||||
override fun visitTree(tree: JCTree?) {}
|
||||
})
|
||||
|
||||
return declarations
|
||||
}
|
||||
|
||||
private fun JCTree.isLocatedInside(declaration: JCTree): Boolean {
|
||||
var found = false
|
||||
|
||||
declaration.accept(object : TreeScanner() {
|
||||
override fun scan(tree: JCTree?) {
|
||||
if (!found && tree === this@isLocatedInside) {
|
||||
found = true
|
||||
}
|
||||
|
||||
if (found) return
|
||||
super.scan(tree)
|
||||
}
|
||||
|
||||
override fun scan(trees: com.sun.tools.javac.util.List<out JCTree>?) {
|
||||
// We don't need to repeat the logic above here as scan(List) calls scan(JCTree)
|
||||
if (found) return
|
||||
super.scan(trees)
|
||||
}
|
||||
})
|
||||
|
||||
return found
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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.kapt3.base.stubs
|
||||
|
||||
data class KotlinPosition(val path: String, val isRelativePath: Boolean, val pos: Int)
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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.kapt3.base.util
|
||||
|
||||
class KaptBaseError : RuntimeException {
|
||||
val kind: Kind
|
||||
|
||||
enum class Kind(val message: String) {
|
||||
EXCEPTION("Exception while annotation processing"),
|
||||
ERROR_RAISED("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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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.kapt3.base.util
|
||||
|
||||
import java.io.PrintWriter
|
||||
|
||||
interface KaptLogger {
|
||||
val isVerbose: Boolean
|
||||
|
||||
val infoWriter: PrintWriter
|
||||
val warnWriter: PrintWriter
|
||||
val errorWriter: PrintWriter
|
||||
|
||||
fun info(message: String)
|
||||
fun warn(message: String)
|
||||
fun error(message: String)
|
||||
|
||||
fun exception(e: Throwable)
|
||||
}
|
||||
|
||||
inline fun KaptLogger.info(message: () -> String) {
|
||||
if (isVerbose) {
|
||||
info(message())
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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.kapt3.base.util
|
||||
|
||||
import java.io.PrintWriter
|
||||
|
||||
class WriterBackedKaptLogger(
|
||||
override val isVerbose: Boolean,
|
||||
override val infoWriter: PrintWriter = PrintWriter(System.out),
|
||||
override val warnWriter: PrintWriter = PrintWriter(System.out),
|
||||
override val errorWriter: PrintWriter = PrintWriter(System.err)
|
||||
) : KaptLogger {
|
||||
override fun info(message: String) {
|
||||
if (isVerbose) {
|
||||
report("INFO", message, infoWriter)
|
||||
}
|
||||
}
|
||||
|
||||
override fun warn(message: String) {
|
||||
report("WARN", message, warnWriter)
|
||||
}
|
||||
|
||||
override fun error(message: String) {
|
||||
report("ERROR", message, errorWriter)
|
||||
}
|
||||
|
||||
override fun exception(e: Throwable) {
|
||||
errorWriter.println("An error occurred:")
|
||||
e.printStackTrace(errorWriter)
|
||||
errorWriter.flush()
|
||||
}
|
||||
|
||||
private fun report(prefix: String, message: String, writer: PrintWriter) {
|
||||
writer.println("[$prefix] $message")
|
||||
writer.flush()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.base.util
|
||||
|
||||
import com.sun.tools.javac.main.Option
|
||||
import com.sun.tools.javac.tree.JCTree
|
||||
import com.sun.tools.javac.tree.TreeMaker
|
||||
import com.sun.tools.javac.util.Options
|
||||
import com.sun.tools.javac.util.List as JavacList
|
||||
import org.jetbrains.kotlin.kapt3.base.plus
|
||||
|
||||
fun isJava9OrLater(): Boolean = !System.getProperty("java.version").startsWith("1.")
|
||||
|
||||
fun Options.putJavacOption(jdk8Name: String, jdk9Name: String, value: String) {
|
||||
val option = if (isJava9OrLater()) {
|
||||
Option.valueOf(jdk9Name)
|
||||
} else {
|
||||
Option.valueOf(jdk8Name)
|
||||
}
|
||||
|
||||
put(option, value)
|
||||
}
|
||||
|
||||
fun TreeMaker.TopLevelJava9Aware(packageClause: JCTree.JCExpression?, declarations: JavacList<JCTree>): JCTree.JCCompilationUnit {
|
||||
return if (isJava9OrLater()) {
|
||||
val topLevelMethod = TreeMaker::class.java.declaredMethods.single { it.name == "TopLevel" }
|
||||
val packageDecl: JCTree? = packageClause?.let {
|
||||
val packageDeclMethod = TreeMaker::class.java.methods.single { it.name == "PackageDecl" }
|
||||
packageDeclMethod.invoke(this, JavacList.nil<JCTree>(), packageClause) as JCTree
|
||||
}
|
||||
val allDeclarations = if (packageDecl != null) JavacList.of(packageDecl) + declarations else declarations
|
||||
topLevelMethod.invoke(this, allDeclarations) as JCTree.JCCompilationUnit
|
||||
} else {
|
||||
TopLevel(JavacList.nil(), packageClause, declarations)
|
||||
}
|
||||
}
|
||||
|
||||
fun JCTree.JCCompilationUnit.getPackageNameJava9Aware(): JCTree? {
|
||||
return if (isJava9OrLater()) {
|
||||
JCTree.JCCompilationUnit::class.java.getDeclaredMethod("getPackageName").invoke(this) as JCTree?
|
||||
} else {
|
||||
this.packageName
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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.kapt3.base
|
||||
|
||||
import com.sun.tools.javac.util.List as JavacList
|
||||
|
||||
inline fun <T, R> mapJList(values: Iterable<T>?, f: (T) -> R?): JavacList<R> {
|
||||
if (values == null) return JavacList.nil()
|
||||
|
||||
var result = JavacList.nil<R>()
|
||||
for (item in values) {
|
||||
f(item)?.let { result = result.append(it) }
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
inline fun <T, R> mapJListIndexed(values: Iterable<T>?, f: (Int, T) -> R?): JavacList<R> {
|
||||
if (values == null) return JavacList.nil()
|
||||
|
||||
var result = JavacList.nil<R>()
|
||||
values.forEachIndexed { index, item ->
|
||||
f(index, item)?.let { result = result.append(it) }
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
inline fun <T> mapPairedValuesJList(valuePairs: List<Any>?, f: (String, Any) -> T?): JavacList<T> {
|
||||
if (valuePairs == null || valuePairs.isEmpty()) return JavacList.nil()
|
||||
|
||||
val size = valuePairs.size
|
||||
var result = JavacList.nil<T>()
|
||||
assert(size % 2 == 0)
|
||||
var index = 0
|
||||
while (index < size) {
|
||||
val key = valuePairs[index] as String
|
||||
val value = valuePairs[index + 1]
|
||||
f(key, value)?.let { result = result.prepend(it) }
|
||||
index += 2
|
||||
}
|
||||
return result.reverse()
|
||||
}
|
||||
|
||||
fun pairedListToMap(valuePairs: List<Any>?): Map<String, Any?> {
|
||||
val map = mutableMapOf<String, Any?>()
|
||||
|
||||
mapPairedValuesJList(valuePairs) { key, value ->
|
||||
map.put(key, value)
|
||||
}
|
||||
|
||||
return map
|
||||
}
|
||||
|
||||
operator fun <T : Any> JavacList<T>.plus(other: JavacList<T>): JavacList<T> {
|
||||
return this.appendList(other)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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.kapt3.base.util
|
||||
|
||||
inline fun <T> measureTimeMillisWithResult(block: () -> T) : Pair<Long, T> {
|
||||
val start = System.currentTimeMillis()
|
||||
val result = block()
|
||||
return Pair(System.currentTimeMillis() - start, result)
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
import org.jetbrains.kotlin.kapt3.base.KaptContext
|
||||
import org.jetbrains.kotlin.kapt3.base.KaptPaths
|
||||
import org.jetbrains.kotlin.kapt3.base.doAnnotationProcessing
|
||||
import org.jetbrains.kotlin.kapt3.base.util.KaptBaseError
|
||||
import org.jetbrains.kotlin.kapt3.base.util.WriterBackedKaptLogger
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import javax.annotation.processing.AbstractProcessor
|
||||
import javax.annotation.processing.Processor
|
||||
import javax.annotation.processing.RoundEnvironment
|
||||
import javax.lang.model.element.TypeElement
|
||||
|
||||
class JavaKaptContextTest {
|
||||
companion object {
|
||||
private val TEST_DATA_DIR = File("plugins/kapt3/kapt3-base/testData/runner")
|
||||
|
||||
fun simpleProcessor() = object : AbstractProcessor() {
|
||||
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
|
||||
for (annotation in annotations) {
|
||||
val annotationName = annotation.simpleName.toString()
|
||||
val annotatedElements = roundEnv.getElementsAnnotatedWith(annotation)
|
||||
|
||||
for (annotatedElement in annotatedElements) {
|
||||
val generatedClassName = annotatedElement.simpleName.toString().capitalize() + annotationName.capitalize()
|
||||
val file = processingEnv.filer.createSourceFile("generated." + generatedClassName)
|
||||
file.openWriter().use {
|
||||
it.write("""
|
||||
package generated;
|
||||
class $generatedClassName {}
|
||||
""".trimIndent())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getSupportedAnnotationTypes() = setOf("test.MyAnnotation")
|
||||
}
|
||||
}
|
||||
|
||||
private fun doAnnotationProcessing(javaSourceFile: File, processor: Processor, outputDir: File) {
|
||||
KaptContext(
|
||||
KaptPaths(
|
||||
projectBaseDir = javaSourceFile.parentFile,
|
||||
compileClasspath = emptyList(),
|
||||
annotationProcessingClasspath = emptyList(),
|
||||
javaSourceRoots = emptyList(),
|
||||
sourcesOutputDir = outputDir,
|
||||
classFilesOutputDir = outputDir,
|
||||
stubsOutputDir = outputDir,
|
||||
incrementalDataOutputDir = outputDir
|
||||
),
|
||||
withJdk = true,
|
||||
logger = WriterBackedKaptLogger(isVerbose = true),
|
||||
mapDiagnosticLocations = true,
|
||||
processorOptions = emptyMap()
|
||||
).doAnnotationProcessing(listOf(javaSourceFile), listOf(processor))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSimple() {
|
||||
val sourceOutputDir = Files.createTempDirectory("kaptRunner").toFile()
|
||||
try {
|
||||
doAnnotationProcessing(File(TEST_DATA_DIR, "Simple.java"), simpleProcessor(), sourceOutputDir)
|
||||
val myMethodFile = File(sourceOutputDir, "generated/MyMethodMyAnnotation.java")
|
||||
assertTrue(myMethodFile.exists())
|
||||
} finally {
|
||||
sourceOutputDir.deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = KaptBaseError::class)
|
||||
fun testException() {
|
||||
val exceptionMessage = "Here we are!"
|
||||
|
||||
val processor = object : AbstractProcessor() {
|
||||
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
|
||||
throw RuntimeException(exceptionMessage)
|
||||
}
|
||||
|
||||
override fun getSupportedAnnotationTypes() = setOf("test.MyAnnotation")
|
||||
}
|
||||
|
||||
try {
|
||||
doAnnotationProcessing(File(TEST_DATA_DIR, "Simple.java"), processor, TEST_DATA_DIR)
|
||||
} catch (e: KaptBaseError) {
|
||||
assertEquals(KaptBaseError.Kind.EXCEPTION, e.kind)
|
||||
assertEquals("Here we are!", e.cause!!.message)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = KaptBaseError::class)
|
||||
fun testParsingError() {
|
||||
try {
|
||||
doAnnotationProcessing(File(TEST_DATA_DIR, "ParseError.java"), simpleProcessor(), TEST_DATA_DIR)
|
||||
} catch (e: KaptBaseError) {
|
||||
assertEquals(KaptBaseError.Kind.ERROR_RAISED, e.kind)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
BLAHBLAH
|
||||
@@ -0,0 +1,39 @@
|
||||
package test;
|
||||
|
||||
/**
|
||||
* KDoc comment.
|
||||
*/
|
||||
class Simple {
|
||||
@MyAnnotation
|
||||
void myMethod() {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
@interface MyAnnotation {
|
||||
|
||||
}
|
||||
|
||||
enum EnumClass {
|
||||
BLACK, WHITE
|
||||
}
|
||||
|
||||
|
||||
enum EnumClass2 {
|
||||
WHITE("A"), RED("B");
|
||||
|
||||
private final String blah;
|
||||
|
||||
EnumClass2(String blah) {
|
||||
this.blah = blah;
|
||||
}
|
||||
}
|
||||
|
||||
enum EnumClass3 {
|
||||
A {
|
||||
@Override
|
||||
void a() {}
|
||||
};
|
||||
|
||||
abstract void a();
|
||||
}
|
||||
Reference in New Issue
Block a user