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 com.intellij.util.containers.ConcurrentFactoryMap
|
||||||
import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks
|
import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks
|
||||||
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
||||||
import org.jetbrains.kotlin.idea.core.script.debug
|
|
||||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
internal class ScriptClassRootsCache(
|
internal class ScriptClassRootsCache(
|
||||||
private val project: Project,
|
private val project: Project,
|
||||||
private val all: Map<VirtualFile, ScriptCompilationConfigurationWrapper>
|
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? {
|
private fun getScriptSdk(compilationConfiguration: ScriptCompilationConfigurationWrapper?): Sdk? {
|
||||||
// workaround for mismatched gradle wrapper and plugin version
|
// workaround for mismatched gradle wrapper and plugin version
|
||||||
val javaHome = try {
|
val javaHome = try {
|
||||||
@@ -35,48 +42,21 @@ internal class ScriptClassRootsCache(
|
|||||||
return getAllProjectSdks().find { it.homeDirectory == javaHome }
|
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 firstScriptSdk: Sdk? by lazy {
|
||||||
val firstCachedScript = all.keys.firstOrNull() ?: return@lazy null
|
val firstCachedScript = all.keys.firstOrNull() ?: return@lazy null
|
||||||
return@lazy getScriptSdk(firstCachedScript)
|
return@lazy getScriptSdk(firstCachedScript)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val allSdks by lazy {
|
private fun Sdk.isAlreadyIndexed(): Boolean {
|
||||||
all.mapNotNull { scriptsSdksCache[it.key] }
|
return ModuleManager.getInstance(project).modules.any { ModuleRootManager.getInstance(it).sdk == this }
|
||||||
.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) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val allDependenciesClassFiles by lazy {
|
val allDependenciesClassFiles by lazy {
|
||||||
val sdkFiles = allNonIndexedSdks.flatMap { it.rootProvider.getFiles(OrderRootType.CLASSES).toList() }
|
ScriptClassRootsStorage.getInstance(project).loadClasspathRoots()
|
||||||
val scriptDependenciesClasspath = all.flatMap { it.value.dependenciesClassPath }.distinct()
|
|
||||||
|
|
||||||
sdkFiles + ScriptConfigurationManager.toVfsRoots(scriptDependenciesClasspath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val allDependenciesSources by lazy {
|
val allDependenciesSources by lazy {
|
||||||
val sdkSources = allNonIndexedSdks.flatMap { it.rootProvider.getFiles(OrderRootType.SOURCES).toList() }
|
ScriptClassRootsStorage.getInstance(project).loadSourcesRoots()
|
||||||
val scriptDependenciesSources = all.flatMap { it.value.dependenciesSources }.distinct()
|
|
||||||
|
|
||||||
sdkSources + ScriptConfigurationManager.toVfsRoots(scriptDependenciesSources)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val allDependenciesClassFilesScope by lazy {
|
val allDependenciesClassFilesScope by lazy {
|
||||||
@@ -116,32 +96,55 @@ internal class ScriptClassRootsCache(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun hasNotCachedRoots(compilationConfiguration: ScriptCompilationConfigurationWrapper): Boolean {
|
fun hasNotCachedRoots(compilationConfiguration: ScriptCompilationConfigurationWrapper): Boolean {
|
||||||
val scriptSdk = getScriptSdk(compilationConfiguration)
|
return !ScriptClassRootsStorage.getInstance(project).containsAll(extractRoots(compilationConfiguration))
|
||||||
?: ScriptConfigurationManager.getScriptDefaultSdk(project)
|
}
|
||||||
|
|
||||||
val wasSdkChanged = scriptSdk != null && !allSdks.contains(scriptSdk)
|
private fun extractRoots(configuration: ScriptCompilationConfigurationWrapper): ScriptClassRootsStorage.Companion.ScriptClassRoots {
|
||||||
if (wasSdkChanged) {
|
val scriptSdk = getScriptSdk(configuration) ?: ScriptConfigurationManager.getScriptDefaultSdk(project)
|
||||||
debug { "sdk was changed: $compilationConfiguration" }
|
if (scriptSdk != null && !scriptSdk.isAlreadyIndexed()) {
|
||||||
return true
|
return ScriptClassRootsStorage.Companion.ScriptClassRoots(
|
||||||
|
configuration.dependenciesClassPath,
|
||||||
|
configuration.dependenciesSources,
|
||||||
|
listOf(scriptSdk)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val newClassRoots = ScriptConfigurationManager.toVfsRoots(compilationConfiguration.dependenciesClassPath)
|
return ScriptClassRootsStorage.Companion.ScriptClassRoots(
|
||||||
for (newClassRoot in newClassRoots) {
|
configuration.dependenciesClassPath,
|
||||||
if (!allDependenciesClassFiles.contains(newClassRoot)) {
|
configuration.dependenciesSources,
|
||||||
debug { "class root was changed: $newClassRoot" }
|
emptyList()
|
||||||
return true
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
val rootsStorage = ScriptClassRootsStorage.getInstance(project)
|
||||||
for (newSourceRoot in newSourceRoots) {
|
rootsStorage.save(
|
||||||
if (!allDependenciesSources.contains(newSourceRoot)) {
|
ScriptClassRootsStorage.Companion.ScriptClassRoots(
|
||||||
debug { "source root was changed: $newSourceRoot" }
|
classpath.toList(),
|
||||||
return true
|
sources.toList(),
|
||||||
}
|
sdks.toList()
|
||||||
}
|
)
|
||||||
|
)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun contains(file: VirtualFile) = all.containsKey(file)
|
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>
|
<idea-plugin>
|
||||||
<extensions defaultExtensionNs="com.intellij">
|
<extensions defaultExtensionNs="com.intellij">
|
||||||
|
<projectService serviceImplementation="org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsStorage"/>
|
||||||
|
|
||||||
<trafficLightRendererContributor implementation="org.jetbrains.kotlin.idea.core.script.ScriptTrafficLightRendererContributor"/>
|
<trafficLightRendererContributor implementation="org.jetbrains.kotlin.idea.core.script.ScriptTrafficLightRendererContributor"/>
|
||||||
</extensions>
|
</extensions>
|
||||||
</idea-plugin>
|
</idea-plugin>
|
||||||
Reference in New Issue
Block a user