Kapt: Fix remaining issues with Java 9 compatibility in converter, add Java 9 tests

This commit is contained in:
Yan Zhulanow
2017-11-14 00:00:34 +09:00
parent dde87ae1f9
commit af57ab8119
4 changed files with 122 additions and 27 deletions
@@ -16,8 +16,7 @@
package org.jetbrains.kotlin.kapt3 package org.jetbrains.kotlin.kapt3
import com.sun.tools.javac.comp.CompileStates import com.sun.tools.javac.comp.CompileStates.*
import com.sun.tools.javac.file.JavacFileManager
import com.sun.tools.javac.main.JavaCompiler import com.sun.tools.javac.main.JavaCompiler
import com.sun.tools.javac.main.Option import com.sun.tools.javac.main.Option
import com.sun.tools.javac.processing.AnnotationProcessingError import com.sun.tools.javac.processing.AnnotationProcessingError
@@ -29,7 +28,6 @@ import org.jetbrains.kotlin.kapt3.util.isJava9OrLater
import org.jetbrains.kotlin.kapt3.util.putJavacOption 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.JavaFileObject import javax.tools.JavaFileObject
import com.sun.tools.javac.util.List as JavacList import com.sun.tools.javac.util.List as JavacList
@@ -71,23 +69,12 @@ fun KaptContext<*>.doAnnotationProcessing(
compiler.initProcessAnnotations(processors) compiler.initProcessAnnotations(processors)
} }
val javaFileObjects = fileManager.getJavaFileObjectsFromFiles(javaSourceFiles) val parsedJavaFiles = parseJavaFiles(javaSourceFiles)
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)) CompileState.PARSE, compiler.enterTrees(parsedJavaFiles + additionalSources))
if (isJava9OrLater) { if (isJava9OrLater) {
val processAnnotationsMethod = compiler.javaClass.getMethod("processAnnotations", JavacList::class.java) val processAnnotationsMethod = compiler.javaClass.getMethod("processAnnotations", JavacList::class.java)
@@ -119,4 +106,26 @@ fun KaptContext<*>.doAnnotationProcessing(
processingEnvironment.close() processingEnvironment.close()
this@doAnnotationProcessing.close() this@doAnnotationProcessing.close()
} }
}
internal 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
} }
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.kapt3.KaptContext
import org.jetbrains.kotlin.kapt3.stubs.KaptStubLineInformation import org.jetbrains.kotlin.kapt3.stubs.KaptStubLineInformation
import org.jetbrains.kotlin.kapt3.stubs.KotlinPosition import org.jetbrains.kotlin.kapt3.stubs.KotlinPosition
import org.jetbrains.kotlin.kapt3.util.MessageCollectorBackedWriter import org.jetbrains.kotlin.kapt3.util.MessageCollectorBackedWriter
import org.jetbrains.kotlin.kapt3.util.isJava9OrLater
import java.io.File import java.io.File
import java.io.PrintWriter import java.io.PrintWriter
import javax.tools.JavaFileObject import javax.tools.JavaFileObject
@@ -74,6 +75,8 @@ class KaptJavaLog(
} }
override fun report(diagnostic: JCDiagnostic) { override fun report(diagnostic: JCDiagnostic) {
System.err.println("Diagnostic code: ${diagnostic.code}")
if (diagnostic.type == JCDiagnostic.DiagnosticType.ERROR && diagnostic.code in IGNORED_DIAGNOSTICS) { if (diagnostic.type == JCDiagnostic.DiagnosticType.ERROR && diagnostic.code in IGNORED_DIAGNOSTICS) {
return return
} }
@@ -102,7 +105,7 @@ class KaptJavaLog(
val kotlinFile = kotlinPosition?.let { getKotlinSourceFile(it) } val kotlinFile = kotlinPosition?.let { getKotlinSourceFile(it) }
if (kotlinPosition != null && kotlinFile != null) { if (kotlinPosition != null && kotlinFile != null) {
val locationMessage = "$KOTLIN_LOCATION_PREFIX${kotlinFile.absolutePath}: (${kotlinPosition.line}, ${kotlinPosition.column})" val locationMessage = "$KOTLIN_LOCATION_PREFIX${kotlinFile.absolutePath}: (${kotlinPosition.line}, ${kotlinPosition.column})"
val locationDiagnostic = diagnosticFactory.note("proc.messager", locationMessage) val locationDiagnostic = diagnosticFactory.note(null as DiagnosticSource?, null, "proc.messager", locationMessage)
val wrappedDiagnostic = JCDiagnostic.MultilineDiagnostic(diagnostic, JavacList.of(locationDiagnostic)) val wrappedDiagnostic = JCDiagnostic.MultilineDiagnostic(diagnostic, JavacList.of(locationDiagnostic))
super.report(wrappedDiagnostic) super.report(wrappedDiagnostic)
@@ -126,9 +129,9 @@ class KaptJavaLog(
val writer = when (diagnostic.type) { val writer = when (diagnostic.type) {
DiagnosticType.FRAGMENT, null -> kotlin.error("Invalid root diagnostic type: ${diagnostic.type}") DiagnosticType.FRAGMENT, null -> kotlin.error("Invalid root diagnostic type: ${diagnostic.type}")
DiagnosticType.NOTE -> super.noticeWriter DiagnosticType.NOTE -> super.getWriter(WriterKind.NOTICE)
DiagnosticType.WARNING -> super.warnWriter DiagnosticType.WARNING -> super.getWriter(WriterKind.WARNING)
DiagnosticType.ERROR -> super.errWriter DiagnosticType.ERROR -> super.getWriter(WriterKind.ERROR)
} }
val formattedMessage = diagnosticFormatter.format(diagnostic, javacMessages.currentLocale) val formattedMessage = diagnosticFormatter.format(diagnostic, javacMessages.currentLocale)
@@ -211,10 +214,32 @@ class KaptJavaLog(
} }
fun KaptContext<*>.kaptError(text: String): JCDiagnostic { fun KaptContext<*>.kaptError(text: String): JCDiagnostic {
return JCDiagnostic.Factory.instance(context).error(null, null, "proc.messager", text) return JCDiagnostic.Factory.instance(context).errorJava9Aware(null, null, "proc.messager", text)
} }
fun KaptContext<*>.kaptError(text: String, target: PsiElement): JCDiagnostic { fun KaptContext<*>.kaptError(text: String, target: PsiElement): JCDiagnostic {
//TODO provide source binding //TODO provide source binding
return JCDiagnostic.Factory.instance(context).error(null, null, "proc.messager", text) 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)
}
} }
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.kapt3.stubs
import com.sun.tools.javac.tree.JCTree import com.sun.tools.javac.tree.JCTree
import com.sun.tools.javac.tree.TreeScanner import com.sun.tools.javac.tree.TreeScanner
import org.jetbrains.kotlin.kapt3.stubs.KaptLineMappingCollector.Companion.KAPT_SIGNATURE_ANNOTATION_FQNAME import org.jetbrains.kotlin.kapt3.stubs.KaptLineMappingCollector.Companion.KAPT_SIGNATURE_ANNOTATION_FQNAME
import org.jetbrains.kotlin.kapt3.util.getPackageNameJava9Aware
class KaptStubLineInformation { class KaptStubLineInformation {
private val offsets = mutableMapOf<JCTree.JCCompilationUnit, KaptLineMappingCollector.LineInfo>() private val offsets = mutableMapOf<JCTree.JCCompilationUnit, KaptLineMappingCollector.LineInfo>()
@@ -84,7 +85,7 @@ class KaptStubLineInformation {
// Unfortunately, we have to do this the hard way, as symbols may be not available yet // 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()") // (for instance, if this code is called inside the "enterTrees()")
val simpleDescriptor = getFqName(declaration, file, "") val simpleDescriptor = getFqName(declaration, file, "")
val packageName = file.packageName?.toString()?.replace('.', '/') val packageName = file.getPackageNameJava9Aware()?.toString()?.replace('.', '/')
return if (packageName == null) simpleDescriptor else "$packageName/$simpleDescriptor" return if (packageName == null) simpleDescriptor else "$packageName/$simpleDescriptor"
} }
@@ -34,8 +34,10 @@ import org.jetbrains.kotlin.kapt3.KaptContext
import org.jetbrains.kotlin.kapt3.doAnnotationProcessing import org.jetbrains.kotlin.kapt3.doAnnotationProcessing
import org.jetbrains.kotlin.kapt3.javac.KaptJavaFileObject import org.jetbrains.kotlin.kapt3.javac.KaptJavaFileObject
import org.jetbrains.kotlin.kapt3.javac.KaptJavaLog import org.jetbrains.kotlin.kapt3.javac.KaptJavaLog
import org.jetbrains.kotlin.kapt3.parseJavaFiles
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.isJava9OrLater
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
import org.jetbrains.kotlin.resolve.jvm.extensions.PartialAnalysisHandlerExtension import org.jetbrains.kotlin.resolve.jvm.extensions.PartialAnalysisHandlerExtension
import org.jetbrains.kotlin.test.ConfigurationKind import org.jetbrains.kotlin.test.ConfigurationKind
@@ -44,8 +46,11 @@ import org.jetbrains.kotlin.test.util.trimTrailingWhitespacesAndAddNewlineAtEOF
import org.jetbrains.kotlin.utils.PathUtil import org.jetbrains.kotlin.utils.PathUtil
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import java.io.File import java.io.File
import java.net.URL
import java.net.URLClassLoader
import java.nio.file.Files import java.nio.file.Files
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit
import com.sun.tools.javac.util.List as JavacList import com.sun.tools.javac.util.List as JavacList
abstract class AbstractKotlinKapt3Test : CodegenTestCase() { abstract class AbstractKotlinKapt3Test : CodegenTestCase() {
@@ -88,6 +93,8 @@ abstract class AbstractKotlinKapt3Test : CodegenTestCase() {
try { try {
check(kaptContext, javaFiles, txtFile, wholeFile) check(kaptContext, javaFiles, txtFile, wholeFile)
} catch (e: Throwable) {
throw RuntimeException(e)
} finally { } finally {
javaFiles.forEach { it.delete() } javaFiles.forEach { it.delete() }
tempFiles.forEach { it.delete() } tempFiles.forEach { it.delete() }
@@ -115,7 +122,8 @@ abstract class AbstractKotlinKapt3Test : CodegenTestCase() {
kaptContext.compiler.log.nerrors = 0 kaptContext.compiler.log.nerrors = 0
try { try {
val parsedJavaFiles = kaptContext.compiler.parseFiles(kaptContext.fileManager.getJavaFileObjectsFromFiles(allJavaFiles)) val parsedJavaFiles = kaptContext.parseJavaFiles(allJavaFiles)
for (file in parsedJavaFiles) { for (file in parsedJavaFiles) {
// By default, JavaFileObject.getName() returns the absolute path to the file. // By default, JavaFileObject.getName() returns the absolute path to the file.
// In our test, such a path will be temporary, so the comparision against it will lead to flaky tests. // In our test, such a path will be temporary, so the comparision against it will lead to flaky tests.
@@ -135,18 +143,70 @@ abstract class AbstractKotlinKapt3Test : CodegenTestCase() {
wholeFile: File) wholeFile: File)
} }
abstract class AbstractClassFileToSourceStubConverterTest : AbstractKotlinKapt3Test() { open class AbstractClassFileToSourceStubConverterTest : AbstractKotlinKapt3Test() {
internal companion object { companion object {
private val KOTLIN_METADATA_GROUP = "[a-z0-9]+ = (\\{.+?\\}|[0-9]+)" private val KOTLIN_METADATA_GROUP = "[a-z0-9]+ = (\\{.+?\\}|[0-9]+)"
private val KOTLIN_METADATA_REGEX = "@kotlin\\.Metadata\\(($KOTLIN_METADATA_GROUP)(, $KOTLIN_METADATA_GROUP)*\\)".toRegex() private val KOTLIN_METADATA_REGEX = "@kotlin\\.Metadata\\(($KOTLIN_METADATA_GROUP)(, $KOTLIN_METADATA_GROUP)*\\)".toRegex()
private val KAPT_METADATA_REGEX = "@kapt\\.internal\\.KaptMetadata\\((value = )?\"[^(].*?\"\\)".toRegex() private val KAPT_METADATA_REGEX = "@kapt\\.internal\\.KaptMetadata\\((value = )?\"[^(].*?\"\\)".toRegex()
private val EXPECTED_ERROR = "EXPECTED_ERROR" private val EXPECTED_ERROR = "EXPECTED_ERROR"
fun removeMetadataAnnotationContents(s: String): String { internal fun removeMetadataAnnotationContents(s: String): String {
return s.replace(KOTLIN_METADATA_REGEX, "@kotlin.Metadata()") return s.replace(KOTLIN_METADATA_REGEX, "@kotlin.Metadata()")
.replace(KAPT_METADATA_REGEX, "@kapt.internal.KaptMetadata()") .replace(KAPT_METADATA_REGEX, "@kapt.internal.KaptMetadata()")
} }
@JvmStatic
fun main(args: Array<String>) {
if (args.isEmpty()) error("1 argument expected, 0 passed")
AbstractClassFileToSourceStubConverterTest().doTest(args[0])
}
}
// This is to suppress "AssertionFailedError: No tests found"
fun testSuppressWarning() {}
override fun doTest(filePath: String) {
super.doTest(filePath)
if (!isJava9OrLater) {
doTestWithJdk9(filePath)
}
}
private fun doTestWithJdk9(filePath: String) {
val jdk9Home = KotlinTestUtils.getJdk9HomeIfPossible() ?: run {
println("JDK9 not found, the test was skipped")
return
}
val javaExe = File(jdk9Home, "bin/java.exe").takeIf { it.exists() } ?: File(jdk9Home, "bin/java")
assert(javaExe.exists()) { "Can't find 'java' executable in $jdk9Home" }
val currentJavaHome = System.getProperty("java.home")
val classpath = collectClasspath(AbstractClassFileToSourceStubConverterTest::class.java.classLoader)
.filter { !it.path.startsWith(currentJavaHome) }
val process = ProcessBuilder(
javaExe.absolutePath,
"--illegal-access=warn",
"-ea",
"-classpath",
classpath.joinToString(File.pathSeparator),
AbstractClassFileToSourceStubConverterTest::class.java.name,
filePath
).inheritIO().start()
process.waitFor(3, TimeUnit.MINUTES)
if (process.exitValue() != 0) {
throw AssertionError("Java 9 test process exited with exit code ${process.exitValue()} \n")
}
}
private fun collectClasspath(classLoader: ClassLoader?): List<URL> = when (classLoader) {
is URLClassLoader -> classLoader.urLs.asList() + collectClasspath(classLoader.parent)
is ClassLoader -> collectClasspath(classLoader.parent)
else -> emptyList()
} }
override fun check(kaptContext: KaptContext<GenerationState>, javaFiles: List<File>, txtFile: File, wholeFile: File) { override fun check(kaptContext: KaptContext<GenerationState>, javaFiles: List<File>, txtFile: File, wholeFile: File) {