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
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<VirtualFile>
fun getAllScriptDependenciesSources(): List<VirtualFile>
fun getAllScriptsDependenciesClassFiles(): Collection<VirtualFile>
fun getAllScriptDependenciesSources(): Collection<VirtualFile>
companion object {
fun getServiceIfCreated(project: Project): ScriptConfigurationManager? =
@@ -162,10 +162,10 @@ class CompositeScriptConfigurationManager(val project: Project) : ScriptConfigur
override fun getAllScriptDependenciesSourcesScope(): GlobalSearchScope =
classpathRoots.allDependenciesSourcesScope
override fun getAllScriptsDependenciesClassFiles(): List<VirtualFile> =
override fun getAllScriptsDependenciesClassFiles(): Collection<VirtualFile> =
classpathRoots.allDependenciesClassFiles
override fun getAllScriptDependenciesSources(): List<VirtualFile> =
override fun getAllScriptDependenciesSources(): Collection<VirtualFile> =
classpathRoots.allDependenciesSources
///////////////////
@@ -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<VirtualFile>
val allDependenciesClassFiles: Set<VirtualFile>
val allDependenciesSources: List<VirtualFile>
val allDependenciesSources: Set<VirtualFile>
init {
allDependenciesClassFiles = mutableSetOf<VirtualFile>().also { result ->
result.addAll(sdks.nonIndexedClassRoots)
classes.mapNotNullTo(result) { classpathEntryToVfs(File(it)) }
}.toList()
}
allDependenciesSources = mutableSetOf<VirtualFile>().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<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 {
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)
}
}
}
@@ -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<KtScriptInitializer>()
?.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<KtScriptInitializer>()
?.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)
}
}