KAPT does not fail with default settings on JDK 16+

If KAPT process is executed on latest JDKs and module
'jdk.compiler' does not export required packages, KAPT will
configure this module to export required packages.

#KT-45545 Fixed
This commit is contained in:
Andrey Uskov
2021-10-07 13:42:53 +03:00
committed by TeamCityServer
parent 31d7d341d4
commit 52e45062cf
3 changed files with 109 additions and 0 deletions
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.base.kapt3.*
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 org.jetbrains.kotlin.kapt3.util.doOpenInternalPackagesIfRequired
import kotlin.system.measureTimeMillis
object Kapt {
@@ -24,6 +25,7 @@ object Kapt {
@JvmStatic
@Suppress("unused")
fun kapt(options: KaptOptions): Boolean {
doOpenInternalPackagesIfRequired()
val logger = WriterBackedKaptLogger(options[KaptFlag.VERBOSE])
if (!Kapt.checkJavacComponentsAccess(logger)) {
@@ -0,0 +1,104 @@
/*
* Copyright 2010-2021 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.util
import sun.misc.Unsafe
import java.lang.Exception
import java.lang.invoke.MethodHandles
import java.lang.reflect.Method
import java.lang.reflect.Modifier
val REQUIRED_PACKAGES_TO_TEST_CLASSES = mapOf(
"com.sun.tools.javac.util" to "Context",
"com.sun.tools.javac.file" to "CacheFSInfo",
"com.sun.tools.javac.tree" to "TreeTranslator",
"com.sun.tools.javac.main" to "CommandLine",
"com.sun.tools.javac.jvm" to "ClassFile",
"com.sun.tools.javac.parser" to "Tokens\$TokenKind",
"com.sun.tools.javac.code" to "Source",
"com.sun.tools.javac.processing" to "PrintingProcessor",
"com.sun.tools.javac.comp" to "AttrContext",
"com.sun.tools.javac.api" to "DiagnosticFormatter\$PositionKind"
)
private fun openPackages(packagesToOpen: Collection<String>) {
@Suppress("UNCHECKED_CAST")
fun allModules(): Collection<Any>? {
// Actually it is similar to ModuleLayer.boot().modules()
try {
val boot = Class.forName("java.lang.ModuleLayer").getMethod("boot").invoke(null) ?: return null
return boot.javaClass.getMethod("modules").invoke(boot) as? Collection<Any>
} catch (_: Exception) {
}
return null
}
@Suppress("UNCHECKED_CAST")
fun getPackages(module: Any) =
// similar to module.packages
module.javaClass.getMethod("getPackages").invoke(module) as Collection<String>
val modules = allModules() ?: return
val unsafe = Unsafe::class.java.getDeclaredField("theUnsafe").apply {
isAccessible = true
}.get(null) as Unsafe
val implLookupField = MethodHandles.Lookup::class.java.getDeclaredField("IMPL_LOOKUP")
val lookup =
unsafe.getObject(
unsafe.staticFieldBase(implLookupField),
unsafe.staticFieldOffset(implLookupField)
) as MethodHandles.Lookup
val modifiers = lookup.findSetter(Method::class.java, "modifiers", Integer.TYPE)
val exportMethod: Method = Class.forName("java.lang.Module").getDeclaredMethod("implAddOpens", String::class.java)
modifiers.invokeExact(exportMethod, Modifier.PUBLIC)
modules.forEach { module ->
getPackages(module).filter { packagesToOpen.contains(it) }.forEach { name ->
exportMethod.invoke(module, name)
}
}
}
private fun unavailableRequiredPackages() = REQUIRED_PACKAGES_TO_TEST_CLASSES.filter { entry ->
try {
val classInstance = Class.forName("${entry.key}.${entry.value}")
if (classInstance.isEnum) {
classInstance.getMethod("values").invoke(null)
} else {
classInstance.getDeclaredConstructor().newInstance()
}
false
} catch (_: IllegalAccessException) {
true
} catch (_: Exception) {
// in old versions of JDK some classes could be unavailable
false
}
}.keys
private var checkDone = false
@Synchronized
fun doOpenInternalPackagesIfRequired() {
if (checkDone)
return
try {
checkDone = true
val unavailablePackages = unavailableRequiredPackages()
if (unavailablePackages.isNotEmpty()) {
openPackages(unavailablePackages)
val failedToOpen = unavailableRequiredPackages()
if (failedToOpen.isNotEmpty()) {
System.err.println(
"WARNING: Some required internal classes are unavailable. Please consider adding the following JVM arguments\n" +
"WARNING: ${failedToOpen.joinToString(" ") { "--add-opens jdk.compiler/$it=ALL-UNNAMED" }}"
)
}
}
} catch (e: Throwable) {
System.err.println("WARNING: Failed to check for unavailable JDK packages. Reason: ${e.message}")
}
}
@@ -42,6 +42,7 @@ import org.jetbrains.kotlin.kapt.cli.KaptCliOption.*
import org.jetbrains.kotlin.kapt3.base.Kapt
import org.jetbrains.kotlin.kapt3.base.util.KaptLogger
import org.jetbrains.kotlin.kapt3.util.MessageCollectorBackedKaptLogger
import org.jetbrains.kotlin.kapt3.util.doOpenInternalPackagesIfRequired
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.platform.TargetPlatform
@@ -62,6 +63,7 @@ class Kapt3CommandLineProcessor : CommandLineProcessor {
override val pluginOptions: Collection<AbstractCliOption> = values().asList()
override fun processOption(option: AbstractCliOption, value: String, configuration: CompilerConfiguration) {
doOpenInternalPackagesIfRequired()
if (option !is KaptCliOption) {
throw CliOptionProcessingException("Unknown option: ${option.optionName}")
}
@@ -158,6 +160,7 @@ class Kapt3CommandLineProcessor : CommandLineProcessor {
class Kapt3ComponentRegistrar : ComponentRegistrar {
override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) {
doOpenInternalPackagesIfRequired()
val contentRoots = configuration[CLIConfigurationKeys.CONTENT_ROOTS] ?: emptyList()
val optionsBuilder = (configuration[KAPT_OPTIONS] ?: KaptOptions.Builder()).apply {