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:
Alexander Udalov
2017-03-31 16:17:07 +03:00
parent 5042bbe4a1
commit 1572d2cf2b
4 changed files with 123 additions and 22 deletions
@@ -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
}
}
@@ -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?
}
@@ -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()
}
}