Support compilation against modular JDK (9+)

For more information about the "jrt" file system, see
http://openjdk.java.net/jeps/220 and
https://bugs.openjdk.java.net/browse/JDK-8066492.

This commit fixes DiagnosticsWithJdk9TestGenerated.testKt11167

 #KT-11167 Fixed
This commit is contained in:
Alexander Udalov
2017-03-29 12:16:51 +03:00
parent a519ab681a
commit 82e6324c45
8 changed files with 381 additions and 47 deletions
@@ -343,24 +343,28 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
private fun setupJdkClasspathRoots(arguments: K2JVMCompilerArguments, configuration: CompilerConfiguration, messageCollector: MessageCollector): ExitCode {
try {
if (!arguments.noJdk) {
if (arguments.jdkHome != null) {
messageCollector.report(LOGGING, "Using JDK home directory ${arguments.jdkHome}")
val classesRoots = PathUtil.getJdkClassesRoots(File(arguments.jdkHome))
if (classesRoots.isEmpty()) {
messageCollector.report(ERROR, "No class roots are found in the JDK path: ${arguments.jdkHome}")
return COMPILATION_ERROR
}
configuration.addJvmClasspathRoots(classesRoots)
}
else {
configuration.addJvmClasspathRoots(PathUtil.getJdkClassesRootsFromCurrentJre())
}
}
else {
if (arguments.noJdk) {
if (arguments.jdkHome != null) {
messageCollector.report(STRONG_WARNING, "The '-jdk-home' option is ignored because '-no-jdk' is specified")
}
return OK
}
if (arguments.jdkHome != null) {
val jdkHome = File(arguments.jdkHome)
configuration.put(JVMConfigurationKeys.JDK_HOME, jdkHome)
val classesRoots = PathUtil.getJdkClassesRoots(jdkHome)
configuration.addJvmClasspathRoots(classesRoots)
messageCollector.report(LOGGING, "Using JDK home directory $jdkHome")
if (classesRoots.isEmpty()) {
messageCollector.report(ERROR, "No class roots are found in the JDK path: $jdkHome")
return COMPILATION_ERROR
}
}
else {
configuration.put(JVMConfigurationKeys.JDK_HOME, File(System.getProperty("java.home")))
configuration.addJvmClasspathRoots(PathUtil.getJdkClassesRootsFromCurrentJre())
}
}
catch (t: Throwable) {
@@ -43,8 +43,7 @@ import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.io.FileUtilRt
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.PersistentFSConstants
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.*
import com.intellij.openapi.vfs.impl.ZipHandler
import com.intellij.psi.FileContextProvider
import com.intellij.psi.PsiElementFinder
@@ -68,8 +67,7 @@ import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.CliModuleVisibilityManagerImpl
import org.jetbrains.kotlin.cli.common.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.STRONG_WARNING
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.*
import org.jetbrains.kotlin.cli.common.toBooleanLenient
import org.jetbrains.kotlin.cli.jvm.JvmRuntimeVersionsConsistencyChecker
import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
@@ -80,6 +78,9 @@ import org.jetbrains.kotlin.cli.jvm.index.JavaRoot
import org.jetbrains.kotlin.cli.jvm.index.JvmDependenciesDynamicCompoundIndex
import org.jetbrains.kotlin.cli.jvm.index.JvmDependenciesIndex
import org.jetbrains.kotlin.cli.jvm.index.JvmUpdateableDependenciesIndexFactory
import org.jetbrains.kotlin.cli.jvm.modules.CoreJrtFileSystem
import org.jetbrains.kotlin.cli.jvm.modules.JavaModuleInfo
import org.jetbrains.kotlin.cli.jvm.modules.ModuleGraph
import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
@@ -191,7 +192,7 @@ class KotlinCoreEnvironment private constructor(
}
}
val initialRoots = configuration.getList(JVMConfigurationKeys.CONTENT_ROOTS).classpathRoots()
val initialRoots = convertClasspathRoots(configuration.getList(JVMConfigurationKeys.CONTENT_ROOTS))
if (!configuration.getBoolean(JVMConfigurationKeys.SKIP_RUNTIME_VERSION_CHECK)) {
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
@@ -230,34 +231,65 @@ class KotlinCoreEnvironment private constructor(
val sourceLinesOfCode: Int by lazy { countLinesOfCode(sourceFiles) }
fun countLinesOfCode(sourceFiles: List<KtFile>): Int =
sourceFiles.sumBy {
val text = it.text
StringUtil.getLineBreakCount(it.text) + (if (StringUtil.endsWithLineBreak(text)) 0 else 1)
sourceFiles.sumBy { sourceFile ->
val text = sourceFile.text
StringUtil.getLineBreakCount(text) + (if (StringUtil.endsWithLineBreak(text)) 0 else 1)
}
private fun Iterable<ContentRoot>.classpathRoots(): List<JavaRoot> =
filterIsInstance(JvmContentRoot::class.java).mapNotNull { javaRoot ->
contentRootToVirtualFile(javaRoot)?.let { virtualFile ->
val prefixPackageFqName = (javaRoot as? JavaSourceRoot)?.packagePrefix?.let {
if (isValidJavaFqName(it)) {
FqName(it)
}
else {
report(STRONG_WARNING, "Invalid package prefix name is ignored: $it")
null
}
}
private fun convertClasspathRoots(roots: Iterable<ContentRoot>): List<JavaRoot> {
val result = mutableListOf<JavaRoot>()
val rootType = when (javaRoot) {
is JavaSourceRoot -> JavaRoot.RootType.SOURCE
is JvmClasspathRoot -> JavaRoot.RootType.BINARY
else -> throw IllegalStateException()
}
for (javaRoot in roots) {
if (javaRoot !is JvmContentRoot) continue
val virtualFile = contentRootToVirtualFile(javaRoot) ?: continue
JavaRoot(virtualFile, rootType, prefixPackageFqName)
val prefixPackageFqName = (javaRoot as? JavaSourceRoot)?.packagePrefix?.let { prefix ->
if (isValidJavaFqName(prefix)) FqName(prefix)
else null.also {
report(STRONG_WARNING, "Invalid package prefix name is ignored: $prefix")
}
}
val rootType = when (javaRoot) {
is JavaSourceRoot -> JavaRoot.RootType.SOURCE
is JvmClasspathRoot -> JavaRoot.RootType.BINARY
else -> error("Unknown root type: $javaRoot")
}
result.add(JavaRoot(virtualFile, rootType, prefixPackageFqName))
}
val jrtFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JRT_PROTOCOL)
if (jrtFileSystem != null) {
addModularJdkRoots(jrtFileSystem, result)
}
return result
}
private fun addModularJdkRoots(fileSystem: VirtualFileSystem, result: MutableList<JavaRoot>) {
val graph = ModuleGraph { 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())
}
}
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")
if (root == null) {
report(ERROR, "Module $moduleName cannot be found in the module graph")
}
else {
result.add(JavaRoot(root, JavaRoot.RootType.BINARY))
}
}
}
private fun updateClasspathFromRootsIndex(index: JvmDependenciesIndex) {
index.indexedRoots.forEach {
projectEnvironment.addSourcesToClasspath(it.file)
@@ -273,10 +305,10 @@ class KotlinCoreEnvironment private constructor(
}
fun updateClasspath(roots: List<ContentRoot>): List<File>? {
return rootsIndex.addNewIndexForRoots(roots.classpathRoots())?.let {
updateClasspathFromRootsIndex(it)
it.indexedRoots.mapNotNull { File(it.file.path.substringBefore(URLUtil.JAR_SEPARATOR)) }.toList()
} ?: emptyList()
return rootsIndex.addNewIndexForRoots(convertClasspathRoots(roots))?.let { newIndex ->
updateClasspathFromRootsIndex(newIndex)
newIndex.indexedRoots.mapNotNull { (file) -> File(file.path.substringBefore(URLUtil.JAR_SEPARATOR)) }.toList()
}.orEmpty()
}
@Suppress("unused") // used externally
@@ -415,7 +447,12 @@ class KotlinCoreEnvironment private constructor(
private fun createApplicationEnvironment(parentDisposable: Disposable, configuration: CompilerConfiguration, configFilePaths: List<String>): JavaCoreApplicationEnvironment {
Extensions.cleanRootArea(parentDisposable)
registerAppExtensionPoints()
val applicationEnvironment = JavaCoreApplicationEnvironment(parentDisposable)
val applicationEnvironment = object : JavaCoreApplicationEnvironment(parentDisposable) {
override fun createJrtFileSystem(): VirtualFileSystem? {
val jdkHome = configuration[JVMConfigurationKeys.JDK_HOME] ?: return null
return CoreJrtFileSystem.create(jdkHome)
}
}
for (configPath in configFilePaths) {
registerApplicationExtensionPointsAndExtensionsFrom(configuration, configPath)
@@ -0,0 +1,62 @@
/*
* 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.util.SystemInfo
import com.intellij.openapi.vfs.DeprecatedVirtualFileSystem
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFile
import java.io.File
import java.net.URI
import java.net.URLClassLoader
import java.nio.file.FileSystem
import java.nio.file.FileSystems
import java.nio.file.Files
import java.nio.file.Path
// There's JrtFileSystem in idea-full which we can't use in the compiler because it depends on NewVirtualFileSystem, absent in intellij-core
class CoreJrtFileSystem(private val fileSystem: FileSystem) : DeprecatedVirtualFileSystem() {
override fun getProtocol(): String = StandardFileSystems.JRT_PROTOCOL
private fun findFileByPath(path: Path): VirtualFile? =
if (Files.exists(path)) CoreLocalPathVirtualFile(this, path) else null
override fun findFileByPath(path: String): VirtualFile? =
findFileByPath(fileSystem.getPath(path))
override fun refresh(asynchronous: Boolean) {}
override fun refreshAndFindFileByPath(path: String): VirtualFile? = findFileByPath(path)
companion object {
fun create(jdkHome: File): CoreJrtFileSystem? {
val rootUri = URI.create(StandardFileSystems.JRT_PROTOCOL + ":/")
val fileSystem =
if (SystemInfo.isJavaVersionAtLeast("9")) {
FileSystems.newFileSystem(rootUri, mapOf("java.home" to jdkHome.absolutePath))
}
else {
val jrtFsJar = File(jdkHome, "lib/jrt-fs.jar")
if (!jrtFsJar.exists()) return null
val classLoader = URLClassLoader(arrayOf(jrtFsJar.toURI().toURL()), null)
FileSystems.newFileSystem(rootUri, emptyMap<String, Nothing>(), classLoader)
}
return CoreJrtFileSystem(fileSystem)
}
}
}
@@ -0,0 +1,89 @@
/*
* 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.util.io.FileUtil
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileSystem
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.attribute.BasicFileAttributes
class CoreLocalPathVirtualFile(private val fileSystem: VirtualFileSystem, private val path: Path) : VirtualFile() {
// TODO: catch IOException?
private val attributes: BasicFileAttributes get() = Files.readAttributes(path, BasicFileAttributes::class.java)
override fun getFileSystem(): VirtualFileSystem = fileSystem
override fun getName(): String =
path.fileName.toString()
override fun getPath(): String =
FileUtil.toSystemIndependentName(path.toString())
override fun isWritable(): Boolean = false
override fun isDirectory(): Boolean = Files.isDirectory(path)
override fun isValid(): Boolean = true
override fun getParent(): VirtualFile? {
val parentPath = path.parent
return if (parentPath != null) CoreLocalPathVirtualFile(fileSystem, parentPath) else null
}
override fun getChildren(): Array<out VirtualFile> {
val paths = try {
Files.newDirectoryStream(path).use(Iterable<Path>::toList)
}
catch (e: IOException) {
emptyList<Path>()
}
return when {
paths.isEmpty() -> VirtualFile.EMPTY_ARRAY
else -> paths.map { path -> CoreLocalPathVirtualFile(fileSystem, path) }.toTypedArray()
}
}
override fun getOutputStream(requestor: Any, newModificationStamp: Long, newTimeStamp: Long): OutputStream =
throw UnsupportedOperationException()
override fun contentsToByteArray(): ByteArray =
Files.readAllBytes(path)
override fun getTimeStamp(): Long =
attributes.lastModifiedTime().toMillis()
override fun getLength(): Long = attributes.size()
override fun refresh(asynchronous: Boolean, recursive: Boolean, postRunnable: Runnable?) {}
override fun getInputStream(): InputStream =
VfsUtilCore.inputStreamSkippingBOM(Files.newInputStream(path).buffered(), this)
override fun getModificationStamp(): Long = 0
override fun equals(other: Any?): Boolean =
other is CoreLocalPathVirtualFile && path == other.path && fileSystem == other.fileSystem
override fun hashCode(): Int =
path.hashCode()
}
@@ -0,0 +1,72 @@
/*
* 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.VirtualFile
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.utils.compactIfPossible
import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.ClassVisitor
import org.jetbrains.org.objectweb.asm.ModuleVisitor
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Opcodes.ACC_TRANSITIVE
import java.io.IOException
class JavaModuleInfo(
val moduleName: String,
val requires: List<Requires>,
val exports: List<Exports>
) {
data class Requires(val moduleName: String, val flags: Int) {
val isTransitive get() = (flags and ACC_TRANSITIVE) != 0
}
data class Exports(val packageFqName: FqName, val flags: Int, val toModules: List<String>)
override fun toString(): String =
"Module $moduleName (${requires.size} requires, ${exports.size} exports)"
companion object {
fun read(file: VirtualFile): JavaModuleInfo? {
val contents = try { file.contentsToByteArray() } catch (e: IOException) { return null }
var moduleName: String? = null
val requires = arrayListOf<Requires>()
val exports = arrayListOf<Exports>()
ClassReader(contents).accept(object : ClassVisitor(Opcodes.ASM6) {
override fun visitModule(name: String, access: Int, version: String?): ModuleVisitor {
moduleName = name
return object : ModuleVisitor(Opcodes.ASM6) {
override fun visitRequire(module: String, access: Int, version: String?) {
requires.add(Requires(module, access))
}
override fun visitExport(packageFqName: String, access: Int, modules: Array<String>?) {
exports.add(Exports(FqName(packageFqName), access, modules?.toList().orEmpty()))
}
}
}
}, ClassReader.SKIP_DEBUG or ClassReader.SKIP_CODE or ClassReader.SKIP_FRAMES)
return if (moduleName != null)
JavaModuleInfo(moduleName!!, requires.compactIfPossible(), exports.compactIfPossible())
else null
}
}
}
@@ -0,0 +1,39 @@
/*
* 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 org.jetbrains.kotlin.storage.LockBasedStorageManager
class ModuleGraph(getModuleInfo: (String) -> JavaModuleInfo) {
private val moduleInfo: (String) -> JavaModuleInfo = LockBasedStorageManager.NO_LOCKS.createMemoizedFunction(getModuleInfo)
fun getAllReachable(rootModules: List<String>): List<String> {
val visited = linkedSetOf<String>()
fun dfs(module: String) {
if (!visited.add(module)) return
for (dependency in moduleInfo(module).requires) {
if (dependency.isTransitive) {
dfs(dependency.moduleName)
}
}
}
rootModules.forEach(::dfs)
return visited.toList()
}
}
@@ -39,6 +39,9 @@ public class JVMConfigurationKeys {
public static final CompilerConfigurationKey<Boolean> INCLUDE_RUNTIME =
CompilerConfigurationKey.create("include runtime to the resulting .jar");
public static final CompilerConfigurationKey<File> JDK_HOME =
CompilerConfigurationKey.create("jdk home");
public static final CompilerConfigurationKey<List<KotlinScriptDefinition>> SCRIPT_DEFINITIONS =
CompilerConfigurationKey.create("script definitions");
@@ -49,6 +49,10 @@ import org.jetbrains.annotations.TestOnly;
import org.jetbrains.kotlin.analyzer.AnalysisResult;
import org.jetbrains.kotlin.builtins.DefaultBuiltIns;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys;
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation;
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity;
import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.cli.jvm.config.JvmContentRootsKt;
@@ -428,6 +432,30 @@ public class KotlinTestUtils {
public static CompilerConfiguration newConfiguration() {
CompilerConfiguration configuration = new CompilerConfiguration();
configuration.put(CommonConfigurationKeys.MODULE_NAME, TEST_MODULE_NAME);
configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, new MessageCollector() {
@Override
public void clear() {
}
@Override
public void report(
@NotNull CompilerMessageSeverity severity, @NotNull String message, @Nullable CompilerMessageLocation location
) {
if (severity == CompilerMessageSeverity.ERROR) {
String prefix = location == null
? ""
: "(" + location.getPath() + ":" + location.getLine() + ":" + location.getColumn() + ") ";
throw new AssertionError(prefix + message);
}
}
@Override
public boolean hasErrors() {
return false;
}
});
return configuration;
}
@@ -466,7 +494,7 @@ public class KotlinTestUtils {
else if (jdkKind == TestJdkKind.FULL_JDK_9) {
String jdk9 = System.getenv("JDK_9");
if (jdk9 != null) {
JvmContentRootsKt.addJvmClasspathRoots(configuration, PathUtil.getJdkClassesRootsFromJre(getJreHome(jdk9)));
configuration.put(JVMConfigurationKeys.JDK_HOME, new File(getJreHome(jdk9)));
}
else {
System.err.println("Environment variable JDK_9 is not set, the test will be skipped");