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 messageCollector: MessageCollector?,
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)
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.FileUtilRt
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.PersistentFSConstants
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileSystem
import com.intellij.openapi.vfs.*
import com.intellij.openapi.vfs.impl.ZipHandler
import com.intellij.psi.FileContextProvider
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.STRONG_WARNING
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.CliScriptReportSink
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.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.CoreJrtFileSystem
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(
PsiManager.getInstance(project), messageCollector,
PsiManager.getInstance(project),
messageCollector,
configuration.getList(JVMConfigurationKeys.ADDITIONAL_JAVA_MODULES),
this::contentRootToVirtualFile
this::contentRootToVirtualFile,
javaModuleFinder
)
val (initialRoots, javaModules) =
@@ -232,8 +237,7 @@ class KotlinCoreEnvironment private constructor(
project.registerService(
JavaModuleResolver::class.java,
CliJavaModuleResolver(classpathRootsResolver.javaModuleGraph, javaModules,
classpathRootsResolver.javaModuleFinder.systemModules.toList())
CliJavaModuleResolver(classpathRootsResolver.javaModuleGraph, javaModules, javaModuleFinder.systemModules.toList())
)
val finderFactory = CliVirtualFileFinderFactory(rootsIndex)
@@ -441,10 +445,7 @@ class KotlinCoreEnvironment private constructor(
Extensions.cleanRootArea(parentDisposable)
registerAppExtensionPoints()
val applicationEnvironment = object : JavaCoreApplicationEnvironment(parentDisposable) {
override fun createJrtFileSystem(): VirtualFileSystem? {
val jdkHome = configuration[JVMConfigurationKeys.JDK_HOME] ?: return null
return CoreJrtFileSystem.create(jdkHome)
}
override fun createJrtFileSystem(): VirtualFileSystem? = CoreJrtFileSystem()
}
for (configPath in configFilePaths) {
@@ -17,13 +17,13 @@
package org.jetbrains.kotlin.cli.jvm.modules
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileSystem
import com.intellij.psi.PsiJavaModule
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModule
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleFinder
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>()
fun addUserModule(module: JavaModule) {
@@ -34,11 +34,10 @@ internal class CliJavaModuleFinder(private val jrtFileSystem: VirtualFileSystem?
get() = systemModules + userModules.values
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? =
jrtFileSystem?.findFileByPath("/modules/$name")?.let(this::findSystemModule)
?: userModules[name]
modulesRoot?.findChild(name)?.let(this::findSystemModule) ?: userModules[name]
private fun findSystemModule(moduleRoot: VirtualFile): JavaModule.Explicit? {
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.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.containers.ConcurrentFactoryMap
import com.intellij.util.io.URLUtil
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)) 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? {
class CoreJrtFileSystem : DeprecatedVirtualFileSystem() {
private val handlers = object : ConcurrentFactoryMap<String, CoreJrtHandler?>() {
override fun create(jdkHomePath: String): CoreJrtHandler? {
val jdkHome = File(jdkHomePath)
val rootUri = URI.create(StandardFileSystems.JRT_PROTOCOL + ":/")
val jrtFsJar = loadJrtFsJar(jdkHome) ?: return null
val fileSystem =
@@ -54,9 +44,43 @@ class CoreJrtFileSystem(private val fileSystem: FileSystem) : DeprecatedVirtualF
val classLoader = URLClassLoader(arrayOf(jrtFsJar.toURI().toURL()), null)
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? =
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.VirtualFile
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.InputStream
import java.io.OutputStream
@@ -27,17 +29,17 @@ import java.nio.file.Files
import java.nio.file.Path
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?
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 =
path.fileName.toString()
override fun getPath(): String =
FileUtil.toSystemIndependentName(path.toString())
FileUtil.toSystemIndependentName(handler.jdkHomePath + URLUtil.JAR_SEPARATOR + path)
override fun isWritable(): Boolean = false
@@ -47,7 +49,7 @@ internal class CoreJrtVirtualFile(private val fileSystem: VirtualFileSystem, pri
override fun getParent(): VirtualFile? {
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> {
@@ -59,7 +61,7 @@ internal class CoreJrtVirtualFile(private val fileSystem: VirtualFileSystem, pri
}
return when {
paths.isEmpty() -> VirtualFile.EMPTY_ARRAY
else -> paths.map { path -> CoreJrtVirtualFile(fileSystem, path) }.toTypedArray()
else -> paths.map { path -> CoreJrtVirtualFile(handler, path) }.toTypedArray()
}
}