From 1760befa37f297189b7b8ae0359e5ef35091cbc0 Mon Sep 17 00:00:00 2001 From: Mikhael Bogdanov Date: Fri, 6 Aug 2021 15:29:49 +0200 Subject: [PATCH] [KAPT] Add experimental JDK 17 support #KT-47583 Fixed --- .../kotlin/kapt3/base/KaptContext.kt | 31 ++- .../kotlin/kapt3/base/javac/KaptJavaLog.kt | 27 ++- .../kotlin/kapt3/base/javac/KaptJavaLog17.kt | 210 ++++++++++++++++++ .../kapt3/base/util/WriterBackedKaptLogger.kt | 2 +- .../kotlin/kapt3/base/util/java9Utils.kt | 1 + .../kapt3/test/AbstractKotlinKapt3Test.kt | 4 +- 6 files changed, 253 insertions(+), 22 deletions(-) create mode 100644 plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/javac/KaptJavaLog17.kt diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/KaptContext.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/KaptContext.kt index 8d8161af6b9..5cf85417ec6 100644 --- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/KaptContext.kt +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/KaptContext.kt @@ -15,10 +15,9 @@ import org.jetbrains.kotlin.base.kapt3.KaptFlag import org.jetbrains.kotlin.base.kapt3.KaptOptions import org.jetbrains.kotlin.kapt3.base.incremental.JavaClassCacheManager import org.jetbrains.kotlin.kapt3.base.incremental.SourcesToReprocess -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.javac.* import org.jetbrains.kotlin.kapt3.base.util.KaptLogger +import org.jetbrains.kotlin.kapt3.base.util.isJava17OrLater import org.jetbrains.kotlin.kapt3.base.util.isJava9OrLater import org.jetbrains.kotlin.kapt3.base.util.putJavacOption import java.io.Closeable @@ -30,7 +29,7 @@ open class KaptContext(val options: KaptOptions, val withJdk: Boolean, val logge val compiler: KaptJavaCompiler val fileManager: KaptJavaFileManager private val javacOptions: Options - val javaLog: KaptJavaLog + val javaLog: KaptJavaLogBase val cacheManager: JavaClassCacheManager? val sourcesToReprocess: SourcesToReprocess @@ -38,12 +37,24 @@ open class KaptContext(val options: KaptOptions, val withJdk: Boolean, val logge protected open fun preregisterTreeMaker(context: Context) {} private fun preregisterLog(context: Context) { - val interceptorData = KaptJavaLog.DiagnosticInterceptorData() + val interceptorData = KaptJavaLogBase.DiagnosticInterceptorData() context.put(Log.logKey, Context.Factory { newContext -> - KaptJavaLog( - options.projectBaseDir, newContext, logger.errorWriter, logger.warnWriter, logger.infoWriter, - interceptorData, options[KaptFlag.MAP_DIAGNOSTIC_LOCATIONS] - ) + if (isJava17OrLater()) { + newContext.put(Log.outKey, logger.infoWriter) + val errKey = (Log::class.java.fields.firstOrNull() { it.name == "errKey" } + ?: error("Can't find errKey field in Log.class")).get(null) + @Suppress("UNCHECKED_CAST") + newContext.put(errKey as Context.Key, logger.errorWriter) + KaptJavaLog17( + options.projectBaseDir, newContext, + interceptorData, options[KaptFlag.MAP_DIAGNOSTIC_LOCATIONS] + ) + } else { + KaptJavaLog( + options.projectBaseDir, newContext, logger.errorWriter, logger.warnWriter, logger.infoWriter, + interceptorData, options[KaptFlag.MAP_DIAGNOSTIC_LOCATIONS] + ) + } }) } @@ -154,7 +165,7 @@ open class KaptContext(val options: KaptOptions, val withJdk: Boolean, val logge ClassReader.instance(context).saveParameterNames = true - javaLog = compiler.log as KaptJavaLog + javaLog = compiler.log as KaptJavaLogBase } override fun close() { diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/javac/KaptJavaLog.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/javac/KaptJavaLog.kt index 0036ea7b229..86f9c41c056 100644 --- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/javac/KaptJavaLog.kt +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/javac/KaptJavaLog.kt @@ -17,7 +17,20 @@ 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 + +interface KaptJavaLogBase { + val interceptorData: DiagnosticInterceptorData + + val reportedDiagnostics: List + + fun flush(kind: Log.WriterKind?) + + fun flush() + + class DiagnosticInterceptorData { + var files: Map = emptyMap() + } +} class KaptJavaLog( private val projectBaseDir: File?, @@ -25,9 +38,9 @@ class KaptJavaLog( errWriter: PrintWriter, warnWriter: PrintWriter, noticeWriter: PrintWriter, - val interceptorData: DiagnosticInterceptorData, + override val interceptorData: KaptJavaLogBase.DiagnosticInterceptorData, private val mapDiagnosticLocations: Boolean -) : Log(context, errWriter, warnWriter, noticeWriter) { +) : Log(context, errWriter, warnWriter, noticeWriter), KaptJavaLogBase { private val stubLineInfo = KaptStubLineInformation() private val javacMessages = JavacMessages.instance(context) @@ -35,7 +48,7 @@ class KaptJavaLog( context.put(Log.outKey, noticeWriter) } - val reportedDiagnostics: List + override val reportedDiagnostics: List get() = _reportedDiagnostics private val _reportedDiagnostics = mutableListOf() @@ -217,10 +230,6 @@ class KaptJavaLog( "compiler.err.not.def.public.cant.access" ) } - - class DiagnosticInterceptorData { - var files: Map = emptyMap() - } } private val LINE_SEPARATOR: String = System.getProperty("line.separator") @@ -256,7 +265,7 @@ private fun JCDiagnostic.Factory.errorJava9Aware( } } -private data class KotlinFileObject(val file: File) : SimpleJavaFileObject(file.toURI(), Kind.SOURCE) { +/*private*/internal 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() diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/javac/KaptJavaLog17.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/javac/KaptJavaLog17.kt new file mode 100644 index 00000000000..debe7d534ea --- /dev/null +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/javac/KaptJavaLog17.kt @@ -0,0 +1,210 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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.stubs.KaptStubLineInformation +import org.jetbrains.kotlin.kapt3.base.stubs.KotlinPosition +import java.io.* +import javax.tools.Diagnostic + +// It's copy/paste as is of KaptJavaLog +// TODO: most likely KaptJavaLog could be substituted with this version. +// The only difference that is writers are passed through context +class KaptJavaLog17( + private val projectBaseDir: File?, + context: Context, + override val interceptorData: KaptJavaLogBase.DiagnosticInterceptorData, + private val mapDiagnosticLocations: Boolean +) : Log(context), KaptJavaLogBase { + private val stubLineInfo = KaptStubLineInformation() + private val javacMessages = JavacMessages.instance(context) + + override val reportedDiagnostics: List + get() = _reportedDiagnostics + + private val _reportedDiagnostics = mutableListOf() + + 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, postfix = 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 Iterable.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.cant.resolve.location", + "compiler.err.duplicate.annotation.missing.container", + "compiler.err.not.def.access.package.cant.access", + "compiler.err.package.not.visible", + "compiler.err.not.def.public.cant.access" + ) + } +} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/util/WriterBackedKaptLogger.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/util/WriterBackedKaptLogger.kt index 8d81f6ce12c..60fb5b0a8a5 100644 --- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/util/WriterBackedKaptLogger.kt +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/util/WriterBackedKaptLogger.kt @@ -10,7 +10,7 @@ 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 warnWriter: PrintWriter = if (isJava17OrLater()) infoWriter else PrintWriter(System.out), override val errorWriter: PrintWriter = PrintWriter(System.err) ) : KaptLogger { override fun info(message: String) { diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/util/java9Utils.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/util/java9Utils.kt index cae184709e7..d5d8e015740 100644 --- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/util/java9Utils.kt +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/util/java9Utils.kt @@ -28,6 +28,7 @@ private fun getJavaVersion(): Int = fun isJava9OrLater() = getJavaVersion() >= 9 fun isJava11OrLater() = getJavaVersion() >= 11 +fun isJava17OrLater() = getJavaVersion() >= 17 fun Options.putJavacOption(jdk8Name: String, jdk9Name: String, value: String) { val option = if (isJava9OrLater()) { 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 209c640d156..308d8929de1 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 @@ -43,7 +43,7 @@ import org.jetbrains.kotlin.kapt3.Kapt3ComponentRegistrar.KaptComponentContribut import org.jetbrains.kotlin.kapt3.KaptContextForStubGeneration import org.jetbrains.kotlin.kapt3.base.KaptContext import org.jetbrains.kotlin.kapt3.base.doAnnotationProcessing -import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaLog +import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaLogBase import org.jetbrains.kotlin.kapt3.base.parseJavaFiles import org.jetbrains.kotlin.kapt3.javac.KaptJavaFileObject import org.jetbrains.kotlin.kapt3.prettyPrint @@ -287,7 +287,7 @@ open class AbstractClassFileToSourceStubConverterTest : AbstractKotlinKapt3Test( .let { removeMetadataAnnotationContents(it) } if (kaptContext.compiler.shouldStop(CompileStates.CompileState.ENTER)) { - val log = Log.instance(kaptContext.context) as KaptJavaLog + val log = Log.instance(kaptContext.context) as KaptJavaLogBase val actualErrors = log.reportedDiagnostics .filter { it.type == JCDiagnostic.DiagnosticType.ERROR }