Improve modular JDK root module detection
According to the spec, "java.se" and every other non-"java.*" module that exports at least one package without qualification, is a root. Currently we only support compilation of a single unnamed module, and apparently unnamed module should read all root modules. #KT-18180 Fixed
This commit is contained in:
@@ -73,6 +73,7 @@ import org.jetbrains.kotlin.cli.common.toBooleanLenient
|
||||
import org.jetbrains.kotlin.cli.jvm.JvmRuntimeVersionsConsistencyChecker
|
||||
import org.jetbrains.kotlin.cli.jvm.config.*
|
||||
import org.jetbrains.kotlin.cli.jvm.index.*
|
||||
import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleFinder
|
||||
import org.jetbrains.kotlin.cli.jvm.modules.CoreJrtFileSystem
|
||||
import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension
|
||||
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
|
||||
@@ -143,6 +144,9 @@ class KotlinCoreEnvironment private constructor(
|
||||
private val sourceFiles = mutableListOf<KtFile>()
|
||||
private val rootsIndex: JvmDependenciesDynamicCompoundIndex
|
||||
|
||||
private val javaModuleFinder: CliJavaModuleFinder
|
||||
private val javaModuleGraph: JavaModuleGraph
|
||||
|
||||
val configuration: CompilerConfiguration = configuration.copy()
|
||||
|
||||
init {
|
||||
@@ -188,6 +192,9 @@ class KotlinCoreEnvironment private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
javaModuleFinder = CliJavaModuleFinder(VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JRT_PROTOCOL))
|
||||
javaModuleGraph = JavaModuleGraph(javaModuleFinder)
|
||||
|
||||
val initialRoots = convertClasspathRoots(configuration.getList(JVMConfigurationKeys.CONTENT_ROOTS))
|
||||
|
||||
if (!configuration.getBoolean(JVMConfigurationKeys.SKIP_RUNTIME_VERSION_CHECK)) {
|
||||
@@ -282,28 +289,22 @@ class KotlinCoreEnvironment private constructor(
|
||||
result.add(JavaRoot(virtualFile, rootType, prefixPackageFqName))
|
||||
}
|
||||
|
||||
val jrtFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JRT_PROTOCOL)
|
||||
if (jrtFileSystem != null) {
|
||||
addModularJdkRoots(jrtFileSystem, result)
|
||||
}
|
||||
addModularRoots(result)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun addModularJdkRoots(fileSystem: VirtualFileSystem, result: MutableList<JavaRoot>) {
|
||||
val graph = JavaModuleGraph { moduleName ->
|
||||
fileSystem.findFileByPath("/modules/$moduleName/module-info.class")?.let((JavaModuleInfo)::read) ?: run {
|
||||
report(ERROR, "Module $moduleName cannot be found in the Java runtime image")
|
||||
JavaModuleInfo(moduleName, emptyList(), emptyList())
|
||||
}
|
||||
private fun addModularRoots(result: MutableList<JavaRoot>) {
|
||||
val jrtFileSystem = javaModuleFinder.jrtFileSystem ?: return
|
||||
|
||||
val rootModules = computeRootModules(javaModuleFinder)
|
||||
val allDependencies = javaModuleGraph.getAllDependencies(rootModules).also { modules ->
|
||||
report(LOGGING, "Loading modules: $modules")
|
||||
}
|
||||
|
||||
val allReachableModules = graph.getAllReachable(listOf("java.base", "java.se")).also { modules ->
|
||||
report(LOGGING, "Loading modules exported by java.se: $modules")
|
||||
}
|
||||
|
||||
for (moduleName in allReachableModules) {
|
||||
val root = fileSystem.findFileByPath("/modules/$moduleName")
|
||||
for (moduleName in allDependencies) {
|
||||
// TODO: support modules not only from Java runtime image, but from a separate module path
|
||||
val root = jrtFileSystem.findFileByPath("/modules/$moduleName")
|
||||
if (root == null) {
|
||||
report(ERROR, "Module $moduleName cannot be found in the module graph")
|
||||
}
|
||||
@@ -313,6 +314,40 @@ class KotlinCoreEnvironment private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
// See http://openjdk.java.net/jeps/261
|
||||
private fun computeRootModules(finder: CliJavaModuleFinder): List<String> {
|
||||
val result = arrayListOf<String>()
|
||||
|
||||
val systemModules = finder.computeAllSystemModules()
|
||||
val javaSeExists = "java.se" in systemModules
|
||||
if (javaSeExists) {
|
||||
// The java.se module is a root, if it exists.
|
||||
result.add("java.se")
|
||||
}
|
||||
|
||||
fun JavaModuleInfo.exportsAtLeastOnePackageUnqualified(): Boolean = exports.any { it.toModules.isEmpty() }
|
||||
|
||||
if (!javaSeExists) {
|
||||
// If it does not exist then every java.* module on the upgrade module path or among the system modules
|
||||
// that exports at least one package, without qualification, is a root.
|
||||
for ((name, module) in systemModules) {
|
||||
if (name.startsWith("java.") && module.exportsAtLeastOnePackageUnqualified()) {
|
||||
result.add(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ((name, module) in systemModules) {
|
||||
// Every non-java.* module on the upgrade module path or among the system modules that exports at least one package,
|
||||
// without qualification, is also a root.
|
||||
if (!name.startsWith("java.") && module.exportsAtLeastOnePackageUnqualified()) {
|
||||
result.add(name)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun updateClasspathFromRootsIndex(index: JvmDependenciesIndex) {
|
||||
index.indexedRoots.forEach {
|
||||
projectEnvironment.addSourcesToClasspath(it.file)
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.cli.jvm.modules
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFileSystem
|
||||
import com.intellij.psi.PsiJavaModule
|
||||
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleFinder
|
||||
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleInfo
|
||||
|
||||
internal class CliJavaModuleFinder(val jrtFileSystem: VirtualFileSystem?) : JavaModuleFinder {
|
||||
internal fun computeAllSystemModules(): Map<String, JavaModuleInfo> {
|
||||
return jrtFileSystem?.findFileByPath("/modules")?.children.orEmpty()
|
||||
.mapNotNull { root -> root.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE) }
|
||||
.mapNotNull((JavaModuleInfo)::read)
|
||||
.associateBy { moduleInfo -> moduleInfo.moduleName }
|
||||
}
|
||||
|
||||
override fun findModule(name: String): JavaModuleInfo? {
|
||||
val file = jrtFileSystem?.findFileByPath("/modules/$name/${PsiJavaModule.MODULE_INFO_CLS_FILE}")
|
||||
if (file != null) {
|
||||
val moduleInfo = JavaModuleInfo.read(file)
|
||||
if (moduleInfo != null) {
|
||||
return moduleInfo
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.resolve.jvm.modules
|
||||
|
||||
interface JavaModuleFinder {
|
||||
fun findModule(name: String): JavaModuleInfo?
|
||||
}
|
||||
+9
-6
@@ -18,22 +18,25 @@ package org.jetbrains.kotlin.resolve.jvm.modules
|
||||
|
||||
import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
||||
|
||||
class JavaModuleGraph(getModuleInfo: (String) -> JavaModuleInfo) {
|
||||
private val moduleInfo: (String) -> JavaModuleInfo = LockBasedStorageManager.NO_LOCKS.createMemoizedFunction(getModuleInfo)
|
||||
class JavaModuleGraph(finder: JavaModuleFinder) {
|
||||
private val moduleInfo: (String) -> JavaModuleInfo? =
|
||||
LockBasedStorageManager.NO_LOCKS.createMemoizedFunctionWithNullableValues(finder::findModule)
|
||||
|
||||
fun getAllReachable(rootModules: List<String>): List<String> {
|
||||
val visited = linkedSetOf<String>()
|
||||
fun getAllDependencies(moduleNames: List<String>): List<String> {
|
||||
// Every module implicitly depends on java.base
|
||||
val visited = linkedSetOf("java.base")
|
||||
|
||||
fun dfs(module: String) {
|
||||
if (!visited.add(module)) return
|
||||
for (dependency in moduleInfo(module).requires) {
|
||||
val moduleInfo = moduleInfo(module) ?: return
|
||||
for (dependency in moduleInfo.requires) {
|
||||
if (dependency.isTransitive) {
|
||||
dfs(dependency.moduleName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rootModules.forEach(::dfs)
|
||||
moduleNames.forEach(::dfs)
|
||||
return visited.toList()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user