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) {