From af57ab8119926d0e24c227379e76717e70cd9913 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Tue, 14 Nov 2017 00:00:34 +0900 Subject: [PATCH] Kapt: Fix remaining issues with Java 9 compatibility in converter, add Java 9 tests --- .../kotlin/kapt3/annotationProcessing.kt | 41 ++++++----- .../kotlin/kapt3/javac/KaptJavaLog.kt | 37 ++++++++-- .../kapt3/stubs/KaptStubLineInformation.kt | 3 +- .../kapt3/test/AbstractKotlinKapt3Test.kt | 68 +++++++++++++++++-- 4 files changed, 122 insertions(+), 27 deletions(-) diff --git a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/annotationProcessing.kt b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/annotationProcessing.kt index 7d3f865f7da..6d7ba4491aa 100644 --- a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/annotationProcessing.kt +++ b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/annotationProcessing.kt @@ -16,8 +16,7 @@ package org.jetbrains.kotlin.kapt3 -import com.sun.tools.javac.comp.CompileStates -import com.sun.tools.javac.file.JavacFileManager +import com.sun.tools.javac.comp.CompileStates.* import com.sun.tools.javac.main.JavaCompiler import com.sun.tools.javac.main.Option 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 java.io.File import javax.annotation.processing.Processor -import javax.tools.JavaFileManager import javax.tools.JavaFileObject import com.sun.tools.javac.util.List as JavacList @@ -71,23 +69,12 @@ fun KaptContext<*>.doAnnotationProcessing( compiler.initProcessAnnotations(processors) } - val javaFileObjects = fileManager.getJavaFileObjectsFromFiles(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) - } + val parsedJavaFiles = parseJavaFiles(javaSourceFiles) compilerAfterAP = try { javaLog.interceptorData.files = parsedJavaFiles.map { it.sourceFile to it }.toMap() val analyzedFiles = compiler.stopIfErrorOccurred( - CompileStates.CompileState.PARSE, compiler.enterTrees(parsedJavaFiles + additionalSources)) + CompileState.PARSE, compiler.enterTrees(parsedJavaFiles + additionalSources)) if (isJava9OrLater) { val processAnnotationsMethod = compiler.javaClass.getMethod("processAnnotations", JavacList::class.java) @@ -119,4 +106,26 @@ fun KaptContext<*>.doAnnotationProcessing( processingEnvironment.close() this@doAnnotationProcessing.close() } +} + +internal fun KaptContext<*>.parseJavaFiles(javaSourceFiles: List): JavacList { + val javaFileObjects = fileManager.getJavaFileObjectsFromFiles(javaSourceFiles) + + return compiler.stopIfErrorOccurred(CompileState.PARSE, + initModulesIfNeeded( + compiler.stopIfErrorOccurred(CompileState.PARSE, + compiler.parseFiles(javaFileObjects)))) +} + +private fun KaptContext<*>.initModulesIfNeeded(files: JavacList): JavacList { + 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) + } + + return files } \ No newline at end of file diff --git a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/javac/KaptJavaLog.kt b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/javac/KaptJavaLog.kt index b3fc13a8b5c..0e00bfdedb4 100644 --- a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/javac/KaptJavaLog.kt +++ b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/javac/KaptJavaLog.kt @@ -28,6 +28,7 @@ import org.jetbrains.kotlin.kapt3.KaptContext import org.jetbrains.kotlin.kapt3.stubs.KaptStubLineInformation import org.jetbrains.kotlin.kapt3.stubs.KotlinPosition import org.jetbrains.kotlin.kapt3.util.MessageCollectorBackedWriter +import org.jetbrains.kotlin.kapt3.util.isJava9OrLater import java.io.File import java.io.PrintWriter import javax.tools.JavaFileObject @@ -74,6 +75,8 @@ class KaptJavaLog( } override fun report(diagnostic: JCDiagnostic) { + System.err.println("Diagnostic code: ${diagnostic.code}") + if (diagnostic.type == JCDiagnostic.DiagnosticType.ERROR && diagnostic.code in IGNORED_DIAGNOSTICS) { return } @@ -102,7 +105,7 @@ class KaptJavaLog( val kotlinFile = kotlinPosition?.let { getKotlinSourceFile(it) } if (kotlinPosition != null && kotlinFile != null) { 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)) super.report(wrappedDiagnostic) @@ -126,9 +129,9 @@ class KaptJavaLog( val writer = when (diagnostic.type) { DiagnosticType.FRAGMENT, null -> kotlin.error("Invalid root diagnostic type: ${diagnostic.type}") - DiagnosticType.NOTE -> super.noticeWriter - DiagnosticType.WARNING -> super.warnWriter - DiagnosticType.ERROR -> super.errWriter + 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) @@ -211,10 +214,32 @@ class KaptJavaLog( } 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 { //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::class.java) + + errorMethod.invoke(this, JCDiagnostic.DiagnosticFlag.MANDATORY, source, pos, key, args) as JCDiagnostic + } + else { + this.error(source, pos, key, *args) + } } \ No newline at end of file diff --git a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/KaptStubLineInformation.kt b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/KaptStubLineInformation.kt index 3810bf6398f..b226fea8918 100644 --- a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/KaptStubLineInformation.kt +++ b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/KaptStubLineInformation.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.kapt3.stubs import com.sun.tools.javac.tree.JCTree import com.sun.tools.javac.tree.TreeScanner import org.jetbrains.kotlin.kapt3.stubs.KaptLineMappingCollector.Companion.KAPT_SIGNATURE_ANNOTATION_FQNAME +import org.jetbrains.kotlin.kapt3.util.getPackageNameJava9Aware class KaptStubLineInformation { private val offsets = mutableMapOf() @@ -84,7 +85,7 @@ class KaptStubLineInformation { // 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.packageName?.toString()?.replace('.', '/') + val packageName = file.getPackageNameJava9Aware()?.toString()?.replace('.', '/') return if (packageName == null) simpleDescriptor else "$packageName/$simpleDescriptor" } diff --git a/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/AbstractKotlinKapt3Test.kt b/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/AbstractKotlinKapt3Test.kt index 126b3d573c8..ee2f87eb43a 100644 --- a/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/AbstractKotlinKapt3Test.kt +++ b/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/AbstractKotlinKapt3Test.kt @@ -34,8 +34,10 @@ import org.jetbrains.kotlin.kapt3.KaptContext import org.jetbrains.kotlin.kapt3.doAnnotationProcessing import org.jetbrains.kotlin.kapt3.javac.KaptJavaFileObject 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.util.KaptLogger +import org.jetbrains.kotlin.kapt3.util.isJava9OrLater import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension import org.jetbrains.kotlin.resolve.jvm.extensions.PartialAnalysisHandlerExtension 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.addToStdlib.firstIsInstance import java.io.File +import java.net.URL +import java.net.URLClassLoader import java.nio.file.Files import java.util.* +import java.util.concurrent.TimeUnit import com.sun.tools.javac.util.List as JavacList abstract class AbstractKotlinKapt3Test : CodegenTestCase() { @@ -88,6 +93,8 @@ abstract class AbstractKotlinKapt3Test : CodegenTestCase() { try { check(kaptContext, javaFiles, txtFile, wholeFile) + } catch (e: Throwable) { + throw RuntimeException(e) } finally { javaFiles.forEach { it.delete() } tempFiles.forEach { it.delete() } @@ -115,7 +122,8 @@ abstract class AbstractKotlinKapt3Test : CodegenTestCase() { kaptContext.compiler.log.nerrors = 0 try { - val parsedJavaFiles = kaptContext.compiler.parseFiles(kaptContext.fileManager.getJavaFileObjectsFromFiles(allJavaFiles)) + val parsedJavaFiles = kaptContext.parseJavaFiles(allJavaFiles) + for (file in parsedJavaFiles) { // 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. @@ -135,18 +143,70 @@ abstract class AbstractKotlinKapt3Test : CodegenTestCase() { wholeFile: File) } -abstract class AbstractClassFileToSourceStubConverterTest : AbstractKotlinKapt3Test() { - internal companion object { +open class AbstractClassFileToSourceStubConverterTest : AbstractKotlinKapt3Test() { + companion object { 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 KAPT_METADATA_REGEX = "@kapt\\.internal\\.KaptMetadata\\((value = )?\"[^(].*?\"\\)".toRegex() 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()") .replace(KAPT_METADATA_REGEX, "@kapt.internal.KaptMetadata()") } + + @JvmStatic + fun main(args: Array) { + 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 = when (classLoader) { + is URLClassLoader -> classLoader.urLs.asList() + collectClasspath(classLoader.parent) + is ClassLoader -> collectClasspath(classLoader.parent) + else -> emptyList() } override fun check(kaptContext: KaptContext, javaFiles: List, txtFile: File, wholeFile: File) {