Implement persistent storage for script class path roots (KT-35886)
Save absolute paths instead of getCanonicalPath usages ^KT-34444 ^KT-35886 Fixed
This commit is contained in:
+55
-52
@@ -17,13 +17,20 @@ import com.intellij.psi.search.NonClasspathDirectoriesScope
|
||||
import com.intellij.util.containers.ConcurrentFactoryMap
|
||||
import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks
|
||||
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
||||
import org.jetbrains.kotlin.idea.core.script.debug
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||
import java.io.File
|
||||
|
||||
internal class ScriptClassRootsCache(
|
||||
private val project: Project,
|
||||
private val all: Map<VirtualFile, ScriptCompilationConfigurationWrapper>
|
||||
) {
|
||||
private val scriptsSdksCache: Map<VirtualFile, Sdk?> =
|
||||
ConcurrentFactoryMap.createWeakMap { file ->
|
||||
return@createWeakMap getScriptSdk(all[file]) ?: ScriptConfigurationManager.getScriptDefaultSdk(project)
|
||||
}
|
||||
|
||||
fun getScriptSdk(file: VirtualFile): Sdk? = scriptsSdksCache[file]
|
||||
|
||||
private fun getScriptSdk(compilationConfiguration: ScriptCompilationConfigurationWrapper?): Sdk? {
|
||||
// workaround for mismatched gradle wrapper and plugin version
|
||||
val javaHome = try {
|
||||
@@ -35,48 +42,21 @@ internal class ScriptClassRootsCache(
|
||||
return getAllProjectSdks().find { it.homeDirectory == javaHome }
|
||||
}
|
||||
|
||||
private val scriptsSdksCache: Map<VirtualFile, Sdk?> =
|
||||
ConcurrentFactoryMap.createWeakMap { file ->
|
||||
val compilationConfiguration = all[file]
|
||||
return@createWeakMap getScriptSdk(compilationConfiguration)
|
||||
?: ScriptConfigurationManager.getScriptDefaultSdk(project)
|
||||
}
|
||||
|
||||
fun getScriptSdk(file: VirtualFile): Sdk? = scriptsSdksCache[file]
|
||||
|
||||
val firstScriptSdk: Sdk? by lazy {
|
||||
val firstCachedScript = all.keys.firstOrNull() ?: return@lazy null
|
||||
return@lazy getScriptSdk(firstCachedScript)
|
||||
}
|
||||
|
||||
private val allSdks by lazy {
|
||||
all.mapNotNull { scriptsSdksCache[it.key] }
|
||||
.distinct()
|
||||
}
|
||||
|
||||
private val allNonIndexedSdks by lazy {
|
||||
all.mapNotNull { scriptsSdksCache[it.key] }
|
||||
.filterNonModuleSdk()
|
||||
.distinct()
|
||||
}
|
||||
|
||||
private fun List<Sdk>.filterNonModuleSdk(): List<Sdk> {
|
||||
val moduleSdks = ModuleManager.getInstance(project).modules.map { ModuleRootManager.getInstance(it).sdk }
|
||||
return filterNot { moduleSdks.contains(it) }
|
||||
private fun Sdk.isAlreadyIndexed(): Boolean {
|
||||
return ModuleManager.getInstance(project).modules.any { ModuleRootManager.getInstance(it).sdk == this }
|
||||
}
|
||||
|
||||
val allDependenciesClassFiles by lazy {
|
||||
val sdkFiles = allNonIndexedSdks.flatMap { it.rootProvider.getFiles(OrderRootType.CLASSES).toList() }
|
||||
val scriptDependenciesClasspath = all.flatMap { it.value.dependenciesClassPath }.distinct()
|
||||
|
||||
sdkFiles + ScriptConfigurationManager.toVfsRoots(scriptDependenciesClasspath)
|
||||
ScriptClassRootsStorage.getInstance(project).loadClasspathRoots()
|
||||
}
|
||||
|
||||
val allDependenciesSources by lazy {
|
||||
val sdkSources = allNonIndexedSdks.flatMap { it.rootProvider.getFiles(OrderRootType.SOURCES).toList() }
|
||||
val scriptDependenciesSources = all.flatMap { it.value.dependenciesSources }.distinct()
|
||||
|
||||
sdkSources + ScriptConfigurationManager.toVfsRoots(scriptDependenciesSources)
|
||||
ScriptClassRootsStorage.getInstance(project).loadSourcesRoots()
|
||||
}
|
||||
|
||||
val allDependenciesClassFilesScope by lazy {
|
||||
@@ -116,32 +96,55 @@ internal class ScriptClassRootsCache(
|
||||
}
|
||||
|
||||
fun hasNotCachedRoots(compilationConfiguration: ScriptCompilationConfigurationWrapper): Boolean {
|
||||
val scriptSdk = getScriptSdk(compilationConfiguration)
|
||||
?: ScriptConfigurationManager.getScriptDefaultSdk(project)
|
||||
return !ScriptClassRootsStorage.getInstance(project).containsAll(extractRoots(compilationConfiguration))
|
||||
}
|
||||
|
||||
val wasSdkChanged = scriptSdk != null && !allSdks.contains(scriptSdk)
|
||||
if (wasSdkChanged) {
|
||||
debug { "sdk was changed: $compilationConfiguration" }
|
||||
return true
|
||||
private fun extractRoots(configuration: ScriptCompilationConfigurationWrapper): ScriptClassRootsStorage.Companion.ScriptClassRoots {
|
||||
val scriptSdk = getScriptSdk(configuration) ?: ScriptConfigurationManager.getScriptDefaultSdk(project)
|
||||
if (scriptSdk != null && !scriptSdk.isAlreadyIndexed()) {
|
||||
return ScriptClassRootsStorage.Companion.ScriptClassRoots(
|
||||
configuration.dependenciesClassPath,
|
||||
configuration.dependenciesSources,
|
||||
listOf(scriptSdk)
|
||||
)
|
||||
}
|
||||
|
||||
val newClassRoots = ScriptConfigurationManager.toVfsRoots(compilationConfiguration.dependenciesClassPath)
|
||||
for (newClassRoot in newClassRoots) {
|
||||
if (!allDependenciesClassFiles.contains(newClassRoot)) {
|
||||
debug { "class root was changed: $newClassRoot" }
|
||||
return true
|
||||
return ScriptClassRootsStorage.Companion.ScriptClassRoots(
|
||||
configuration.dependenciesClassPath,
|
||||
configuration.dependenciesSources,
|
||||
emptyList()
|
||||
)
|
||||
}
|
||||
|
||||
init {
|
||||
saveClassRootsToStorage()
|
||||
}
|
||||
|
||||
private fun saveClassRootsToStorage() {
|
||||
if (all.isEmpty()) return
|
||||
|
||||
val classpath = mutableSetOf<File>()
|
||||
val sources = mutableSetOf<File>()
|
||||
val sdks = mutableSetOf<Sdk>()
|
||||
|
||||
for ((file, configuration) in all) {
|
||||
val scriptSdk = getScriptSdk(file)
|
||||
if (scriptSdk != null && !scriptSdk.isAlreadyIndexed()) {
|
||||
sdks.add(scriptSdk)
|
||||
}
|
||||
|
||||
classpath.addAll(configuration.dependenciesClassPath)
|
||||
sources.addAll(configuration.dependenciesSources)
|
||||
}
|
||||
|
||||
val newSourceRoots = ScriptConfigurationManager.toVfsRoots(compilationConfiguration.dependenciesSources)
|
||||
for (newSourceRoot in newSourceRoots) {
|
||||
if (!allDependenciesSources.contains(newSourceRoot)) {
|
||||
debug { "source root was changed: $newSourceRoot" }
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
val rootsStorage = ScriptClassRootsStorage.getInstance(project)
|
||||
rootsStorage.save(
|
||||
ScriptClassRootsStorage.Companion.ScriptClassRoots(
|
||||
classpath.toList(),
|
||||
sources.toList(),
|
||||
sdks.toList()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun contains(file: VirtualFile) = all.containsKey(file)
|
||||
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.core.script.configuration.utils
|
||||
|
||||
import com.intellij.openapi.components.*
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.ProjectJdkTable
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.roots.OrderRootType
|
||||
import com.intellij.openapi.vfs.StandardFileSystems
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.util.io.URLUtil
|
||||
import com.intellij.util.xmlb.XmlSerializerUtil
|
||||
import org.jetbrains.kotlin.idea.core.script.debug
|
||||
import java.io.File
|
||||
|
||||
@State(
|
||||
name = "ScriptClassRootsStorage",
|
||||
storages = [Storage(StoragePathMacros.CACHE_FILE)]
|
||||
)
|
||||
class ScriptClassRootsStorage : PersistentStateComponent<ScriptClassRootsStorage> {
|
||||
private var classpath: Set<String> = hashSetOf()
|
||||
private var sources: Set<String> = hashSetOf()
|
||||
private var sdks: Set<String> = hashSetOf()
|
||||
|
||||
override fun getState(): ScriptClassRootsStorage? {
|
||||
return this
|
||||
}
|
||||
|
||||
override fun loadState(state: ScriptClassRootsStorage) {
|
||||
XmlSerializerUtil.copyBean(state, this)
|
||||
}
|
||||
|
||||
private fun toStringNames(sdks: Collection<Sdk>): Set<String> {
|
||||
return sdks.map { it.name }.toSet()
|
||||
}
|
||||
|
||||
private fun toStringValues(prop: Collection<File>): Set<String> {
|
||||
return prop.mapNotNull {
|
||||
when {
|
||||
it.isDirectory -> it.absolutePath
|
||||
it.isFile -> it.absolutePath + URLUtil.JAR_SEPARATOR
|
||||
else -> null
|
||||
}
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
private fun toVirtualFiles(prop: Set<String>, sources: Boolean): List<VirtualFile> {
|
||||
val rootType = if (sources) OrderRootType.SOURCES else OrderRootType.CLASSES
|
||||
return prop.mapNotNull { ProjectJdkTable.getInstance().findJdk(it) }
|
||||
.flatMap { it.rootProvider.getFiles(rootType).toList() }
|
||||
}
|
||||
|
||||
private fun toVirtualFiles(prop: Collection<String>): List<VirtualFile> {
|
||||
return prop.mapNotNull {
|
||||
StandardFileSystems.local()?.findFileByPath(it)?.let {
|
||||
return@mapNotNull it
|
||||
}
|
||||
|
||||
StandardFileSystems.jar()?.findFileByPath(it)?.let {
|
||||
return@mapNotNull it
|
||||
}
|
||||
|
||||
// TODO: report this somewhere, but do not throw: assert(res != null, { "Invalid classpath entry '$this': exists: ${exists()}, is directory: $isDirectory, is file: $isFile" })
|
||||
|
||||
null
|
||||
}.distinct()
|
||||
}
|
||||
|
||||
fun containsAll(configuration: ScriptClassRoots): Boolean {
|
||||
if (!classpath.containsAll(toStringValues(configuration.classpathFiles))) {
|
||||
debug { "class roots were changed: old = $classpath, new = ${configuration.classpathFiles}" }
|
||||
return false
|
||||
}
|
||||
if (!sources.containsAll(toStringValues(configuration.sourcesFiles))) {
|
||||
debug { "source roots were changed: old = $sources, new = ${configuration.sourcesFiles}" }
|
||||
return false
|
||||
}
|
||||
if (!sdks.containsAll(toStringNames(configuration.sdks))) {
|
||||
debug { "sdk classes were changed: old = $sdks, new = ${configuration.sdks.map { it.homePath }}" }
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun save(configuration: ScriptClassRoots) {
|
||||
// TODO: do not drop all storage on save: KT-34444
|
||||
classpath = toStringValues(configuration.classpathFiles)
|
||||
sources = toStringValues(configuration.sourcesFiles)
|
||||
|
||||
sdks = toStringNames(configuration.sdks)
|
||||
}
|
||||
|
||||
fun loadClasspathRoots(): List<VirtualFile> {
|
||||
return toVirtualFiles(sdks, false) + toVirtualFiles(classpath)
|
||||
}
|
||||
|
||||
fun loadSourcesRoots(): List<VirtualFile> {
|
||||
return toVirtualFiles(sdks, true) + toVirtualFiles(sources)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getInstance(project: Project): ScriptClassRootsStorage =
|
||||
ServiceManager.getService(project, ScriptClassRootsStorage::class.java)
|
||||
|
||||
data class ScriptClassRoots(
|
||||
val classpathFiles: List<File>,
|
||||
val sourcesFiles: List<File>,
|
||||
val sdks: List<Sdk>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
<idea-plugin>
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<projectService serviceImplementation="org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsStorage"/>
|
||||
|
||||
<trafficLightRendererContributor implementation="org.jetbrains.kotlin.idea.core.script.ScriptTrafficLightRendererContributor"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
Reference in New Issue
Block a user