Store kotlin-script-cache-dependencies as project file attribute

To avoid extra root project scan we need to store already processed script cache dependencies

^KT-41640 Fixed
This commit is contained in:
Vladimir Dolzhenko
2020-10-02 13:23:43 +00:00
parent 941a506885
commit abc4aef403
5 changed files with 118 additions and 44 deletions
@@ -17,7 +17,6 @@
package org.jetbrains.kotlin.idea.core.script package org.jetbrains.kotlin.idea.core.script
import com.intellij.openapi.components.ServiceManager import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.components.serviceIfCreated
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
@@ -107,8 +106,8 @@ interface ScriptConfigurationManager {
fun getAllScriptsDependenciesClassFilesScope(): GlobalSearchScope fun getAllScriptsDependenciesClassFilesScope(): GlobalSearchScope
fun getAllScriptDependenciesSourcesScope(): GlobalSearchScope fun getAllScriptDependenciesSourcesScope(): GlobalSearchScope
fun getAllScriptsDependenciesClassFiles(): List<VirtualFile> fun getAllScriptsDependenciesClassFiles(): Collection<VirtualFile>
fun getAllScriptDependenciesSources(): List<VirtualFile> fun getAllScriptDependenciesSources(): Collection<VirtualFile>
companion object { companion object {
fun getServiceIfCreated(project: Project): ScriptConfigurationManager? = fun getServiceIfCreated(project: Project): ScriptConfigurationManager? =
@@ -162,10 +162,10 @@ class CompositeScriptConfigurationManager(val project: Project) : ScriptConfigur
override fun getAllScriptDependenciesSourcesScope(): GlobalSearchScope = override fun getAllScriptDependenciesSourcesScope(): GlobalSearchScope =
classpathRoots.allDependenciesSourcesScope classpathRoots.allDependenciesSourcesScope
override fun getAllScriptsDependenciesClassFiles(): List<VirtualFile> = override fun getAllScriptsDependenciesClassFiles(): Collection<VirtualFile> =
classpathRoots.allDependenciesClassFiles classpathRoots.allDependenciesClassFiles
override fun getAllScriptDependenciesSources(): List<VirtualFile> = override fun getAllScriptDependenciesSources(): Collection<VirtualFile> =
classpathRoots.allDependenciesSources classpathRoots.allDependenciesSources
/////////////////// ///////////////////
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.idea.core.script.ucache package org.jetbrains.kotlin.idea.core.script.ucache
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.OrderRootType import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
@@ -12,9 +13,14 @@ import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.NonClasspathDirectoriesScope.compose 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.classpathEntryToVfs
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager.Companion.toVfsRoots 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.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import java.io.File import java.io.File
import java.io.Serializable
import java.lang.ref.Reference import java.lang.ref.Reference
import java.lang.ref.SoftReference import java.lang.ref.SoftReference
@@ -86,25 +92,25 @@ class ScriptClassRootsCache(
val firstScriptSdk: Sdk? val firstScriptSdk: Sdk?
get() = sdks.first get() = sdks.first
val allDependenciesClassFiles: List<VirtualFile> val allDependenciesClassFiles: Set<VirtualFile>
val allDependenciesSources: List<VirtualFile> val allDependenciesSources: Set<VirtualFile>
init { init {
allDependenciesClassFiles = mutableSetOf<VirtualFile>().also { result -> allDependenciesClassFiles = mutableSetOf<VirtualFile>().also { result ->
result.addAll(sdks.nonIndexedClassRoots) result.addAll(sdks.nonIndexedClassRoots)
classes.mapNotNullTo(result) { classpathEntryToVfs(File(it)) } classes.mapNotNullTo(result) { classpathEntryToVfs(File(it)) }
}.toList() }
allDependenciesSources = mutableSetOf<VirtualFile>().also { result -> allDependenciesSources = mutableSetOf<VirtualFile>().also { result ->
result.addAll(sdks.nonIndexedSourceRoots) result.addAll(sdks.nonIndexedSourceRoots)
sources.mapNotNullTo(result) { classpathEntryToVfs(File(it)) } 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? = fun getScriptConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? =
getHeavyScriptInfo(file.path)?.scriptConfiguration getHeavyScriptInfo(file.path)?.scriptConfiguration
@@ -115,9 +121,9 @@ class ScriptClassRootsCache(
fun getScriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope = fun getScriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope =
getHeavyScriptInfo(file.path)?.classFilesScope ?: GlobalSearchScope.EMPTY_SCOPE getHeavyScriptInfo(file.path)?.classFilesScope ?: GlobalSearchScope.EMPTY_SCOPE
fun diff(old: ScriptClassRootsCache?): Updates = fun diff(project: Project, old: ScriptClassRootsCache?): Updates =
when (old) { when (old) {
null -> FullUpdate(this) null -> FullUpdate(project, this)
this -> NotChanged(this) this -> NotChanged(this)
else -> IncrementalUpdates( else -> IncrementalUpdates(
this, this,
@@ -174,14 +180,15 @@ class ScriptClassRootsCache(
get() = hasNewRoots || updatedScripts.isNotEmpty() || hasOldRoots 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 changed: Boolean get() = true
override val hasUpdatedScripts: Boolean get() = true override val hasUpdatedScripts: Boolean get() = true
override fun isScriptChanged(scriptPath: String): Boolean = true override fun isScriptChanged(scriptPath: String): Boolean = true
override val hasNewRoots: Boolean override val hasNewRoots: Boolean
get() { 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<String>,
val sources: Set<String>
) : 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) }
)
@@ -189,7 +189,9 @@ abstract class ScriptClassRootsUpdater(
} }
} }
lastSeen = updates.cache val scriptClassRootsCache = updates.cache
ScriptCacheDependencies(scriptClassRootsCache).save(project)
lastSeen = scriptClassRootsCache
} finally { } finally {
synchronized(this) { synchronized(this) {
scheduledUpdate = null scheduledUpdate = null
@@ -203,7 +205,7 @@ abstract class ScriptClassRootsUpdater(
val new = recreateRootsCache() val new = recreateRootsCache()
if (cache.compareAndSet(old, new)) { if (cache.compareAndSet(old, new)) {
afterUpdate() afterUpdate()
return new.diff(lastSeen) return new.diff(project, lastSeen)
} }
} }
} }
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.idea.scripting.gradle package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
@@ -31,41 +30,39 @@ fun getGradleScriptInputsStamp(
project: Project, project: Project,
file: VirtualFile, file: VirtualFile,
givenKtFile: KtFile? = null, givenKtFile: KtFile? = null,
givenTimeStamp: Long = System.currentTimeMillis() givenTimeStamp: Long = file.modificationStamp
): GradleKotlinScriptConfigurationInputs? { ): GradleKotlinScriptConfigurationInputs? {
if (!isGradleKotlinScript(file)) return null if (!isGradleKotlinScript(file)) return null
return runReadAction { 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()
val result = StringBuilder() ktFile.script?.blockExpression
ktFile.script?.blockExpression ?.getChildrenOfType<KtScriptInitializer>()
?.getChildrenOfType<KtScriptInitializer>() ?.forEach {
?.forEach { val call = it.children.singleOrNull() as? KtCallExpression
val call = it.children.singleOrNull() as? KtCallExpression val callRef = call?.firstChild?.text ?: return@forEach
val callRef = call?.firstChild?.text ?: return@forEach if (callRef in sections) {
if (callRef in sections) { result.append(callRef)
result.append(callRef) val lambda = call.lambdaArguments.singleOrNull()
val lambda = call.lambdaArguments.singleOrNull() lambda?.accept(object : PsiRecursiveElementVisitor(false) {
lambda?.accept(object : PsiRecursiveElementVisitor(false) { override fun visitElement(element: PsiElement) {
override fun visitElement(element: PsiElement) { super.visitElement(element)
super.visitElement(element) when (element) {
when (element) { is PsiWhiteSpace -> if (element.text.contains("\n")) result.append("\n")
is PsiWhiteSpace -> if (element.text.contains("\n")) result.append("\n") is PsiComment -> {
is PsiComment -> {
}
is LeafPsiElement -> result.append(element.text)
} }
is LeafPsiElement -> result.append(element.text)
} }
}) }
result.append("\n") })
} result.append("\n")
} }
}
val buildRoot = GradleBuildRootsManager.getInstance(project).findScriptBuildRoot(file)?.nearest as? GradleBuildRoot val buildRoot = GradleBuildRootsManager.getInstance(project).findScriptBuildRoot(file)?.nearest as? GradleBuildRoot
GradleKotlinScriptConfigurationInputs(result.toString(), givenTimeStamp, buildRoot?.pathPrefix) GradleKotlinScriptConfigurationInputs(result.toString(), givenTimeStamp, buildRoot?.pathPrefix)
} else null
} }
} }