Include JDK home path into virtual file paths in CoreJrtFileSystem

The problem in KT-19833 is caused by the fact that the application
environment instance is shared between consecutive runs of the compiler
in one Make action (KotlinCoreEnvironment.ourApplicationEnvironment).
If the JDK 8 module is compiled first, the created application
environment has no JRT file system, and once the JDK 9 module is
compiled later, that environment is not recreated and thus classes from
JDK 9 are unresolved.

To mitigate this, split the CoreJrtFileSystem implementation into the
file system itself which is global per application, and CoreJrtHandler
which is bound to a particular JDK home location. CoreJrtVirtualFile
paths now consist of the path to the JDK home, the "!/" separator, and
the path to the file itself, e.g.
"/usr/lib/jvm/java9!/modules/java.base/java/lang/Object.class". The
implementation is inspired by CoreJarFileSystem & CoreJarHandler.

No tests added because the application environment is _not_ shared in
tests. Also, a JDK 9 module is going to be added to the Kotlin project
soon, and that will serve as a test

 #KT-19833 Fixed
This commit is contained in:
Alexander Udalov
2017-08-21 18:37:20 +03:00
parent c142db15fa
commit 08e090bf5e
5 changed files with 68 additions and 42 deletions
@@ -50,9 +50,9 @@ internal class ClasspathRootsResolver(
private val psiManager: PsiManager, private val psiManager: PsiManager,
private val messageCollector: MessageCollector?, private val messageCollector: MessageCollector?,
private val additionalModules: List<String>, private val additionalModules: List<String>,
private val contentRootToVirtualFile: (JvmContentRoot) -> VirtualFile? private val contentRootToVirtualFile: (JvmContentRoot) -> VirtualFile?,
private val javaModuleFinder: CliJavaModuleFinder
) { ) {
val javaModuleFinder = CliJavaModuleFinder(VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JRT_PROTOCOL))
val javaModuleGraph = JavaModuleGraph(javaModuleFinder) val javaModuleGraph = JavaModuleGraph(javaModuleFinder)
data class RootsAndModules(val roots: List<JavaRoot>, val modules: List<JavaModule>) data class RootsAndModules(val roots: List<JavaRoot>, val modules: List<JavaModule>)
@@ -42,10 +42,7 @@ import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.io.FileUtilRt import com.intellij.openapi.util.io.FileUtilRt
import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.PersistentFSConstants import com.intellij.openapi.vfs.*
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileSystem
import com.intellij.openapi.vfs.impl.ZipHandler import com.intellij.openapi.vfs.impl.ZipHandler
import com.intellij.psi.FileContextProvider import com.intellij.psi.FileContextProvider
import com.intellij.psi.PsiElementFinder import com.intellij.psi.PsiElementFinder
@@ -74,13 +71,14 @@ 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.ERROR
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.STRONG_WARNING import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.STRONG_WARNING
import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.script.CliScriptReportSink
import org.jetbrains.kotlin.cli.common.script.CliScriptDependenciesProvider import org.jetbrains.kotlin.cli.common.script.CliScriptDependenciesProvider
import org.jetbrains.kotlin.cli.common.script.CliScriptReportSink
import org.jetbrains.kotlin.cli.common.toBooleanLenient import org.jetbrains.kotlin.cli.common.toBooleanLenient
import org.jetbrains.kotlin.cli.jvm.JvmRuntimeVersionsConsistencyChecker import org.jetbrains.kotlin.cli.jvm.JvmRuntimeVersionsConsistencyChecker
import org.jetbrains.kotlin.cli.jvm.config.* import org.jetbrains.kotlin.cli.jvm.config.*
import org.jetbrains.kotlin.cli.jvm.index.* import org.jetbrains.kotlin.cli.jvm.index.*
import org.jetbrains.kotlin.cli.jvm.javac.JavacWrapperRegistrar import org.jetbrains.kotlin.cli.jvm.javac.JavacWrapperRegistrar
import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleFinder
import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleResolver import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleResolver
import org.jetbrains.kotlin.cli.jvm.modules.CoreJrtFileSystem import org.jetbrains.kotlin.cli.jvm.modules.CoreJrtFileSystem
import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension
@@ -197,10 +195,17 @@ class KotlinCoreEnvironment private constructor(
} }
} }
val jdkHome = configuration.get(JVMConfigurationKeys.JDK_HOME)
val jrtFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JRT_PROTOCOL)
val javaModuleFinder = CliJavaModuleFinder(jdkHome?.path?.let { path ->
jrtFileSystem?.findFileByPath(path + URLUtil.JAR_SEPARATOR)
})
classpathRootsResolver = ClasspathRootsResolver( classpathRootsResolver = ClasspathRootsResolver(
PsiManager.getInstance(project), messageCollector, PsiManager.getInstance(project),
messageCollector,
configuration.getList(JVMConfigurationKeys.ADDITIONAL_JAVA_MODULES), configuration.getList(JVMConfigurationKeys.ADDITIONAL_JAVA_MODULES),
this::contentRootToVirtualFile this::contentRootToVirtualFile,
javaModuleFinder
) )
val (initialRoots, javaModules) = val (initialRoots, javaModules) =
@@ -232,8 +237,7 @@ class KotlinCoreEnvironment private constructor(
project.registerService( project.registerService(
JavaModuleResolver::class.java, JavaModuleResolver::class.java,
CliJavaModuleResolver(classpathRootsResolver.javaModuleGraph, javaModules, CliJavaModuleResolver(classpathRootsResolver.javaModuleGraph, javaModules, javaModuleFinder.systemModules.toList())
classpathRootsResolver.javaModuleFinder.systemModules.toList())
) )
val finderFactory = CliVirtualFileFinderFactory(rootsIndex) val finderFactory = CliVirtualFileFinderFactory(rootsIndex)
@@ -441,10 +445,7 @@ class KotlinCoreEnvironment private constructor(
Extensions.cleanRootArea(parentDisposable) Extensions.cleanRootArea(parentDisposable)
registerAppExtensionPoints() registerAppExtensionPoints()
val applicationEnvironment = object : JavaCoreApplicationEnvironment(parentDisposable) { val applicationEnvironment = object : JavaCoreApplicationEnvironment(parentDisposable) {
override fun createJrtFileSystem(): VirtualFileSystem? { override fun createJrtFileSystem(): VirtualFileSystem? = CoreJrtFileSystem()
val jdkHome = configuration[JVMConfigurationKeys.JDK_HOME] ?: return null
return CoreJrtFileSystem.create(jdkHome)
}
} }
for (configPath in configFilePaths) { for (configPath in configFilePaths) {
@@ -17,13 +17,13 @@
package org.jetbrains.kotlin.cli.jvm.modules package org.jetbrains.kotlin.cli.jvm.modules
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileSystem
import com.intellij.psi.PsiJavaModule import com.intellij.psi.PsiJavaModule
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModule import org.jetbrains.kotlin.resolve.jvm.modules.JavaModule
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleFinder import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleFinder
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleInfo import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleInfo
internal class CliJavaModuleFinder(private val jrtFileSystem: VirtualFileSystem?) : JavaModuleFinder { internal class CliJavaModuleFinder(jrtFileSystemRoot: VirtualFile?) : JavaModuleFinder {
private val modulesRoot = jrtFileSystemRoot?.findChild("modules")
private val userModules = linkedMapOf<String, JavaModule>() private val userModules = linkedMapOf<String, JavaModule>()
fun addUserModule(module: JavaModule) { fun addUserModule(module: JavaModule) {
@@ -34,11 +34,10 @@ internal class CliJavaModuleFinder(private val jrtFileSystem: VirtualFileSystem?
get() = systemModules + userModules.values get() = systemModules + userModules.values
val systemModules: Sequence<JavaModule.Explicit> val systemModules: Sequence<JavaModule.Explicit>
get() = jrtFileSystem?.findFileByPath("/modules")?.children.orEmpty().asSequence().mapNotNull(this::findSystemModule) get() = modulesRoot?.children.orEmpty().asSequence().mapNotNull(this::findSystemModule)
override fun findModule(name: String): JavaModule? = override fun findModule(name: String): JavaModule? =
jrtFileSystem?.findFileByPath("/modules/$name")?.let(this::findSystemModule) modulesRoot?.findChild(name)?.let(this::findSystemModule) ?: userModules[name]
?: userModules[name]
private fun findSystemModule(moduleRoot: VirtualFile): JavaModule.Explicit? { private fun findSystemModule(moduleRoot: VirtualFile): JavaModule.Explicit? {
val file = moduleRoot.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE) ?: return null val file = moduleRoot.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE) ?: return null
@@ -20,30 +20,20 @@ import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.vfs.DeprecatedVirtualFileSystem import com.intellij.openapi.vfs.DeprecatedVirtualFileSystem
import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.containers.ConcurrentFactoryMap
import com.intellij.util.io.URLUtil
import java.io.File import java.io.File
import java.net.URI import java.net.URI
import java.net.URLClassLoader import java.net.URLClassLoader
import java.nio.file.FileSystem
import java.nio.file.FileSystems import java.nio.file.FileSystems
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path 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 // 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() { class CoreJrtFileSystem : DeprecatedVirtualFileSystem() {
override fun getProtocol(): String = StandardFileSystems.JRT_PROTOCOL private val handlers = object : ConcurrentFactoryMap<String, CoreJrtHandler?>() {
override fun create(jdkHomePath: String): CoreJrtHandler? {
private fun findFileByPath(path: Path): VirtualFile? = val jdkHome = File(jdkHomePath)
if (Files.exists(path)) CoreJrtVirtualFile(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 rootUri = URI.create(StandardFileSystems.JRT_PROTOCOL + ":/")
val jrtFsJar = loadJrtFsJar(jdkHome) ?: return null val jrtFsJar = loadJrtFsJar(jdkHome) ?: return null
val fileSystem = val fileSystem =
@@ -54,9 +44,43 @@ class CoreJrtFileSystem(private val fileSystem: FileSystem) : DeprecatedVirtualF
val classLoader = URLClassLoader(arrayOf(jrtFsJar.toURI().toURL()), null) val classLoader = URLClassLoader(arrayOf(jrtFsJar.toURI().toURL()), null)
FileSystems.newFileSystem(rootUri, emptyMap<String, Nothing>(), classLoader) FileSystems.newFileSystem(rootUri, emptyMap<String, Nothing>(), classLoader)
} }
return CoreJrtFileSystem(fileSystem) return CoreJrtHandler(this@CoreJrtFileSystem, jdkHomePath, fileSystem.getPath(""))
} }
}
internal class CoreJrtHandler(
val virtualFileSystem: CoreJrtFileSystem,
val jdkHomePath: String,
private val root: Path
) {
fun findFile(fileName: String): VirtualFile? {
val path = root.resolve(fileName)
return if (Files.exists(path)) CoreJrtVirtualFile(this, path) else null
}
}
override fun getProtocol(): String = StandardFileSystems.JRT_PROTOCOL
override fun findFileByPath(path: String): VirtualFile? {
val (jdkHomePath, pathInImage) = splitPath(path)
return handlers[jdkHomePath]?.findFile(pathInImage)
}
private fun splitPath(path: String): Pair<String, String> {
val separator = path.indexOf(URLUtil.JAR_SEPARATOR)
if (separator < 0) {
throw IllegalArgumentException("Path in CoreJrtFileSystem must contain a separator: $path")
}
val localPath = path.substring(0, separator)
val pathInJar = path.substring(separator + 2)
return Pair(localPath, pathInJar)
}
override fun refresh(asynchronous: Boolean) {}
override fun refreshAndFindFileByPath(path: String): VirtualFile? = findFileByPath(path)
companion object {
private fun loadJrtFsJar(jdkHome: File): File? = private fun loadJrtFsJar(jdkHome: File): File? =
File(jdkHome, "lib/jrt-fs.jar").takeIf(File::exists) File(jdkHome, "lib/jrt-fs.jar").takeIf(File::exists)
@@ -20,6 +20,8 @@ import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileSystem import com.intellij.openapi.vfs.VirtualFileSystem
import com.intellij.util.io.URLUtil
import org.jetbrains.kotlin.cli.jvm.modules.CoreJrtFileSystem.CoreJrtHandler
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
@@ -27,17 +29,17 @@ import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.attribute.BasicFileAttributes import java.nio.file.attribute.BasicFileAttributes
internal class CoreJrtVirtualFile(private val fileSystem: VirtualFileSystem, private val path: Path) : VirtualFile() { internal class CoreJrtVirtualFile(private val handler: CoreJrtHandler, private val path: Path) : VirtualFile() {
// TODO: catch IOException? // TODO: catch IOException?
private val attributes: BasicFileAttributes get() = Files.readAttributes(path, BasicFileAttributes::class.java) private val attributes: BasicFileAttributes get() = Files.readAttributes(path, BasicFileAttributes::class.java)
override fun getFileSystem(): VirtualFileSystem = fileSystem override fun getFileSystem(): VirtualFileSystem = handler.virtualFileSystem
override fun getName(): String = override fun getName(): String =
path.fileName.toString() path.fileName.toString()
override fun getPath(): String = override fun getPath(): String =
FileUtil.toSystemIndependentName(path.toString()) FileUtil.toSystemIndependentName(handler.jdkHomePath + URLUtil.JAR_SEPARATOR + path)
override fun isWritable(): Boolean = false override fun isWritable(): Boolean = false
@@ -47,7 +49,7 @@ internal class CoreJrtVirtualFile(private val fileSystem: VirtualFileSystem, pri
override fun getParent(): VirtualFile? { override fun getParent(): VirtualFile? {
val parentPath = path.parent val parentPath = path.parent
return if (parentPath != null) CoreJrtVirtualFile(fileSystem, parentPath) else null return if (parentPath != null) CoreJrtVirtualFile(handler, parentPath) else null
} }
override fun getChildren(): Array<out VirtualFile> { override fun getChildren(): Array<out VirtualFile> {
@@ -59,7 +61,7 @@ internal class CoreJrtVirtualFile(private val fileSystem: VirtualFileSystem, pri
} }
return when { return when {
paths.isEmpty() -> VirtualFile.EMPTY_ARRAY paths.isEmpty() -> VirtualFile.EMPTY_ARRAY
else -> paths.map { path -> CoreJrtVirtualFile(fileSystem, path) }.toTypedArray() else -> paths.map { path -> CoreJrtVirtualFile(handler, path) }.toTypedArray()
} }
} }