Kapt: Support Java 9 in compiler plugin

This commit is contained in:
Yan Zhulanow
2017-10-17 20:54:09 +03:00
parent e97b3ed324
commit c6eab6f72e
5 changed files with 107 additions and 22 deletions
@@ -36,6 +36,7 @@ import org.jetbrains.kotlin.kapt3.AptMode.*
import org.jetbrains.kotlin.kapt3.diagnostic.KaptError import org.jetbrains.kotlin.kapt3.diagnostic.KaptError
import org.jetbrains.kotlin.kapt3.stubs.ClassFileToSourceStubConverter import org.jetbrains.kotlin.kapt3.stubs.ClassFileToSourceStubConverter
import org.jetbrains.kotlin.kapt3.util.KaptLogger import org.jetbrains.kotlin.kapt3.util.KaptLogger
import org.jetbrains.kotlin.kapt3.util.getPackageNameJava9Aware
import org.jetbrains.kotlin.modules.TargetId import org.jetbrains.kotlin.modules.TargetId
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
@@ -271,7 +272,7 @@ abstract class AbstractKapt3Extension(
for (stub in stubs) { for (stub in stubs) {
val className = (stub.defs.first { it is JCTree.JCClassDecl } as JCTree.JCClassDecl).simpleName.toString() val className = (stub.defs.first { it is JCTree.JCClassDecl } as JCTree.JCClassDecl).simpleName.toString()
val packageName = stub.packageName?.toString() ?: "" val packageName = stub.getPackageNameJava9Aware()?.toString() ?: ""
val packageDir = if (packageName.isEmpty()) stubsOutputDir else File(stubsOutputDir, packageName.replace('.', '/')) val packageDir = if (packageName.isEmpty()) stubsOutputDir else File(stubsOutputDir, packageName.replace('.', '/'))
packageDir.mkdirs() packageDir.mkdirs()
File(packageDir, className + ".java").writeText(stub.toString()) File(packageDir, className + ".java").writeText(stub.toString())
@@ -25,9 +25,12 @@ import com.sun.tools.javac.processing.JavacFiler
import com.sun.tools.javac.processing.JavacProcessingEnvironment import com.sun.tools.javac.processing.JavacProcessingEnvironment
import com.sun.tools.javac.tree.JCTree import com.sun.tools.javac.tree.JCTree
import org.jetbrains.kotlin.kapt3.diagnostic.KaptError import org.jetbrains.kotlin.kapt3.diagnostic.KaptError
import org.jetbrains.kotlin.kapt3.util.isJava9OrLater
import org.jetbrains.kotlin.kapt3.util.putJavacOption
import java.io.File import java.io.File
import javax.annotation.processing.Processor import javax.annotation.processing.Processor
import javax.tools.JavaFileManager import javax.tools.JavaFileManager
import javax.tools.JavaFileObject
import com.sun.tools.javac.util.List as JavacList import com.sun.tools.javac.util.List as JavacList
fun KaptContext<*>.doAnnotationProcessing( fun KaptContext<*>.doAnnotationProcessing(
@@ -45,32 +48,55 @@ fun KaptContext<*>.doAnnotationProcessing(
put(Option.PROC, "only") // Only process annotations put(Option.PROC, "only") // Only process annotations
if (!withJdk) { if (!withJdk) {
put(Option.BOOTCLASSPATH, "") // No boot classpath putJavacOption("BOOTCLASSPATH", "BOOT_CLASS_PATH", "") // No boot classpath
} }
put(Option.CLASSPATH, compileClasspath.joinToString(File.pathSeparator) { it.canonicalPath }) putJavacOption("CLASSPATH", "CLASS_PATH", compileClasspath.joinToString(File.pathSeparator) { it.canonicalPath })
put(Option.PROCESSORPATH, annotationProcessingClasspath.joinToString(File.pathSeparator) { it.canonicalPath }) putJavacOption("PROCESSORPATH", "PROCESSOR_PATH", annotationProcessingClasspath.joinToString(File.pathSeparator) { it.canonicalPath })
put(Option.PROCESSOR, annotationProcessors) put(Option.PROCESSOR, annotationProcessors)
put(Option.S, sourcesOutputDir.canonicalPath) put(Option.S, sourcesOutputDir.canonicalPath)
put(Option.D, classesOutputDir.canonicalPath) put(Option.D, classesOutputDir.canonicalPath)
put(Option.ENCODING, "UTF-8") put(Option.ENCODING, "UTF-8")
} }
val fileManager = context.get(JavaFileManager::class.java) as JavacFileManager
val processingEnvironment = JavacProcessingEnvironment.instance(context) val processingEnvironment = JavacProcessingEnvironment.instance(context)
val compilerAfterAP: JavaCompiler val compilerAfterAP: JavaCompiler
try { try {
compiler.initProcessAnnotations(processors) if (isJava9OrLater) {
val initProcessAnnotationsMethod = JavaCompiler::class.java.declaredMethods.single { it.name == "initProcessAnnotations" }
initProcessAnnotationsMethod.invoke(compiler, processors, emptyList<JavaFileObject>(), emptyList<String>())
}
else {
compiler.initProcessAnnotations(processors)
}
val javaFileObjects = fileManager.getJavaFileObjectsFromFiles(javaSourceFiles) val javaFileObjects = fileManager.getJavaFileObjectsFromFiles(javaSourceFiles)
val parsedJavaFiles = compiler.parseFiles(javaFileObjects) var parsedJavaFiles = compiler.stopIfErrorOccurred(
CompileStates.CompileState.PARSE, compiler.parseFiles(javaFileObjects))
if (isJava9OrLater) {
val initModulesMethod = compiler.javaClass.getMethod("initModules", JavacList::class.java)
@Suppress("UNCHECKED_CAST")
parsedJavaFiles = compiler.stopIfErrorOccurred(
CompileStates.CompileState.PARSE,
initModulesMethod.invoke(compiler, parsedJavaFiles) as JavacList<JCTree.JCCompilationUnit>)
}
compilerAfterAP = try { compilerAfterAP = try {
javaLog.interceptorData.files = parsedJavaFiles.map { it.sourceFile to it }.toMap() javaLog.interceptorData.files = parsedJavaFiles.map { it.sourceFile to it }.toMap()
val analyzedFiles = compiler.stopIfErrorOccurred( val analyzedFiles = compiler.stopIfErrorOccurred(
CompileStates.CompileState.PARSE, compiler.enterTrees(parsedJavaFiles + additionalSources)) CompileStates.CompileState.PARSE, compiler.enterTrees(parsedJavaFiles + additionalSources))
compiler.processAnnotations(analyzedFiles)
if (isJava9OrLater) {
val processAnnotationsMethod = compiler.javaClass.getMethod("processAnnotations", JavacList::class.java)
processAnnotationsMethod.invoke(compiler, analyzedFiles)
compiler
}
else {
compiler.processAnnotations(analyzedFiles)
}
} catch (e: AnnotationProcessingError) { } catch (e: AnnotationProcessingError) {
throw KaptError(KaptError.Kind.EXCEPTION, e.cause ?: e) throw KaptError(KaptError.Kind.EXCEPTION, e.cause ?: e)
} }
@@ -16,10 +16,8 @@
package org.jetbrains.kotlin.kapt3.javac package org.jetbrains.kotlin.kapt3.javac
import com.sun.tools.javac.file.BaseFileObject
import com.sun.tools.javac.file.JavacFileManager
import com.sun.tools.javac.tree.JCTree import com.sun.tools.javac.tree.JCTree
import java.io.* import org.jetbrains.kotlin.kapt3.util.getPackageNameJava9Aware
import java.net.URI import java.net.URI
import java.net.URL import java.net.URL
import javax.lang.model.element.Modifier import javax.lang.model.element.Modifier
@@ -29,12 +27,9 @@ import javax.tools.JavaFileObject
class KaptJavaFileObject( class KaptJavaFileObject(
val compilationUnit: JCTree.JCCompilationUnit, val compilationUnit: JCTree.JCCompilationUnit,
val clazz: JCTree.JCClassDecl, val clazz: JCTree.JCClassDecl,
fileManager: JavacFileManager,
val timestamp: Long = System.currentTimeMillis() val timestamp: Long = System.currentTimeMillis()
) : BaseFileObject(fileManager) { ) : JavaFileObject {
override fun getShortName() = clazz.simpleName.toString() override fun toString() = "${javaClass.simpleName}[$name]"
override fun inferBinaryName(path: MutableIterable<File>?) = throw UnsupportedOperationException()
override fun isNameCompatible(simpleName: String?, kind: JavaFileObject.Kind?): Boolean { override fun isNameCompatible(simpleName: String?, kind: JavaFileObject.Kind?): Boolean {
if (simpleName == null || kind == null) return false if (simpleName == null || kind == null) return false
@@ -44,7 +39,7 @@ class KaptJavaFileObject(
override fun getKind() = JavaFileObject.Kind.SOURCE override fun getKind() = JavaFileObject.Kind.SOURCE
override fun getName(): String { override fun getName(): String {
val packageName = compilationUnit.packageName val packageName = compilationUnit.getPackageNameJava9Aware()
if (packageName == null || packageName.toString() == "") { if (packageName == null || packageName.toString() == "") {
return clazz.name.toString() + ".java" return clazz.name.toString() + ".java"
} }
@@ -120,8 +120,9 @@ class ClassFileToSourceStubConverter(
JavacList.nil(), JavacList.nil(),
JavacList.nil()) JavacList.nil())
val topLevel = treeMaker.TopLevel(JavacList.nil(), treeMaker.SimpleName("error"), JavacList.of(nonExistentClass)) val topLevel = treeMaker.TopLevelJava9Aware(treeMaker.SimpleName("error"), JavacList.of(nonExistentClass))
topLevel.sourcefile = KaptJavaFileObject(topLevel, nonExistentClass, fileManager)
topLevel.sourcefile = KaptJavaFileObject(topLevel, nonExistentClass)
// We basically don't need to add binding for NonExistentClass // We basically don't need to add binding for NonExistentClass
return topLevel return topLevel
@@ -135,7 +136,6 @@ class ClassFileToSourceStubConverter(
// Nested classes will be processed during the outer classes conversion // Nested classes will be processed during the outer classes conversion
if ((descriptor as? ClassDescriptor)?.isNested ?: false) return null if ((descriptor as? ClassDescriptor)?.isNested ?: false) return null
val packageAnnotations = JavacList.nil<JCAnnotation>()
val packageName = ktFile.packageFqName.asString() val packageName = ktFile.packageFqName.asString()
val packageClause = if (packageName.isEmpty()) null else treeMaker.FqName(packageName) val packageClause = if (packageName.isEmpty()) null else treeMaker.FqName(packageName)
@@ -144,9 +144,9 @@ class ClassFileToSourceStubConverter(
val imports = if (correctErrorTypes) convertImports(ktFile, classDeclaration) else JavacList.nil() val imports = if (correctErrorTypes) convertImports(ktFile, classDeclaration) else JavacList.nil()
val classes = JavacList.of<JCTree>(classDeclaration) val classes = JavacList.of<JCTree>(classDeclaration)
val topLevel = treeMaker.TopLevel(packageAnnotations, packageClause, imports + classes) val topLevel = treeMaker.TopLevelJava9Aware(packageClause, imports + classes)
KaptJavaFileObject(topLevel, classDeclaration, fileManager).apply { KaptJavaFileObject(topLevel, classDeclaration).apply {
topLevel.sourcefile = this topLevel.sourcefile = this
_bindings[clazz.name] = this _bindings[clazz.name] = this
} }
@@ -0,0 +1,63 @@
/*
* 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.util
import com.intellij.openapi.util.SystemInfo
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.plus
internal val isJava9OrLater: Boolean
get() = SystemInfo.isJavaVersionAtLeast("9")
internal fun Options.putJavacOption(jdk8Name: String, jdk9Name: String, value: String) {
val option = if (isJava9OrLater) {
Option.valueOf(jdk9Name)
}
else {
Option.valueOf(jdk8Name)
}
put(option, value)
}
internal 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)
}
}
internal fun JCTree.JCCompilationUnit.getPackageNameJava9Aware(): JCTree? {
return if (isJava9OrLater) {
JCTree.JCCompilationUnit::class.java.getDeclaredMethod("getPackageName").invoke(this) as JCTree?
}
else {
this.packageName
}
}