From 961e8c2c74ccba9b406fbdd04f759f6284578f68 Mon Sep 17 00:00:00 2001 From: Natalia Selezneva Date: Thu, 22 Aug 2019 11:59:32 +0300 Subject: [PATCH] Refactor the mechanism how script configurations are updated Introduce file attributes cache, which is only applicable when no dependencies are cached in memory cache Refactor ScriptDependenciesCache to memory cache that is able to get configuration from cache and checks if it is up to date Move all public methods to ScriptDependenciesManager Introduce ScriptClassRootsManager that checks if root change event should be called after script configuration update --- .../kotlin/codegen/CodegenTestCase.java | 2 +- .../idea/caches/project/ScriptModuleInfos.kt | 2 +- .../highlighter/KotlinHighlightingUtil.kt | 4 +- .../idea/core/script/IdeScriptReportSink.kt | 13 - .../core/script/ScriptClassRootsManager.kt | 87 +++++ .../script/ScriptConfigurationManagerImpl.kt | 335 ++++++++++++++++++ ...e.kt => ScriptConfigurationMemoryCache.kt} | 156 ++++---- .../core/script/ScriptDefinitionsManager.kt | 12 +- .../script/ScriptDependenciesFileAttribute.kt | 83 ----- .../core/script/ScriptDependenciesManager.kt | 109 +++--- .../ScriptNewDependenciesNotification.kt | 10 +- .../ScriptTrafficLightRendererContributor.kt | 2 +- .../ScriptsCompilationConfigurationUpdater.kt | 195 ---------- .../idea/core/script/ScriptsListener.kt | 88 +++++ .../AsyncScriptDependenciesLoader.kt | 159 --------- .../script/dependencies/BackgroundLoader.kt | 108 ++++++ ...omFileAttributeScriptDependenciesLoader.kt | 66 ---- .../FromRefinedConfigurationLoader.kt | 40 +++ .../OutsiderFileDependenciesLoader.kt | 37 +- .../ScriptConfigurationFileAttributeCache.kt | 134 +++++++ .../dependencies/ScriptDependenciesLoader.kt | 163 +-------- .../SyncScriptDependenciesLoader.kt | 33 -- .../kotlin/idea/core/script/scriptUtils.kt | 31 ++ .../compile/KtScratchExecutionSession.kt | 3 +- idea/resources/META-INF/plugin-common.xml | 8 +- .../inspections/UnusedSymbolInspection.kt | 4 +- .../idea/util/ImportInsertHelperImpl.kt | 4 +- ...actConfigureProjectByChangingFileTest.java | 6 +- .../definitions/ScriptDependenciesProvider.kt | 7 +- .../definitions/dynamicConfigurations.kt | 3 +- .../ScriptExtraImportsProviderExtension.kt | 4 +- .../scripting/resolve/LazyScriptDescriptor.kt | 2 +- .../ScriptsCompilationDependencies.kt | 3 +- .../plugin/impl/KJvmReplCompilerImpl.kt | 2 +- .../plugin/impl/ScriptJvmCompilerImpls.kt | 2 +- .../plugin/impl/compilationContext.kt | 3 +- .../plugin/repl/GenericReplCompiler.kt | 3 +- 37 files changed, 1025 insertions(+), 898 deletions(-) create mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptClassRootsManager.kt create mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptConfigurationManagerImpl.kt rename idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/{ScriptsCompilationConfigurationCache.kt => ScriptConfigurationMemoryCache.kt} (66%) delete mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDependenciesFileAttribute.kt delete mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptsCompilationConfigurationUpdater.kt create mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptsListener.kt delete mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/AsyncScriptDependenciesLoader.kt create mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/BackgroundLoader.kt delete mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/FromFileAttributeScriptDependenciesLoader.kt create mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/FromRefinedConfigurationLoader.kt delete mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/SyncScriptDependenciesLoader.kt create mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/scriptUtils.kt diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/CodegenTestCase.java b/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/CodegenTestCase.java index ac50b520189..c864c57f0a7 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/CodegenTestCase.java +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/CodegenTestCase.java @@ -431,7 +431,7 @@ public abstract class CodegenTestCase extends KtUsefulTestCase { if (externalImportsProvider != null) { myEnvironment.getSourceFiles().forEach( file -> { - ScriptCompilationConfigurationWrapper refinedConfiguration = ErrorHandlingKt.valueOrNull(externalImportsProvider.getScriptConfigurationResult(file)); + ScriptCompilationConfigurationWrapper refinedConfiguration = externalImportsProvider.getScriptConfiguration(file); if (refinedConfiguration != null) { files.addAll(refinedConfiguration.getDependenciesClassPath()); } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/ScriptModuleInfos.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/ScriptModuleInfos.kt index 9257cd331aa..f7188369530 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/ScriptModuleInfos.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/ScriptModuleInfos.kt @@ -105,7 +105,7 @@ sealed class ScriptDependenciesInfo(val project: Project) : IdeaModuleInfo, Bina class ForProject(project: Project) : ScriptDependenciesInfo(project) { override val sdk: Sdk? get() { - return ScriptDependenciesManager.getInstance(project).getAllScriptsSdks().firstOrNull() + return ScriptDependenciesManager.getInstance(project).getFirstScriptsSdk() ?: ScriptDependenciesManager.getScriptDefaultSdk(project) } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/KotlinHighlightingUtil.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/KotlinHighlightingUtil.kt index f45450df9a9..58024055515 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/KotlinHighlightingUtil.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/KotlinHighlightingUtil.kt @@ -22,7 +22,7 @@ import org.jetbrains.kotlin.idea.caches.project.NotUnderContentRootModuleInfo import org.jetbrains.kotlin.idea.caches.project.getModuleInfo import org.jetbrains.kotlin.idea.core.script.IdeScriptReportSink import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionsManager -import org.jetbrains.kotlin.idea.core.script.ScriptsCompilationConfigurationUpdater +import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesManager import org.jetbrains.kotlin.idea.util.ProjectRootsUtil import org.jetbrains.kotlin.idea.util.isRunningInCidrIde import org.jetbrains.kotlin.psi.KtCodeFragment @@ -70,7 +70,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 (!ScriptsCompilationConfigurationUpdater.areDependenciesCached(ktFile)) return false + if (!ScriptDependenciesManager.getInstance(ktFile.project).isConfigurationCached(ktFile)) return false if (IdeScriptReportSink.getReports(ktFile).any { it.severity == ScriptDiagnostic.Severity.FATAL }) { return false } diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/IdeScriptReportSink.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/IdeScriptReportSink.kt index 17a865e42c5..e13a9bc888e 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/IdeScriptReportSink.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/IdeScriptReportSink.kt @@ -16,13 +16,9 @@ package org.jetbrains.kotlin.idea.core.script -import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer -import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.project.Project import com.intellij.openapi.util.Key import com.intellij.openapi.vfs.VirtualFile -import com.intellij.psi.PsiManager -import com.intellij.ui.EditorNotifications import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.UserDataProperty import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink @@ -34,15 +30,6 @@ class IdeScriptReportSink(val project: Project) : ScriptReportSink { // TODO: persist errors between launches? scriptFile.scriptDiagnostics = reports - - ApplicationManager.getApplication().invokeLater { - if (scriptFile.isValid && !project.isDisposed) { - PsiManager.getInstance(project).findFile(scriptFile)?.let { psiFile -> - DaemonCodeAnalyzer.getInstance(project).restart(psiFile) - EditorNotifications.getInstance(project).updateNotifications(scriptFile) - } - } - } } companion object { diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptClassRootsManager.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptClassRootsManager.kt new file mode 100644 index 00000000000..57a62c9496c --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptClassRootsManager.kt @@ -0,0 +1,87 @@ +/* + * 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 + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.TransactionGuard +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ex.ProjectRootManagerEx +import com.intellij.openapi.util.EmptyRunnable +import com.intellij.openapi.vfs.VirtualFile +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. + */ +internal class ScriptClassRootsManager(val project: Project) { + private var newRootsPresent: Boolean = false + private val concurrentTransactions = AtomicInteger() + + fun checkNonCachedRoots( + cache: ScriptConfigurationMemoryCache, + file: VirtualFile, + configuration: ScriptCompilationConfigurationWrapper + ) { + checkInTransaction() + if (cache.hasNotCachedRoots(configuration)) { + synchronized(this) { + debug(file) { "new class roots found: $configuration" } + checkInTransaction() + newRootsPresent = true + } + } + } + + private fun checkInTransaction() { + check(concurrentTransactions.get() > 0) + } + + inline fun transaction(body: () -> Unit) { + startTransaction() + try { + body() + } finally { + commit() + } + } + + fun startTransaction() { + concurrentTransactions.incrementAndGet() + } + + fun commit() { + concurrentTransactions.decrementAndGet() + + // run indexing even in inner transaction + // (outer transaction may be async, so it would be better to not wait it) + startIndexingIfNeeded() + } + + @Synchronized + private fun startIndexingIfNeeded() { + if (!newRootsPresent) return + newRootsPresent = false + + val doNotifyRootsChanged = Runnable { + runWriteAction { + if (project.isDisposed) return@runWriteAction + + debug { "roots change event" } + + ProjectRootManagerEx.getInstanceEx(project)?.makeRootsChange(EmptyRunnable.getInstance(), false, true) + ScriptDependenciesModificationTracker.getInstance(project).incModificationCount() + } + } + + if (ApplicationManager.getApplication().isUnitTestMode) { + TransactionGuard.submitTransaction(project, doNotifyRootsChanged) + } else { + TransactionGuard.getInstance().submitTransactionLater(project, doNotifyRootsChanged) + } + } +} \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptConfigurationManagerImpl.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptConfigurationManagerImpl.kt new file mode 100644 index 00000000000..99c6845b323 --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptConfigurationManagerImpl.kt @@ -0,0 +1,335 @@ +/* + * 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 + +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.ScriptDependenciesManager.Companion.toVfsRoots +import org.jetbrains.kotlin.idea.core.script.dependencies.* +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.resolve.ScriptCompilationConfigurationResult +import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper +import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink +import kotlin.script.experimental.api.ScriptDiagnostic +import kotlin.script.experimental.api.valueOrNull + +class ScriptConfigurationManagerImpl internal constructor( + private val project: Project +) : ScriptConfigurationManager, ScriptDependenciesManager { + private val rootsManager = ScriptClassRootsManager(project) + + private val memoryCache = ScriptConfigurationMemoryCache(project) + private val fileAttributesCache = ScriptConfigurationFileAttributeCache() + + private val fromRefinedLoader = FromRefinedConfigurationLoader() + private val loaders = arrayListOf( + OutsiderFileDependenciesLoader(this), + fromRefinedLoader, + fileAttributesCache + ) + + private val backgroundLoader = BackgroundLoader(project, rootsManager, ::reloadConfigurationAsync) + + private val listener = ScriptsListener(project, this) + + /** + * Save configurations into cache. + * Start indexing for new class/source roots. + * Re-highlight opened scripts with changed configuration. + */ + override fun saveCompilationConfigurationAfterImport(files: List>) { + rootsManager.transaction { + for ((file, result) in files) { + saveConfiguration(file, result, skipNotification = true) + } + } + } + + /** + * 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): Boolean { + if (!ScriptDefinitionsManager.getInstance(project).isReady()) return false + + val notCached = files.filterNot { isConfigurationUpToDate(it.originalFile.virtualFile) } + if (notCached.isNotEmpty()) { + rootsManager.transaction { + for (file in notCached) { + reloadConfiguration(file) + } + } + return true + } + + 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() { + clearAndRehighlight() + } + + @Deprecated("Use getScriptClasspath(KtFile) instead") + override fun getScriptClasspath(file: VirtualFile): List { + val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return emptyList() + return getScriptClasspath(ktFile) + } + + override fun getScriptClasspath(file: KtFile): List = + 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) { + 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 ScriptDependenciesManager.getConfiguration + */ + private fun saveConfiguration( + file: VirtualFile, + newResult: ScriptCompilationConfigurationResult, + skipNotification: Boolean = false, + skipSaveToAttributes: Boolean = false + ) { + debug(file) { "configuration received = $newResult" } + + 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) + } + saveChangedConfiguration(file, newConfiguration, skipSaveToAttributes) + } else { + debug(file) { + "configuration changed, notification is shown: old = $oldConfiguration, new = $newConfiguration" + } + file.addScriptDependenciesNotificationPanel( + newConfiguration, project, + onClick = { + file.removeScriptDependenciesNotificationPanel(project) + rootsManager.transaction { + saveChangedConfiguration(file, it, skipSaveToAttributes) + } + } + ) + } + } + } + } + + private fun saveChangedConfiguration( + file: VirtualFile, + newConfiguration: ScriptCompilationConfigurationWrapper?, + skipSaveToAttributes: Boolean + ) { + debug(file) { "configuration changed = $newConfiguration" } + + 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() + } + + updateHighlighting(listOf(file)) + } + + private fun saveReports( + file: VirtualFile, + newReports: List + ) { + val oldReports = IdeScriptReportSink.getReports(file) + if (oldReports != newReports) { + debug(file) { "new script reports = $newReports" } + + ServiceManager.getService(project, ScriptReportSink::class.java).attachReports(file, newReports) + + GlobalScope.launch(EDT(project)) { + if (project.isDisposed) return@launch + + EditorNotifications.getInstance(project).updateAllNotifications() + } + } + } + + private fun updateHighlighting(files: List) { + 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 clearAndRehighlight() { + updateHighlighting(memoryCache.clearConfigurationCaches()) + } + + private fun scriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope { + return memoryCache.scriptsDependenciesClasspathScopeCache[file] ?: GlobalSearchScope.EMPTY_SCOPE + } + + private fun scriptSdk(file: VirtualFile): Sdk? { + return memoryCache.scriptsSdksCache[file] + } +} \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptsCompilationConfigurationCache.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptConfigurationMemoryCache.kt similarity index 66% rename from idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptsCompilationConfigurationCache.kt rename to idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptConfigurationMemoryCache.kt index 45659f8575b..d254bb4351b 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptsCompilationConfigurationCache.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptConfigurationMemoryCache.kt @@ -6,7 +6,6 @@ package org.jetbrains.kotlin.idea.core.script import com.intellij.ProjectTopics -import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer import com.intellij.openapi.extensions.Extensions import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.project.Project @@ -23,12 +22,10 @@ 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 kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks -import org.jetbrains.kotlin.idea.core.util.EDT +import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationMemoryCache.Companion.MAX_SCRIPTS_CACHED +import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper import java.util.concurrent.locks.ReentrantReadWriteLock import kotlin.concurrent.write @@ -36,9 +33,8 @@ import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty import kotlin.reflect.KProperty0 import kotlin.reflect.jvm.isAccessible -import kotlin.script.experimental.api.valueOrNull -class ScriptsCompilationConfigurationCache(private val project: Project) { +class ScriptConfigurationMemoryCache internal constructor(private val project: Project) { companion object { const val MAX_SCRIPTS_CACHED = 50 @@ -48,29 +44,37 @@ class ScriptsCompilationConfigurationCache(private val project: Project) { val connection = project.messageBus.connect() connection.subscribe(ProjectTopics.PROJECT_ROOTS, object : ModuleRootListener { override fun rootsChanged(event: ModuleRootEvent) { - clearCaches() + clearClassRootsCaches() } }) } private val cacheLock = ReentrantReadWriteLock() - private val scriptDependenciesCache = SLRUCacheWithLock() - private val scriptsModificationStampsCache = SLRUCacheWithLock() + private val scriptDependenciesCache = SLRUCacheWithLock() + private val scriptsModificationStampsCache = SLRUCacheWithLock() - operator fun get(file: VirtualFile): ScriptCompilationConfigurationResult? = scriptDependenciesCache.get(file) + fun getCachedConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? = scriptDependenciesCache.get(file) - fun shouldRunDependenciesUpdate(file: KtFile): Boolean { - return scriptsModificationStampsCache.replace(file, file.modificationStamp) != file.modificationStamp + fun isConfigurationUpToDate(file: VirtualFile): Boolean { + return scriptsModificationStampsCache.get(file) == file.modificationStamp } - private val scriptsDependenciesClasspathScopeCache: MutableMap = + fun setUpToDate(file: VirtualFile) { + scriptsModificationStampsCache.replace(file, file.modificationStamp) + } + + fun replaceConfiguration(file: VirtualFile, new: ScriptCompilationConfigurationWrapper) { + scriptDependenciesCache.replace(file, new) + } + + val scriptsDependenciesClasspathScopeCache: MutableMap = ConcurrentFactoryMap.createWeakMap { file -> - val compilationConfiguration = getCachedConfigurationOrLoad(file) - ?: return@createWeakMap null + val compilationConfiguration = getConfiguration(file) + ?: return@createWeakMap GlobalSearchScope.EMPTY_SCOPE val roots = compilationConfiguration.dependenciesClassPath - val sdk = getScriptSdk(file) + val sdk = scriptsSdksCache[file] @Suppress("FoldInitializerAndIfToElvis") if (sdk == null) { @@ -83,29 +87,19 @@ class ScriptsCompilationConfigurationCache(private val project: Project) { ) } - fun scriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope { - return scriptsDependenciesClasspathScopeCache[file] ?: GlobalSearchScope.EMPTY_SCOPE - } - - private val scriptsSdksCache: MutableMap = + val scriptsSdksCache: MutableMap = ConcurrentFactoryMap.createWeakMap { file -> - val compilationConfiguration = getCachedConfigurationOrLoad(file) + val compilationConfiguration = getConfiguration(file) return@createWeakMap getScriptSdk(compilationConfiguration) ?: ScriptDependenciesManager.getScriptDefaultSdk(project) } - fun getScriptSdk(file: VirtualFile): Sdk? { - return scriptsSdksCache[file] - } + private fun getConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? { + val configuration = getCachedConfiguration(file) - private fun getCachedConfigurationOrLoad(file: VirtualFile): ScriptCompilationConfigurationWrapper? { - val compilationConfiguration = scriptDependenciesCache.get(file)?.valueOrNull() + if (configuration != null) return configuration - if (compilationConfiguration != null) { - return compilationConfiguration - } - - val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return null - return ScriptDependenciesManager.getInstance(project).getRefinedCompilationConfiguration(ktFile)?.valueOrNull() + val ktFile = runReadAction { PsiManager.getInstance(project).findFile(file) as? KtFile } ?: return null + return ScriptDependenciesManager.getInstance(project).getConfiguration(ktFile) } private fun getScriptSdk(compilationConfiguration: ScriptCompilationConfigurationWrapper?): Sdk? { @@ -119,15 +113,21 @@ class ScriptsCompilationConfigurationCache(private val project: Project) { return getAllProjectSdks().find { it.homeDirectory == javaHome } } - val allSdks by ClearableLazyValue(cacheLock) { + val firstScriptSdk: Sdk? + get() { + val firstCachedScript = scriptDependenciesCache.getAll().firstOrNull()?.key ?: return null + return scriptsSdksCache[firstCachedScript] + } + + private val allSdks by ClearableLazyValue(cacheLock) { scriptDependenciesCache.getAll() - .mapNotNull { getScriptSdk(it.key) } + .mapNotNull { scriptsSdksCache[it.key] } .distinct() } private val allNonIndexedSdks by ClearableLazyValue(cacheLock) { scriptDependenciesCache.getAll() - .mapNotNull { getScriptSdk(it.key) } + .mapNotNull { scriptsSdksCache[it.key] } .filterNonModuleSdk() .distinct() } @@ -137,7 +137,7 @@ class ScriptsCompilationConfigurationCache(private val project: Project) { .flatMap { it.rootProvider.getFiles(OrderRootType.CLASSES).toList() } val scriptDependenciesClasspath = scriptDependenciesCache.getAll() - .flatMap { it.value.valueOrNull()?.dependenciesClassPath ?: emptyList() }.distinct() + .flatMap { it.value.dependenciesClassPath }.distinct() sdkFiles + ScriptDependenciesManager.toVfsRoots(scriptDependenciesClasspath) } @@ -147,7 +147,7 @@ class ScriptsCompilationConfigurationCache(private val project: Project) { .flatMap { it.rootProvider.getFiles(OrderRootType.SOURCES).toList() } val scriptDependenciesSources = scriptDependenciesCache.getAll() - .flatMap { it.value.valueOrNull()?.dependenciesSources ?: emptyList() }.distinct() + .flatMap { it.value.dependenciesSources }.distinct() sdkSources + ScriptDependenciesManager.toVfsRoots(scriptDependenciesSources) } @@ -164,12 +164,20 @@ class ScriptsCompilationConfigurationCache(private val project: Project) { return filterNot { moduleSdks.contains(it) } } - private fun onChange(files: List) { - clearCaches() - updateHighlighting(files) + fun clearConfigurationCaches(): List { + debug { "configuration caches cleared" } + + val keys = scriptDependenciesCache.getAll().map { it.key }.toList() + + scriptDependenciesCache.clear() + scriptsModificationStampsCache.clear() + + return keys } - private fun clearCaches() { + fun clearClassRootsCaches() { + debug { "class roots caches cleared" } + this::allSdks.clearValue() this::allNonIndexedSdks.clearValue() @@ -180,6 +188,7 @@ class ScriptsCompilationConfigurationCache(private val project: Project) { this::allDependenciesSourcesScope.clearValue() scriptsDependenciesClasspathScopeCache.clear() + scriptsSdksCache.clear() val kotlinScriptDependenciesClassFinder = Extensions.getArea(project).getExtensionPoint(PsiElementFinder.EP_NAME).extensions @@ -187,53 +196,36 @@ class ScriptsCompilationConfigurationCache(private val project: Project) { .single() kotlinScriptDependenciesClassFinder.clearCache() - } - private fun updateHighlighting(files: List) { ScriptDependenciesModificationTracker.getInstance(project).incModificationCount() - - GlobalScope.launch(EDT(project)) { - files.filter { it.isValid }.forEach { - PsiManager.getInstance(project).findFile(it)?.let { psiFile -> - DaemonCodeAnalyzer.getInstance(project).restart(psiFile) - } - } - } } fun hasNotCachedRoots(compilationConfiguration: ScriptCompilationConfigurationWrapper): Boolean { val scriptSdk = getScriptSdk(compilationConfiguration) ?: ScriptDependenciesManager.getScriptDefaultSdk(project) - return (scriptSdk != null && !allSdks.contains(scriptSdk)) || - !allDependenciesClassFiles.containsAll(ScriptDependenciesManager.toVfsRoots(compilationConfiguration.dependenciesClassPath)) || - !allDependenciesSources.containsAll(ScriptDependenciesManager.toVfsRoots(compilationConfiguration.dependenciesSources)) - } - - fun clear() { - val keys = scriptDependenciesCache.getAll().map { it.key }.toList() - - scriptDependenciesCache.clear() - scriptsModificationStampsCache.clear() - - updateHighlighting(keys) - } - - fun save(file: VirtualFile, new: ScriptCompilationConfigurationResult): Boolean { - val old = scriptDependenciesCache.replace(file, new) - val changed = new != old - if (changed) { - onChange(listOf(file)) + val wasSdkChanged = scriptSdk != null && !allSdks.contains(scriptSdk) + if (wasSdkChanged) { + debug { "sdk was changed: $compilationConfiguration" } + return true } - return changed + val newClassRoots = ScriptDependenciesManager.toVfsRoots(compilationConfiguration.dependenciesClassPath) + for (newClassRoot in newClassRoots) { + if (!allDependenciesClassFiles.contains(newClassRoot)) { + debug { "class root was changed: $newClassRoot" } + return true + } + } + + val newSourceRoots = ScriptDependenciesManager.toVfsRoots(compilationConfiguration.dependenciesSources) + for (newSourceRoot in newSourceRoots) { + if (!allDependenciesSources.contains(newSourceRoot)) { + debug { "source root was changed: $newSourceRoot" } + return true + } + } + return false } - fun delete(file: VirtualFile): Boolean { - val changed = scriptDependenciesCache.remove(file) - if (changed) { - onChange(listOf(file)) - } - return changed - } } private fun KProperty0.clearValue() { @@ -268,8 +260,8 @@ private class SLRUCacheWithLock { private val lock = ReentrantReadWriteLock() val cache = SLRUMap( - ScriptsCompilationConfigurationCache.MAX_SCRIPTS_CACHED, - ScriptsCompilationConfigurationCache.MAX_SCRIPTS_CACHED + MAX_SCRIPTS_CACHED, + MAX_SCRIPTS_CACHED ) fun get(value: K): V? = lock.write { diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDefinitionsManager.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDefinitionsManager.kt index 2d0c1bf4075..ff120dba581 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDefinitionsManager.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDefinitionsManager.kt @@ -36,7 +36,6 @@ import com.intellij.openapi.projectRoots.ex.PathUtilEx import com.intellij.openapi.roots.ProjectRootManager import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VirtualFile -import com.intellij.ui.EditorNotifications import com.intellij.util.containers.SLRUMap import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.caches.project.SdkInfo @@ -191,13 +190,7 @@ class ScriptDefinitionsManager(private val project: Project) : LazyScriptDefinit scriptDefinitionsCacheLock.write { scriptDefinitionsCache.clear() } // TODO: clear by script type/definition - ServiceManager.getService(project, ScriptsCompilationConfigurationCache::class.java).clear() - - ApplicationManager.getApplication().invokeLater { - if (!project.isDisposed) { - EditorNotifications.getInstance(project).updateAllNotifications() - } - } + ScriptDependenciesManager.getInstance(project).clearConfigurationCachesAndRehighlight() } private fun ScriptDefinitionsSource.safeGetDefinitions(): List { @@ -218,9 +211,6 @@ class ScriptDefinitionsManager(private val project: Project) : LazyScriptDefinit } } - -private val LOG = Logger.getInstance("ScriptTemplatesProviders") - // TODO: consider rewriting to return sequence fun loadDefinitionsFromTemplates( templateClassNames: List, diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDependenciesFileAttribute.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDependenciesFileAttribute.kt deleted file mode 100644 index 2ac2d82691e..00000000000 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDependenciesFileAttribute.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2010-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jetbrains.kotlin.idea.core.script - -import com.intellij.openapi.vfs.VirtualFile -import org.jetbrains.kotlin.idea.core.util.* -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.scripting.definitions.getScriptOriginalFile -import java.io.* -import kotlin.script.experimental.api.ScriptCompilationConfiguration -import kotlin.script.experimental.dependencies.ScriptDependencies - -var KtFile.scriptDependencies: ScriptDependencies? - get() = this.getScriptOriginalFile()?.virtualFile?.scriptDependencies - set(value) { - this.getScriptOriginalFile()?.virtualFile?.scriptDependencies = value - } - -var VirtualFile.scriptDependencies: ScriptDependencies? by cachedFileAttribute( - name = "kotlin-script-dependencies", - version = 3, - read = { - ScriptDependencies( - classpath = readFileList(), - imports = readStringList(), - javaHome = readNullable(DataInput::readFile), - scripts = readFileList(), - sources = readFileList() - ) - }, - write = { - with(it) { - writeFileList(classpath) - writeStringList(imports) - writeNullable(javaHome, DataOutput::writeFile) - writeFileList(scripts) - writeFileList(sources) - } - } -) - -var KtFile.scriptCompilationConfiguration: ScriptCompilationConfiguration? - get() = this.getScriptOriginalFile()?.virtualFile?.scriptCompilationConfiguration - set(value) { - this.getScriptOriginalFile()?.virtualFile?.scriptCompilationConfiguration = value - } - -var VirtualFile.scriptCompilationConfiguration: ScriptCompilationConfiguration? by cachedFileAttribute( - name = "kotlin-script-compilation-configuration", - version = 1, - read = { - val size = readInt() - val bytes = ByteArray(size) - read(bytes, 0, size) - val bis = ByteArrayInputStream(bytes) - ObjectInputStream(bis).use { ois -> - ois.readObject() as ScriptCompilationConfiguration - } - }, - write = { - val os = ByteArrayOutputStream() - ObjectOutputStream(os).use { oos -> - oos.writeObject(it) - } - val bytes = os.toByteArray() - writeInt(bytes.size) - write(bytes) - } -) diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDependenciesManager.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDependenciesManager.kt index 269f86594c6..faf466e3f99 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDependenciesManager.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDependenciesManager.kt @@ -17,7 +17,6 @@ package org.jetbrains.kotlin.idea.core.script import com.intellij.openapi.components.ServiceManager -import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.JavaSdkType import com.intellij.openapi.projectRoots.Sdk @@ -25,18 +24,17 @@ import com.intellij.openapi.roots.ProjectRootManager import com.intellij.openapi.vfs.StandardFileSystems 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.util.io.URLUtil import org.jetbrains.annotations.TestOnly import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks -import org.jetbrains.kotlin.idea.core.script.dependencies.SyncScriptDependenciesLoader import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider -import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult +import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper import java.io.File -import kotlin.script.experimental.api.valueOrNull - +import kotlin.script.experimental.api.asSuccess +import kotlin.script.experimental.api.makeFailureResult // NOTE: this service exists exclusively because ScriptDependencyManager // cannot be registered as implementing two services (state would be duplicated) @@ -45,40 +43,73 @@ class IdeScriptDependenciesProvider( project: Project ) : ScriptDependenciesProvider(project) { override fun getScriptConfigurationResult(file: KtFile): ScriptCompilationConfigurationResult? { - return scriptDependenciesManager.getRefinedCompilationConfiguration(file) + val configuration = getScriptConfiguration(file) + val reports = IdeScriptReportSink.getReports(file) + if (configuration == null && reports.isNotEmpty()) { + return makeFailureResult(reports) + } + return configuration?.asSuccess(reports) } + + override fun getScriptConfiguration(file: KtFile): ScriptCompilationConfigurationWrapper? { + return scriptDependenciesManager.getConfiguration(file) + } + } -// TODO: rename and provide alias for compatibility - this is not only about dependencies anymore -class ScriptDependenciesManager internal constructor( - private val cacheUpdater: ScriptsCompilationConfigurationUpdater, - private val cache: ScriptsCompilationConfigurationCache, - private val project: Project -) { +/** + * 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. + */ +interface ScriptConfigurationManager { + /** + * Save configurations into cache. + * Start indexing for new class/source roots. + * Re-highlight opened scripts with changed configuration. + */ + fun saveCompilationConfigurationAfterImport(files: List>) + + /** + * 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): Boolean + + /** + * 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 { - val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return emptyList() - return getScriptClasspath(ktFile) - } + fun getScriptClasspath(file: VirtualFile): List - fun getScriptClasspath(file: KtFile): List = - toVfsRoots(cacheUpdater.getCurrentCompilationConfiguration(file)?.valueOrNull()?.dependenciesClassPath.orEmpty()) - - fun getRefinedCompilationConfiguration(file: KtFile): ScriptCompilationConfigurationResult? = - cacheUpdater.getCurrentCompilationConfiguration(file) - - fun getScriptDependenciesClassFilesScope(file: VirtualFile) = cache.scriptDependenciesClassFilesScope(file) - fun getScriptSdk(file: VirtualFile) = cache.getScriptSdk(file) - - fun getAllScriptsSdks() = cache.allSdks - - fun getAllScriptsDependenciesClassFilesScope() = cache.allDependenciesClassFilesScope - fun getAllScriptDependenciesSourcesScope() = cache.allDependenciesSourcesScope - - fun getAllScriptsDependenciesClassFiles() = cache.allDependenciesClassFiles - fun getAllScriptDependenciesSources() = cache.allDependenciesSources + fun getScriptClasspath(file: KtFile): List + fun getConfiguration(file: KtFile): ScriptCompilationConfigurationWrapper? + fun getScriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope + fun getScriptSdk(file: VirtualFile): Sdk? + fun getFirstScriptsSdk(): Sdk? + fun getAllScriptsDependenciesClassFilesScope(): GlobalSearchScope + fun getAllScriptDependenciesSourcesScope(): GlobalSearchScope + fun getAllScriptsDependenciesClassFiles(): List + fun getAllScriptDependenciesSources(): List +} +@Deprecated("Use ScriptConfigurationManager instead") +interface ScriptDependenciesManager : ScriptConfigurationManager { companion object { @JvmStatic fun getInstance(project: Project): ScriptDependenciesManager = @@ -93,7 +124,7 @@ class ScriptDependenciesManager internal constructor( return anyJavaSdk } - log.warn( + LOG.warn( "Default Script SDK is null: " + "projectSdk = ${ProjectRootManager.getInstance(project).projectSdk}, " + "all sdks = ${getAllProjectSdks().joinToString("\n")}" @@ -118,17 +149,9 @@ class ScriptDependenciesManager internal constructor( return res } - internal val log = Logger.getInstance(ScriptDependenciesManager::class.java) - @TestOnly fun updateScriptDependenciesSynchronously(file: PsiFile, project: Project) { - val loader = SyncScriptDependenciesLoader(project) - val scriptDefinition = file.findScriptDefinition() ?: return - assert(file is KtFile) { - "PsiFile should be a KtFile, otherwise script dependencies cannot be loaded" - } - loader.loadDependencies(file as KtFile, scriptDefinition) - loader.notifyRootsChanged() + (getInstance(project) as ScriptConfigurationManagerImpl).updateScriptDependenciesSynchronously(file) } } } diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptNewDependenciesNotification.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptNewDependenciesNotification.kt index 59e6d510321..319fdcbe8e9 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptNewDependenciesNotification.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptNewDependenciesNotification.kt @@ -16,7 +16,7 @@ import com.intellij.ui.EditorNotificationPanel 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.ScriptCompilationConfigurationResult +import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper fun VirtualFile.removeScriptDependenciesNotificationPanel(project: Project) { withSelectedEditor(project) { manager -> @@ -28,9 +28,9 @@ fun VirtualFile.removeScriptDependenciesNotificationPanel(project: Project) { } fun VirtualFile.addScriptDependenciesNotificationPanel( - compilationConfigurationResult: ScriptCompilationConfigurationResult, + compilationConfigurationResult: ScriptCompilationConfigurationWrapper, project: Project, - onClick: (ScriptCompilationConfigurationResult) -> Unit + onClick: (ScriptCompilationConfigurationWrapper) -> Unit ) { withSelectedEditor(project) { manager -> val existingPanel = notificationPanel @@ -61,8 +61,8 @@ private fun VirtualFile.withSelectedEditor(project: Project, f: FileEditor.(File private var FileEditor.notificationPanel: NewScriptDependenciesNotificationPanel? by UserDataProperty(Key.create("script.dependencies.panel")) private class NewScriptDependenciesNotificationPanel( - onClick: (ScriptCompilationConfigurationResult) -> Unit, - val compilationConfigurationResult: ScriptCompilationConfigurationResult, + onClick: (ScriptCompilationConfigurationWrapper) -> Unit, + val compilationConfigurationResult: ScriptCompilationConfigurationWrapper, project: Project ) : EditorNotificationPanel() { diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptTrafficLightRendererContributor.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptTrafficLightRendererContributor.kt index b93d1bb11b5..634815799dc 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptTrafficLightRendererContributor.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptTrafficLightRendererContributor.kt @@ -29,7 +29,7 @@ class ScriptTrafficLightRendererContributor : TrafficLightRendererContributor { if (!ScriptDefinitionsManager.getInstance(file.project).isReady()) { status.reasonWhySuspended = "Loading kotlin script definitions" status.errorAnalyzingFinished = false - } else if (!ScriptsCompilationConfigurationUpdater.areDependenciesCached(file)) { + } else if (!ScriptDependenciesManager.getInstance(project).isConfigurationCached(file)) { status.reasonWhySuspended = "Loading kotlin script dependencies" status.errorAnalyzingFinished = false } diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptsCompilationConfigurationUpdater.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptsCompilationConfigurationUpdater.kt deleted file mode 100644 index d4456fee99b..00000000000 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptsCompilationConfigurationUpdater.kt +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2010-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jetbrains.kotlin.idea.core.script - -import com.intellij.openapi.application.Application -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.components.ServiceManager -import com.intellij.openapi.editor.EditorFactory -import com.intellij.openapi.editor.event.DocumentEvent -import com.intellij.openapi.editor.event.DocumentListener -import com.intellij.openapi.fileEditor.FileDocumentManager -import com.intellij.openapi.fileEditor.FileEditorManager -import com.intellij.openapi.fileEditor.FileEditorManagerEvent -import com.intellij.openapi.fileEditor.FileEditorManagerListener -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Key -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.psi.PsiManager -import com.intellij.util.Alarm -import org.jetbrains.annotations.TestOnly -import org.jetbrains.kotlin.idea.core.script.dependencies.AsyncScriptDependenciesLoader -import org.jetbrains.kotlin.idea.core.script.dependencies.FromFileAttributeScriptDependenciesLoader -import org.jetbrains.kotlin.idea.core.script.dependencies.OutsiderFileDependenciesLoader -import org.jetbrains.kotlin.idea.core.script.dependencies.SyncScriptDependenciesLoader -import org.jetbrains.kotlin.idea.util.ProjectRootsUtil -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.NotNullableUserDataProperty -import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition -import org.jetbrains.kotlin.scripting.definitions.isNonScript -import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult - -class ScriptsCompilationConfigurationUpdater( - private val project: Project, - private val cache: ScriptsCompilationConfigurationCache -) { - private val scriptsQueue = Alarm(Alarm.ThreadToUse.SWING_THREAD, project) - private val scriptChangesListenerDelay = 1400 - - private val loaders = arrayListOf( - FromFileAttributeScriptDependenciesLoader(project), - OutsiderFileDependenciesLoader(project), - AsyncScriptDependenciesLoader(project), - SyncScriptDependenciesLoader(project) - ) - - init { - listenForChangesInScripts() - } - - fun getCurrentCompilationConfiguration(file: KtFile): ScriptCompilationConfigurationResult? { - val virtualFile = file.originalFile.virtualFile - - cache[virtualFile]?.let { return it } - - // Try-catch block is a temporary workaround for ProcessCanceledException - // that may happen during VirtualFile to PsiFile transformation - try { - updateDependencies(file) - } finally { - makeRootsChangeIfNeeded() - } - - return cache[virtualFile] - } - - fun updateDependenciesIfNeeded(files: List): Boolean { - if (!ScriptDefinitionsManager.getInstance(project).isReady()) return false - - var wasDependenciesUpdateStarted = false - for (file in files) { - if (!areDependenciesCached(file)) { - wasDependenciesUpdateStarted = true - updateDependencies(file) - } - } - - if (wasDependenciesUpdateStarted) { - makeRootsChangeIfNeeded() - } - - return wasDependenciesUpdateStarted - } - - private fun updateDependencies(file: KtFile) { - if (!ScriptDefinitionsManager.getInstance(project).isReady()) return - if (!cache.shouldRunDependenciesUpdate(file)) return - - val scriptDefinition = file.findScriptDefinition() ?: return - loaders.filter { it.isApplicable(file, scriptDefinition) }.forEach { it.loadDependencies(file, scriptDefinition) } - } - - private fun makeRootsChangeIfNeeded() { - loaders.firstOrNull { - it.notifyRootsChanged() - } - } - - private fun listenForChangesInScripts() { - project.messageBus.connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, object : FileEditorManagerListener { - override fun fileOpened(source: FileEditorManager, file: VirtualFile) { - runScriptDependenciesUpdateIfNeeded(file) - } - - override fun selectionChanged(event: FileEditorManagerEvent) { - event.newFile?.let { runScriptDependenciesUpdateIfNeeded(it) } - } - - private fun runScriptDependenciesUpdateIfNeeded(file: VirtualFile) { - val ktFile = getKtFileToStartConfigurationUpdate(file) ?: return - - updateDependencies(ktFile) - makeRootsChangeIfNeeded() - } - }) - - EditorFactory.getInstance().eventMulticaster.addDocumentListener(object : DocumentListener { - override fun documentChanged(event: DocumentEvent) { - val document = event.document - val file = FileDocumentManager.getInstance().getFile(document)?.takeIf { it.isInLocalFileSystem } ?: return - val ktFile = getKtFileToStartConfigurationUpdate(file) ?: return - - // only update dependencies for scripts that were touched recently - if (cache[file] == null) { - return - } - - scriptsQueue.cancelAllRequests() - - scriptsQueue.addRequest( - { - FileDocumentManager.getInstance().saveDocument(document) - updateDependencies(ktFile) - makeRootsChangeIfNeeded() - }, - scriptChangesListenerDelay, - true - ) - } - }, project.messageBus.connect()) - } - - private fun getKtFileToStartConfigurationUpdate(file: VirtualFile): KtFile? { - if (project.isDisposed || !file.isValid || file.isNonScript()) { - return null - } - - if ( - ApplicationManager.getApplication().isUnitTestMode && - ApplicationManager.getApplication().isScriptDependenciesUpdaterDisabled == true - ) { - return null - } - - val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return null - if (ProjectRootsUtil.isInProjectSource(ktFile, includeScriptsOutsideSourceRoots = true)) { - return ktFile - } - - return null - } - - private fun areDependenciesCached(file: VirtualFile): Boolean { - return cache[file] != null || file.scriptDependencies != null || file.scriptCompilationConfiguration != null - } - - companion object { - @JvmStatic - fun getInstance(project: Project): ScriptsCompilationConfigurationUpdater = - ServiceManager.getService(project, ScriptsCompilationConfigurationUpdater::class.java) - - fun areDependenciesCached(file: KtFile): Boolean { - return getInstance(file.project).areDependenciesCached(file.virtualFile) - } - } -} - -@set: TestOnly -var Application.isScriptDependenciesUpdaterDisabled by NotNullableUserDataProperty( - Key.create("SCRIPT_DEPENDENCIES_UPDATER_DISABLED"), - false -) \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptsListener.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptsListener.kt new file mode 100644 index 00000000000..9e56f7f51d9 --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptsListener.kt @@ -0,0 +1,88 @@ +/* + * 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 + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.editor.EditorFactory +import com.intellij.openapi.editor.event.DocumentEvent +import com.intellij.openapi.editor.event.DocumentListener +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.fileEditor.FileEditorManagerEvent +import com.intellij.openapi.fileEditor.FileEditorManagerListener +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiManager +import com.intellij.util.Alarm +import org.jetbrains.kotlin.idea.util.ProjectRootsUtil +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.scripting.definitions.isNonScript + +class ScriptsListener( + private val project: Project, + private val scriptsManager: ScriptConfigurationManagerImpl +) { + private val scriptsQueue = Alarm(Alarm.ThreadToUse.SWING_THREAD, project) + private val scriptChangesListenerDelay = 1400 + + init { + listenForChangesInScripts() + } + + private fun listenForChangesInScripts() { + project.messageBus.connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, object : FileEditorManagerListener { + override fun fileOpened(source: FileEditorManager, file: VirtualFile) { + runScriptDependenciesUpdateIfNeeded(file) + } + + override fun selectionChanged(event: FileEditorManagerEvent) { + event.newFile?.let { runScriptDependenciesUpdateIfNeeded(it) } + } + + private fun runScriptDependenciesUpdateIfNeeded(file: VirtualFile) { + val ktFile = getKtFileToStartConfigurationUpdate(file) ?: return + + scriptsManager.updateConfigurationsIfNotCached(listOf(ktFile)) + } + }) + + EditorFactory.getInstance().eventMulticaster.addDocumentListener(object : DocumentListener { + override fun documentChanged(event: DocumentEvent) { + val document = event.document + val file = FileDocumentManager.getInstance().getFile(document)?.takeIf { it.isInLocalFileSystem } ?: return + val ktFile = getKtFileToStartConfigurationUpdate(file) ?: return + + scriptsQueue.cancelAllRequests() + + scriptsQueue.addRequest( + { scriptsManager.updateConfigurationsIfNotCached(listOf(ktFile)) }, + scriptChangesListenerDelay, + true + ) + } + }, project.messageBus.connect()) + } + + private fun getKtFileToStartConfigurationUpdate(file: VirtualFile): KtFile? { + if (project.isDisposed || !file.isValid || file.isNonScript()) { + return null + } + + if ( + ApplicationManager.getApplication().isUnitTestMode && + ApplicationManager.getApplication().isScriptDependenciesUpdaterDisabled == true + ) { + return null + } + + val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return null + if (ProjectRootsUtil.isInProjectSource(ktFile, includeScriptsOutsideSourceRoots = true)) { + return ktFile + } + + return null + } +} \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/AsyncScriptDependenciesLoader.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/AsyncScriptDependenciesLoader.kt deleted file mode 100644 index 9e3f0b08333..00000000000 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/AsyncScriptDependenciesLoader.kt +++ /dev/null @@ -1,159 +0,0 @@ -/* - * 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.dependencies - -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 kotlinx.coroutines.Runnable -import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings -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.refineScriptCompilationConfiguration -import java.util.concurrent.ConcurrentLinkedQueue -import java.util.concurrent.locks.ReentrantReadWriteLock -import kotlin.concurrent.write - -// TODO: rename and provide alias for compatibility - this is not only about dependencies anymore -class AsyncScriptDependenciesLoader internal constructor(project: Project) : ScriptDependenciesLoader(project) { - private val lock = ReentrantReadWriteLock() - - private var notifyRootChange: Boolean = false - private var backgroundTasksQueue: LoaderBackgroundTask? = null - - override fun isApplicable( - file: KtFile, - scriptDefinition: ScriptDefinition - ): Boolean { - return isAsyncDependencyResolver(scriptDefinition) - } - - override fun loadDependencies( - file: KtFile, - scriptDefinition: ScriptDefinition - ) { - lock.write { - if (backgroundTasksQueue == null) { - backgroundTasksQueue = LoaderBackgroundTask() - backgroundTasksQueue!!.addTask(file) - backgroundTasksQueue!!.start() - } else { - backgroundTasksQueue!!.addTask(file) - } - } - } - - override fun shouldShowNotification(): Boolean = !KotlinScriptingSettings.getInstance(project).isAutoReloadEnabled - - override fun notifyRootsChanged(): Boolean { - lock.write { - if (notifyRootChange) return false - - if (backgroundTasksQueue == null) { - return submitMakeRootsChange() - } - - notifyRootChange = true - - backgroundTasksQueue!!.addOnFinishTask { - lock.write { - notifyRootChange = false - } - submitMakeRootsChange() - } - } - - return false - } - - private fun runDependenciesUpdate(file: KtFile) { - val scriptDef = file.findScriptDefinition() ?: return - - debug(file) { "start async dependencies loading" } - - val result = refineScriptCompilationConfiguration(KtFileScriptSource(file), scriptDef, project) - - debug(file) { "finish async dependencies loading" } - - processRefinedConfiguration(result, file.originalFile.virtualFile) - } - - private inner class LoaderBackgroundTask { - private val sequenceOfFiles: ConcurrentLinkedQueue = ConcurrentLinkedQueue() - private var forceStop : Boolean = false - private var startedSilently : Boolean = false - - private var onFinish: (() -> Unit)? = null - - fun start() { - if (shouldShowNotification()) { - startSilently() - } else { - startWithProgress() - } - } - - private fun restartWithProgress() { - forceStop = true - startWithProgress() - forceStop = false - } - - private fun startSilently() { - startedSilently = true - BackgroundTaskUtil.executeOnPooledThread(project, Runnable { - loadDependencies(EmptyProgressIndicator()) - }) - } - - private fun startWithProgress() { - startedSilently = false - object : Task.Backgroundable(project, "Kotlin: Loading script dependencies...", true) { - override fun run(indicator: ProgressIndicator) { - loadDependencies(indicator) - } - - }.queue() - } - - fun addTask(file: KtFile) { - if (sequenceOfFiles.contains(file)) return - - 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() - } - } - - fun addOnFinishTask(task: () -> Unit) { - onFinish = task - } - - private fun loadDependencies(indicator: ProgressIndicator?) { - while (true) { - if (forceStop) return - if (indicator?.isCanceled == true || sequenceOfFiles.isEmpty()) { - lock.write { - onFinish?.invoke() - backgroundTasksQueue = null - } - return - } - runDependenciesUpdate(sequenceOfFiles.poll()) - } - } - } -} - - diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/BackgroundLoader.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/BackgroundLoader.kt new file mode 100644 index 00000000000..ddf8f614ce5 --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/BackgroundLoader.kt @@ -0,0 +1,108 @@ +/* + * 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.dependencies + +import com.intellij.openapi.progress.EmptyProgressIndicator +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.util.BackgroundTaskUtil +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.idea.core.script.ScriptClassRootsManager +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 + +internal class BackgroundLoader( + private val project: Project, + private val rootsManager: ScriptClassRootsManager, + private val loadDependencies: (KtFile) -> Unit +) { + private var task: Task? = null + + @Synchronized + fun scheduleAsync(file: KtFile) { + if (task == null) { + startBatch(file) + } else { + task!!.addTask(file) + } + } + + @Synchronized + private fun startBatch(file: KtFile) { + rootsManager.startTransaction() + task = Task() + task!!.addTask(file) + task!!.start() + } + + @Synchronized + private fun endBatch() { + task = null + rootsManager.commit() + } + + private inner class Task { + private val sequenceOfFiles: ConcurrentLinkedQueue = ConcurrentLinkedQueue() + private var forceStop: Boolean = false + private var startedSilently: Boolean = false + + fun start() { + if (KotlinScriptingSettings.getInstance(project).isAutoReloadEnabled) { + startWithProgress() + } else { + startSilently() + } + } + + private fun restartWithProgress() { + forceStop = true + startWithProgress() + forceStop = false + } + + private fun startSilently() { + startedSilently = true + BackgroundTaskUtil.executeOnPooledThread(project, Runnable { + loadDependencies(EmptyProgressIndicator()) + }) + } + + private fun startWithProgress() { + startedSilently = false + object : com.intellij.openapi.progress.Task.Backgroundable(project, "Kotlin: Loading script dependencies...", true) { + override fun run(indicator: ProgressIndicator) { + loadDependencies(indicator) + } + }.queue() + } + + fun addTask(file: KtFile) { + if (file in sequenceOfFiles) return + + 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() + } + } + + private fun loadDependencies(indicator: ProgressIndicator?) { + while (true) { + if (forceStop) return + if (indicator?.isCanceled == true || sequenceOfFiles.isEmpty()) { + endBatch() + return + } + + loadDependencies(sequenceOfFiles.poll()) + } + } + } +} \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/FromFileAttributeScriptDependenciesLoader.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/FromFileAttributeScriptDependenciesLoader.kt deleted file mode 100644 index 1e5c46cffc7..00000000000 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/FromFileAttributeScriptDependenciesLoader.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.dependencies - -import com.intellij.openapi.project.Project -import com.intellij.openapi.vfs.VirtualFile -import org.jetbrains.kotlin.idea.core.script.scriptCompilationConfiguration -import org.jetbrains.kotlin.idea.core.script.scriptDependencies -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition -import org.jetbrains.kotlin.scripting.resolve.KtFileScriptSource -import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper -import kotlin.script.experimental.api.asSuccess - -// TODO: rename and provide alias for compatibility - this is not only about dependencies anymore -class FromFileAttributeScriptDependenciesLoader(project: Project) : ScriptDependenciesLoader(project) { - - override fun isApplicable( - file: KtFile, - scriptDefinition: ScriptDefinition - ): Boolean { - return file.scriptDependencies != null || file.scriptCompilationConfiguration != null - } - - override fun loadDependencies( - file: KtFile, - scriptDefinition: ScriptDefinition - ) { - val virtualFile = file.originalFile.virtualFile - virtualFile.scriptCompilationConfiguration?.let { - ScriptCompilationConfigurationWrapper.FromCompilationConfiguration(KtFileScriptSource(file), it).apply { - debug(virtualFile) { "refined configuration from fileAttributes = $it" } - } - } ?: virtualFile.scriptDependencies?.let { - ScriptCompilationConfigurationWrapper.FromLegacy(KtFileScriptSource(file), it, scriptDefinition).apply { - debug(virtualFile) { "dependencies from fileAttributes = $it" } - } - }?.let { - if (areDependenciesValid(virtualFile, it)) { - saveToCache(virtualFile, it.asSuccess(), skipSaveToAttributes = true) - } else { - virtualFile.scriptCompilationConfiguration = null - virtualFile.scriptDependencies = null - } - } - } - - override fun shouldShowNotification(): Boolean = false - - private fun areDependenciesValid(file: VirtualFile, configuration: ScriptCompilationConfigurationWrapper.FromLegacy): Boolean { - return configuration.dependenciesClassPath.all { - if (it.exists()) { - true - } else { - debug(file) { - "classpath root saved to file attribute doesn't exist: ${it.path}" - } - false - } - - } - } -} \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/FromRefinedConfigurationLoader.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/FromRefinedConfigurationLoader.kt new file mode 100644 index 00000000000..6cb96853adc --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/FromRefinedConfigurationLoader.kt @@ -0,0 +1,40 @@ +/* + * 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.dependencies + +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.dependencies.AsyncDependenciesResolver + +class FromRefinedConfigurationLoader internal constructor() : ScriptDependenciesLoader { + override fun isAsync(file: KtFile, scriptDefinition: ScriptDefinition): Boolean { + return scriptDefinition.asLegacyOrNull()?.dependencyResolver?.let { + it is AsyncDependenciesResolver || it is LegacyResolverWrapper + } ?: false + } + + override fun loadDependencies( + firstLoad: Boolean, + file: KtFile, + scriptDefinition: ScriptDefinition + ): ScriptCompilationConfigurationResult? { + debug(file) { "start dependencies loading" } + + val result = refineScriptCompilationConfiguration(KtFileScriptSource(file), scriptDefinition, file.project) + + debug(file) { "finish dependencies loading" } + + return result + } +} + + diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/OutsiderFileDependenciesLoader.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/OutsiderFileDependenciesLoader.kt index 92aaa59e79b..06131f19dbf 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/OutsiderFileDependenciesLoader.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/OutsiderFileDependenciesLoader.kt @@ -5,34 +5,31 @@ package org.jetbrains.kotlin.idea.core.script.dependencies -import com.intellij.openapi.project.Project import com.intellij.psi.PsiManager -import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesManager +import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManagerImpl import org.jetbrains.kotlin.idea.highlighter.OutsidersPsiFileSupportUtils -import org.jetbrains.kotlin.idea.highlighter.OutsidersPsiFileSupportWrapper 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 OutsiderFileDependenciesLoader(project: Project) : ScriptDependenciesLoader(project) { - override fun isApplicable( - file: KtFile, - scriptDefinition: ScriptDefinition - ): Boolean { - val virtualFile = file.virtualFile ?: return false - return OutsidersPsiFileSupportWrapper.isOutsiderFile(virtualFile) - } +class OutsiderFileDependenciesLoader(private val manager: ScriptConfigurationManagerImpl) : ScriptDependenciesLoader { + override val skipSaveToAttributes: Boolean + get() = true + + override val skipNotification: Boolean + get() = true override fun loadDependencies( + firstLoad: Boolean, file: KtFile, scriptDefinition: ScriptDefinition - ) { - val virtualFile = file.virtualFile ?: return - val fileOrigin = OutsidersPsiFileSupportUtils.getOutsiderFileOrigin(project, virtualFile) ?: return - val psiFileOrigin = PsiManager.getInstance(project).findFile(fileOrigin) as? KtFile ?: return - val compilationConfiguration = - ScriptDependenciesManager.getInstance(project).getRefinedCompilationConfiguration(psiFileOrigin) ?: return - saveToCache(virtualFile, compilationConfiguration) - } + ): ScriptCompilationConfigurationResult? { + val virtualFile = file.virtualFile ?: return null + val project = file.project - override fun shouldShowNotification(): Boolean = 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() + } } \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/ScriptConfigurationFileAttributeCache.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/ScriptConfigurationFileAttributeCache.kt index e69de29bb2d..7b5be74eb4e 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/ScriptConfigurationFileAttributeCache.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/ScriptConfigurationFileAttributeCache.kt @@ -0,0 +1,134 @@ +/* + * 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.dependencies + +import com.intellij.openapi.vfs.VirtualFile +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.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 : ScriptDependenciesLoader { + operator fun contains(file: VirtualFile): Boolean = + file.scriptDependencies != null || file.scriptCompilationConfiguration != null + + override val skipSaveToAttributes: Boolean + get() = true + + override val skipNotification: Boolean + get() = true + + override fun loadDependencies( + firstLoad: Boolean, + file: KtFile, + scriptDefinition: ScriptDefinition + ): ScriptCompilationConfigurationResult? { + if (!firstLoad) return null + + val virtualFile = file.originalFile.virtualFile + + val configurationFromAttributes = + virtualFile.scriptCompilationConfiguration?.let { + FromCompilationConfiguration(KtFileScriptSource(file), it) + } ?: virtualFile.scriptDependencies?.let { + FromLegacy(KtFileScriptSource(file), it, scriptDefinition) + } ?: return null + + + debug(virtualFile) { "configuration from fileAttributes = $configurationFromAttributes" } + + if (!areDependenciesValid(virtualFile, configurationFromAttributes)) { + save(virtualFile, null) + return null + } + + return configurationFromAttributes.asSuccess() + } + + private fun areDependenciesValid(file: VirtualFile, configuration: ScriptCompilationConfigurationWrapper): Boolean { + return configuration.dependenciesClassPath.all { + if (it.exists()) { + true + } else { + debug(file) { + "classpath root saved to file attribute doesn't exist: ${it.path}" + } + false + } + + } + } + + fun save(file: VirtualFile, value: ScriptCompilationConfigurationWrapper?) { + if (value == null) { + file.scriptDependencies = null + file.scriptCompilationConfiguration = null + } else { + if (value is ScriptCompilationConfigurationWrapper.FromLegacy) { + file.scriptDependencies = value.legacyDependencies + } else { + if (file.scriptDependencies != null) file.scriptDependencies = null + file.scriptCompilationConfiguration = value.configuration + } + } + } + +} + +private var VirtualFile.scriptDependencies: ScriptDependencies? by cachedFileAttribute( + name = "kotlin-script-dependencies", + version = 3, + read = { + ScriptDependencies( + classpath = readFileList(), + imports = readStringList(), + javaHome = readNullable(DataInput::readFile), + scripts = readFileList(), + sources = readFileList() + ) + }, + write = { + with(it) { + writeFileList(classpath) + writeStringList(imports) + writeNullable(javaHome, DataOutput::writeFile) + writeFileList(scripts) + writeFileList(sources) + } + } +) + +private var VirtualFile.scriptCompilationConfiguration: ScriptCompilationConfiguration? by cachedFileAttribute( + name = "kotlin-script-compilation-configuration", + version = 1, + read = { + val size = readInt() + val bytes = ByteArray(size) + read(bytes, 0, size) + val bis = ByteArrayInputStream(bytes) + ObjectInputStream(bis).use { ois -> + ois.readObject() as ScriptCompilationConfiguration + } + }, + write = { + val os = ByteArrayOutputStream() + ObjectOutputStream(os).use { oos -> + oos.writeObject(it) + } + val bytes = os.toByteArray() + writeInt(bytes.size) + write(bytes) + } +) diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/ScriptDependenciesLoader.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/ScriptDependenciesLoader.kt index 61ac106ccb8..5be80fe45fa 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/ScriptDependenciesLoader.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/ScriptDependenciesLoader.kt @@ -5,160 +5,23 @@ package org.jetbrains.kotlin.idea.core.script.dependencies -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.TransactionGuard -import com.intellij.openapi.components.ServiceManager -import com.intellij.openapi.diagnostic.Logger -import com.intellij.openapi.project.Project -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.* -import org.jetbrains.kotlin.idea.util.application.runWriteAction 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.LegacyResolverWrapper import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult -import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper -import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink -import kotlin.script.experimental.api.ResultWithDiagnostics -import kotlin.script.experimental.api.valueOrNull -import kotlin.script.experimental.dependencies.AsyncDependenciesResolver -// TODO: rename and provide alias for compatibility - this is not only about dependencies anymore -abstract class ScriptDependenciesLoader(protected val project: Project) { +// TODO: rename - this is not only about dependencies anymore +internal interface ScriptDependenciesLoader { + fun isAsync(file: KtFile, scriptDefinition: ScriptDefinition) = false - abstract fun isApplicable(file: KtFile, scriptDefinition: ScriptDefinition): Boolean - abstract fun loadDependencies(file: KtFile, scriptDefinition: ScriptDefinition) + val skipSaveToAttributes: Boolean + get() = false - protected abstract fun shouldShowNotification(): Boolean + val skipNotification: Boolean + get() = false - protected var shouldNotifyRootsChanged = false - - protected val cache: ScriptsCompilationConfigurationCache = ServiceManager.getService(project, ScriptsCompilationConfigurationCache::class.java) - - private val reporter: ScriptReportSink = ServiceManager.getService(project, ScriptReportSink::class.java) - - protected fun processRefinedConfiguration(result: ScriptCompilationConfigurationResult, file: VirtualFile) { - debug(file) { "refined script compilation configuration from ${this.javaClass} received = $result" } - - val oldResult = cache[file] - - if (oldResult == null) { - save(result, file) - attachReportsIfChanged(result, file) - return - } - - if (oldResult != result) { - if (shouldShowNotification() - && oldResult.valueOrNull() != result.valueOrNull() // Only compilation configuration changed - && !ApplicationManager.getApplication().isUnitTestMode - ) { - debug(file) { - "dependencies changed, notification is shown: old = $oldResult, new = $result" - } - file.addScriptDependenciesNotificationPanel(result, project) { - save(it, file) - attachReportsIfChanged(result, file) - submitMakeRootsChange() - } - } else { - debug(file) { - "dependencies changed, new dependencies are applied automatically: old = $oldResult, new = $result" - } - save(result, file) - attachReportsIfChanged(result, file) - } - } else { - attachReportsIfChanged(result, file) - - if (shouldShowNotification()) { - file.removeScriptDependenciesNotificationPanel(project) - } - } - } - - private fun attachReportsIfChanged(result: ResultWithDiagnostics<*>, file: VirtualFile) { - reporter.attachReports(file, result.reports) - } - - private fun save(compilationConfigurationResult: ScriptCompilationConfigurationResult?, file: VirtualFile) { - if (shouldShowNotification()) { - file.removeScriptDependenciesNotificationPanel(project) - } - if (compilationConfigurationResult != null) { - saveToCache(file, compilationConfigurationResult) - } - } - - protected fun saveToCache( - file: VirtualFile, compilationConfigurationResult: ScriptCompilationConfigurationResult, skipSaveToAttributes: Boolean = false - ) { - val rootsChanged = compilationConfigurationResult.valueOrNull()?.let { cache.hasNotCachedRoots(it) } ?: false - if (cache.save(file, compilationConfigurationResult) - && !skipSaveToAttributes - && compilationConfigurationResult is ResultWithDiagnostics.Success - ) { - debug(file) { - "refined configuration is saved to file attributes: $compilationConfigurationResult" - } - if (compilationConfigurationResult.value is ScriptCompilationConfigurationWrapper.FromLegacy) - file.scriptDependencies = compilationConfigurationResult.value.legacyDependencies - else - file.scriptCompilationConfiguration = compilationConfigurationResult.value.configuration - } - - if (rootsChanged) { - shouldNotifyRootsChanged = true - } - } - - - open fun notifyRootsChanged(): Boolean = submitMakeRootsChange() - - protected fun submitMakeRootsChange(): Boolean { - if (!shouldNotifyRootsChanged) return false - - val doNotifyRootsChanged = Runnable { - runWriteAction { - if (project.isDisposed) return@runWriteAction - - debug(null) { "root change event for ${this.javaClass}" } - - shouldNotifyRootsChanged = false - ProjectRootManagerEx.getInstanceEx(project)?.makeRootsChange(EmptyRunnable.getInstance(), false, true) - ScriptDependenciesModificationTracker.getInstance(project).incModificationCount() - } - } - - if (ApplicationManager.getApplication().isUnitTestMode) { - TransactionGuard.submitTransaction(project, doNotifyRootsChanged) - } else { - TransactionGuard.getInstance().submitTransactionLater(project, doNotifyRootsChanged) - } - - return true - } - - protected fun isAsyncDependencyResolver(scriptDef: ScriptDefinition): Boolean = - scriptDef.asLegacyOrNull()?.dependencyResolver?.let { - it is AsyncDependenciesResolver || it is LegacyResolverWrapper - } ?: false - -} - -private val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.script") - -internal fun debug(file: VirtualFile? = null, message: () -> String) { - if (LOG.isDebugEnabled) { - LOG.debug("[KOTLIN SCRIPT] ${file?.path} " + message()) - } -} - -internal fun debug(file: KtFile, message: () -> String) { - if (LOG.isDebugEnabled) { - LOG.debug("[KOTLIN SCRIPT] ${file.originalFile.virtualFile?.path} " + message()) - } -} + fun loadDependencies( + firstLoad: Boolean, + file: KtFile, + scriptDefinition: ScriptDefinition + ): ScriptCompilationConfigurationResult? +} \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/SyncScriptDependenciesLoader.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/SyncScriptDependenciesLoader.kt deleted file mode 100644 index b10e88d16d3..00000000000 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/SyncScriptDependenciesLoader.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2010-2018 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.dependencies - -import com.intellij.openapi.project.Project -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition -import org.jetbrains.kotlin.scripting.resolve.KtFileScriptSource -import org.jetbrains.kotlin.scripting.resolve.refineScriptCompilationConfiguration - -class SyncScriptDependenciesLoader(project: Project) : ScriptDependenciesLoader(project) { - override fun isApplicable( - file: KtFile, - scriptDefinition: ScriptDefinition - ): Boolean { - return !isAsyncDependencyResolver(scriptDefinition) - } - - override fun loadDependencies( - file: KtFile, - scriptDefinition: ScriptDefinition - ) { - debug(file) { "start sync dependencies loading" } - val result = refineScriptCompilationConfiguration(KtFileScriptSource(file), scriptDefinition, project) - debug(file) { "finish sync dependencies loading" } - processRefinedConfiguration(result, file.originalFile.virtualFile) - } - - override fun shouldShowNotification(): Boolean = false -} \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/scriptUtils.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/scriptUtils.kt new file mode 100644 index 00000000000..1d448606cc2 --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/scriptUtils.kt @@ -0,0 +1,31 @@ +/* + * 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 + +import com.intellij.openapi.application.Application +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.util.Key +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.NotNullableUserDataProperty + +@set: org.jetbrains.annotations.TestOnly +var Application.isScriptDependenciesUpdaterDisabled by NotNullableUserDataProperty( + Key.create("SCRIPT_DEPENDENCIES_UPDATER_DISABLED"), + false +) + +internal val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.script") + +internal fun debug(file: KtFile, message: () -> String) { + debug(file.originalFile.virtualFile, message) +} + +internal fun debug(file: VirtualFile? = null, message: () -> String) { + if (LOG.isDebugEnabled) { + LOG.debug("[KOTLIN SCRIPT] ${file?.let { file.path + " "} ?: ""}" + message()) + } +} \ No newline at end of file diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/compile/KtScratchExecutionSession.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/compile/KtScratchExecutionSession.kt index ba914c3cca6..eabf5a8f0ea 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/compile/KtScratchExecutionSession.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/compile/KtScratchExecutionSession.kt @@ -30,7 +30,6 @@ import org.jetbrains.kotlin.idea.util.JavaParametersBuilder import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.psi.* import java.io.File -import kotlin.script.experimental.api.valueOrNull class KtScratchExecutionSession( private val file: ScratchFile, @@ -166,7 +165,7 @@ class KtScratchExecutionSession( } ScriptDependenciesManager.getInstance(originalFile.project) - .getRefinedCompilationConfiguration(originalFile)?.valueOrNull()?.let { + .getConfiguration(originalFile)?.let { javaParameters.classPath.addAll(it.dependenciesClassPath.map { f -> f.absolutePath }) } diff --git a/idea/resources/META-INF/plugin-common.xml b/idea/resources/META-INF/plugin-common.xml index 063ab7c9b09..66439b25e51 100644 --- a/idea/resources/META-INF/plugin-common.xml +++ b/idea/resources/META-INF/plugin-common.xml @@ -297,13 +297,7 @@ serviceImplementation="org.jetbrains.kotlin.idea.core.script.IdeScriptDependenciesProvider"/> - - - - + serviceImplementation="org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManagerImpl"/> diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt index 16f6560c90f..463f17edecf 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt @@ -43,7 +43,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.core.isInheritable -import org.jetbrains.kotlin.idea.core.script.ScriptsCompilationConfigurationUpdater +import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesManager import org.jetbrains.kotlin.idea.core.toDescriptor import org.jetbrains.kotlin.idea.findUsages.KotlinFindUsagesHandlerFactory import org.jetbrains.kotlin.idea.findUsages.handlers.KotlinFindClassUsagesHandler @@ -136,7 +136,7 @@ class UnusedSymbolInspection : AbstractKotlinInspection() { val usedScripts = findScriptsWithUsages(declaration) if (usedScripts.isNotEmpty()) { - if (ScriptsCompilationConfigurationUpdater.getInstance(declaration.project).updateDependenciesIfNeeded(usedScripts)) { + if (ScriptDependenciesManager.getInstance(declaration.project).updateConfigurationsIfNotCached(usedScripts)) { return TOO_MANY_OCCURRENCES } } diff --git a/idea/src/org/jetbrains/kotlin/idea/util/ImportInsertHelperImpl.kt b/idea/src/org/jetbrains/kotlin/idea/util/ImportInsertHelperImpl.kt index c0b264354db..5eba6d378f2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/util/ImportInsertHelperImpl.kt +++ b/idea/src/org/jetbrains/kotlin/idea/util/ImportInsertHelperImpl.kt @@ -49,7 +49,6 @@ import org.jetbrains.kotlin.resolve.scopes.utils.findVariable import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider import org.jetbrains.kotlin.utils.addIfNotNull import java.util.* -import kotlin.script.experimental.api.valueOrNull class ImportInsertHelperImpl(private val project: Project) : ImportInsertHelper() { @@ -66,8 +65,7 @@ class ImportInsertHelperImpl(private val project: Project) : ImportInsertHelper( val scriptExtraImports = contextFile.takeIf { it.isScript() }?.let { ktFile -> val scriptDependencies = ScriptDependenciesProvider.getInstance(ktFile.project) - ?.getScriptConfigurationResult(ktFile.originalFile as KtFile) - ?.valueOrNull() + ?.getScriptConfiguration(ktFile.originalFile as KtFile) scriptDependencies?.defaultImports?.map { ImportPath.fromString(it) } }.orEmpty() diff --git a/idea/tests/org/jetbrains/kotlin/idea/configuration/AbstractConfigureProjectByChangingFileTest.java b/idea/tests/org/jetbrains/kotlin/idea/configuration/AbstractConfigureProjectByChangingFileTest.java index 43c81bb775b..75d12909276 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/configuration/AbstractConfigureProjectByChangingFileTest.java +++ b/idea/tests/org/jetbrains/kotlin/idea/configuration/AbstractConfigureProjectByChangingFileTest.java @@ -22,7 +22,7 @@ import com.intellij.testFramework.LightProjectDescriptor; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.kotlin.idea.core.script.ScriptsCompilationConfigurationUpdaterKt; +import org.jetbrains.kotlin.idea.core.script.ScriptUtilsKt; import org.jetbrains.kotlin.test.InTextDirectivesUtils; import org.jetbrains.kotlin.test.KotlinTestUtils; import org.jetbrains.plugins.groovy.GroovyFileType; @@ -42,12 +42,12 @@ public abstract class AbstractConfigureProjectByChangingFileTest FileTypeManager.getInstance().associateExtension(GroovyFileType.GROOVY_FILE_TYPE, "gradle") ); - ScriptsCompilationConfigurationUpdaterKt.setScriptDependenciesUpdaterDisabled(ApplicationManager.getApplication(), true); + ScriptUtilsKt.setScriptDependenciesUpdaterDisabled(ApplicationManager.getApplication(), true); } @Override protected void tearDown() throws Exception { - ScriptsCompilationConfigurationUpdaterKt.setScriptDependenciesUpdaterDisabled(ApplicationManager.getApplication(), false); + ScriptUtilsKt.setScriptDependenciesUpdaterDisabled(ApplicationManager.getApplication(), false); moduleInfoFile = null; super.tearDown(); } diff --git a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/ScriptDependenciesProvider.kt b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/ScriptDependenciesProvider.kt index 0dfa85c7ac8..8191335f687 100644 --- a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/ScriptDependenciesProvider.kt +++ b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/ScriptDependenciesProvider.kt @@ -12,6 +12,7 @@ import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult +import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper import kotlin.script.experimental.api.valueOrNull import kotlin.script.experimental.dependencies.ScriptDependencies @@ -22,17 +23,19 @@ open class ScriptDependenciesProvider constructor( @Deprecated("Migrating to configuration refinement", level = DeprecationLevel.ERROR) fun getScriptDependencies(file: VirtualFile): ScriptDependencies? { val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return null - return getScriptConfigurationResult(ktFile)?.valueOrNull()?.legacyDependencies + return getScriptConfiguration(ktFile)?.legacyDependencies } @Deprecated("Migrating to configuration refinement", level = DeprecationLevel.ERROR) fun getScriptDependencies(file: PsiFile): ScriptDependencies? { if (file !is KtFile) return null - return getScriptConfigurationResult(file)?.valueOrNull()?.legacyDependencies + return getScriptConfiguration(file)?.legacyDependencies } open fun getScriptConfigurationResult(file: KtFile): ScriptCompilationConfigurationResult? = null + open fun getScriptConfiguration(file: KtFile): ScriptCompilationConfigurationWrapper? = getScriptConfigurationResult(file)?.valueOrNull() + companion object { fun getInstance(project: Project): ScriptDependenciesProvider? = ServiceManager.getService(project, ScriptDependenciesProvider::class.java) diff --git a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/dynamicConfigurations.kt b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/dynamicConfigurations.kt index b701019e739..ac1b9581957 100644 --- a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/dynamicConfigurations.kt +++ b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/dynamicConfigurations.kt @@ -11,7 +11,6 @@ import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResu import kotlin.script.experimental.api.ScriptCompilationConfiguration import kotlin.script.experimental.api.ScriptDiagnostic import kotlin.script.experimental.api.valueOr -import kotlin.script.experimental.api.valueOrNull fun PsiFile.findScriptCompilationConfiguration(): ScriptCompilationConfiguration? { // Do not use psiFile.script, see comments in findScriptDefinition @@ -21,7 +20,7 @@ fun PsiFile.findScriptCompilationConfiguration(): ScriptCompilationConfiguration val provider = ScriptDependenciesProvider.getInstance(project) // Ignoring the error here, assuming that it will be reported elsewhere anyway (this is important scenario in IDE) - return provider?.getScriptConfigurationResult(this)?.valueOrNull()?.configuration + return provider?.getScriptConfiguration(this)?.configuration ?: findScriptDefinition()?.compilationConfiguration } diff --git a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/extensions/ScriptExtraImportsProviderExtension.kt b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/extensions/ScriptExtraImportsProviderExtension.kt index cb1c98d7d96..c8455d2ee76 100644 --- a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/extensions/ScriptExtraImportsProviderExtension.kt +++ b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/extensions/ScriptExtraImportsProviderExtension.kt @@ -11,7 +11,6 @@ import org.jetbrains.kotlin.psi.KtImportInfo import org.jetbrains.kotlin.resolve.ImportPath import org.jetbrains.kotlin.resolve.extensions.ExtraImportsProviderExtension import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider -import kotlin.script.experimental.api.valueOrNull class ScriptExtraImportsProviderExtension : ExtraImportsProviderExtension { @@ -30,8 +29,7 @@ class ScriptExtraImportsProviderExtension : ExtraImportsProviderExtension { override fun getExtraImports(ktFile: KtFile): Collection = ktFile.takeIf { it.isScript() }?.let { file -> val refinedConfiguration = ScriptDependenciesProvider.getInstance(file.project) - ?.getScriptConfigurationResult(file.originalFile as KtFile) - ?.valueOrNull() + ?.getScriptConfiguration(file.originalFile as KtFile) refinedConfiguration?.defaultImports?.map { ScriptExtraImportImpl( ImportPath.fromString(it) diff --git a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/resolve/LazyScriptDescriptor.kt b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/resolve/LazyScriptDescriptor.kt index 7694ba43318..e954163bfeb 100644 --- a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/resolve/LazyScriptDescriptor.kt +++ b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/resolve/LazyScriptDescriptor.kt @@ -205,7 +205,7 @@ class LazyScriptDescriptor( val res = ArrayList() val importedScriptsFiles = ScriptDependenciesProvider.getInstance(scriptInfo.script.project) - ?.getScriptConfigurationResult(scriptInfo.script.containingKtFile)?.valueOrNull()?.importedScripts + ?.getScriptConfiguration(scriptInfo.script.containingKtFile)?.importedScripts if (importedScriptsFiles != null) { val findImportedScriptDescriptor = ImportedScriptDescriptorsFinder() importedScriptsFiles.mapNotNullTo(res) { diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/dependencies/ScriptsCompilationDependencies.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/dependencies/ScriptsCompilationDependencies.kt index b36a43ba3dd..922cc9d78e9 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/dependencies/ScriptsCompilationDependencies.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/dependencies/ScriptsCompilationDependencies.kt @@ -13,7 +13,6 @@ import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider import java.io.File -import kotlin.script.experimental.api.valueOrNull data class ScriptsCompilationDependencies( val classpath: List, @@ -42,7 +41,7 @@ fun collectScriptsCompilationDependencies( while (true) { val newRemainingSources = ArrayList() for (source in remainingSources) { - val refinedConfiguration = importsProvider.getScriptConfigurationResult(source)?.valueOrNull() + val refinedConfiguration = importsProvider.getScriptConfiguration(source) if (refinedConfiguration != null) { collectedClassPath.addAll(refinedConfiguration.dependenciesClassPath) diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/KJvmReplCompilerImpl.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/KJvmReplCompilerImpl.kt index d08f5ed3f79..2b73ff71c76 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/KJvmReplCompilerImpl.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/KJvmReplCompilerImpl.kt @@ -154,7 +154,7 @@ class KJvmReplCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : sourceFiles.first(), sourceDependencies ) { ktFile -> - dependenciesProvider?.getScriptConfigurationResult(ktFile)?.valueOrNull()?.configuration + dependenciesProvider?.getScriptConfiguration(ktFile)?.configuration ?: context.baseScriptCompilationConfiguration } diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/ScriptJvmCompilerImpls.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/ScriptJvmCompilerImpls.kt index fe93747f60e..825dbce9bfd 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/ScriptJvmCompilerImpls.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/ScriptJvmCompilerImpls.kt @@ -98,7 +98,7 @@ private fun compileImpl( val dependenciesProvider = ScriptDependenciesProvider.getInstance(context.environment.project) val getScriptConfiguration = { ktFile: KtFile -> - (dependenciesProvider?.getScriptConfigurationResult(ktFile)?.valueOrNull()?.configuration ?: context.baseScriptCompilationConfiguration) + (dependenciesProvider?.getScriptConfiguration(ktFile)?.configuration ?: context.baseScriptCompilationConfiguration) .with { // Adjust definitions so all compiler dependencies are saved in the resulting compilation configuration, so evaluation // performed with the expected classpath diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/compilationContext.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/compilationContext.kt index 657e51e7ee1..dd1ba5e80a7 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/compilationContext.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/compilationContext.kt @@ -33,7 +33,6 @@ import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider import kotlin.script.experimental.api.ScriptCompilationConfiguration import kotlin.script.experimental.api.compilerOptions import kotlin.script.experimental.api.dependencies -import kotlin.script.experimental.api.valueOrNull import kotlin.script.experimental.host.ScriptingHostConfiguration import kotlin.script.experimental.jvm.JvmDependency import kotlin.script.experimental.jvm.jdkHome @@ -244,7 +243,7 @@ private fun CompilerConfiguration.updateWithRefinedConfigurations( ) { val dependenciesProvider = ScriptDependenciesProvider.getInstance(context.environment.project) val updatedCompilerOptions = sourceFiles.flatMap { - dependenciesProvider?.getScriptConfigurationResult(it)?.valueOrNull()?.configuration?.get( + dependenciesProvider?.getScriptConfiguration(it)?.configuration?.get( ScriptCompilationConfiguration.compilerOptions ) ?: emptyList() } diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/GenericReplCompiler.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/GenericReplCompiler.kt index 95c42b2b5c2..11ed61c8900 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/GenericReplCompiler.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/GenericReplCompiler.kt @@ -23,7 +23,6 @@ import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider import java.io.File import java.util.concurrent.locks.ReentrantReadWriteLock import kotlin.concurrent.write -import kotlin.script.experimental.api.valueOrNull // WARNING: not thread safe, assuming external synchronization @@ -71,7 +70,7 @@ open class GenericReplCompiler( } val newDependencies = - ScriptDependenciesProvider.getInstance(checker.environment.project)?.getScriptConfigurationResult(psiFile)?.valueOrNull() + ScriptDependenciesProvider.getInstance(checker.environment.project)?.getScriptConfiguration(psiFile) ?.legacyDependencies var classpathAddendum: List? = null if (compilerState.lastDependencies != newDependencies) {