From 1572d2cf2bc6130b20bd9e4e78c91d207df7ac0c Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Fri, 31 Mar 2017 16:17:07 +0300 Subject: [PATCH] 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 --- .../cli/jvm/compiler/KotlinCoreEnvironment.kt | 67 ++++++++++++++----- .../cli/jvm/modules/CliJavaModuleFinder.kt | 42 ++++++++++++ .../resolve/jvm/modules/JavaModuleFinder.kt | 21 ++++++ .../resolve/jvm/modules/JavaModuleGraph.kt | 15 +++-- 4 files changed, 123 insertions(+), 22 deletions(-) create mode 100644 compiler/cli/src/org/jetbrains/kotlin/cli/jvm/modules/CliJavaModuleFinder.kt create mode 100644 compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/modules/JavaModuleFinder.kt diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt index 258c4fe9d39..c2f3ef659be 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt @@ -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() 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) { - 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) { + 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 { + val result = arrayListOf() + + 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) diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/modules/CliJavaModuleFinder.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/modules/CliJavaModuleFinder.kt new file mode 100644 index 00000000000..b6ded54c118 --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/modules/CliJavaModuleFinder.kt @@ -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 { + 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 + } +} diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/modules/JavaModuleFinder.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/modules/JavaModuleFinder.kt new file mode 100644 index 00000000000..b101937b79f --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/modules/JavaModuleFinder.kt @@ -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? +} diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/modules/JavaModuleGraph.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/modules/JavaModuleGraph.kt index fe8cb16ed9b..94100ca14f2 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/modules/JavaModuleGraph.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/modules/JavaModuleGraph.kt @@ -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): List { - val visited = linkedSetOf() + fun getAllDependencies(moduleNames: List): List { + // 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() } }