diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptConfigurationManager.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptConfigurationManager.kt index 5dbb8f9408c..3974056ed01 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptConfigurationManager.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptConfigurationManager.kt @@ -17,7 +17,6 @@ package org.jetbrains.kotlin.idea.core.script import com.intellij.openapi.components.ServiceManager -import com.intellij.openapi.components.serviceIfCreated import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.util.Key @@ -107,8 +106,8 @@ interface ScriptConfigurationManager { fun getAllScriptsDependenciesClassFilesScope(): GlobalSearchScope fun getAllScriptDependenciesSourcesScope(): GlobalSearchScope - fun getAllScriptsDependenciesClassFiles(): List - fun getAllScriptDependenciesSources(): List + fun getAllScriptsDependenciesClassFiles(): Collection + fun getAllScriptDependenciesSources(): Collection companion object { fun getServiceIfCreated(project: Project): ScriptConfigurationManager? = diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/CompositeScriptConfigurationManager.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/CompositeScriptConfigurationManager.kt index 1378bb9b315..de4c4f6d7d9 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/CompositeScriptConfigurationManager.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/CompositeScriptConfigurationManager.kt @@ -162,10 +162,10 @@ class CompositeScriptConfigurationManager(val project: Project) : ScriptConfigur override fun getAllScriptDependenciesSourcesScope(): GlobalSearchScope = classpathRoots.allDependenciesSourcesScope - override fun getAllScriptsDependenciesClassFiles(): List = + override fun getAllScriptsDependenciesClassFiles(): Collection = classpathRoots.allDependenciesClassFiles - override fun getAllScriptDependenciesSources(): List = + override fun getAllScriptDependenciesSources(): Collection = classpathRoots.allDependenciesSources /////////////////// diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ucache/ScriptClassRootsCache.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ucache/ScriptClassRootsCache.kt index 1ecc912fe3e..9b48c9901bc 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ucache/ScriptClassRootsCache.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ucache/ScriptClassRootsCache.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.idea.core.script.ucache +import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.roots.OrderRootType import com.intellij.openapi.vfs.VirtualFile @@ -12,9 +13,14 @@ import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.NonClasspathDirectoriesScope.compose import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager.Companion.classpathEntryToVfs import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager.Companion.toVfsRoots +import org.jetbrains.kotlin.idea.core.script.ucache.ScriptCacheDependencies.Companion.scriptCacheDependencies +import org.jetbrains.kotlin.idea.core.util.cachedFileAttribute +import org.jetbrains.kotlin.idea.core.util.readObject +import org.jetbrains.kotlin.idea.core.util.writeObject import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper import java.io.File +import java.io.Serializable import java.lang.ref.Reference import java.lang.ref.SoftReference @@ -86,25 +92,25 @@ class ScriptClassRootsCache( val firstScriptSdk: Sdk? get() = sdks.first - val allDependenciesClassFiles: List + val allDependenciesClassFiles: Set - val allDependenciesSources: List + val allDependenciesSources: Set init { allDependenciesClassFiles = mutableSetOf().also { result -> result.addAll(sdks.nonIndexedClassRoots) classes.mapNotNullTo(result) { classpathEntryToVfs(File(it)) } - }.toList() + } allDependenciesSources = mutableSetOf().also { result -> result.addAll(sdks.nonIndexedSourceRoots) sources.mapNotNullTo(result) { classpathEntryToVfs(File(it)) } - }.toList() + } } - val allDependenciesClassFilesScope = compose(allDependenciesClassFiles) + val allDependenciesClassFilesScope = compose(allDependenciesClassFiles.toList()) - val allDependenciesSourcesScope = compose(allDependenciesSources) + val allDependenciesSourcesScope = compose(allDependenciesSources.toList()) fun getScriptConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? = getHeavyScriptInfo(file.path)?.scriptConfiguration @@ -115,9 +121,9 @@ class ScriptClassRootsCache( fun getScriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope = getHeavyScriptInfo(file.path)?.classFilesScope ?: GlobalSearchScope.EMPTY_SCOPE - fun diff(old: ScriptClassRootsCache?): Updates = + fun diff(project: Project, old: ScriptClassRootsCache?): Updates = when (old) { - null -> FullUpdate(this) + null -> FullUpdate(project, this) this -> NotChanged(this) else -> IncrementalUpdates( this, @@ -174,14 +180,15 @@ class ScriptClassRootsCache( get() = hasNewRoots || updatedScripts.isNotEmpty() || hasOldRoots } - class FullUpdate(override val cache: ScriptClassRootsCache) : Updates { + class FullUpdate(private val project: Project, override val cache: ScriptClassRootsCache) : Updates { override val changed: Boolean get() = true override val hasUpdatedScripts: Boolean get() = true override fun isScriptChanged(scriptPath: String): Boolean = true override val hasNewRoots: Boolean get() { - return cache.allDependenciesClassFiles.isNotEmpty() || cache.allDependenciesSources.isNotEmpty() + val scriptCacheDependencies = project.scriptCacheDependencies() + return scriptCacheDependencies?.sameAs(cache) == false } } @@ -193,3 +200,72 @@ class ScriptClassRootsCache( } } +internal class ScriptCacheDependencies( + val classFiles: Set, + val sources: Set +) : Serializable { + constructor(cache: ScriptClassRootsCache) : this( + cache.allDependenciesClassFiles.mapTo(HashSet(), VirtualFile::getPath), + cache.allDependenciesSources.mapTo(HashSet(), VirtualFile::getPath) + ) + + fun sameAs(cache: ScriptClassRootsCache): Boolean { + if (cache.allDependenciesClassFiles.size != classFiles.size || + cache.allDependenciesSources.size != sources.size + ) { + return false + } + + return cache.allDependenciesClassFiles.firstOrNull { + !classFiles.contains(it.path) + } == null && cache.allDependenciesSources.firstOrNull { + !sources.contains(it.path) + } == null + } + + fun save(project: Project) { + project.scriptCacheDependenciesFile()?.scriptCacheDependencies = this + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ScriptCacheDependencies + + return classFiles == other.classFiles && sources == other.sources + } + + override fun hashCode(): Int { + var result = classFiles.hashCode() + result = 31 * result + sources.hashCode() + return result + } + + companion object { + private fun Project.scriptCacheDependenciesFile(): VirtualFile? { + var file = this.projectFile ?: return null + while (!file.isDirectory || file.name == Project.DIRECTORY_STORE_FOLDER) { + file = file.parent + } + + return file + } + + fun Project.scriptCacheDependencies(): ScriptCacheDependencies? = + try { + scriptCacheDependenciesFile()?.scriptCacheDependencies + } catch (e: Exception) { + null + } + + } + +} + +private var VirtualFile.scriptCacheDependencies: ScriptCacheDependencies? by cachedFileAttribute( + name = "kotlin-script-cache-dependencies", + version = 1, + read = { readObject() }, + write = { writeObject(it) } +) diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ucache/ScriptClassRootsUpdater.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ucache/ScriptClassRootsUpdater.kt index 532d0dbc42c..5e7e43cfe77 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ucache/ScriptClassRootsUpdater.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ucache/ScriptClassRootsUpdater.kt @@ -189,7 +189,9 @@ abstract class ScriptClassRootsUpdater( } } - lastSeen = updates.cache + val scriptClassRootsCache = updates.cache + ScriptCacheDependencies(scriptClassRootsCache).save(project) + lastSeen = scriptClassRootsCache } finally { synchronized(this) { scheduledUpdate = null @@ -203,7 +205,7 @@ abstract class ScriptClassRootsUpdater( val new = recreateRootsCache() if (cache.compareAndSet(old, new)) { afterUpdate() - return new.diff(lastSeen) + return new.diff(project, lastSeen) } } } diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/utils.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/utils.kt index c988d03a657..6b5635e1650 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/utils.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/utils.kt @@ -5,7 +5,6 @@ package org.jetbrains.kotlin.idea.scripting.gradle -import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile @@ -31,41 +30,39 @@ fun getGradleScriptInputsStamp( project: Project, file: VirtualFile, givenKtFile: KtFile? = null, - givenTimeStamp: Long = System.currentTimeMillis() + givenTimeStamp: Long = file.modificationStamp ): GradleKotlinScriptConfigurationInputs? { if (!isGradleKotlinScript(file)) return null return runReadAction { - val ktFile = givenKtFile ?: PsiManager.getInstance(project).findFile(file) as? KtFile + val ktFile = givenKtFile ?: PsiManager.getInstance(project).findFile(file) as? KtFile ?: return@runReadAction null - if (ktFile != null) { - val result = StringBuilder() - ktFile.script?.blockExpression - ?.getChildrenOfType() - ?.forEach { - val call = it.children.singleOrNull() as? KtCallExpression - val callRef = call?.firstChild?.text ?: return@forEach - if (callRef in sections) { - result.append(callRef) - val lambda = call.lambdaArguments.singleOrNull() - lambda?.accept(object : PsiRecursiveElementVisitor(false) { - override fun visitElement(element: PsiElement) { - super.visitElement(element) - when (element) { - is PsiWhiteSpace -> if (element.text.contains("\n")) result.append("\n") - is PsiComment -> { - } - is LeafPsiElement -> result.append(element.text) + val result = StringBuilder() + ktFile.script?.blockExpression + ?.getChildrenOfType() + ?.forEach { + val call = it.children.singleOrNull() as? KtCallExpression + val callRef = call?.firstChild?.text ?: return@forEach + if (callRef in sections) { + result.append(callRef) + val lambda = call.lambdaArguments.singleOrNull() + lambda?.accept(object : PsiRecursiveElementVisitor(false) { + override fun visitElement(element: PsiElement) { + super.visitElement(element) + when (element) { + is PsiWhiteSpace -> if (element.text.contains("\n")) result.append("\n") + is PsiComment -> { } + is LeafPsiElement -> result.append(element.text) } - }) - result.append("\n") - } + } + }) + result.append("\n") } + } - val buildRoot = GradleBuildRootsManager.getInstance(project).findScriptBuildRoot(file)?.nearest as? GradleBuildRoot - GradleKotlinScriptConfigurationInputs(result.toString(), givenTimeStamp, buildRoot?.pathPrefix) - } else null + val buildRoot = GradleBuildRootsManager.getInstance(project).findScriptBuildRoot(file)?.nearest as? GradleBuildRoot + GradleKotlinScriptConfigurationInputs(result.toString(), givenTimeStamp, buildRoot?.pathPrefix) } }