ide, scripting: rework script configuration caching, introduce persistent fs cache
## Simplify ScriptConfigurationManager mental model Everything related to cache managing moved to `org.jetbrains.kotlin.idea.core.script.configuration`. DefaultScriptConfigurationManager slitted into: - `AbstractScriptConfigurationManager` - `DefaultScriptConfigurationManager` - `ScriptClassRootsCache` The main idea is to simplify the mental model of ScriptConfigurationManager. Concrete implementation should provide just two things: - `createCache(): ScriptConfigurationCache` that should return cache implementation with `get` and `set` methods - `reloadOutOfDateConfiguration(...)` will be called on [cache] miss or when [file] is changed. Implementation should initiate loading of [file]'s script configuration and call [saveChangedConfiguration] immediately or in some future (e.g. after user will click "apply context" or/and configuration will be calculated by some background thread). Everything around it is implemented in `AbstractScriptConfigurationManager`. `ScriptClassRootsCache` is extracted from `DefaultScriptConfigurationManager`: this simplifies `AbstractScriptConfigurationManager` Also it simplifies ScriptClassRootsCache reset: we can just drop old `ScriptClassRootsCache` and create new one instead of resetting all internals of `ScriptClassRootsCache`. Please see KDoc of these classes for more details: - AbstractScriptConfigurationManager - DefaultScriptConfigurationManager - ScriptClassRootsCache ## Cache and up-to-date checks Writer should put related inputs snapshot for loaded configuration. This allows reader make up-to-date check for existed entry to avoid starting loading process for same inputs. Cache entry may be marked out-of-date. Also it can be marked up-to-date, but it means that we have up-to-date loaded configuration, ScriptConfigurationSnapshot.configuration will still contain old applied configuration. todo: make it more clear by splitting cache entry into loaded and applied configuration with it's own inputs. ## Loaders Previously both sync and background loaders may be started for same files - this was useful, but not required anymore and at the same time greatly complicates the model. So, it was dropped and now exactly one loader should work for given file (first applicable loader will be called). Also `ScriptConfigurationLoadingContext` is introduced to limit access to the `ScriptConfigurationManager` internals. ## Listeners and updating configuration This things is introduces: - `ScriptChangesNotifier` - `ScriptChangeListener` - `ScriptConfigurationUpdater` `ScriptChangesNotifier` will call first applicable `ScriptChangeListener` when editor is activated or document changed. (it treated as applicable if [editorActivated] or [documentChanged] will return true). Listener may call [ScriptConfigurationUpdater] to invalidate configuration and schedule reloading. Plugins may override default listening logic by adding it's oven `ScriptChangeListener`. `ScriptConfigurationUpdater` was extracted from `ScriptConfigurationManagerImpl`. ## Fixed issues and implement persistent FS cache - Script configuration was not reloaded if query for same file already in progress or ready but not yet applied - Don't start async loaders for outsider files - Unused imports: don't start loading script configuration if the result will be not applied automatically - Fix cases when virtual file can be null See `DefaultScriptConfigurationManager.reloadOutOfDateConfiguration` KDoc for more details on dealing with concurrent operations. ## Background executor and progress indicator This things changed and fixed: - progress displayed not only when we have more then 3 loading tasks, but also after 1sec of loading (including case with 1 loading task for example) - current file is displayed under the progress bar - progress indication added: it works similar to indexing indicator. When progress bar is displayed, max is fixed. When all work done for this max, progress will be started from zero and new max is fixed, and so on - cancel will drop all loading operation, not just current - concurrency issues fixed: EA-5244265 and other related to switching between silent and under progress worker See `BackgroundExecutor` for more details. ## Gradle specific behavior We have plans to extract separate implementation for Gradle scripts, but it would be better to support something at this point. For now, we have custom listener, loader and up-to-date check for default configuration manager: - listener will do forced reload on editor activation, even it is already up-to-date this is required for Gradle scripts, since it's classpath may depend on other files (`.properties` for example) - loader will force save fails before running loader. also it will skip loading if `kotlin.gradle.scripts.useIdeaProjectImport` registry key is enabled - loader will return inputs with overridden up-to-date checks: file will be considered out-of-date only when `buildscript` or `plugins` blocks are changed
This commit is contained in:
committed by
Natalia Selezneva
parent
915c9c367c
commit
9d2cc484aa
+1
-1
@@ -72,7 +72,7 @@ object KotlinHighlightingUtil {
|
||||
@Suppress("DEPRECATION")
|
||||
private fun shouldHighlightScript(ktFile: KtFile): Boolean {
|
||||
if (isRunningInCidrIde) return false // There is no Java support in CIDR. So do not highlight errors in KTS if running in CIDR.
|
||||
if (!ScriptConfigurationManager.getInstance(ktFile.project).isConfigurationCached(ktFile)) return false
|
||||
if (!ScriptConfigurationManager.getInstance(ktFile.project).hasConfiguration(ktFile)) return false
|
||||
if (IdeScriptReportSink.getReports(ktFile).any { it.severity == ScriptDiagnostic.Severity.FATAL }) {
|
||||
return false
|
||||
}
|
||||
|
||||
+45
-32
@@ -28,7 +28,10 @@ import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.util.io.URLUtil
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptConfigurationManager
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.AbstractScriptConfigurationManager
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigurationSnapshot
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.LoadedScriptConfiguration
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
|
||||
@@ -58,48 +61,58 @@ class IdeScriptDependenciesProvider(project: Project) : ScriptDependenciesProvid
|
||||
/**
|
||||
* Facade for loading and caching Kotlin script files configuration.
|
||||
*
|
||||
* Loaded configuration will be cached in [memoryCache] and [fileAttributesCache].
|
||||
* This service also starts indexing of new dependency roots and runs highlighting
|
||||
* of opened files.
|
||||
* of opened files when configuration will be loaded or updated.
|
||||
*/
|
||||
interface ScriptConfigurationManager {
|
||||
/**
|
||||
* Get cached configuration for [file] or load it.
|
||||
* May return null even configuration was loaded but was not yet applied.
|
||||
*/
|
||||
fun getConfiguration(file: KtFile): ScriptCompilationConfigurationWrapper?
|
||||
|
||||
@Deprecated("Use getScriptClasspath(KtFile) instead")
|
||||
fun getScriptClasspath(file: VirtualFile): List<VirtualFile>
|
||||
|
||||
/**
|
||||
* @see [getConfiguration]
|
||||
*/
|
||||
fun getScriptClasspath(file: KtFile): List<VirtualFile>
|
||||
|
||||
/**
|
||||
* Check if configuration is already cached for [file] (in cache or FileAttributes).
|
||||
* The result may be true, even cached configuration is considered out-of-date.
|
||||
*
|
||||
* Supposed to be used to switch highlighting off for scripts without configuration
|
||||
* to avoid all file being highlighted in red.
|
||||
*/
|
||||
fun hasConfiguration(file: KtFile): Boolean
|
||||
|
||||
/**
|
||||
* See [ScriptConfigurationUpdater].
|
||||
*/
|
||||
val updater: ScriptConfigurationUpdater
|
||||
|
||||
/**
|
||||
* Clear all caches and re-highlighting opened scripts
|
||||
*/
|
||||
fun clearConfigurationCachesAndRehighlight()
|
||||
|
||||
/**
|
||||
* Save configurations into cache.
|
||||
* Start indexing for new class/source roots.
|
||||
* Re-highlight opened scripts with changed configuration.
|
||||
*/
|
||||
fun saveCompilationConfigurationAfterImport(files: List<Pair<VirtualFile, ScriptCompilationConfigurationResult>>)
|
||||
fun saveCompilationConfigurationAfterImport(files: List<Pair<VirtualFile, LoadedScriptConfiguration>>)
|
||||
|
||||
/**
|
||||
* Start configuration update for files if configuration isn't up to date.
|
||||
* Start indexing for new class/source roots.
|
||||
*
|
||||
* @return true if update was started for any file, false if all configurations are cached
|
||||
*/
|
||||
fun updateConfigurationsIfNotCached(files: List<KtFile>): Boolean
|
||||
///////////////
|
||||
// classpath roots info:
|
||||
|
||||
/**
|
||||
* Check if configuration is already cached for [file] (in cache or FileAttributes).
|
||||
* Don't check if file was changed after the last update.
|
||||
* Supposed to be used to switch highlighting off for scripts without configuration.
|
||||
* to avoid all file being highlighted in red.
|
||||
*/
|
||||
fun isConfigurationCached(file: KtFile): Boolean
|
||||
|
||||
/**
|
||||
* Clear configuration caches
|
||||
* Start re-highlighting for opened scripts
|
||||
*/
|
||||
fun clearConfigurationCachesAndRehighlight()
|
||||
|
||||
@Deprecated("Use getScriptClasspath(KtFile) instead")
|
||||
fun getScriptClasspath(file: VirtualFile): List<VirtualFile>
|
||||
|
||||
fun getScriptClasspath(file: KtFile): List<VirtualFile>
|
||||
fun getConfiguration(file: KtFile): ScriptCompilationConfigurationWrapper?
|
||||
fun getScriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope
|
||||
fun getScriptSdk(file: VirtualFile): Sdk?
|
||||
fun getFirstScriptsSdk(): Sdk?
|
||||
|
||||
fun getScriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope
|
||||
|
||||
fun getAllScriptsDependenciesClassFilesScope(): GlobalSearchScope
|
||||
fun getAllScriptDependenciesSourcesScope(): GlobalSearchScope
|
||||
fun getAllScriptsDependenciesClassFiles(): List<VirtualFile>
|
||||
@@ -146,7 +159,7 @@ interface ScriptConfigurationManager {
|
||||
|
||||
@TestOnly
|
||||
fun updateScriptDependenciesSynchronously(file: PsiFile, project: Project) {
|
||||
(getInstance(project) as DefaultScriptConfigurationManager).updateScriptDependenciesSynchronously(file)
|
||||
(getInstance(project) as AbstractScriptConfigurationManager).updateScriptDependenciesSynchronously(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-4
@@ -17,6 +17,10 @@ import com.intellij.ui.HyperlinkLabel
|
||||
import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings
|
||||
import org.jetbrains.kotlin.psi.UserDataProperty
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||
import java.awt.event.ComponentAdapter
|
||||
import java.awt.event.ComponentEvent
|
||||
import java.awt.event.ComponentListener
|
||||
import javax.swing.JPanel
|
||||
|
||||
fun VirtualFile.removeScriptDependenciesNotificationPanel(project: Project) {
|
||||
withSelectedEditor(project) { manager ->
|
||||
@@ -30,7 +34,8 @@ fun VirtualFile.removeScriptDependenciesNotificationPanel(project: Project) {
|
||||
fun VirtualFile.addScriptDependenciesNotificationPanel(
|
||||
compilationConfigurationResult: ScriptCompilationConfigurationWrapper,
|
||||
project: Project,
|
||||
onClick: (ScriptCompilationConfigurationWrapper) -> Unit
|
||||
onClick: () -> Unit,
|
||||
onHide: () -> Unit
|
||||
) {
|
||||
withSelectedEditor(project) { manager ->
|
||||
val existingPanel = notificationPanel
|
||||
@@ -45,6 +50,11 @@ fun VirtualFile.addScriptDependenciesNotificationPanel(
|
||||
|
||||
val panel = NewScriptDependenciesNotificationPanel(onClick, compilationConfigurationResult, project)
|
||||
notificationPanel = panel
|
||||
panel.addComponentListener(object: ComponentAdapter() {
|
||||
override fun componentHidden(e: ComponentEvent) {
|
||||
onHide()
|
||||
}
|
||||
})
|
||||
manager.addTopComponent(this, panel)
|
||||
}
|
||||
}
|
||||
@@ -63,7 +73,7 @@ private fun VirtualFile.withSelectedEditor(project: Project, f: FileEditor.(File
|
||||
private var FileEditor.notificationPanel: NewScriptDependenciesNotificationPanel? by UserDataProperty<FileEditor, NewScriptDependenciesNotificationPanel>(Key.create("script.dependencies.panel"))
|
||||
|
||||
private class NewScriptDependenciesNotificationPanel(
|
||||
onClick: (ScriptCompilationConfigurationWrapper) -> Unit,
|
||||
onClick: () -> Unit,
|
||||
val compilationConfigurationResult: ScriptCompilationConfigurationWrapper,
|
||||
project: Project
|
||||
) : EditorNotificationPanel() {
|
||||
@@ -71,11 +81,11 @@ private class NewScriptDependenciesNotificationPanel(
|
||||
init {
|
||||
setText("There is a new script context available.")
|
||||
createComponentActionLabel("Apply context") {
|
||||
onClick(compilationConfigurationResult)
|
||||
onClick()
|
||||
}
|
||||
|
||||
createComponentActionLabel("Enable auto-reload") {
|
||||
onClick(compilationConfigurationResult)
|
||||
onClick()
|
||||
KotlinScriptingSettings.getInstance(project).isAutoReloadEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ class ScriptTrafficLightRendererContributor : TrafficLightRendererContributor {
|
||||
if (!ScriptDefinitionsManager.getInstance(file.project).isReady()) {
|
||||
status.reasonWhySuspended = "Loading kotlin script definitions"
|
||||
status.errorAnalyzingFinished = false
|
||||
} else if (!ScriptConfigurationManager.getInstance(project).isConfigurationCached(file)) {
|
||||
} else if (!ScriptConfigurationManager.getInstance(project).hasConfiguration(file)) {
|
||||
status.reasonWhySuspended = "Loading kotlin script dependencies"
|
||||
status.errorAnalyzingFinished = false
|
||||
}
|
||||
|
||||
+283
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
|
||||
import com.intellij.openapi.extensions.Extensions
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiElementFinder
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.idea.core.script.*
|
||||
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager.Companion.toVfsRoots
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigurationSnapshot
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationCache
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsIndexer
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.utils.getKtFile
|
||||
import org.jetbrains.kotlin.idea.core.util.EDT
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
|
||||
import org.jetbrains.kotlin.scripting.definitions.isNonScript
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
/**
|
||||
* Abstract [ScriptConfigurationManager] implementation based on [cache] and [reloadOutOfDateConfiguration].
|
||||
* Among this two methods concrete implementation should provide script changes listening (by calling [updater] on some event).
|
||||
*
|
||||
* Basically all requests routed to [cache]. If there is no entry in [cache] or it is considered out-of-date,
|
||||
* then [reloadOutOfDateConfiguration] will be called, which, in turn, should call [saveChangedConfiguration]
|
||||
* immediately or in some future (e.g. after user will click "apply context" or/and configuration will
|
||||
* be calculated by some background thread).
|
||||
*
|
||||
* [classpathRoots] will be calculated lazily based on [cache]d configurations.
|
||||
* Every change in [cache] will invalidate [classpathRoots] cache.
|
||||
* Some internal state changes in [cache] may also invalidate [classpathRoots] by calling [clearClassRootsCaches]
|
||||
* (for example, when cache loaded from FS to memory)
|
||||
*/
|
||||
internal abstract class AbstractScriptConfigurationManager(
|
||||
protected val project: Project
|
||||
) : ScriptConfigurationManager {
|
||||
protected val rootsIndexer = ScriptClassRootsIndexer(project)
|
||||
|
||||
@Suppress("LeakingThis")
|
||||
protected val cache: ScriptConfigurationCache = createCache()
|
||||
|
||||
protected abstract fun createCache(): ScriptConfigurationCache
|
||||
|
||||
/**
|
||||
* Will be called on [cache] miss or when [file] is changed.
|
||||
* Implementation should initiate loading of [file]'s script configuration and call [saveChangedConfiguration]
|
||||
* immediately or in some future
|
||||
* (e.g. after user will click "apply context" or/and configuration will be calculated by some background thread).
|
||||
*
|
||||
* @param isFirstLoad may be set explicitly for optimization reasons (to avoid expensive fs cache access)
|
||||
* @param loadEvenWillNotBeApplied may should be set to false only on requests from particular editor, when
|
||||
* user can see potential notification and accept new configuration. In other cases this should be `false` since
|
||||
* loaded configuration will be just leaved in hidden user notification and cannot be used in any way.
|
||||
* @param forceSync should be used in tests only
|
||||
*/
|
||||
protected abstract fun reloadOutOfDateConfiguration(
|
||||
file: KtFile,
|
||||
isFirstLoad: Boolean = getCachedConfiguration(file.originalFile.virtualFile) == null,
|
||||
loadEvenWillNotBeApplied: Boolean = false,
|
||||
forceSync: Boolean = false
|
||||
)
|
||||
|
||||
@Deprecated("Use getScriptClasspath(KtFile) instead")
|
||||
override fun getScriptClasspath(file: VirtualFile): List<VirtualFile> {
|
||||
val ktFile = project.getKtFile(file) ?: return emptyList()
|
||||
return getScriptClasspath(ktFile)
|
||||
}
|
||||
|
||||
override fun getScriptClasspath(file: KtFile): List<VirtualFile> =
|
||||
toVfsRoots(getConfiguration(file)?.dependenciesClassPath.orEmpty())
|
||||
|
||||
fun getCachedConfiguration(file: VirtualFile?): CachedConfigurationSnapshot? {
|
||||
if (file == null) return null
|
||||
return cache[file]
|
||||
}
|
||||
|
||||
override fun hasConfiguration(file: KtFile): Boolean {
|
||||
return getCachedConfiguration(file.originalFile.virtualFile) != null
|
||||
}
|
||||
|
||||
override fun getConfiguration(file: KtFile): ScriptCompilationConfigurationWrapper? {
|
||||
return getConfiguration(file.originalFile.virtualFile, file)
|
||||
}
|
||||
|
||||
fun getConfiguration(
|
||||
virtualFile: VirtualFile,
|
||||
preloadedKtFile: KtFile? = null
|
||||
): ScriptCompilationConfigurationWrapper? {
|
||||
val cached = getCachedConfiguration(virtualFile)
|
||||
if (cached != null) return cached.configuration
|
||||
|
||||
val ktFile = project.getKtFile(virtualFile, preloadedKtFile) ?: return null
|
||||
rootsIndexer.transaction {
|
||||
reloadOutOfDateConfiguration(ktFile, isFirstLoad = true)
|
||||
}
|
||||
|
||||
return getCachedConfiguration(virtualFile)?.configuration
|
||||
}
|
||||
|
||||
override val updater: ScriptConfigurationUpdater = object : ScriptConfigurationUpdater {
|
||||
override fun ensureUpToDatedConfigurationSuggested(file: KtFile) {
|
||||
reloadIfOutOfDate(listOf(file), true)
|
||||
}
|
||||
|
||||
override fun ensureConfigurationUpToDate(files: List<KtFile>): Boolean {
|
||||
return reloadIfOutOfDate(files, false)
|
||||
}
|
||||
|
||||
override fun forceConfigurationReload(file: KtFile) {
|
||||
val virtualFile = file.originalFile.virtualFile ?: return
|
||||
cache.markOutOfDate(virtualFile)
|
||||
|
||||
rootsIndexer.transaction {
|
||||
reloadOutOfDateConfiguration(file, loadEvenWillNotBeApplied = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadIfOutOfDate(files: List<KtFile>, loadEvenWillNotBeApplied: Boolean): Boolean {
|
||||
if (!ScriptDefinitionsManager.getInstance(project).isReady()) return false
|
||||
|
||||
var upToDate = true
|
||||
rootsIndexer.transaction {
|
||||
files.forEach { file ->
|
||||
val virtualFile = file.originalFile.virtualFile
|
||||
if (virtualFile != null) {
|
||||
val state = cache[virtualFile]
|
||||
if (state == null || !state.inputs.isUpToDate(project, virtualFile, file)) {
|
||||
upToDate = false
|
||||
reloadOutOfDateConfiguration(
|
||||
file,
|
||||
isFirstLoad = state == null,
|
||||
loadEvenWillNotBeApplied = loadEvenWillNotBeApplied
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return upToDate
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
internal fun updateScriptDependenciesSynchronously(file: PsiFile) {
|
||||
file.findScriptDefinition() ?: return
|
||||
|
||||
file as? KtFile ?: error("PsiFile $file should be a KtFile, otherwise script dependencies cannot be loaded")
|
||||
|
||||
val virtualFile = file.virtualFile
|
||||
if (cache[virtualFile]?.inputs?.isUpToDate(project, virtualFile, file) == true) return
|
||||
|
||||
rootsIndexer.transaction {
|
||||
reloadOutOfDateConfiguration(file, isFirstLoad = true, forceSync = true)
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun saveChangedConfiguration(
|
||||
file: VirtualFile,
|
||||
newConfigurationSnapshot: CachedConfigurationSnapshot?
|
||||
) {
|
||||
rootsIndexer.checkInTransaction()
|
||||
val newConfiguration = newConfigurationSnapshot?.configuration
|
||||
debug(file) { "configuration changed = $newConfiguration" }
|
||||
|
||||
if (newConfiguration != null) {
|
||||
if (hasNotCachedRoots(newConfiguration)) {
|
||||
rootsIndexer.markNewRoot(file, newConfiguration)
|
||||
}
|
||||
|
||||
cache[file] = newConfigurationSnapshot
|
||||
|
||||
clearClassRootsCaches()
|
||||
}
|
||||
|
||||
updateHighlighting(listOf(file))
|
||||
}
|
||||
|
||||
private fun hasNotCachedRoots(configuration: ScriptCompilationConfigurationWrapper): Boolean {
|
||||
return classpathRoots.hasNotCachedRoots(configuration)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear configuration caches
|
||||
* Start re-highlighting for opened scripts
|
||||
*/
|
||||
override fun clearConfigurationCachesAndRehighlight() {
|
||||
ScriptDependenciesModificationTracker.getInstance(project).incModificationCount()
|
||||
|
||||
// todo: invalidate caches?
|
||||
|
||||
if (project.isOpen) {
|
||||
val openedScripts = FileEditorManager.getInstance(project).openFiles.filterNot { it.isNonScript() }
|
||||
updateHighlighting(openedScripts)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateHighlighting(files: List<VirtualFile>) {
|
||||
if (files.isEmpty()) return
|
||||
|
||||
GlobalScope.launch(EDT(project)) {
|
||||
if (project.isDisposed) return@launch
|
||||
|
||||
val openFiles = FileEditorManager.getInstance(project).openFiles
|
||||
val openScripts = files.filter { it.isValid && openFiles.contains(it) }
|
||||
|
||||
openScripts.forEach {
|
||||
PsiManager.getInstance(project).findFile(it)?.let { psiFile ->
|
||||
DaemonCodeAnalyzer.getInstance(project).restart(psiFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// ScriptRootsCache
|
||||
|
||||
private val classpathRootsLock = ReentrantLock()
|
||||
@Volatile
|
||||
private var _classpathRoots: ScriptClassRootsCache? = null
|
||||
private val classpathRoots: ScriptClassRootsCache
|
||||
get() {
|
||||
val value1 = _classpathRoots
|
||||
if (value1 != null) return value1
|
||||
|
||||
classpathRootsLock.withLock {
|
||||
val value2 = _classpathRoots
|
||||
if (value2 != null) return value2
|
||||
|
||||
val value3 = ScriptClassRootsCache(project, cache)
|
||||
_classpathRoots = value3
|
||||
return value3
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearClassRootsCaches() {
|
||||
debug { "class roots caches cleared" }
|
||||
|
||||
classpathRootsLock.withLock {
|
||||
_classpathRoots = null
|
||||
}
|
||||
|
||||
val kotlinScriptDependenciesClassFinder =
|
||||
Extensions.getArea(project).getExtensionPoint(PsiElementFinder.EP_NAME).extensions
|
||||
.filterIsInstance<KotlinScriptDependenciesClassFinder>()
|
||||
.single()
|
||||
|
||||
kotlinScriptDependenciesClassFinder.clearCache()
|
||||
|
||||
ScriptDependenciesModificationTracker.getInstance(project).incModificationCount()
|
||||
}
|
||||
|
||||
override fun getScriptSdk(file: VirtualFile): Sdk? = classpathRoots.getScriptSdk(file)
|
||||
|
||||
override fun getFirstScriptsSdk(): Sdk? = classpathRoots.getFirstScriptsSdk()
|
||||
|
||||
override fun getScriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope =
|
||||
classpathRoots.getScriptDependenciesClassFilesScope(file)
|
||||
|
||||
override fun getAllScriptsDependenciesClassFilesScope(): GlobalSearchScope = classpathRoots.allDependenciesClassFilesScope
|
||||
|
||||
override fun getAllScriptDependenciesSourcesScope(): GlobalSearchScope = classpathRoots.allDependenciesSourcesScope
|
||||
|
||||
override fun getAllScriptsDependenciesClassFiles(): List<VirtualFile> = classpathRoots.allDependenciesClassFiles
|
||||
|
||||
override fun getAllScriptDependenciesSources(): List<VirtualFile> = classpathRoots.allDependenciesSources
|
||||
}
|
||||
+244
-271
@@ -8,287 +8,297 @@ package org.jetbrains.kotlin.idea.core.script.configuration
|
||||
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.ui.EditorNotifications
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.idea.core.script.*
|
||||
import org.jetbrains.kotlin.idea.core.script.LOG
|
||||
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager.Companion.toVfsRoots
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigurationSnapshot
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationCache
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationFileAttributeCache
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationMemoryCache
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptsListener
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.FromRefinedConfigurationLoader
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.OutsiderFileDependenciesLoader
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptDependenciesLoader
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.utils.BackgroundLoader
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsManager
|
||||
import org.jetbrains.kotlin.idea.core.script.debug
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.DefaultScriptChangeListener
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangesNotifier
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.*
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.utils.BackgroundExecutor
|
||||
import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings
|
||||
import org.jetbrains.kotlin.idea.core.util.EDT
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
|
||||
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
|
||||
import org.jetbrains.kotlin.scripting.definitions.isNonScript
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink
|
||||
import java.util.*
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
import kotlin.script.experimental.api.ScriptDiagnostic
|
||||
import kotlin.script.experimental.api.valueOrNull
|
||||
|
||||
class DefaultScriptConfigurationManager internal constructor(private val project: Project) :
|
||||
ScriptConfigurationManager {
|
||||
private val rootsManager = ScriptClassRootsManager(project)
|
||||
/**
|
||||
* Standard implementation of scripts configuration loading and caching
|
||||
* (we have plans to extract separate implementation for Gradle scripts).
|
||||
*
|
||||
* ## Loading initiation
|
||||
*
|
||||
* [getConfiguration] will be called when we need to show or analyze some script file.
|
||||
*
|
||||
* As described in [AbstractScriptConfigurationManager], configuration may be loaded from [cache]
|
||||
* or [reloadOutOfDateConfiguration] will be called on [cache] miss.
|
||||
*
|
||||
* There are 2 tiers [cache]: memory and FS. For now FS cache implemented by [ScriptConfigurationLoader]
|
||||
* because we are not storing classpath roots yet. As a workaround cache.all() will return only memory
|
||||
* cached configurations. So, for now we are indexing roots that loaded from FS with
|
||||
* default [reloadOutOfDateConfiguration] mechanics. todo(KT-34444): implement fs classpath roots cache
|
||||
*
|
||||
* [notifier] will call first applicable [listeners] when editor is activated or document changed.
|
||||
* Listener may call [updater] to invalidate configuration and schedule reloading.
|
||||
*
|
||||
* Also, [ScriptConfigurationUpdater.ensureConfigurationUpToDate] may be called from [UnusedSymbolInspection]
|
||||
* to ensure that configuration of all scripts containing some symbol are up-to-date or try load it in sync.
|
||||
* Note: it makes sense only in case of "auto apply" mode and sync loader, in other cases all symbols just
|
||||
* will be treated as used.
|
||||
*
|
||||
* ## Loading
|
||||
*
|
||||
* When requested, configuration will be loaded using first applicable [loaders].
|
||||
* It can work synchronously or asynchronously.
|
||||
*
|
||||
* Synchronous loader will be called just immediately. Despite this, its result may not be applied immediately,
|
||||
* see next section for details.
|
||||
*
|
||||
* Asynchronous loader will be called in background thread (by [BackgroundExecutor]).
|
||||
*
|
||||
* ## Applying
|
||||
*
|
||||
* By default loaded configuration will *not* be applied immediately. Instead, we show in editor notification
|
||||
* that suggests user to apply changed configuration. This was done to avoid sporadically starting indexing of new roots,
|
||||
* which may happens regularly for large Gradle projects.
|
||||
*
|
||||
* Notification will be displayed when configuration is going to be updated. First configuration will be loaded
|
||||
* without notification.
|
||||
*
|
||||
* This behavior may be disabled by enabling "auto reload" in project settings.
|
||||
* When enabled, all loaded configurations will be applied immediately, without any notification.
|
||||
*
|
||||
* ## Concurrency
|
||||
*
|
||||
* Each files may be in on of this state:
|
||||
* - scriptDefinition is not ready
|
||||
* - not loaded
|
||||
* - up-to-date
|
||||
* - invalid, in queue (in [BackgroundExecutor] queue)
|
||||
* - invalid, loading
|
||||
* - invalid, waiting for apply
|
||||
*
|
||||
* [reloadOutOfDateConfiguration] guard this states. See it's docs for more details.
|
||||
*/
|
||||
internal class DefaultScriptConfigurationManager(project: Project) :
|
||||
AbstractScriptConfigurationManager(project) {
|
||||
private val backgroundExecutor = BackgroundExecutor(project, rootsIndexer)
|
||||
|
||||
private val memoryCache = ScriptConfigurationMemoryCache(project)
|
||||
private val fileAttributesCache = ScriptConfigurationFileAttributeCache()
|
||||
|
||||
private val fromRefinedLoader = FromRefinedConfigurationLoader()
|
||||
private val loaders = arrayListOf(
|
||||
OutsiderFileDependenciesLoader(this),
|
||||
fromRefinedLoader,
|
||||
fileAttributesCache
|
||||
private val loaders: List<ScriptConfigurationLoader> = listOf(
|
||||
ScriptOutsiderFileConfigurationLoader(project),
|
||||
ScriptConfigurationFileAttributeCache(project),
|
||||
DefaultScriptConfigurationLoader(project)
|
||||
)
|
||||
|
||||
private val backgroundLoader = BackgroundLoader(project, rootsManager, ::reloadConfigurationAsync)
|
||||
private val listeners: List<ScriptChangeListener> = listOf(
|
||||
DefaultScriptChangeListener()
|
||||
)
|
||||
|
||||
private val listener = ScriptsListener(project, this)
|
||||
private val notifier = ScriptChangesNotifier(project, updater, listeners)
|
||||
|
||||
/**
|
||||
* Loaded but not applied result.
|
||||
* Weakness required since it is hard to track editor and notification hiding.
|
||||
*/
|
||||
private val notApplied = WeakHashMap<VirtualFile, LoadedScriptConfiguration>()
|
||||
private val saveLock = ReentrantLock()
|
||||
|
||||
override fun createCache(): ScriptConfigurationCache {
|
||||
return object : ScriptConfigurationMemoryCache(project) {
|
||||
override fun markOutOfDate(file: VirtualFile) {
|
||||
super.markOutOfDate(file)
|
||||
|
||||
synchronized(notApplied) {
|
||||
notApplied.remove(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will be called on [cache] miss to initiate loading of [file]'s script configuration.
|
||||
*
|
||||
* ## Concurrency
|
||||
*
|
||||
* Each files may be in on of the states described below:
|
||||
* - scriptDefinition is not ready. `ScriptDefinitionsManager.getInstance(project).isReady() == false`.
|
||||
* [clearConfigurationCachesAndRehighlight] will be called when [ScriptDefinitionsManager] will be ready
|
||||
* which will call [reloadOutOfDateConfiguration] for opened editors.
|
||||
* - unknown. When [isFirstLoad] true (`cache[file] == null`).
|
||||
* - up-to-date. `cache[file]?.upToDate == true`.
|
||||
* - invalid, in queue. `cache[file]?.upToDate == false && file in backgroundExecutor`.
|
||||
* - invalid, loading. `cache[file]?.upToDate == false && file !in backgroundExecutor`.
|
||||
* - invalid, waiting for apply. `cache[file]?.upToDate == false && file !in backgroundExecutor` and has notification panel?
|
||||
*
|
||||
* Async:
|
||||
* - up-to-date:
|
||||
* [reloadOutOfDateConfiguration] will not be called.
|
||||
* - `unknown` and `invalid, in queue`:
|
||||
* Concurrent async loading will be guarded by `backgroundExecutor.ensureScheduled`
|
||||
* (only one task per file will be scheduled at same time)
|
||||
* - `invalid`:
|
||||
* Loading should be rescheduled, since the work already started for old input.
|
||||
* This will work, because file will be removed from backgroundExecutor.
|
||||
* - `loading`: Scheduled loading for unchanged file will be noop thanks to isUpToDate check
|
||||
* - `not applied`: Scheduled loading for unchanged file with loaded but not applied
|
||||
* configuration will be also noop thanks check in [notApplied] map.
|
||||
*
|
||||
* Sync:
|
||||
* - up-to-date:
|
||||
* [reloadOutOfDateConfiguration] will not be called.
|
||||
* - all other states, i.e: `unknown`, `invalid, in queue`, `invalid, loading` and `invalid, ready for apply`:
|
||||
* everything will be computed just in place, possible concurrently.
|
||||
* [suggestOrSaveConfiguration] calls will be serialized by the [saveLock]
|
||||
*/
|
||||
override fun reloadOutOfDateConfiguration(
|
||||
file: KtFile,
|
||||
isFirstLoad: Boolean,
|
||||
loadEvenWillNotBeApplied: Boolean,
|
||||
forceSync: Boolean
|
||||
) {
|
||||
val virtualFile = file.originalFile.virtualFile ?: return
|
||||
|
||||
val autoReloadEnabled = KotlinScriptingSettings.getInstance(project).isAutoReloadEnabled
|
||||
val shouldLoad = isFirstLoad || loadEvenWillNotBeApplied || autoReloadEnabled
|
||||
if (!shouldLoad) return
|
||||
|
||||
if (!ScriptDefinitionsManager.getInstance(project).isReady()) return
|
||||
val scriptDefinition = file.findScriptDefinition() ?: return
|
||||
|
||||
val (async, sync) = loaders.partition { it.shouldRunInBackground(scriptDefinition) }
|
||||
|
||||
val syncLoader = sync.firstOrNull { it.loadDependencies(isFirstLoad, virtualFile, scriptDefinition, loadingContext) }
|
||||
if (syncLoader == null) {
|
||||
// run async loader
|
||||
if (forceSync) {
|
||||
async.first { it.loadDependencies(isFirstLoad, virtualFile, scriptDefinition, loadingContext) }
|
||||
} else {
|
||||
backgroundExecutor.ensureScheduled(virtualFile) {
|
||||
// don't start loading if nothing was changed
|
||||
// (in case we checking for up-to-date and loading concurrently)
|
||||
val cached = getCachedConfiguration(virtualFile)
|
||||
if (cached?.inputs?.isUpToDate(project, virtualFile) != true) {
|
||||
val prevNotApplied = synchronized(notApplied) { notApplied[virtualFile] }
|
||||
if (prevNotApplied?.inputs?.isUpToDate(project, virtualFile) == true) {
|
||||
// reuse loaded but not applied result
|
||||
// (in case we checking for up-to-date and waiting notification answer concurrently)
|
||||
loadingContext.suggestNewConfiguration(
|
||||
virtualFile,
|
||||
prevNotApplied
|
||||
)
|
||||
} else {
|
||||
synchronized(notApplied) {
|
||||
notApplied.remove(virtualFile)
|
||||
}
|
||||
|
||||
val actualIsFirstLoad = cached == null
|
||||
async.first { it.loadDependencies(actualIsFirstLoad, virtualFile, scriptDefinition, loadingContext) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save configurations into cache.
|
||||
* Start indexing for new class/source roots.
|
||||
* Re-highlight opened scripts with changed configuration.
|
||||
*/
|
||||
override fun saveCompilationConfigurationAfterImport(files: List<Pair<VirtualFile, ScriptCompilationConfigurationResult>>) {
|
||||
rootsManager.transaction {
|
||||
override fun saveCompilationConfigurationAfterImport(files: List<Pair<VirtualFile, LoadedScriptConfiguration>>) {
|
||||
rootsIndexer.transaction {
|
||||
for ((file, result) in files) {
|
||||
saveConfiguration(file, result, skipNotification = true)
|
||||
loadingContext.saveNewConfiguration(file, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start configuration update for files if configuration isn't up to date.
|
||||
* Start indexing for new class/source roots.
|
||||
*
|
||||
* @return true if update was started for any file, false if all configurations are cached
|
||||
*/
|
||||
override fun updateConfigurationsIfNotCached(files: List<KtFile>): Boolean {
|
||||
if (!ScriptDefinitionsManager.getInstance(project).isReady()) return false
|
||||
private val loadingContext = object : ScriptConfigurationLoadingContext {
|
||||
override fun getCachedConfiguration(file: VirtualFile): CachedConfigurationSnapshot? =
|
||||
this@DefaultScriptConfigurationManager.getCachedConfiguration(file)
|
||||
|
||||
val notCached = files.filterNot { isConfigurationUpToDate(it.originalFile.virtualFile) }
|
||||
if (notCached.isNotEmpty()) {
|
||||
rootsManager.transaction {
|
||||
for (file in notCached) {
|
||||
reloadConfiguration(file)
|
||||
}
|
||||
}
|
||||
return true
|
||||
override fun suggestNewConfiguration(file: VirtualFile, newResult: LoadedScriptConfiguration) {
|
||||
suggestOrSaveConfiguration(file, newResult, false)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if configuration is already cached for [file] (in cache or FileAttributes).
|
||||
* Don't check if file was changed after the last update.
|
||||
* Supposed to be used to switch highlighting off for scripts without configuration.
|
||||
* to avoid all file being highlighted in red.
|
||||
*/
|
||||
override fun isConfigurationCached(file: KtFile): Boolean {
|
||||
return isConfigurationCached(file.originalFile.virtualFile)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear configuration caches
|
||||
* Start re-highlighting for opened scripts
|
||||
*/
|
||||
override fun clearConfigurationCachesAndRehighlight() {
|
||||
ScriptDependenciesModificationTracker.getInstance(project).incModificationCount()
|
||||
|
||||
if (project.isOpen) {
|
||||
rehighlightOpenedScripts()
|
||||
override fun saveNewConfiguration(file: VirtualFile, newResult: LoadedScriptConfiguration) {
|
||||
suggestOrSaveConfiguration(file, newResult, true)
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use getScriptClasspath(KtFile) instead")
|
||||
override fun getScriptClasspath(file: VirtualFile): List<VirtualFile> {
|
||||
val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return emptyList()
|
||||
return getScriptClasspath(ktFile)
|
||||
}
|
||||
|
||||
override fun getScriptClasspath(file: KtFile): List<VirtualFile> =
|
||||
toVfsRoots(getConfiguration(file)?.dependenciesClassPath.orEmpty())
|
||||
|
||||
override fun getConfiguration(file: KtFile): ScriptCompilationConfigurationWrapper? {
|
||||
val virtualFile = file.originalFile.virtualFile
|
||||
|
||||
val cached = getCachedConfiguration(virtualFile)
|
||||
if (cached != null) {
|
||||
return cached
|
||||
}
|
||||
|
||||
if (ScriptDefinitionsManager.getInstance(project).isReady() && !isConfigurationUpToDate(virtualFile)) {
|
||||
rootsManager.transaction {
|
||||
reloadConfiguration(file)
|
||||
}
|
||||
}
|
||||
|
||||
return getCachedConfiguration(virtualFile)
|
||||
}
|
||||
|
||||
override fun getScriptDependenciesClassFilesScope(file: VirtualFile) = scriptDependenciesClassFilesScope(file)
|
||||
override fun getScriptSdk(file: VirtualFile) = scriptSdk(file)
|
||||
|
||||
override fun getFirstScriptsSdk() = memoryCache.firstScriptSdk
|
||||
|
||||
override fun getAllScriptsDependenciesClassFilesScope() = memoryCache.allDependenciesClassFilesScope
|
||||
override fun getAllScriptDependenciesSourcesScope() = memoryCache.allDependenciesSourcesScope
|
||||
|
||||
override fun getAllScriptsDependenciesClassFiles() = memoryCache.allDependenciesClassFiles
|
||||
override fun getAllScriptDependenciesSources() = memoryCache.allDependenciesSources
|
||||
|
||||
@TestOnly
|
||||
fun updateScriptDependenciesSynchronously(file: PsiFile) {
|
||||
val scriptDefinition = file.findScriptDefinition() ?: return
|
||||
assert(file is KtFile) {
|
||||
"PsiFile should be a KtFile, otherwise script dependencies cannot be loaded"
|
||||
}
|
||||
|
||||
if (isConfigurationUpToDate(file.virtualFile)) return
|
||||
|
||||
rootsManager.transaction {
|
||||
val result = fromRefinedLoader.loadDependencies(true, file as KtFile, scriptDefinition)
|
||||
if (result != null) {
|
||||
saveConfiguration(file.originalFile.virtualFile, result, skipNotification = true, skipSaveToAttributes = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadConfiguration(file: KtFile) {
|
||||
val virtualFile = file.originalFile.virtualFile
|
||||
|
||||
memoryCache.setUpToDate(virtualFile)
|
||||
|
||||
val scriptDefinition = file.findScriptDefinition() ?: return
|
||||
|
||||
val syncLoaders = loaders.filterNot { it.isAsync(file, scriptDefinition) }
|
||||
reloadConfigurationBy(file, scriptDefinition, syncLoaders)
|
||||
|
||||
val asyncLoaders = loaders - syncLoaders
|
||||
if (asyncLoaders.isNotEmpty()) {
|
||||
backgroundLoader.scheduleAsync(file)
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadConfigurationAsync(file: KtFile) {
|
||||
val scriptDefinition = file.findScriptDefinition() ?: return
|
||||
|
||||
val asyncLoaders = loaders.filter { it.isAsync(file, scriptDefinition) }
|
||||
|
||||
if (asyncLoaders.size > 1) {
|
||||
LOG.warn("There are more than one async compilation configuration loader. " +
|
||||
"This mean that the last one will overwrite the results of the previous ones: " +
|
||||
asyncLoaders.joinToString { it.javaClass.name })
|
||||
}
|
||||
|
||||
reloadConfigurationBy(file, scriptDefinition, asyncLoaders)
|
||||
}
|
||||
|
||||
private fun reloadConfigurationBy(file: KtFile, scriptDefinition: ScriptDefinition, loaders: List<ScriptDependenciesLoader>) {
|
||||
val firstLoad = memoryCache.getCachedConfiguration(file.originalFile.virtualFile) == null
|
||||
|
||||
loaders.forEach { loader ->
|
||||
val result = loader.loadDependencies(firstLoad, file, scriptDefinition)
|
||||
if (result != null) {
|
||||
return saveConfiguration(file.originalFile.virtualFile, result, loader.skipNotification, loader.skipSaveToAttributes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save [newResult] for [file] into caches and update highlih.
|
||||
* Should be called inside `rootsManager.transaction { ... }`.
|
||||
*
|
||||
* @param skipNotification forces loading new configuration even if auto reload is disabled.
|
||||
* @param skipSaveToAttributes skips saving to FileAttributes (used in [ScriptConfigurationFileAttributeCache] only).
|
||||
*
|
||||
* @sample ScriptConfigurationManager.getConfiguration
|
||||
*/
|
||||
private fun saveConfiguration(
|
||||
private fun suggestOrSaveConfiguration(
|
||||
file: VirtualFile,
|
||||
newResult: ScriptCompilationConfigurationResult,
|
||||
skipNotification: Boolean = false,
|
||||
skipSaveToAttributes: Boolean = false
|
||||
newResult: LoadedScriptConfiguration,
|
||||
skipNotification: Boolean
|
||||
) {
|
||||
debug(file) { "configuration received = $newResult" }
|
||||
saveLock.withLock {
|
||||
debug(file) { "configuration received = $newResult" }
|
||||
|
||||
saveReports(file, newResult.reports)
|
||||
saveReports(file, newResult.reports)
|
||||
|
||||
val newConfiguration = newResult.valueOrNull()
|
||||
if (newConfiguration != null) {
|
||||
val oldConfiguration = getCachedConfiguration(file)
|
||||
if (oldConfiguration == newConfiguration) {
|
||||
file.removeScriptDependenciesNotificationPanel(project)
|
||||
} else {
|
||||
val autoReload = skipNotification
|
||||
|| oldConfiguration == null
|
||||
|| KotlinScriptingSettings.getInstance(project).isAutoReloadEnabled
|
||||
|| ApplicationManager.getApplication().isUnitTestMode
|
||||
|
||||
if (autoReload) {
|
||||
if (oldConfiguration != null) {
|
||||
file.removeScriptDependenciesNotificationPanel(project)
|
||||
}
|
||||
rootsManager.transaction {
|
||||
saveChangedConfiguration(file, newConfiguration, skipSaveToAttributes)
|
||||
}
|
||||
val newConfiguration = newResult.configuration
|
||||
if (newConfiguration != null) {
|
||||
val newConfigurationSnapshot = CachedConfigurationSnapshot(newResult.inputs, newConfiguration)
|
||||
val oldConfiguration = getCachedConfiguration(file)?.configuration
|
||||
if (oldConfiguration == newConfiguration) {
|
||||
file.removeScriptDependenciesNotificationPanel(project)
|
||||
} else {
|
||||
debug(file) {
|
||||
"configuration changed, notification is shown: old = $oldConfiguration, new = $newConfiguration"
|
||||
}
|
||||
file.addScriptDependenciesNotificationPanel(
|
||||
newConfiguration, project,
|
||||
onClick = {
|
||||
val autoReload = skipNotification
|
||||
|| oldConfiguration == null
|
||||
|| KotlinScriptingSettings.getInstance(project).isAutoReloadEnabled
|
||||
|| ApplicationManager.getApplication().isUnitTestMode
|
||||
|
||||
if (autoReload) {
|
||||
if (oldConfiguration != null) {
|
||||
file.removeScriptDependenciesNotificationPanel(project)
|
||||
rootsManager.transaction {
|
||||
saveChangedConfiguration(file, it, skipSaveToAttributes)
|
||||
}
|
||||
}
|
||||
)
|
||||
saveChangedConfiguration(file, newConfigurationSnapshot)
|
||||
} else {
|
||||
debug(file) {
|
||||
"configuration changed, notification is shown: old = $oldConfiguration, new = $newConfiguration"
|
||||
}
|
||||
synchronized(notApplied) {
|
||||
notApplied[file] = newResult
|
||||
}
|
||||
file.addScriptDependenciesNotificationPanel(
|
||||
newConfiguration, project,
|
||||
onClick = {
|
||||
file.removeScriptDependenciesNotificationPanel(project)
|
||||
rootsIndexer.transaction {
|
||||
saveChangedConfiguration(file, newConfigurationSnapshot)
|
||||
}
|
||||
},
|
||||
onHide = {
|
||||
synchronized(notApplied) {
|
||||
notApplied.remove(file)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveChangedConfiguration(
|
||||
file: VirtualFile,
|
||||
newConfiguration: ScriptCompilationConfigurationWrapper?,
|
||||
skipSaveToAttributes: Boolean
|
||||
) {
|
||||
debug(file) { "configuration changed = $newConfiguration" }
|
||||
override fun saveChangedConfiguration(file: VirtualFile, newConfigurationSnapshot: CachedConfigurationSnapshot?) {
|
||||
super.saveChangedConfiguration(file, newConfigurationSnapshot)
|
||||
|
||||
if (newConfiguration != null) {
|
||||
rootsManager.checkNonCachedRoots(memoryCache, file, newConfiguration)
|
||||
|
||||
if (!skipSaveToAttributes) {
|
||||
debug(file) { "configuration saved to file attributes: $newConfiguration" }
|
||||
|
||||
fileAttributesCache.save(file, newConfiguration)
|
||||
}
|
||||
|
||||
memoryCache.replaceConfiguration(file, newConfiguration)
|
||||
memoryCache.clearClassRootsCaches()
|
||||
synchronized(notApplied) {
|
||||
notApplied.remove(file)
|
||||
}
|
||||
|
||||
updateHighlighting(listOf(file))
|
||||
}
|
||||
|
||||
private fun saveReports(
|
||||
@@ -304,49 +314,12 @@ class DefaultScriptConfigurationManager internal constructor(private val project
|
||||
GlobalScope.launch(EDT(project)) {
|
||||
if (project.isDisposed) return@launch
|
||||
|
||||
val ktFile = PsiManager.getInstance(project).findFile(file)
|
||||
if (ktFile != null) {
|
||||
DaemonCodeAnalyzer.getInstance(project).restart(ktFile)
|
||||
}
|
||||
EditorNotifications.getInstance(project).updateAllNotifications()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateHighlighting(files: List<VirtualFile>) {
|
||||
if (files.isEmpty()) return
|
||||
|
||||
GlobalScope.launch(EDT(project)) {
|
||||
if (project.isDisposed) return@launch
|
||||
|
||||
val openFiles = FileEditorManager.getInstance(project).openFiles
|
||||
val openScripts = files.filter { it.isValid && openFiles.contains(it) }
|
||||
|
||||
openScripts.forEach {
|
||||
PsiManager.getInstance(project).findFile(it)?.let { psiFile ->
|
||||
DaemonCodeAnalyzer.getInstance(project).restart(psiFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCachedConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? =
|
||||
memoryCache.getCachedConfiguration(file)
|
||||
|
||||
private fun isConfigurationCached(file: VirtualFile): Boolean {
|
||||
return getCachedConfiguration(file) != null || file in fileAttributesCache
|
||||
}
|
||||
|
||||
private fun isConfigurationUpToDate(file: VirtualFile): Boolean {
|
||||
return isConfigurationCached(file) && memoryCache.isConfigurationUpToDate(file)
|
||||
}
|
||||
|
||||
private fun rehighlightOpenedScripts() {
|
||||
val openedScripts = FileEditorManager.getInstance(project).openFiles.filterNot { it.isNonScript() }
|
||||
updateHighlighting(openedScripts)
|
||||
}
|
||||
|
||||
private fun scriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope {
|
||||
return memoryCache.scriptsDependenciesClasspathScopeCache[file] ?: GlobalSearchScope.EMPTY_SCOPE
|
||||
}
|
||||
|
||||
private fun scriptSdk(file: VirtualFile): Sdk? {
|
||||
return memoryCache.scriptsSdksCache[file]
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.cache
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||
|
||||
interface CachedConfigurationInputs {
|
||||
fun isUpToDate(project: Project, file: VirtualFile, ktFile: KtFile? = null): Boolean
|
||||
|
||||
object OutOfDate : CachedConfigurationInputs {
|
||||
override fun isUpToDate(project: Project, file: VirtualFile, ktFile: KtFile?): Boolean = false
|
||||
}
|
||||
|
||||
data class PsiModificationStamp(val modificationStamp: Long) : CachedConfigurationInputs {
|
||||
override fun isUpToDate(project: Project, file: VirtualFile, ktFile: KtFile?): Boolean =
|
||||
ktFile?.modificationStamp == modificationStamp
|
||||
}
|
||||
}
|
||||
|
||||
data class CachedConfigurationSnapshot(
|
||||
val inputs: CachedConfigurationInputs,
|
||||
val configuration: ScriptCompilationConfigurationWrapper
|
||||
)
|
||||
|
||||
interface ScriptConfigurationCache {
|
||||
operator fun get(file: VirtualFile): CachedConfigurationSnapshot?
|
||||
operator fun set(file: VirtualFile, configurationSnapshot: CachedConfigurationSnapshot)
|
||||
fun markOutOfDate(file: VirtualFile)
|
||||
|
||||
fun all(): Collection<Pair<VirtualFile, ScriptCompilationConfigurationWrapper>>
|
||||
}
|
||||
+33
-24
@@ -5,47 +5,56 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.core.script.configuration.cache
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.LoadedScriptConfiguration
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoader
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoadingContext
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.utils.getKtFile
|
||||
import org.jetbrains.kotlin.idea.core.script.debug
|
||||
import org.jetbrains.kotlin.idea.core.util.*
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
|
||||
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
|
||||
import org.jetbrains.kotlin.scripting.resolve.KtFileScriptSource
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper.FromCompilationConfiguration
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper.FromLegacy
|
||||
import java.io.*
|
||||
import kotlin.script.experimental.api.ScriptCompilationConfiguration
|
||||
import kotlin.script.experimental.api.asSuccess
|
||||
import kotlin.script.experimental.dependencies.ScriptDependencies
|
||||
|
||||
class ScriptConfigurationFileAttributeCache :
|
||||
ScriptConfigurationLoader {
|
||||
operator fun contains(file: VirtualFile): Boolean =
|
||||
file.scriptDependencies != null || file.scriptCompilationConfiguration != null
|
||||
|
||||
override val skipSaveToAttributes: Boolean
|
||||
get() = true
|
||||
|
||||
override val skipNotification: Boolean
|
||||
get() = true
|
||||
|
||||
internal class ScriptConfigurationFileAttributeCache(
|
||||
val project: Project
|
||||
) : ScriptConfigurationLoader {
|
||||
/**
|
||||
* todo(KT-34444): this should be changed to storing all roots in the persistent file cache
|
||||
*/
|
||||
override fun loadDependencies(
|
||||
firstLoad: Boolean,
|
||||
file: KtFile,
|
||||
scriptDefinition: ScriptDefinition
|
||||
): ScriptCompilationConfigurationResult? {
|
||||
if (!firstLoad) return null
|
||||
isFirstLoad: Boolean,
|
||||
virtualFile: VirtualFile,
|
||||
scriptDefinition: ScriptDefinition,
|
||||
context: ScriptConfigurationLoadingContext
|
||||
): Boolean {
|
||||
if (!isFirstLoad) return false
|
||||
|
||||
val virtualFile = file.originalFile.virtualFile
|
||||
val fromFs = load(virtualFile) ?: return false
|
||||
// todo(KT-34444): save inputs to fs
|
||||
val result = LoadedScriptConfiguration(CachedConfigurationInputs.OutOfDate, listOf(), fromFs)
|
||||
context.saveNewConfiguration(virtualFile, result)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun load(
|
||||
virtualFile: VirtualFile
|
||||
): ScriptCompilationConfigurationWrapper? {
|
||||
val ktFile = project.getKtFile(virtualFile) ?: return null
|
||||
val scriptSource = KtFileScriptSource(ktFile)
|
||||
|
||||
val configurationFromAttributes =
|
||||
virtualFile.scriptCompilationConfiguration?.let {
|
||||
FromCompilationConfiguration(KtFileScriptSource(file), it)
|
||||
FromCompilationConfiguration(scriptSource, it)
|
||||
} ?: virtualFile.scriptDependencies?.let {
|
||||
FromLegacy(KtFileScriptSource(file), it, scriptDefinition)
|
||||
FromLegacy(scriptSource, it, ktFile.findScriptDefinition())
|
||||
} ?: return null
|
||||
|
||||
|
||||
@@ -56,7 +65,7 @@ class ScriptConfigurationFileAttributeCache :
|
||||
return null
|
||||
}
|
||||
|
||||
return configurationFromAttributes.asSuccess()
|
||||
return configurationFromAttributes
|
||||
}
|
||||
|
||||
private fun areDependenciesValid(file: VirtualFile, configuration: ScriptCompilationConfigurationWrapper): Boolean {
|
||||
@@ -78,7 +87,7 @@ class ScriptConfigurationFileAttributeCache :
|
||||
file.scriptDependencies = null
|
||||
file.scriptCompilationConfiguration = null
|
||||
} else {
|
||||
if (value is FromLegacy) {
|
||||
if (value is ScriptCompilationConfigurationWrapper.FromLegacy) {
|
||||
file.scriptDependencies = value.legacyDependencies
|
||||
} else {
|
||||
if (file.scriptDependencies != null) file.scriptDependencies = null
|
||||
|
||||
+23
-303
@@ -5,326 +5,46 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.core.script.configuration.cache
|
||||
|
||||
import com.intellij.ProjectTopics
|
||||
import com.intellij.openapi.extensions.Extensions
|
||||
import com.intellij.openapi.module.ModuleManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.roots.ModuleRootEvent
|
||||
import com.intellij.openapi.roots.ModuleRootListener
|
||||
import com.intellij.openapi.roots.ModuleRootManager
|
||||
import com.intellij.openapi.roots.OrderRootType
|
||||
import com.intellij.openapi.vfs.VfsUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiElementFinder
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.search.NonClasspathDirectoriesScope
|
||||
import com.intellij.util.containers.ConcurrentFactoryMap
|
||||
import com.intellij.util.containers.SLRUMap
|
||||
import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks
|
||||
import org.jetbrains.kotlin.idea.core.script.KotlinScriptDependenciesClassFinder
|
||||
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
||||
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationMemoryCache.Companion.MAX_SCRIPTS_CACHED
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.AbstractScriptConfigurationManager
|
||||
import org.jetbrains.kotlin.idea.core.script.debug
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import kotlin.concurrent.write
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlin.reflect.KProperty0
|
||||
import kotlin.reflect.jvm.isAccessible
|
||||
|
||||
class ScriptConfigurationMemoryCache internal constructor(private val project: Project) {
|
||||
import java.io.DataInput
|
||||
import java.io.DataOutput
|
||||
|
||||
abstract class ScriptConfigurationMemoryCache(
|
||||
val project: Project
|
||||
) : ScriptConfigurationCache {
|
||||
companion object {
|
||||
const val MAX_SCRIPTS_CACHED = 50
|
||||
}
|
||||
|
||||
init {
|
||||
val connection = project.messageBus.connect()
|
||||
connection.subscribe(ProjectTopics.PROJECT_ROOTS, object : ModuleRootListener {
|
||||
override fun rootsChanged(event: ModuleRootEvent) {
|
||||
clearClassRootsCaches()
|
||||
}
|
||||
})
|
||||
private val memoryCache = SLRUMap<VirtualFile, CachedConfigurationSnapshot>(MAX_SCRIPTS_CACHED, MAX_SCRIPTS_CACHED)
|
||||
|
||||
@Synchronized
|
||||
override operator fun get(file: VirtualFile): CachedConfigurationSnapshot? {
|
||||
return memoryCache.get(file)
|
||||
}
|
||||
|
||||
private val cacheLock = ReentrantReadWriteLock()
|
||||
|
||||
private val scriptDependenciesCache =
|
||||
SLRUCacheWithLock<VirtualFile, ScriptCompilationConfigurationWrapper>()
|
||||
private val scriptsModificationStampsCache =
|
||||
SLRUCacheWithLock<VirtualFile, Long>()
|
||||
|
||||
fun getCachedConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? = scriptDependenciesCache.get(file)
|
||||
|
||||
fun isConfigurationUpToDate(file: VirtualFile): Boolean {
|
||||
return scriptsModificationStampsCache.get(file) == file.modificationStamp
|
||||
}
|
||||
|
||||
fun setUpToDate(file: VirtualFile) {
|
||||
scriptsModificationStampsCache.replace(file, file.modificationStamp)
|
||||
}
|
||||
|
||||
fun replaceConfiguration(file: VirtualFile, new: ScriptCompilationConfigurationWrapper) {
|
||||
scriptDependenciesCache.replace(file, new)
|
||||
}
|
||||
|
||||
val scriptsDependenciesClasspathScopeCache: MutableMap<VirtualFile, GlobalSearchScope> =
|
||||
ConcurrentFactoryMap.createWeakMap { file ->
|
||||
val compilationConfiguration = getConfiguration(file)
|
||||
?: return@createWeakMap GlobalSearchScope.EMPTY_SCOPE
|
||||
|
||||
val roots = compilationConfiguration.dependenciesClassPath
|
||||
val sdk = scriptsSdksCache[file]
|
||||
|
||||
@Suppress("FoldInitializerAndIfToElvis")
|
||||
if (sdk == null) {
|
||||
return@createWeakMap NonClasspathDirectoriesScope.compose(
|
||||
ScriptConfigurationManager.toVfsRoots(
|
||||
roots
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return@createWeakMap NonClasspathDirectoriesScope.compose(
|
||||
sdk.rootProvider.getFiles(OrderRootType.CLASSES).toList() +
|
||||
ScriptConfigurationManager.toVfsRoots(roots)
|
||||
)
|
||||
}
|
||||
|
||||
val scriptsSdksCache: MutableMap<VirtualFile, Sdk?> =
|
||||
ConcurrentFactoryMap.createWeakMap { file ->
|
||||
val compilationConfiguration = getConfiguration(file)
|
||||
return@createWeakMap getScriptSdk(compilationConfiguration) ?: ScriptConfigurationManager.getScriptDefaultSdk(
|
||||
project
|
||||
)
|
||||
}
|
||||
|
||||
private fun getConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? {
|
||||
val configuration = getCachedConfiguration(file)
|
||||
|
||||
if (configuration != null) return configuration
|
||||
|
||||
val ktFile = runReadAction { PsiManager.getInstance(project).findFile(file) as? KtFile } ?: return null
|
||||
return ScriptConfigurationManager.getInstance(project)
|
||||
.getConfiguration(ktFile)
|
||||
}
|
||||
|
||||
private fun getScriptSdk(compilationConfiguration: ScriptCompilationConfigurationWrapper?): Sdk? {
|
||||
// workaround for mismatched gradle wrapper and plugin version
|
||||
val javaHome = try {
|
||||
compilationConfiguration?.javaHome?.let { VfsUtil.findFileByIoFile(it, true) }
|
||||
} catch (e: Throwable) {
|
||||
null
|
||||
} ?: return null
|
||||
|
||||
return getAllProjectSdks().find { it.homeDirectory == javaHome }
|
||||
}
|
||||
|
||||
val firstScriptSdk: Sdk?
|
||||
get() {
|
||||
val firstCachedScript = scriptDependenciesCache.getAll().firstOrNull()?.key ?: return null
|
||||
return scriptsSdksCache[firstCachedScript]
|
||||
}
|
||||
|
||||
private val allSdks by ClearableLazyValue(cacheLock) {
|
||||
scriptDependenciesCache.getAll()
|
||||
.mapNotNull { scriptsSdksCache[it.key] }
|
||||
.distinct()
|
||||
}
|
||||
|
||||
private val allNonIndexedSdks by ClearableLazyValue(
|
||||
cacheLock
|
||||
) {
|
||||
scriptDependenciesCache.getAll()
|
||||
.mapNotNull { scriptsSdksCache[it.key] }
|
||||
.filterNonModuleSdk()
|
||||
.distinct()
|
||||
}
|
||||
|
||||
val allDependenciesClassFiles by ClearableLazyValue(
|
||||
cacheLock
|
||||
) {
|
||||
val sdkFiles = allNonIndexedSdks
|
||||
.flatMap { it.rootProvider.getFiles(OrderRootType.CLASSES).toList() }
|
||||
|
||||
val scriptDependenciesClasspath = scriptDependenciesCache.getAll()
|
||||
.flatMap { it.value.dependenciesClassPath }.distinct()
|
||||
|
||||
sdkFiles + ScriptConfigurationManager.toVfsRoots(
|
||||
scriptDependenciesClasspath
|
||||
@Synchronized
|
||||
override operator fun set(file: VirtualFile, configurationSnapshot: CachedConfigurationSnapshot) {
|
||||
memoryCache.put(
|
||||
file,
|
||||
configurationSnapshot
|
||||
)
|
||||
}
|
||||
|
||||
val allDependenciesSources by ClearableLazyValue(cacheLock) {
|
||||
val sdkSources = allNonIndexedSdks
|
||||
.flatMap { it.rootProvider.getFiles(OrderRootType.SOURCES).toList() }
|
||||
|
||||
val scriptDependenciesSources = scriptDependenciesCache.getAll()
|
||||
.flatMap { it.value.dependenciesSources }.distinct()
|
||||
sdkSources + ScriptConfigurationManager.toVfsRoots(
|
||||
scriptDependenciesSources
|
||||
)
|
||||
}
|
||||
|
||||
val allDependenciesClassFilesScope by ClearableLazyValue(
|
||||
cacheLock
|
||||
) {
|
||||
NonClasspathDirectoriesScope.compose(allDependenciesClassFiles)
|
||||
}
|
||||
|
||||
val allDependenciesSourcesScope by ClearableLazyValue(
|
||||
cacheLock
|
||||
) {
|
||||
NonClasspathDirectoriesScope.compose(allDependenciesSources)
|
||||
}
|
||||
|
||||
private fun List<Sdk>.filterNonModuleSdk(): List<Sdk> {
|
||||
val moduleSdks = ModuleManager.getInstance(project).modules.map { ModuleRootManager.getInstance(it).sdk }
|
||||
return filterNot { moduleSdks.contains(it) }
|
||||
}
|
||||
|
||||
fun clearConfigurationCaches(): List<VirtualFile> {
|
||||
debug { "configuration caches cleared" }
|
||||
|
||||
val keys = scriptDependenciesCache.getAll().map { it.key }.toList()
|
||||
|
||||
scriptDependenciesCache.clear()
|
||||
scriptsModificationStampsCache.clear()
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
fun clearClassRootsCaches() {
|
||||
debug { "class roots caches cleared" }
|
||||
|
||||
this::allSdks.clearValue()
|
||||
this::allNonIndexedSdks.clearValue()
|
||||
|
||||
this::allDependenciesClassFiles.clearValue()
|
||||
this::allDependenciesClassFilesScope.clearValue()
|
||||
|
||||
this::allDependenciesSources.clearValue()
|
||||
this::allDependenciesSourcesScope.clearValue()
|
||||
|
||||
scriptsDependenciesClasspathScopeCache.clear()
|
||||
scriptsSdksCache.clear()
|
||||
|
||||
val kotlinScriptDependenciesClassFinder =
|
||||
Extensions.getArea(project).getExtensionPoint(PsiElementFinder.EP_NAME).extensions
|
||||
.filterIsInstance<KotlinScriptDependenciesClassFinder>()
|
||||
.single()
|
||||
|
||||
kotlinScriptDependenciesClassFinder.clearCache()
|
||||
|
||||
ScriptDependenciesModificationTracker.getInstance(project).incModificationCount()
|
||||
}
|
||||
|
||||
fun hasNotCachedRoots(compilationConfiguration: ScriptCompilationConfigurationWrapper): Boolean {
|
||||
val scriptSdk = getScriptSdk(compilationConfiguration) ?: ScriptConfigurationManager.getScriptDefaultSdk(
|
||||
project
|
||||
)
|
||||
val wasSdkChanged = scriptSdk != null && !allSdks.contains(scriptSdk)
|
||||
if (wasSdkChanged) {
|
||||
debug { "sdk was changed: $compilationConfiguration" }
|
||||
return true
|
||||
}
|
||||
|
||||
val newClassRoots = ScriptConfigurationManager.toVfsRoots(
|
||||
compilationConfiguration.dependenciesClassPath
|
||||
)
|
||||
for (newClassRoot in newClassRoots) {
|
||||
if (!allDependenciesClassFiles.contains(newClassRoot)) {
|
||||
debug { "class root was changed: $newClassRoot" }
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
val newSourceRoots = ScriptConfigurationManager.toVfsRoots(
|
||||
compilationConfiguration.dependenciesSources
|
||||
)
|
||||
for (newSourceRoot in newSourceRoots) {
|
||||
if (!allDependenciesSources.contains(newSourceRoot)) {
|
||||
debug { "source root was changed: $newSourceRoot" }
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun <R> KProperty0<R>.clearValue() {
|
||||
isAccessible = true
|
||||
(getDelegate() as ClearableLazyValue<*, *>).clear()
|
||||
}
|
||||
|
||||
private class ClearableLazyValue<in R, out T : Any>(
|
||||
private val lock: ReentrantReadWriteLock,
|
||||
private val compute: () -> T
|
||||
) : ReadOnlyProperty<R, T> {
|
||||
override fun getValue(thisRef: R, property: KProperty<*>): T {
|
||||
lock.write {
|
||||
if (value == null) {
|
||||
value = compute()
|
||||
}
|
||||
return value!!
|
||||
@Synchronized
|
||||
override fun markOutOfDate(file: VirtualFile) {
|
||||
val old = memoryCache[file]
|
||||
if (old != null) {
|
||||
memoryCache.put(file, old.copy(inputs = CachedConfigurationInputs.OutOfDate))
|
||||
}
|
||||
}
|
||||
|
||||
private var value: T? = null
|
||||
|
||||
|
||||
fun clear() {
|
||||
lock.write {
|
||||
value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SLRUCacheWithLock<K, V> {
|
||||
private val lock = ReentrantReadWriteLock()
|
||||
|
||||
val cache = SLRUMap<K, V>(
|
||||
MAX_SCRIPTS_CACHED,
|
||||
MAX_SCRIPTS_CACHED
|
||||
)
|
||||
|
||||
fun get(value: K): V? = lock.write {
|
||||
cache[value]
|
||||
}
|
||||
|
||||
fun getOrPut(key: K, defaultValue: () -> V): V = lock.write {
|
||||
val value = cache.get(key)
|
||||
return if (value == null) {
|
||||
val answer = defaultValue()
|
||||
replace(key, answer)
|
||||
answer
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
fun remove(file: K) = lock.write {
|
||||
cache.remove(file)
|
||||
}
|
||||
|
||||
fun getAll(): Collection<Map.Entry<K, V>> = lock.write {
|
||||
cache.entrySet()
|
||||
}
|
||||
|
||||
fun clear() = lock.write {
|
||||
cache.clear()
|
||||
}
|
||||
|
||||
fun replace(file: K, value: V): V? = lock.write {
|
||||
val old = get(file)
|
||||
cache.put(file, value)
|
||||
old
|
||||
}
|
||||
@Synchronized
|
||||
override fun all() = memoryCache.entrySet().map { it.key to it.value.configuration }
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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.listener
|
||||
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
open class DefaultScriptChangeListener : ScriptChangeListener {
|
||||
override fun editorActivated(file: KtFile, updater: ScriptConfigurationUpdater): Boolean {
|
||||
updater.ensureUpToDatedConfigurationSuggested(file)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun documentChanged(file: KtFile, updater: ScriptConfigurationUpdater): Boolean {
|
||||
updater.ensureUpToDatedConfigurationSuggested(file)
|
||||
return true
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.listener
|
||||
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptConfigurationManager
|
||||
|
||||
/**
|
||||
* [ScriptChangesNotifier] will call first applicable [ScriptChangeListener] when editor is activated or document changed.
|
||||
* (it treated as applicable if [editorActivated] or [documentChanged] will return true).
|
||||
*
|
||||
* Listener may call [ScriptConfigurationUpdater] to invalidate configuration and schedule reloading.
|
||||
*
|
||||
* @see DefaultScriptConfigurationManager for more details.
|
||||
*/
|
||||
interface ScriptChangeListener {
|
||||
fun editorActivated(file: KtFile, updater: ScriptConfigurationUpdater): Boolean
|
||||
fun documentChanged(file: KtFile, updater: ScriptConfigurationUpdater): Boolean
|
||||
}
|
||||
+5
-4
@@ -23,9 +23,10 @@ import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.scripting.definitions.isNonScript
|
||||
|
||||
class ScriptChangesNotifier(
|
||||
internal class ScriptChangesNotifier(
|
||||
private val project: Project,
|
||||
private val scriptsManager: DefaultScriptConfigurationManager
|
||||
private val updater: ScriptConfigurationUpdater,
|
||||
private val listeners: List<ScriptChangeListener>
|
||||
) {
|
||||
private val scriptsQueue = Alarm(Alarm.ThreadToUse.SWING_THREAD, project)
|
||||
private val scriptChangesListenerDelay = 1400
|
||||
@@ -47,7 +48,7 @@ class ScriptChangesNotifier(
|
||||
private fun runScriptDependenciesUpdateIfNeeded(file: VirtualFile) {
|
||||
val ktFile = getKtFileToStartConfigurationUpdate(file) ?: return
|
||||
|
||||
scriptsManager.updateConfigurationsIfNotCached(listOf(ktFile))
|
||||
listeners.first { it.editorActivated(ktFile, updater) }
|
||||
}
|
||||
})
|
||||
|
||||
@@ -60,7 +61,7 @@ class ScriptChangesNotifier(
|
||||
scriptsQueue.cancelAllRequests()
|
||||
|
||||
scriptsQueue.addRequest(
|
||||
{ scriptsManager.updateConfigurationsIfNotCached(listOf(ktFile)) },
|
||||
{ listeners.first { it.documentChanged(ktFile, updater) } },
|
||||
scriptChangesListenerDelay,
|
||||
true
|
||||
)
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.listener
|
||||
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
interface ScriptConfigurationUpdater {
|
||||
/**
|
||||
* Ensure that configuration for [file] is up-to-date,
|
||||
* or new configuration loading is started
|
||||
*/
|
||||
fun ensureUpToDatedConfigurationSuggested(file: KtFile)
|
||||
|
||||
/**
|
||||
* Check that configurations for given [files] is up-to-date or try synchronously update
|
||||
* them. Synchronous update will be occurred only if auto apply is enabled and
|
||||
* configuration for given file will not be loaded in background.
|
||||
*
|
||||
* @return true, if all files are already up-to-date or updated during this call
|
||||
*/
|
||||
fun ensureConfigurationUpToDate(files: List<KtFile>): Boolean
|
||||
|
||||
/**
|
||||
* Invalidate current configuration and start loading new
|
||||
*/
|
||||
fun forceConfigurationReload(file: KtFile)
|
||||
}
|
||||
+35
-16
@@ -5,37 +5,56 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.core.script.configuration.loader
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigurationInputs
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.utils.getKtFile
|
||||
import org.jetbrains.kotlin.idea.core.script.debug
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
|
||||
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
|
||||
import org.jetbrains.kotlin.scripting.resolve.KtFileScriptSource
|
||||
import org.jetbrains.kotlin.scripting.resolve.LegacyResolverWrapper
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
|
||||
import org.jetbrains.kotlin.scripting.resolve.refineScriptCompilationConfiguration
|
||||
import kotlin.script.experimental.api.valueOrNull
|
||||
import kotlin.script.experimental.dependencies.AsyncDependenciesResolver
|
||||
|
||||
class DefaultScriptConfigurationLoader internal constructor() :
|
||||
ScriptConfigurationLoader {
|
||||
override fun isAsync(file: KtFile, scriptDefinition: ScriptDefinition): Boolean {
|
||||
return scriptDefinition.asLegacyOrNull<KotlinScriptDefinition>()?.dependencyResolver?.let {
|
||||
it is AsyncDependenciesResolver || it is LegacyResolverWrapper
|
||||
} ?: false
|
||||
}
|
||||
open class DefaultScriptConfigurationLoader(val project: Project) : ScriptConfigurationLoader {
|
||||
override fun shouldRunInBackground(scriptDefinition: ScriptDefinition): Boolean =
|
||||
scriptDefinition
|
||||
.asLegacyOrNull<KotlinScriptDefinition>()
|
||||
?.dependencyResolver
|
||||
?.let { it is AsyncDependenciesResolver || it is LegacyResolverWrapper }
|
||||
?: false
|
||||
|
||||
override fun loadDependencies(
|
||||
firstLoad: Boolean,
|
||||
file: KtFile,
|
||||
scriptDefinition: ScriptDefinition
|
||||
): ScriptCompilationConfigurationResult? {
|
||||
isFirstLoad: Boolean,
|
||||
virtualFile: VirtualFile,
|
||||
scriptDefinition: ScriptDefinition,
|
||||
context: ScriptConfigurationLoadingContext
|
||||
): Boolean {
|
||||
val file = project.getKtFile(virtualFile) ?: return false
|
||||
|
||||
debug(file) { "start dependencies loading" }
|
||||
|
||||
val result = refineScriptCompilationConfiguration(KtFileScriptSource(file), scriptDefinition, file.project)
|
||||
val inputs = getInputsStamp(file)
|
||||
val scriptingApiResult = refineScriptCompilationConfiguration(
|
||||
KtFileScriptSource(file), scriptDefinition, file.project
|
||||
)
|
||||
|
||||
val result = LoadedScriptConfiguration(
|
||||
inputs,
|
||||
scriptingApiResult.reports,
|
||||
scriptingApiResult.valueOrNull()
|
||||
)
|
||||
|
||||
context.suggestNewConfiguration(virtualFile, result)
|
||||
|
||||
debug(file) { "finish dependencies loading" }
|
||||
|
||||
return result
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected open fun getInputsStamp(file: KtFile): CachedConfigurationInputs =
|
||||
CachedConfigurationInputs.PsiModificationStamp(file.modificationStamp)
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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.loader
|
||||
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigurationInputs
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||
import kotlin.script.experimental.api.ScriptDiagnostic
|
||||
|
||||
data class LoadedScriptConfiguration(
|
||||
val inputs: CachedConfigurationInputs,
|
||||
val reports: List<ScriptDiagnostic>,
|
||||
val configuration: ScriptCompilationConfigurationWrapper?
|
||||
)
|
||||
+17
-14
@@ -5,23 +5,26 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.core.script.configuration.loader
|
||||
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
|
||||
|
||||
// TODO: rename - this is not only about dependencies anymore
|
||||
/**
|
||||
* Provides the way to loading and saving script configuration.
|
||||
*
|
||||
* @see [org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptConfigurationManager] for more details.
|
||||
*/
|
||||
internal interface ScriptConfigurationLoader {
|
||||
fun isAsync(file: KtFile, scriptDefinition: ScriptDefinition) = false
|
||||
|
||||
val skipSaveToAttributes: Boolean
|
||||
get() = false
|
||||
|
||||
val skipNotification: Boolean
|
||||
get() = false
|
||||
fun shouldRunInBackground(scriptDefinition: ScriptDefinition): Boolean = false
|
||||
|
||||
/**
|
||||
* Implementation should load configuration and call `context.suggestNewConfiguration` or `saveNewConfiguration`.
|
||||
*
|
||||
* @return true when this loader is applicable.
|
||||
*/
|
||||
fun loadDependencies(
|
||||
firstLoad: Boolean,
|
||||
file: KtFile,
|
||||
scriptDefinition: ScriptDefinition
|
||||
): ScriptCompilationConfigurationResult?
|
||||
isFirstLoad: Boolean,
|
||||
virtualFile: VirtualFile,
|
||||
scriptDefinition: ScriptDefinition,
|
||||
context: ScriptConfigurationLoadingContext
|
||||
): Boolean
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.loader
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigurationSnapshot
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationFileAttributeCache
|
||||
|
||||
interface ScriptConfigurationLoadingContext {
|
||||
fun getCachedConfiguration(file: VirtualFile): CachedConfigurationSnapshot?
|
||||
|
||||
/**
|
||||
* Show notification about new configuration with suggestion to apply it.
|
||||
* User may disable this notifications, in this case configuration will be saved immediately.
|
||||
*
|
||||
* If configuration is null, then the result will be treated as failed, and
|
||||
* reports will be displayed immediately.
|
||||
*
|
||||
* @sample DefaultScriptConfigurationLoader.loadDependencies
|
||||
*/
|
||||
fun suggestNewConfiguration(
|
||||
file: VirtualFile,
|
||||
newResult: LoadedScriptConfiguration
|
||||
)
|
||||
|
||||
/**
|
||||
* Save [newResult] for [file] into caches and update highlighting.
|
||||
*
|
||||
* @sample ScriptConfigurationFileAttributeCache.loadDependencies
|
||||
*/
|
||||
fun saveNewConfiguration(
|
||||
file: VirtualFile,
|
||||
newResult: LoadedScriptConfiguration
|
||||
)
|
||||
}
|
||||
+23
-20
@@ -5,32 +5,35 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.core.script.configuration.loader
|
||||
|
||||
import com.intellij.psi.PsiManager
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptConfigurationManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.kotlin.idea.highlighter.OutsidersPsiFileSupportUtils
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
|
||||
import kotlin.script.experimental.api.asSuccess
|
||||
|
||||
class ScriptOutsiderFileConfigurationLoader(private val manager: DefaultScriptConfigurationManager) :
|
||||
internal class ScriptOutsiderFileConfigurationLoader(val project: Project) :
|
||||
ScriptConfigurationLoader {
|
||||
override val skipSaveToAttributes: Boolean
|
||||
get() = true
|
||||
|
||||
override val skipNotification: Boolean
|
||||
get() = true
|
||||
|
||||
override fun loadDependencies(
|
||||
firstLoad: Boolean,
|
||||
file: KtFile,
|
||||
scriptDefinition: ScriptDefinition
|
||||
): ScriptCompilationConfigurationResult? {
|
||||
val virtualFile = file.virtualFile ?: return null
|
||||
val project = file.project
|
||||
isFirstLoad: Boolean,
|
||||
virtualFile: VirtualFile,
|
||||
scriptDefinition: ScriptDefinition,
|
||||
context: ScriptConfigurationLoadingContext
|
||||
): Boolean {
|
||||
if (!isFirstLoad) return false
|
||||
|
||||
val fileOrigin = OutsidersPsiFileSupportUtils.getOutsiderFileOrigin(project, virtualFile) ?: return null
|
||||
val psiFileOrigin = PsiManager.getInstance(project).findFile(fileOrigin) as? KtFile ?: return null
|
||||
return manager.getConfiguration(psiFileOrigin)?.asSuccess()
|
||||
val fileOrigin = OutsidersPsiFileSupportUtils.getOutsiderFileOrigin(
|
||||
project,
|
||||
virtualFile
|
||||
) ?: return false
|
||||
|
||||
val original = context.getCachedConfiguration(fileOrigin)
|
||||
if (original != null) {
|
||||
context.saveNewConfiguration(virtualFile, LoadedScriptConfiguration(original.inputs, listOf(), original.configuration))
|
||||
}
|
||||
|
||||
// todo(KT-34615): initiate loading configuration for original file and subscribe to it's result?
|
||||
// should we show "new context" notification for both files?
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
+190
-59
@@ -5,102 +5,233 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.core.script.configuration.utils
|
||||
|
||||
import com.intellij.openapi.progress.EmptyProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.Task
|
||||
import com.intellij.openapi.progress.util.BackgroundTaskUtil
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.util.Alarm
|
||||
import com.intellij.util.containers.HashSetQueue
|
||||
import org.jetbrains.kotlin.idea.core.script.debug
|
||||
import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Sequentially loads script configuration in background.
|
||||
* Loading tasks scheduled by calling [ensureScheduled].
|
||||
*
|
||||
* Progress indicator will be shown after [PROGRESS_INDICATOR_DELAY] ms or if
|
||||
* more then [PROGRESS_INDICATOR_MIN_QUEUE] tasks scheduled.
|
||||
*
|
||||
* States:
|
||||
* silentWorker underProgressWorker
|
||||
* - sleep
|
||||
* - silent x
|
||||
* - silent and under progress x x
|
||||
* - under progress x
|
||||
*/
|
||||
internal class BackgroundExecutor(
|
||||
private val project: Project,
|
||||
private val rootsManager: ScriptClassRootsIndexer,
|
||||
private val loadDependencies: (KtFile) -> Unit
|
||||
val project: Project,
|
||||
val rootsManager: ScriptClassRootsIndexer
|
||||
) {
|
||||
private var task: Task? = null
|
||||
companion object {
|
||||
const val PROGRESS_INDICATOR_DELAY = 1000
|
||||
const val PROGRESS_INDICATOR_MIN_QUEUE = 3
|
||||
}
|
||||
|
||||
private val work = Any()
|
||||
private val queue: Queue<LoadTask> = HashSetQueue()
|
||||
|
||||
/**
|
||||
* Let's fix queue size when progress bar displayed.
|
||||
* Progress for rest items will be counted from zero
|
||||
*/
|
||||
private var currentProgressSize: Int = 0
|
||||
private var currentProgressDone: Int = 0
|
||||
|
||||
private var silentWorker: SilentWorker? = null
|
||||
private var underProgressWorker: UnderProgressWorker? = null
|
||||
private val longRunningAlarm = Alarm(Alarm.ThreadToUse.POOLED_THREAD, project)
|
||||
private var longRunningAlaramRequested = false
|
||||
|
||||
private var inTransaction: Boolean = false
|
||||
private var currentFile: VirtualFile? = null
|
||||
|
||||
class LoadTask(val key: VirtualFile, val actions: () -> Unit) {
|
||||
override fun equals(other: Any?) =
|
||||
this === other || (other is LoadTask && key == other.key)
|
||||
|
||||
override fun hashCode() = key.hashCode()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun scheduleAsync(file: KtFile) {
|
||||
if (task == null) {
|
||||
startBatch(file)
|
||||
} else {
|
||||
task!!.addTask(file)
|
||||
fun ensureScheduled(key: VirtualFile, actions: () -> Unit) {
|
||||
val task = LoadTask(key, actions)
|
||||
|
||||
if (queue.add(task)) {
|
||||
debug(task.key) { "added to update queue" }
|
||||
|
||||
updateProgress()
|
||||
|
||||
// If the queue is longer than PROGRESS_INDICATOR_MIN_QUEUE, show progress and cancel button
|
||||
if (queue.size > PROGRESS_INDICATOR_MIN_QUEUE) {
|
||||
requireUnderProgressWorker()
|
||||
} else {
|
||||
requireSilentWorker()
|
||||
|
||||
if (!longRunningAlaramRequested) {
|
||||
longRunningAlaramRequested = true
|
||||
longRunningAlarm.addRequest(
|
||||
{
|
||||
longRunningAlaramRequested = false
|
||||
requireUnderProgressWorker()
|
||||
},
|
||||
PROGRESS_INDICATOR_DELAY
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun startBatch(file: KtFile) {
|
||||
private fun requireSilentWorker() {
|
||||
if (silentWorker == null && underProgressWorker == null) {
|
||||
silentWorker = SilentWorker().also { it.start() }
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun requireUnderProgressWorker() {
|
||||
if (queue.isEmpty() && silentWorker == null) return
|
||||
|
||||
silentWorker?.stopGracefully()
|
||||
if (underProgressWorker == null) {
|
||||
underProgressWorker = UnderProgressWorker().also { it.start() }
|
||||
restartProgressBar()
|
||||
updateProgress()
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun restartProgressBar() {
|
||||
currentProgressSize = queue.size
|
||||
currentProgressDone = 0
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun updateProgress() {
|
||||
underProgressWorker?.progressIndicator?.let {
|
||||
it.text2 = currentFile?.path ?: ""
|
||||
if (queue.size == 0) {
|
||||
// last file
|
||||
it.isIndeterminate = true
|
||||
} else {
|
||||
it.isIndeterminate = false
|
||||
if (currentProgressDone > currentProgressSize) {
|
||||
restartProgressBar()
|
||||
}
|
||||
it.fraction = currentProgressDone.toDouble() / currentProgressSize.toDouble()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun ensureInTransaction() {
|
||||
if (inTransaction) return
|
||||
inTransaction = true
|
||||
rootsManager.startTransaction()
|
||||
task = Task()
|
||||
task!!.addTask(file)
|
||||
task!!.start()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun endBatch() {
|
||||
task = null
|
||||
check(inTransaction)
|
||||
rootsManager.commit()
|
||||
inTransaction = false
|
||||
}
|
||||
|
||||
private inner class Task {
|
||||
private val sequenceOfFiles: ConcurrentLinkedQueue<KtFile> = ConcurrentLinkedQueue()
|
||||
private var forceStop: Boolean = false
|
||||
private var startedSilently: Boolean = false
|
||||
private abstract inner class Worker {
|
||||
private var shouldStop = false
|
||||
|
||||
fun start() {
|
||||
if (KotlinScriptingSettings.getInstance(project).isAutoReloadEnabled) {
|
||||
startWithProgress()
|
||||
} else {
|
||||
startSilently()
|
||||
open fun start() {
|
||||
ensureInTransaction()
|
||||
}
|
||||
|
||||
fun stopGracefully() {
|
||||
shouldStop = true
|
||||
}
|
||||
|
||||
protected open fun checkCancelled() = false
|
||||
protected abstract fun close()
|
||||
|
||||
protected fun run() {
|
||||
try {
|
||||
while (true) {
|
||||
// prevent parallel work in both silent and under progress
|
||||
synchronized(work) {
|
||||
val next = synchronized(this@BackgroundExecutor) {
|
||||
if (shouldStop) return
|
||||
|
||||
if (checkCancelled() || queue.isEmpty()) {
|
||||
endBatch()
|
||||
return
|
||||
}
|
||||
|
||||
queue.poll()?.also {
|
||||
currentFile = it.key
|
||||
currentProgressDone++
|
||||
updateProgress()
|
||||
}
|
||||
}
|
||||
|
||||
next?.actions?.invoke()
|
||||
|
||||
synchronized(work) {
|
||||
currentFile = null
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun restartWithProgress() {
|
||||
forceStop = true
|
||||
startWithProgress()
|
||||
forceStop = false
|
||||
}
|
||||
private inner class UnderProgressWorker : Worker() {
|
||||
var progressIndicator: ProgressIndicator? = null
|
||||
|
||||
private fun startSilently() {
|
||||
startedSilently = true
|
||||
BackgroundTaskUtil.executeOnPooledThread(project, Runnable {
|
||||
loadDependencies(EmptyProgressIndicator())
|
||||
})
|
||||
}
|
||||
override fun start() {
|
||||
super.start()
|
||||
|
||||
private fun startWithProgress() {
|
||||
startedSilently = false
|
||||
object : com.intellij.openapi.progress.Task.Backgroundable(project, "Kotlin: Loading script dependencies...", true) {
|
||||
object : Task.Backgroundable(project, "Kotlin: Loading script dependencies...", true) {
|
||||
override fun run(indicator: ProgressIndicator) {
|
||||
loadDependencies(indicator)
|
||||
progressIndicator = indicator
|
||||
updateProgress()
|
||||
run()
|
||||
}
|
||||
}.queue()
|
||||
}
|
||||
|
||||
fun addTask(file: KtFile) {
|
||||
if (file in sequenceOfFiles) return
|
||||
override fun checkCancelled(): Boolean = progressIndicator?.isCanceled == true
|
||||
|
||||
debug(file) { "added to update queue" }
|
||||
|
||||
sequenceOfFiles.add(file)
|
||||
|
||||
// If the queue is longer than 3, show progress and cancel button
|
||||
if (sequenceOfFiles.size > 3 && startedSilently) {
|
||||
restartWithProgress()
|
||||
override fun close() {
|
||||
synchronized(this@BackgroundExecutor) {
|
||||
underProgressWorker = null
|
||||
progressIndicator = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadDependencies(indicator: ProgressIndicator?) {
|
||||
while (true) {
|
||||
if (forceStop) return
|
||||
if (indicator?.isCanceled == true || sequenceOfFiles.isEmpty()) {
|
||||
endBatch()
|
||||
return
|
||||
}
|
||||
private inner class SilentWorker : Worker() {
|
||||
override fun start() {
|
||||
super.start()
|
||||
|
||||
loadDependencies(sequenceOfFiles.poll())
|
||||
BackgroundTaskUtil.executeOnPooledThread(project, Runnable {
|
||||
run()
|
||||
})
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
synchronized(this@BackgroundExecutor) {
|
||||
silentWorker = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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.module.ModuleManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.roots.ModuleRootManager
|
||||
import com.intellij.openapi.roots.OrderRootType
|
||||
import com.intellij.openapi.vfs.VfsUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
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.configuration.cache.ScriptConfigurationCache
|
||||
import org.jetbrains.kotlin.idea.core.script.debug
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||
|
||||
internal class ScriptClassRootsCache(
|
||||
private val project: Project,
|
||||
private val cache: ScriptConfigurationCache
|
||||
) {
|
||||
private val all: Collection<Pair<VirtualFile, ScriptCompilationConfigurationWrapper>> get() = cache.all()
|
||||
private fun getConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? = cache[file]?.configuration
|
||||
|
||||
private fun getScriptSdk(compilationConfiguration: ScriptCompilationConfigurationWrapper?): Sdk? {
|
||||
// workaround for mismatched gradle wrapper and plugin version
|
||||
val javaHome = try {
|
||||
compilationConfiguration?.javaHome?.let { VfsUtil.findFileByIoFile(it, true) }
|
||||
} catch (e: Throwable) {
|
||||
null
|
||||
} ?: return null
|
||||
|
||||
return getAllProjectSdks().find { it.homeDirectory == javaHome }
|
||||
}
|
||||
|
||||
private val scriptsSdksCache: Map<VirtualFile, Sdk?> =
|
||||
ConcurrentFactoryMap.createWeakMap { file ->
|
||||
val compilationConfiguration = getConfiguration(file)
|
||||
return@createWeakMap getScriptSdk(compilationConfiguration)
|
||||
?: ScriptConfigurationManager.getScriptDefaultSdk(project)
|
||||
}
|
||||
|
||||
fun getScriptSdk(file: VirtualFile): Sdk? = scriptsSdksCache[file]
|
||||
|
||||
fun getFirstScriptsSdk(): Sdk? {
|
||||
val firstCachedScript = all.firstOrNull()?.first ?: return null
|
||||
return scriptsSdksCache[firstCachedScript]
|
||||
}
|
||||
|
||||
private val allSdks by lazy {
|
||||
all.mapNotNull { scriptsSdksCache[it.first] }
|
||||
.distinct()
|
||||
}
|
||||
|
||||
private val allNonIndexedSdks by lazy {
|
||||
all.mapNotNull { scriptsSdksCache[it.first] }
|
||||
.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 sdkFiles = allNonIndexedSdks.flatMap { it.rootProvider.getFiles(OrderRootType.CLASSES).toList() }
|
||||
val scriptDependenciesClasspath = all.flatMap { it.second.dependenciesClassPath }.distinct()
|
||||
|
||||
sdkFiles + ScriptConfigurationManager.toVfsRoots(scriptDependenciesClasspath)
|
||||
}
|
||||
|
||||
val allDependenciesSources by lazy {
|
||||
val sdkSources = allNonIndexedSdks.flatMap { it.rootProvider.getFiles(OrderRootType.SOURCES).toList() }
|
||||
val scriptDependenciesSources = all.flatMap { it.second.dependenciesSources }.distinct()
|
||||
|
||||
sdkSources + ScriptConfigurationManager.toVfsRoots(scriptDependenciesSources)
|
||||
}
|
||||
|
||||
val allDependenciesClassFilesScope by lazy {
|
||||
NonClasspathDirectoriesScope.compose(allDependenciesClassFiles)
|
||||
}
|
||||
|
||||
val allDependenciesSourcesScope by lazy {
|
||||
NonClasspathDirectoriesScope.compose(allDependenciesSources)
|
||||
}
|
||||
|
||||
private val scriptsDependenciesClasspathScopeCache: MutableMap<VirtualFile, GlobalSearchScope> =
|
||||
ConcurrentFactoryMap.createWeakMap { file ->
|
||||
val compilationConfiguration = getConfiguration(file)
|
||||
?: return@createWeakMap GlobalSearchScope.EMPTY_SCOPE
|
||||
|
||||
val roots = compilationConfiguration.dependenciesClassPath
|
||||
val sdk = scriptsSdksCache[file]
|
||||
|
||||
@Suppress("FoldInitializerAndIfToElvis")
|
||||
if (sdk == null) {
|
||||
return@createWeakMap NonClasspathDirectoriesScope.compose(
|
||||
ScriptConfigurationManager.toVfsRoots(
|
||||
roots
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return@createWeakMap NonClasspathDirectoriesScope.compose(
|
||||
sdk.rootProvider.getFiles(OrderRootType.CLASSES).toList() + ScriptConfigurationManager.toVfsRoots(
|
||||
roots
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun getScriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope {
|
||||
return scriptsDependenciesClasspathScopeCache[file] ?: GlobalSearchScope.EMPTY_SCOPE
|
||||
}
|
||||
|
||||
fun hasNotCachedRoots(compilationConfiguration: ScriptCompilationConfigurationWrapper): Boolean {
|
||||
val scriptSdk = getScriptSdk(compilationConfiguration)
|
||||
?: ScriptConfigurationManager.getScriptDefaultSdk(project)
|
||||
|
||||
val wasSdkChanged = scriptSdk != null && !allSdks.contains(scriptSdk)
|
||||
if (wasSdkChanged) {
|
||||
debug { "sdk was changed: $compilationConfiguration" }
|
||||
return true
|
||||
}
|
||||
|
||||
val newClassRoots = ScriptConfigurationManager.toVfsRoots(compilationConfiguration.dependenciesClassPath)
|
||||
for (newClassRoot in newClassRoots) {
|
||||
if (!allDependenciesClassFiles.contains(newClassRoot)) {
|
||||
debug { "class root was changed: $newClassRoot" }
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
val newSourceRoots = ScriptConfigurationManager.toVfsRoots(compilationConfiguration.dependenciesSources)
|
||||
for (newSourceRoot in newSourceRoots) {
|
||||
if (!allDependenciesSources.contains(newSourceRoot)) {
|
||||
debug { "source root was changed: $newSourceRoot" }
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
+10
-20
@@ -12,41 +12,32 @@ import com.intellij.openapi.roots.ex.ProjectRootManagerEx
|
||||
import com.intellij.openapi.util.EmptyRunnable
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationMemoryCache
|
||||
import org.jetbrains.kotlin.idea.core.script.debug
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
/**
|
||||
* Utility for postponing indexing of new roots at the end of some bulk operation.
|
||||
* Utility for postponing indexing of new roots to the end of some bulk operation.
|
||||
*/
|
||||
internal class ScriptClassRootsIndexer(val project: Project) {
|
||||
private var newRootsPresent: Boolean = false
|
||||
private val concurrentTransactions = AtomicInteger()
|
||||
|
||||
fun checkNonCachedRoots(
|
||||
cache: ScriptConfigurationMemoryCache,
|
||||
file: VirtualFile,
|
||||
configuration: ScriptCompilationConfigurationWrapper
|
||||
) {
|
||||
@Synchronized
|
||||
fun markNewRoot(file: VirtualFile, configuration: ScriptCompilationConfigurationWrapper) {
|
||||
debug(file) { "new class roots found: $configuration" }
|
||||
checkInTransaction()
|
||||
if (cache.hasNotCachedRoots(configuration)) {
|
||||
synchronized(this) {
|
||||
debug(file) { "new class roots found: $configuration" }
|
||||
checkInTransaction()
|
||||
newRootsPresent = true
|
||||
}
|
||||
}
|
||||
newRootsPresent = true
|
||||
}
|
||||
|
||||
private fun checkInTransaction() {
|
||||
check(concurrentTransactions.get() > 0) { "Transaction is not started" }
|
||||
fun checkInTransaction() {
|
||||
check(concurrentTransactions.get() > 0)
|
||||
}
|
||||
|
||||
inline fun transaction(body: () -> Unit) {
|
||||
inline fun <T> transaction(body: () -> T): T {
|
||||
startTransaction()
|
||||
try {
|
||||
return try {
|
||||
body()
|
||||
} finally {
|
||||
commit()
|
||||
@@ -77,8 +68,7 @@ internal class ScriptClassRootsIndexer(val project: Project) {
|
||||
debug { "roots change event" }
|
||||
|
||||
ProjectRootManagerEx.getInstanceEx(project)?.makeRootsChange(EmptyRunnable.getInstance(), false, true)
|
||||
ScriptDependenciesModificationTracker.getInstance(project)
|
||||
.incModificationCount()
|
||||
ScriptDependenciesModificationTracker.getInstance(project).incModificationCount()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiManager
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
fun Project.getKtFile(
|
||||
virtualFile: VirtualFile?,
|
||||
ktFile: KtFile? = null
|
||||
): KtFile? {
|
||||
if (virtualFile == null) return null
|
||||
if (ktFile != null) {
|
||||
check(ktFile.virtualFile == virtualFile)
|
||||
return ktFile
|
||||
} else {
|
||||
return runReadAction { PsiManager.getInstance(this).findFile(virtualFile) as? KtFile }
|
||||
}
|
||||
}
|
||||
+10
-4
@@ -23,8 +23,10 @@ import com.intellij.openapi.vfs.VfsUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.pom.Navigatable
|
||||
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigurationInputs
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.getGradleScriptInputsStamp
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.LoadedScriptConfiguration
|
||||
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||
import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
|
||||
@@ -32,7 +34,6 @@ import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData
|
||||
import org.jetbrains.plugins.gradle.settings.GradleExecutionSettings
|
||||
import org.jetbrains.plugins.gradle.util.GradleConstants
|
||||
import java.io.File
|
||||
import kotlin.script.experimental.api.ResultWithDiagnostics
|
||||
import kotlin.script.experimental.dependencies.ScriptDependencies
|
||||
|
||||
class KotlinGradleBuildScriptsDataService : AbstractProjectDataService<GradleSourceSetData, Void>() {
|
||||
@@ -57,16 +58,21 @@ class KotlinGradleBuildScriptsDataService : AbstractProjectDataService<GradleSou
|
||||
)
|
||||
val javaHome = File(gradleExeSettings.javaHome ?: return)
|
||||
|
||||
val files = mutableListOf<Pair<VirtualFile, ScriptCompilationConfigurationResult>>()
|
||||
val files = mutableListOf<Pair<VirtualFile, LoadedScriptConfiguration>>()
|
||||
|
||||
projectDataNode.gradleKotlinBuildScripts?.forEach { buildScript ->
|
||||
val scriptFile = File(buildScript.file)
|
||||
val virtualFile = VfsUtil.findFile(scriptFile.toPath(), true)!!
|
||||
|
||||
// todo(KT-34440): take inputs snapshot before starting import
|
||||
val inputs = getGradleScriptInputsStamp(project, virtualFile)
|
||||
|
||||
files.add(
|
||||
Pair(
|
||||
virtualFile,
|
||||
ResultWithDiagnostics.Success(
|
||||
LoadedScriptConfiguration(
|
||||
inputs ?: CachedConfigurationInputs.OutOfDate,
|
||||
listOf(),
|
||||
ScriptCompilationConfigurationWrapper.FromLegacy(
|
||||
VirtualFileScriptSource(virtualFile),
|
||||
ScriptDependencies(
|
||||
|
||||
@@ -136,7 +136,7 @@ class UnusedSymbolInspection : AbstractKotlinInspection() {
|
||||
|
||||
val usedScripts = findScriptsWithUsages(declaration)
|
||||
if (usedScripts.isNotEmpty()) {
|
||||
if (ScriptConfigurationManager.getInstance(declaration.project).updateConfigurationsIfNotCached(usedScripts)) {
|
||||
if (!ScriptConfigurationManager.getInstance(declaration.project).updater.ensureConfigurationUpToDate(usedScripts)) {
|
||||
return TOO_MANY_OCCURRENCES
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user