From 59bdf67aa2242ce1c78ce8b753926e3a93f724f0 Mon Sep 17 00:00:00 2001 From: Natalia Selezneva Date: Thu, 16 Apr 2020 16:18:16 +0300 Subject: [PATCH] Refactoring: extract separate manager to update script configurations of gradle scripts The main goal is to extract logic that is related to Gradle scripts only to separate place. Configurations for gradle scripts are updated during import simultaneously, there is no need to manage script configuration update for one file Note that for Gradle less than 6.0 The DefaultScriptingSupport is used --- ...ScriptConfigurationNotificationFactory.kt} | 18 +- .../core/script/ScriptConfigurationManager.kt | 20 +- .../AbstractScriptConfigurationManager.kt | 403 ++++++------------ .../DefaultScriptConfigurationManager.kt | 275 +++++++----- .../configuration/ScriptingSupportHelper.kt | 36 ++ .../cache/ScriptConfigurationCache.kt | 2 + .../cache/ScriptConfigurationMemoryCache.kt | 3 + .../listener/ScriptChangeListener.kt | 1 - .../utils/DefaultClassRootsCache.kt | 101 +++++ .../utils/ScriptClassRootsCache.kt | 150 +++---- .../utils/ScriptClassRootsIndexer.kt | 5 +- .../utils/ScriptClassRootsStorage.kt | 115 +++-- .../scripting/gradle/GradleClassRootsCache.kt | 53 +++ .../scripting/gradle/GradleImportHelper.kt | 23 +- .../gradle/GradleImportHelper.kt.201 | 32 +- .../gradle/GradleScriptConfigurationLoader.kt | 6 - .../GradleScriptConfigurationManager.kt | 178 ++++++++ ...eScriptConfigurationNotificationFactory.kt | 32 -- .../gradle/importing/KotlinDslScriptModel.kt | 4 +- .../KotlinDslScrtiptModelProcessor.kt | 112 +++-- .../idea/scripting/gradle/serizliation.kt | 143 +++++++ .../gradle/KotlinDslScriptModelsTest.kt | 49 +++ idea/resources/META-INF/extensions/ide.xml | 9 - idea/resources/META-INF/gradle.xml | 4 +- idea/resources/META-INF/gradle.xml.201 | 4 +- idea/resources/META-INF/plugin-common.xml | 2 +- idea/resources/META-INF/scripting-support.xml | 13 + 27 files changed, 1126 insertions(+), 667 deletions(-) rename idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/{ScriptConfigurationNotificationFactory.kt => LoadScriptConfigurationNotificationFactory.kt} (84%) create mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/ScriptingSupportHelper.kt create mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/DefaultClassRootsCache.kt create mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleClassRootsCache.kt create mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptConfigurationManager.kt delete mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptConfigurationNotificationFactory.kt create mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/serizliation.kt create mode 100644 idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/KotlinDslScriptModelsTest.kt diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptConfigurationNotificationFactory.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/LoadScriptConfigurationNotificationFactory.kt similarity index 84% rename from idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptConfigurationNotificationFactory.kt rename to idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/LoadScriptConfigurationNotificationFactory.kt index ca57b194fb1..aab2960e052 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptConfigurationNotificationFactory.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/LoadScriptConfigurationNotificationFactory.kt @@ -6,7 +6,6 @@ package org.jetbrains.kotlin.idea.core.script import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.fileEditor.FileEditor import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project @@ -15,28 +14,17 @@ import com.intellij.openapi.util.Ref import com.intellij.openapi.vfs.VirtualFile import com.intellij.ui.EditorNotificationPanel import com.intellij.ui.HyperlinkLabel -import org.jetbrains.annotations.TestOnly import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings import org.jetbrains.kotlin.idea.core.util.KotlinIdeaCoreBundle import org.jetbrains.kotlin.psi.UserDataProperty -interface ScriptConfigurationNotificationFactory { - fun showNotification(file: VirtualFile, project: Project, onClick: () -> Unit): Boolean - fun hideNotification(file: VirtualFile, project: Project): Boolean - - companion object { - val NOTIFICATION_FACTORY: ExtensionPointName = - ExtensionPointName.create("org.jetbrains.kotlin.scripting.idea.notificationFactory") - } -} - -class DefaultScriptConfigurationNotificationFactory : ScriptConfigurationNotificationFactory { - override fun showNotification(file: VirtualFile, project: Project, onClick: () -> Unit): Boolean { +object LoadScriptConfigurationNotificationFactory { + fun showNotification(file: VirtualFile, project: Project, onClick: () -> Unit): Boolean { file.addLoadConfigurationNotificationPanel(project, onClick) return true } - override fun hideNotification(file: VirtualFile, project: Project): Boolean { + fun hideNotification(file: VirtualFile, project: Project): Boolean { file.removeLoadConfigurationNotificationPanel(project) return true } diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptConfigurationManager.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptConfigurationManager.kt index 3a65a322d98..c4a374bb7ca 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptConfigurationManager.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptConfigurationManager.kt @@ -29,8 +29,8 @@ import com.intellij.psi.search.GlobalSearchScope import com.intellij.util.io.URLUtil import org.jetbrains.annotations.TestOnly import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks -import org.jetbrains.kotlin.idea.core.script.configuration.AbstractScriptConfigurationManager -import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationSnapshot +import org.jetbrains.kotlin.idea.core.script.configuration.CompositeManager +import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptingSupport import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoader import org.jetbrains.kotlin.psi.KtFile @@ -38,6 +38,7 @@ import org.jetbrains.kotlin.psi.UserDataProperty import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance import java.io.File import kotlin.script.experimental.api.asSuccess import kotlin.script.experimental.api.makeFailureResult @@ -106,13 +107,6 @@ interface ScriptConfigurationManager { */ fun clearConfigurationCachesAndRehighlight() - /** - * Save configurations into cache. - * Start indexing for new class/source roots. - * Re-highlight opened scripts with changed configuration. - */ - fun saveCompilationConfigurationAfterImport(files: List>) - /////////////// // classpath roots info: @@ -168,12 +162,16 @@ interface ScriptConfigurationManager { @TestOnly fun updateScriptDependenciesSynchronously(file: PsiFile) { // TODO: review the usages of this method - (getInstance(file.project) as AbstractScriptConfigurationManager).updateScriptDependenciesSynchronously(file) + (getInstance(file.project) as CompositeManager).managers + .firstIsInstance() + .updateScriptDependenciesSynchronously(file) } @TestOnly fun clearCaches(project: Project) { - (getInstance(project) as AbstractScriptConfigurationManager).clearCaches() + (getInstance(project) as CompositeManager).managers + .firstIsInstance() + .clearCaches() } fun clearManualConfigurationLoadingIfNeeded(file: VirtualFile) { diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/AbstractScriptConfigurationManager.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/AbstractScriptConfigurationManager.kt index 07c110a4230..503d8ade1b4 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/AbstractScriptConfigurationManager.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/AbstractScriptConfigurationManager.kt @@ -6,293 +6,45 @@ package org.jetbrains.kotlin.idea.core.script.configuration import com.intellij.ProjectTopics -import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer +import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.extensions.Extensions -import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.roots.ModuleRootEvent import com.intellij.openapi.roots.ModuleRootListener import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElementFinder -import com.intellij.psi.PsiFile -import com.intellij.psi.PsiManager import com.intellij.psi.search.GlobalSearchScope -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import org.jetbrains.annotations.TestOnly -import org.jetbrains.kotlin.idea.core.script.* -import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager.Companion.toVfsRoots -import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationCache -import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationCacheScope -import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationSnapshot -import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationState +import org.jetbrains.kotlin.idea.core.script.KotlinScriptDependenciesClassFinder +import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager +import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker +import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangesNotifier import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoader import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache -import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsIndexer import org.jetbrains.kotlin.idea.core.script.configuration.utils.getKtFile -import org.jetbrains.kotlin.idea.core.util.EDT +import org.jetbrains.kotlin.idea.core.script.debug import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition import org.jetbrains.kotlin.scripting.definitions.isNonScript import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock -/** - * Abstract [ScriptConfigurationManager] implementation based on [cache] and [reloadOutOfDateConfiguration]. - * Among this two methods concrete implementation should provide script changes listening (by calling [updater] on some event). - * - * Basically all requests routed to [cache]. If there is no entry in [cache] or it is considered out-of-date, - * then [reloadOutOfDateConfiguration] will be called, which, in turn, should call [setAppliedConfiguration] - * immediately or in some future (e.g. after user will click "apply context" or/and configuration will - * be calculated by some background thread). - * - * [classpathRoots] will be calculated lazily based on [cache]d configurations. - * Every change in [cache] will invalidate [classpathRoots] cache. - * Some internal state changes in [cache] may also invalidate [classpathRoots] by calling [clearClassRootsCaches] - * (for example, when cache loaded from FS to memory) - */ -internal abstract class AbstractScriptConfigurationManager( - protected val project: Project -) : ScriptConfigurationManager { - protected val rootsIndexer = ScriptClassRootsIndexer(project) +abstract class ScriptingSupport { + abstract fun isRelated(file: VirtualFile): Boolean - @Suppress("LeakingThis") - protected val cache: ScriptConfigurationCache = createCache() + abstract fun clearCaches() + abstract fun hasCachedConfiguration(file: KtFile): Boolean + abstract fun getOrLoadConfiguration(virtualFile: VirtualFile, preloadedKtFile: KtFile? = null): ScriptCompilationConfigurationWrapper? - protected abstract fun createCache(): ScriptConfigurationCache - - /** - * Will be called on [cache] miss or when [file] is changed. - * Implementation should initiate loading of [file]'s script configuration and call [setAppliedConfiguration] - * immediately or in some future - * (e.g. after user will click "apply context" or/and configuration will be calculated by some background thread). - * - * @param isFirstLoad may be set explicitly for optimization reasons (to avoid expensive fs cache access) - * @param loadEvenWillNotBeApplied may should be set to false only on requests from particular editor, when - * user can see potential notification and accept new configuration. In other cases this should be `false` since - * loaded configuration will be just leaved in hidden user notification and cannot be used in any way. - * @param forceSync should be used in tests only - * @param isPostponedLoad is used to postspone loading: show a notification for out of date script and start loading when user request - */ - protected abstract fun reloadOutOfDateConfiguration( - file: KtFile, - isFirstLoad: Boolean = getAppliedConfiguration(file.originalFile.virtualFile) == null, - loadEvenWillNotBeApplied: Boolean = false, - forceSync: Boolean = false, - isPostponedLoad: Boolean = false - ) - - /** - * Will be called on user action - * Load configuration event it is already cached or inputs are up-to-date - * - * @param loader is used to load configuration. Other loaders aren't taken into account. - */ - protected abstract fun forceReloadConfiguration( - file: KtFile, - loader: ScriptConfigurationLoader - ) - - @Deprecated("Use getScriptClasspath(KtFile) instead") - override fun getScriptClasspath(file: VirtualFile): List { - val ktFile = project.getKtFile(file) ?: return emptyList() - return getScriptClasspath(ktFile) - } - - override fun getScriptClasspath(file: KtFile): List = - toVfsRoots(getConfiguration(file)?.dependenciesClassPath.orEmpty()) - - fun getCachedConfigurationState(file: VirtualFile?): ScriptConfigurationState? { - if (file == null) return null - return cache[file] - } - - fun getAppliedConfiguration(file: VirtualFile?): ScriptConfigurationSnapshot? = - getCachedConfigurationState(file)?.applied - - override fun hasConfiguration(file: KtFile): Boolean { - return getAppliedConfiguration(file.originalFile.virtualFile) != null - } - - override fun getConfiguration(file: KtFile): ScriptCompilationConfigurationWrapper? { - return getConfiguration(file.originalFile.virtualFile, file) - } - - fun getConfiguration( - virtualFile: VirtualFile, - preloadedKtFile: KtFile? = null - ): ScriptCompilationConfigurationWrapper? { - val cached = getAppliedConfiguration(virtualFile) - if (cached != null) return cached.configuration - - val ktFile = project.getKtFile(virtualFile, preloadedKtFile) ?: return null - rootsIndexer.transaction { - reloadOutOfDateConfiguration(ktFile, isFirstLoad = true) - } - - return getAppliedConfiguration(virtualFile)?.configuration - } - - override fun forceReloadConfiguration( - file: VirtualFile, - loader: ScriptConfigurationLoader - ): ScriptCompilationConfigurationWrapper? { - val ktFile = project.getKtFile(file, null) ?: return null - - rootsIndexer.transaction { - forceReloadConfiguration(ktFile, loader) - } - - return getAppliedConfiguration(file)?.configuration - } - - override val updater: ScriptConfigurationUpdater = object : ScriptConfigurationUpdater { - override fun ensureUpToDatedConfigurationSuggested(file: KtFile) { - reloadIfOutOfDate(listOf(file), loadEvenWillNotBeApplied = true, isPostponedLoad = false) - } - - override fun ensureConfigurationUpToDate(files: List): Boolean { - return reloadIfOutOfDate(files, loadEvenWillNotBeApplied = false, isPostponedLoad = false) - } - - override fun postponeConfigurationReload(scope: ScriptConfigurationCacheScope) { - cache.markOutOfDate(scope) - } - - override fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile) { - reloadIfOutOfDate(listOf(file), loadEvenWillNotBeApplied = true, isPostponedLoad = true) - } - } - - private fun reloadIfOutOfDate(files: List, loadEvenWillNotBeApplied: Boolean, isPostponedLoad: Boolean): Boolean { - if (!ScriptDefinitionsManager.getInstance(project).isReady()) return false - - var upToDate = true - rootsIndexer.transaction { - files.forEach { file -> - val virtualFile = file.originalFile.virtualFile - if (virtualFile != null) { - val state = cache[virtualFile] - if (state == null || !state.isUpToDate(project, virtualFile, file)) { - upToDate = false - reloadOutOfDateConfiguration( - file, - isFirstLoad = state == null, - loadEvenWillNotBeApplied = loadEvenWillNotBeApplied, - isPostponedLoad = isPostponedLoad - ) - } - } - } - } - - return upToDate - } - - @TestOnly - internal fun updateScriptDependenciesSynchronously(file: PsiFile) { - file.findScriptDefinition() ?: return - - file as? KtFile ?: error("PsiFile $file should be a KtFile, otherwise script dependencies cannot be loaded") - - val virtualFile = file.virtualFile - if (cache[virtualFile]?.isUpToDate(project, virtualFile, file) == true) return - - rootsIndexer.transaction { - reloadOutOfDateConfiguration(file, forceSync = true, loadEvenWillNotBeApplied = true) - } - } - - protected open fun setAppliedConfiguration( - file: VirtualFile, - newConfigurationSnapshot: ScriptConfigurationSnapshot? - ) { - rootsIndexer.checkInTransaction() - val newConfiguration = newConfigurationSnapshot?.configuration - debug(file) { "configuration changed = $newConfiguration" } - - if (newConfiguration != null) { - if (hasNotCachedRoots(newConfiguration)) { - rootsIndexer.markNewRoot(file, newConfiguration) - } - - cache.setApplied(file, newConfigurationSnapshot) - - clearClassRootsCaches() - } - - updateHighlighting(listOf(file)) - } - - protected fun setLoadedConfiguration( - file: VirtualFile, - configurationSnapshot: ScriptConfigurationSnapshot - ) { - cache.setLoaded(file, configurationSnapshot) - } - - private fun hasNotCachedRoots(configuration: ScriptCompilationConfigurationWrapper): Boolean { - return classpathRoots.hasNotCachedRoots(configuration) - } - - init { - val connection = project.messageBus.connect(project) - connection.subscribe(ProjectTopics.PROJECT_ROOTS, object : ModuleRootListener { - override fun rootsChanged(event: ModuleRootEvent) { - clearClassRootsCaches() - } - }) - } - - /** - * Clear configuration caches - * Start re-highlighting for opened scripts - */ - override fun clearConfigurationCachesAndRehighlight() { - ScriptDependenciesModificationTracker.getInstance(project).incModificationCount() - - // todo: cache.clear() - clearClassRootsCaches() - - if (project.isOpen) { - val openedScripts = FileEditorManager.getInstance(project).openFiles.filterNot { it.isNonScript() } - updateHighlighting(openedScripts) - } - } - - @TestOnly - internal fun clearCaches() { - cache.clear() - } - - 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) - } - } - } - } - - /////////////////// - // ScriptRootsCache + abstract val updater: ScriptConfigurationUpdater private val classpathRootsLock = ReentrantLock() @Volatile private var _classpathRoots: ScriptClassRootsCache? = null - private val classpathRoots: ScriptClassRootsCache + val classpathRoots: ScriptClassRootsCache get() { val value1 = _classpathRoots if (value1 != null) return value1 @@ -301,13 +53,16 @@ internal abstract class AbstractScriptConfigurationManager( val value2 = _classpathRoots if (value2 != null) return value2 - val value3 = ScriptClassRootsCache(project, cache.allApplied()) + val value3 = recreateRootsCache() + value3.saveClassRootsToStorage() _classpathRoots = value3 return value3 } } - private fun clearClassRootsCaches() { + protected abstract fun recreateRootsCache(): ScriptClassRootsCache + + fun clearClassRootsCaches(project: Project) { debug { "class roots caches cleared" } classpathRootsLock.withLock { @@ -324,31 +79,133 @@ internal abstract class AbstractScriptConfigurationManager( ScriptDependenciesModificationTracker.getInstance(project).incModificationCount() } + companion object { + val SCRIPTING_SUPPORT: ExtensionPointName = + ExtensionPointName.create("org.jetbrains.kotlin.scripting.idea.scriptingSupport") + } +} + +class CompositeManager(val project: Project) : ScriptConfigurationManager { + @Suppress("unused") + private val notifier = ScriptChangesNotifier(project, updater) + + // todo public for tests + val managers = ScriptingSupport.SCRIPTING_SUPPORT.getPoint(project).extensionList + + private fun getRelatedManager(file: VirtualFile): ScriptingSupport = managers.first { it.isRelated(file) } + private fun getRelatedManager(file: KtFile): ScriptingSupport = + getRelatedManager(file.originalFile.virtualFile) + + private fun getOrLoadConfiguration( + virtualFile: VirtualFile, + preloadedKtFile: KtFile? = null + ): ScriptCompilationConfigurationWrapper? = + getRelatedManager(virtualFile).getOrLoadConfiguration(virtualFile, preloadedKtFile) + + override fun getConfiguration(file: KtFile) = getOrLoadConfiguration(file.originalFile.virtualFile, file) + + override fun hasConfiguration(file: KtFile): Boolean = + getRelatedManager(file).hasCachedConfiguration(file) + + override val updater: ScriptConfigurationUpdater + get() = object : ScriptConfigurationUpdater { + override fun ensureUpToDatedConfigurationSuggested(file: KtFile) = + getRelatedManager(file).updater.ensureUpToDatedConfigurationSuggested(file) + + override fun ensureConfigurationUpToDate(files: List): Boolean = + files.groupBy { getRelatedManager(it) }.all { (manager, files) -> + manager.updater.ensureConfigurationUpToDate(files) + } + + override fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile) = + getRelatedManager(file).updater.suggestToUpdateConfigurationIfOutOfDate(file) + } + + init { + val connection = project.messageBus.connect(project) + connection.subscribe(ProjectTopics.PROJECT_ROOTS, object : ModuleRootListener { + override fun rootsChanged(event: ModuleRootEvent) { + managers.forEach { + it.clearClassRootsCaches(project) + } + } + }) + } + /** * Returns script classpath roots * Loads script configuration if classpath roots don't contain [file] yet */ private fun getActualClasspathRoots(file: VirtualFile): ScriptClassRootsCache { + val classpathRoots = getRelatedManager(file).classpathRoots if (classpathRoots.contains(file)) { return classpathRoots } - getConfiguration(file) - return classpathRoots + getOrLoadConfiguration(file) + + return getRelatedManager(file).classpathRoots } - override fun getScriptSdk(file: VirtualFile): Sdk? = getActualClasspathRoots(file).getScriptSdk(file) + override fun getScriptSdk(file: VirtualFile): Sdk? = + getActualClasspathRoots(file).getScriptSdk(file) - override fun getFirstScriptsSdk(): Sdk? = classpathRoots.firstScriptSdk + override fun getFirstScriptsSdk(): Sdk? { + managers.forEach { + it.classpathRoots.firstScriptSdk?.let { sdk -> return sdk } + } + + return null + } override fun getScriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope = getActualClasspathRoots(file).getScriptDependenciesClassFilesScope(file) - override fun getAllScriptsDependenciesClassFilesScope(): GlobalSearchScope = classpathRoots.allDependenciesClassFilesScope + override fun getAllScriptsDependenciesClassFilesScope(): GlobalSearchScope = + GlobalSearchScope.union(managers.map { it.classpathRoots.allDependenciesClassFilesScope }) - override fun getAllScriptDependenciesSourcesScope(): GlobalSearchScope = classpathRoots.allDependenciesSourcesScope + override fun getAllScriptDependenciesSourcesScope(): GlobalSearchScope = + GlobalSearchScope.union(managers.map { it.classpathRoots.allDependenciesSourcesScope }) - override fun getAllScriptsDependenciesClassFiles(): List = classpathRoots.allDependenciesClassFiles + override fun getAllScriptsDependenciesClassFiles(): List = + managers.flatMap { it.classpathRoots.allDependenciesClassFiles } + + override fun getAllScriptDependenciesSources(): List = + managers.flatMap { it.classpathRoots.allDependenciesSources } + + override fun forceReloadConfiguration(file: VirtualFile, loader: ScriptConfigurationLoader): ScriptCompilationConfigurationWrapper? { + val ktFile = project.getKtFile(file, null) ?: return null + + return managers.firstIsInstanceOrNull()?.forceReloadConfiguration(ktFile, loader) + } + + /////////////////// + // Adapters for deprecated API + // + + @Deprecated("Use getScriptClasspath(KtFile) instead") + override fun getScriptClasspath(file: VirtualFile): List { + val ktFile = project.getKtFile(file) ?: return emptyList() + return getScriptClasspath(ktFile) + } + + override fun getScriptClasspath(file: KtFile): List = + ScriptConfigurationManager.toVfsRoots(getConfiguration(file)?.dependenciesClassPath.orEmpty()) + + private fun clearCaches() { + managers.forEach { + it.clearCaches() + } + } + + override fun clearConfigurationCachesAndRehighlight() { + ScriptDependenciesModificationTracker.getInstance(project).incModificationCount() + + clearCaches() + + ScriptingSupportHelper.updateHighlighting(project) { + !it.isNonScript() + } + } +} - override fun getAllScriptDependenciesSources(): List = classpathRoots.allDependenciesSources -} \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/DefaultScriptConfigurationManager.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/DefaultScriptConfigurationManager.kt index 0a514ec618f..f80cab65c44 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/DefaultScriptConfigurationManager.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/DefaultScriptConfigurationManager.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 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. */ @@ -11,95 +11,36 @@ import com.intellij.openapi.components.ServiceManager import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.intellij.ui.EditorNotifications import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch +import org.jetbrains.annotations.TestOnly import org.jetbrains.kotlin.idea.core.script.* import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptConfigurationManagerExtensions.LOADER -import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationFileAttributeCache -import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationMemoryCache -import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationSnapshot -import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangesNotifier +import org.jetbrains.kotlin.idea.core.script.configuration.cache.* import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater import org.jetbrains.kotlin.idea.core.script.configuration.loader.DefaultScriptConfigurationLoader import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoader import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoadingContext import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptOutsiderFileConfigurationLoader import org.jetbrains.kotlin.idea.core.script.configuration.utils.* -import org.jetbrains.kotlin.idea.core.script.configuration.utils.DefaultBackgroundExecutor 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.ScriptCompilationConfigurationWrapper import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock -import kotlin.script.experimental.api.* +import kotlin.script.experimental.api.ScriptDiagnostic -/** - * Standard implementation of scripts configuration loading and caching - * (we have plans to extract separate implementation for Gradle scripts). - * - * ## Loading initiation - * - * [getConfiguration] will be called when we need to show or analyze some script file. - * - * As described in [AbstractScriptConfigurationManager], configuration may be loaded from [cache] - * or [reloadOutOfDateConfiguration] will be called on [cache] miss. - * - * There are 2 tiers [cache]: memory and FS. For now FS cache implemented by [ScriptConfigurationLoader] - * because we are not storing classpath roots yet. As a workaround cache.all() will return only memory - * cached configurations. So, for now we are indexing roots that loaded from FS with - * default [reloadOutOfDateConfiguration] mechanics. todo(KT-34444): implement fs classpath roots cache - * - * [notifier] will call first applicable [listeners] when editor is activated or document changed. - * Listener may call [updater] to invalidate configuration and schedule reloading. - * - * Also, [ScriptConfigurationUpdater.ensureConfigurationUpToDate] may be called from [UnusedSymbolInspection] - * to ensure that configuration of all scripts containing some symbol are up-to-date or try load it in sync. - * Note: it makes sense only in case of "auto apply" mode and sync loader, in other cases all symbols just - * will be treated as used. - * - * ## Loading - * - * When requested, configuration will be loaded using first applicable [loaders]. - * It can work synchronously or asynchronously. - * - * Synchronous loader will be called just immediately. Despite this, its result may not be applied immediately, - * see next section for details. - * - * Asynchronous loader will be called in background thread (by [BackgroundExecutor]). - * - * ## Applying - * - * By default loaded configuration will *not* be applied immediately. Instead, we show in editor notification - * that suggests user to apply changed configuration. This was done to avoid sporadically starting indexing of new roots, - * which may happens regularly for large Gradle projects. - * - * Notification will be displayed when configuration is going to be updated. First configuration will be loaded - * without notification. - * - * This behavior may be disabled by enabling "auto reload" in project settings. - * When enabled, all loaded configurations will be applied immediately, without any notification. - * - * ## Concurrency - * - * Each files may be in on of this state: - * - scriptDefinition is not ready - * - not loaded - * - up-to-date - * - invalid, in queue (in [BackgroundExecutor] queue) - * - invalid, loading - * - invalid, waiting for apply - * - * [reloadOutOfDateConfiguration] guard this states. See it's docs for more details. - */ -internal class DefaultScriptConfigurationManager(project: Project) : - AbstractScriptConfigurationManager(project) { - - internal val backgroundExecutor: BackgroundExecutor = +class DefaultScriptingSupport(project: Project) : DefaultScriptingSupportBase(project) { + // TODO public for tests + val backgroundExecutor: BackgroundExecutor = if (ApplicationManager.getApplication().isUnitTestMode) TestingBackgroundExecutor(rootsIndexer) else DefaultBackgroundExecutor(project, rootsIndexer) @@ -114,8 +55,6 @@ internal class DefaultScriptConfigurationManager(project: Project) : yield(defaultLoader) } - private val notifier = ScriptChangesNotifier(project, updater) - private val saveLock = ReentrantLock() override fun createCache() = object : ScriptConfigurationMemoryCache(project) { @@ -189,10 +128,8 @@ internal class DefaultScriptConfigurationManager(project: Project) : loaders.firstOrNull { it.loadDependencies(isFirstLoad, file, scriptDefinition, loadingContext) } } else { if (postponeLoading) { - ScriptConfigurationNotificationFactory.NOTIFICATION_FACTORY.getPoint(project).extensionList.firstOrNull { - it.showNotification(virtualFile, project) { - runAsyncLoaders(file, virtualFile, scriptDefinition, async, isLoadingPostponed = true) - } + LoadScriptConfigurationNotificationFactory.showNotification(virtualFile, project) { + runAsyncLoaders(file, virtualFile, scriptDefinition, async, isLoadingPostponed = true) } } else { runAsyncLoaders(file, virtualFile, scriptDefinition, async, isLoadingPostponed = false) @@ -224,11 +161,11 @@ internal class DefaultScriptConfigurationManager(project: Project) : } } - override fun forceReloadConfiguration(file: KtFile, loader: ScriptConfigurationLoader) { - val virtualFile = file.originalFile.virtualFile ?: return + fun forceReloadConfiguration(file: KtFile, loader: ScriptConfigurationLoader): ScriptCompilationConfigurationWrapper? { + val virtualFile = file.originalFile.virtualFile ?: return null - if (!ScriptDefinitionsManager.getInstance(project).isReady()) return - val scriptDefinition = file.findScriptDefinition() ?: return + if (!ScriptDefinitionsManager.getInstance(project).isReady()) return null + val scriptDefinition = file.findScriptDefinition() ?: return null if (!loader.shouldRunInBackground(scriptDefinition)) { loader.loadDependencies(false, file, scriptDefinition, loadingContext) @@ -237,24 +174,13 @@ internal class DefaultScriptConfigurationManager(project: Project) : loader.loadDependencies(false, file, scriptDefinition, loadingContext) } } - } - /** - * Save configurations into cache. - * Start indexing for new class/source roots. - * Re-highlight opened scripts with changed configuration. - */ - override fun saveCompilationConfigurationAfterImport(files: List>) { - rootsIndexer.transaction { - for ((file, result) in files) { - loadingContext.saveNewConfiguration(file, result) - } - } + return getAppliedConfiguration(virtualFile)?.configuration } private val loadingContext = object : ScriptConfigurationLoadingContext { override fun getCachedConfiguration(file: VirtualFile): ScriptConfigurationSnapshot? = - this@DefaultScriptConfigurationManager.getAppliedConfiguration(file) + getAppliedConfiguration(file) override fun suggestNewConfiguration(file: VirtualFile, newResult: ScriptConfigurationSnapshot) { suggestOrSaveConfiguration(file, newResult, false) @@ -275,9 +201,7 @@ internal class DefaultScriptConfigurationManager(project: Project) : setLoadedConfiguration(file, newResult) - ScriptConfigurationNotificationFactory.NOTIFICATION_FACTORY.getPoint(project).extensionList.firstOrNull { - it.hideNotification(file, project) - } + LoadScriptConfigurationNotificationFactory.hideNotification(file, project) val newConfiguration = newResult.configuration if (newConfiguration == null) { @@ -349,10 +273,169 @@ internal class DefaultScriptConfigurationManager(project: Project) : } } +abstract class DefaultScriptingSupportBase(val project: Project) : ScriptingSupport() { + protected val rootsIndexer = ScriptClassRootsIndexer(project) + + @Suppress("LeakingThis") + protected val cache: ScriptConfigurationCache = createCache() + + protected abstract fun createCache(): ScriptConfigurationCache + + /** + * Will be called on [cache] miss or when [file] is changed. + * Implementation should initiate loading of [file]'s script configuration and call [setAppliedConfiguration] + * immediately or in some future + * (e.g. after user will click "apply context" or/and configuration will be calculated by some background thread). + * + * @param isFirstLoad may be set explicitly for optimization reasons (to avoid expensive fs cache access) + * @param loadEvenWillNotBeApplied may should be set to false only on requests from particular editor, when + * user can see potential notification and accept new configuration. In other cases this should be `false` since + * loaded configuration will be just leaved in hidden user notification and cannot be used in any way. + * @param forceSync should be used in tests only + * @param isPostponedLoad is used to postspone loading: show a notification for out of date script and start loading when user request + */ + protected abstract fun reloadOutOfDateConfiguration( + file: KtFile, + isFirstLoad: Boolean = getAppliedConfiguration(file.originalFile.virtualFile) == null, + loadEvenWillNotBeApplied: Boolean = false, + forceSync: Boolean = false, + isPostponedLoad: Boolean = false + ) + + fun getCachedConfigurationState(file: VirtualFile?): ScriptConfigurationState? { + if (file == null) return null + return cache[file] + } + + fun getAppliedConfiguration(file: VirtualFile?): ScriptConfigurationSnapshot? = + getCachedConfigurationState(file)?.applied + + override fun isRelated(file: VirtualFile): Boolean = true + + override fun hasCachedConfiguration(file: KtFile): Boolean = + getAppliedConfiguration(file.originalFile.virtualFile) != null + + override fun getOrLoadConfiguration( + virtualFile: VirtualFile, + preloadedKtFile: KtFile? + ): ScriptCompilationConfigurationWrapper? { + val cached = getAppliedConfiguration(virtualFile) + if (cached != null) return cached.configuration + + val ktFile = project.getKtFile(virtualFile, preloadedKtFile) ?: return null + rootsIndexer.transaction { + reloadOutOfDateConfiguration(ktFile, isFirstLoad = true) + } + + return getAppliedConfiguration(virtualFile)?.configuration + } + + override val updater: ScriptConfigurationUpdater = object : ScriptConfigurationUpdater { + override fun ensureUpToDatedConfigurationSuggested(file: KtFile) { + reloadIfOutOfDate(listOf(file), loadEvenWillNotBeApplied = true, isPostponedLoad = false) + } + + override fun ensureConfigurationUpToDate(files: List): Boolean { + return reloadIfOutOfDate(files, loadEvenWillNotBeApplied = false, isPostponedLoad = false) + } + + override fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile) { + reloadIfOutOfDate(listOf(file), loadEvenWillNotBeApplied = true, isPostponedLoad = true) + } + } + + private fun reloadIfOutOfDate(files: List, loadEvenWillNotBeApplied: Boolean, isPostponedLoad: Boolean): Boolean { + if (!ScriptDefinitionsManager.getInstance(project).isReady()) return false + + var upToDate = true + rootsIndexer.transaction { + files.forEach { file -> + val virtualFile = file.originalFile.virtualFile + if (virtualFile != null) { + val state = cache[virtualFile] + if (state == null || !state.isUpToDate(project, virtualFile, file)) { + upToDate = false + reloadOutOfDateConfiguration( + file, + isFirstLoad = state == null, + loadEvenWillNotBeApplied = loadEvenWillNotBeApplied, + isPostponedLoad = isPostponedLoad + ) + } + } + } + } + + return upToDate + } + + protected open fun setAppliedConfiguration( + file: VirtualFile, + newConfigurationSnapshot: ScriptConfigurationSnapshot? + ) { + rootsIndexer.checkInTransaction() + val newConfiguration = newConfigurationSnapshot?.configuration + debug(file) { "configuration changed = $newConfiguration" } + + if (newConfiguration != null) { + if (hasNotCachedRoots(newConfiguration)) { + debug(file) { "new class roots found: $newConfiguration" } + rootsIndexer.markNewRoot() + } + + cache.setApplied(file, newConfigurationSnapshot) + + clearClassRootsCaches(project) + } + + ScriptingSupportHelper.updateHighlighting(project) { + it == file + } + } + + protected fun setLoadedConfiguration( + file: VirtualFile, + configurationSnapshot: ScriptConfigurationSnapshot + ) { + cache.setLoaded(file, configurationSnapshot) + } + + private fun hasNotCachedRoots(configuration: ScriptCompilationConfigurationWrapper): Boolean { + return classpathRoots.hasNotCachedRoots(DefaultClassRootsCache.extractRoots(project, configuration)) + } + + @TestOnly + internal fun updateScriptDependenciesSynchronously(file: PsiFile) { + file.findScriptDefinition() ?: return + + file as? KtFile ?: error("PsiFile $file should be a KtFile, otherwise script dependencies cannot be loaded") + + val virtualFile = file.virtualFile + if (cache[virtualFile]?.isUpToDate(project, virtualFile, file) == true) return + + rootsIndexer.transaction { + reloadOutOfDateConfiguration(file, forceSync = true, loadEvenWillNotBeApplied = true) + } + } + + override fun clearCaches() { + cache.clear() + } + + override fun recreateRootsCache(): ScriptClassRootsCache { + return DefaultClassRootsCache( + project, + cache.allApplied() + ) + } +} + object DefaultScriptConfigurationManagerExtensions { val LOADER: ExtensionPointName = ExtensionPointName.create("org.jetbrains.kotlin.scripting.idea.loader") } val ScriptConfigurationManager.testingBackgroundExecutor - get() = (this as DefaultScriptConfigurationManager).backgroundExecutor as TestingBackgroundExecutor + get() = (this as CompositeManager).managers + .firstIsInstance() + .backgroundExecutor as TestingBackgroundExecutor \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/ScriptingSupportHelper.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/ScriptingSupportHelper.kt new file mode 100644 index 00000000000..35e1375c9ff --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/ScriptingSupportHelper.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.core.script.configuration + +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiManager +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import org.jetbrains.kotlin.idea.core.util.EDT + +object ScriptingSupportHelper { + fun updateHighlighting(project: Project, filter: (VirtualFile) -> Boolean) { + if (!project.isOpen) return + + val openFiles = FileEditorManager.getInstance(project).openFiles + val openedScripts = openFiles.filter { filter(it) } + + if (openedScripts.isEmpty()) return + + GlobalScope.launch(EDT(project)) { + if (project.isDisposed) return@launch + + openedScripts.forEach { + PsiManager.getInstance(project).findFile(it)?.let { psiFile -> + DaemonCodeAnalyzer.getInstance(project).restart(psiFile) + } + } + } + } +} \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationCache.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationCache.kt index 02dfc17b0a3..9a5877d5e5b 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationCache.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationCache.kt @@ -33,6 +33,8 @@ interface ScriptConfigurationCache { fun allApplied(): Map fun clear() + + fun getAnyLoadedScript(): ScriptCompilationConfigurationWrapper? } sealed class ScriptConfigurationCacheScope { diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationMemoryCache.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationMemoryCache.kt index 34720ae9e15..56ce446feb3 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationMemoryCache.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationMemoryCache.kt @@ -24,6 +24,9 @@ open class ScriptConfigurationMemoryCache( return memoryCache.get(file) } + override fun getAnyLoadedScript(): ScriptCompilationConfigurationWrapper? = + memoryCache.entrySet().firstOrNull()?.value?.loaded?.configuration + @Synchronized override fun setApplied(file: VirtualFile, configurationSnapshot: ScriptConfigurationSnapshot) { val old = memoryCache[file] ?: ScriptConfigurationState() diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptChangeListener.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptChangeListener.kt index bb5fa4d1781..4834c161a50 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptChangeListener.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptChangeListener.kt @@ -9,7 +9,6 @@ import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiManager -import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptConfigurationManager import org.jetbrains.kotlin.idea.util.ProjectRootsUtil import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.psi.KtFile diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/DefaultClassRootsCache.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/DefaultClassRootsCache.kt new file mode 100644 index 00000000000..69cd74b1d45 --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/DefaultClassRootsCache.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.core.script.configuration.utils + +import com.intellij.openapi.project.Project +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.util.containers.ConcurrentFactoryMap +import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager +import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper +import java.io.File + +internal class DefaultClassRootsCache( + project: Project, + private val all: Map +) : ScriptClassRootsCache(project, extractRoots(all, project)) { + + override val fileToConfiguration: (VirtualFile) -> ScriptCompilationConfigurationWrapper? + get() = { all[it] } + + override fun contains(file: VirtualFile): Boolean = file in all + + override val rootsCacheKey = ScriptClassRootsStorage.Companion.Key("default") + + private val scriptsSdksCache: Map = + ConcurrentFactoryMap.createWeakMap { file -> + return@createWeakMap getScriptSdk( + all[file]?.javaHome + ) ?: ScriptConfigurationManager.getScriptDefaultSdk(project) + } + + override fun getScriptSdk(file: VirtualFile): Sdk? = scriptsSdksCache[file] + + override val firstScriptSdk: Sdk? by lazy { + val firstCachedScript = all.keys.firstOrNull() ?: return@lazy null + return@lazy getScriptSdk(firstCachedScript) + } + + companion object { + fun extractRoots( + project: Project, + configuration: ScriptCompilationConfigurationWrapper + ): ScriptClassRootsStorage.Companion.ScriptClassRoots { + val scriptSdk = + getScriptSdkOfDefault( + configuration.javaHome, + project + ) + if (scriptSdk != null && !scriptSdk.isAlreadyIndexed(project)) { + return ScriptClassRootsStorage.Companion.ScriptClassRoots( + toStringValues( + configuration.dependenciesClassPath + ), + toStringValues( + configuration.dependenciesSources + ), + setOf(scriptSdk) + ) + } + + return ScriptClassRootsStorage.Companion.ScriptClassRoots( + toStringValues( + configuration.dependenciesClassPath + ), + toStringValues( + configuration.dependenciesSources + ), + emptySet() + ) + } + + fun extractRoots( + all: Map, + project: Project + ): ScriptClassRootsStorage.Companion.ScriptClassRoots { + val classpath = mutableSetOf() + val sources = mutableSetOf() + val sdks = mutableSetOf() + + for ((_, configuration) in all) { + val scriptSdk = + getScriptSdk(configuration.javaHome) + if (scriptSdk != null && !scriptSdk.isAlreadyIndexed(project)) { + sdks.add(scriptSdk) + } + + classpath.addAll(configuration.dependenciesClassPath) + sources.addAll(configuration.dependenciesSources) + } + + return ScriptClassRootsStorage.Companion.ScriptClassRoots( + toStringValues(classpath), + toStringValues(sources), + sdks + ) + } + } +} \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/ScriptClassRootsCache.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/ScriptClassRootsCache.kt index 88df523b0d2..a97796d5077 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/ScriptClassRootsCache.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/ScriptClassRootsCache.kt @@ -10,53 +10,41 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.roots.ModuleRootManager import com.intellij.openapi.roots.OrderRootType -import com.intellij.openapi.vfs.VfsUtil -import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.* import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.NonClasspathDirectoriesScope import com.intellij.util.containers.ConcurrentFactoryMap -import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager +import org.jetbrains.kotlin.idea.util.getProjectJdkTableSafe import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper import java.io.File +import java.nio.file.FileSystems -internal class ScriptClassRootsCache( +abstract class ScriptClassRootsCache( private val project: Project, - private val all: Map + private val roots: ScriptClassRootsStorage.Companion.ScriptClassRoots ) { - private val scriptsSdksCache: Map = - ConcurrentFactoryMap.createWeakMap { file -> - return@createWeakMap getScriptSdk(all[file]) ?: ScriptConfigurationManager.getScriptDefaultSdk(project) - } + protected abstract val fileToConfiguration: (VirtualFile) -> ScriptCompilationConfigurationWrapper? - fun getScriptSdk(file: VirtualFile): Sdk? = scriptsSdksCache[file] + protected abstract val rootsCacheKey: ScriptClassRootsStorage.Companion.Key - private fun getScriptSdk(compilationConfiguration: ScriptCompilationConfigurationWrapper?): Sdk? { - // workaround for mismatched gradle wrapper and plugin version - val javaHome = try { - compilationConfiguration?.javaHome?.let { VfsUtil.findFileByIoFile(it, true) } - } catch (e: Throwable) { - null - } ?: return null + abstract val firstScriptSdk: Sdk? - return getAllProjectSdks().find { it.homeDirectory == javaHome } - } + abstract fun getScriptSdk(file: VirtualFile): Sdk? - val firstScriptSdk: Sdk? by lazy { - val firstCachedScript = all.keys.firstOrNull() ?: return@lazy null - return@lazy getScriptSdk(firstCachedScript) - } + abstract fun contains(file: VirtualFile): Boolean - private fun Sdk.isAlreadyIndexed(): Boolean { - return ModuleManager.getInstance(project).modules.any { ModuleRootManager.getInstance(it).sdk == this } - } + class Fat( + val scriptConfiguration: ScriptCompilationConfigurationWrapper, + val classFilesScope: GlobalSearchScope + ) val allDependenciesClassFiles by lazy { - ScriptClassRootsStorage.getInstance(project).loadClasspathRoots() + ScriptClassRootsStorage.getInstance(project).loadClasspathRoots(rootsCacheKey) } val allDependenciesSources by lazy { - ScriptClassRootsStorage.getInstance(project).loadSourcesRoots() + ScriptClassRootsStorage.getInstance(project).loadSourcesRoots(rootsCacheKey) } val allDependenciesClassFilesScope by lazy { @@ -67,85 +55,71 @@ internal class ScriptClassRootsCache( NonClasspathDirectoriesScope.compose(allDependenciesSources) } - private val scriptsDependenciesClasspathScopeCache: MutableMap = + private val scriptsDependenciesCache: MutableMap = ConcurrentFactoryMap.createWeakMap { file -> - val compilationConfiguration = all[file] - ?: return@createWeakMap GlobalSearchScope.EMPTY_SCOPE + val configuration = fileToConfiguration(file) ?: return@createWeakMap null - val roots = compilationConfiguration.dependenciesClassPath - val sdk = scriptsSdksCache[file] + val roots = configuration.dependenciesClassPath + val sdk = getScriptSdk(file) @Suppress("FoldInitializerAndIfToElvis") if (sdk == null) { - return@createWeakMap NonClasspathDirectoriesScope.compose( - ScriptConfigurationManager.toVfsRoots( - roots - ) + return@createWeakMap Fat( + configuration, + NonClasspathDirectoriesScope.compose(ScriptConfigurationManager.toVfsRoots(roots)) ) } - return@createWeakMap NonClasspathDirectoriesScope.compose( - sdk.rootProvider.getFiles(OrderRootType.CLASSES).toList() + ScriptConfigurationManager.toVfsRoots( - roots + return@createWeakMap Fat( + configuration, + NonClasspathDirectoriesScope.compose( + sdk.rootProvider.getFiles(OrderRootType.CLASSES).toList() + ScriptConfigurationManager.toVfsRoots(roots) ) ) } fun getScriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope { - return scriptsDependenciesClasspathScopeCache[file] ?: GlobalSearchScope.EMPTY_SCOPE + return scriptsDependenciesCache[file]?.classFilesScope ?: GlobalSearchScope.EMPTY_SCOPE } - fun hasNotCachedRoots(compilationConfiguration: ScriptCompilationConfigurationWrapper): Boolean { - return !ScriptClassRootsStorage.getInstance(project).containsAll(extractRoots(compilationConfiguration)) + fun getScriptConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? { + return scriptsDependenciesCache[file]?.scriptConfiguration } - private fun extractRoots(configuration: ScriptCompilationConfigurationWrapper): ScriptClassRootsStorage.Companion.ScriptClassRoots { - val scriptSdk = getScriptSdk(configuration) ?: ScriptConfigurationManager.getScriptDefaultSdk(project) - if (scriptSdk != null && !scriptSdk.isAlreadyIndexed()) { - return ScriptClassRootsStorage.Companion.ScriptClassRoots( - configuration.dependenciesClassPath, - configuration.dependenciesSources, - listOf(scriptSdk) - ) - } - - return ScriptClassRootsStorage.Companion.ScriptClassRoots( - configuration.dependenciesClassPath, - configuration.dependenciesSources, - emptyList() - ) + fun hasNotCachedRoots(roots: ScriptClassRootsStorage.Companion.ScriptClassRoots): Boolean { + return !ScriptClassRootsStorage.getInstance(project).containsAll(rootsCacheKey, roots) } - init { - saveClassRootsToStorage() - } - - private fun saveClassRootsToStorage() { - if (all.isEmpty()) return - - val classpath = mutableSetOf() - val sources = mutableSetOf() - val sdks = mutableSetOf() - - for ((file, configuration) in all) { - val scriptSdk = getScriptSdk(file) - if (scriptSdk != null && !scriptSdk.isAlreadyIndexed()) { - sdks.add(scriptSdk) - } - - classpath.addAll(configuration.dependenciesClassPath) - sources.addAll(configuration.dependenciesSources) - } - + fun saveClassRootsToStorage() { val rootsStorage = ScriptClassRootsStorage.getInstance(project) - rootsStorage.save( - ScriptClassRootsStorage.Companion.ScriptClassRoots( - classpath.toList(), - sources.toList(), - sdks.toList() - ) - ) + if (roots.classpathFiles.isNotEmpty() || roots.sourcesFiles.isNotEmpty() || roots.sdks.isNotEmpty()) { + rootsStorage.save(rootsCacheKey, roots) + } } - fun contains(file: VirtualFile) = all.containsKey(file) -} \ No newline at end of file + companion object { + fun toStringValues(prop: Collection): Set { + return prop.mapNotNull { it.absolutePath }.toSet() + } + + fun getScriptSdkOfDefault(javaHomeStr: File?, project: Project): Sdk? { + return getScriptSdk(javaHomeStr) ?: ScriptConfigurationManager.getScriptDefaultSdk(project) + } + + fun getScriptSdk(javaHomeStr: File?): Sdk? { + // workaround for mismatched gradle wrapper and plugin version + val javaHome = try { + javaHomeStr?.let { VfsUtil.findFileByIoFile(it, true) } + } catch (e: Throwable) { + null + } ?: return null + + return getProjectJdkTableSafe().allJdks.find { it.homeDirectory == javaHome } + } + + fun Sdk.isAlreadyIndexed(project: Project): Boolean { + return ModuleManager.getInstance(project).modules.any { ModuleRootManager.getInstance(it).sdk == this } + } + } +} + diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/ScriptClassRootsIndexer.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/ScriptClassRootsIndexer.kt index 1c2547fb474..efe5be39fc6 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/ScriptClassRootsIndexer.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/ScriptClassRootsIndexer.kt @@ -20,13 +20,12 @@ import java.util.concurrent.atomic.AtomicInteger /** * Utility for postponing indexing of new roots to the end of some bulk operation. */ -internal class ScriptClassRootsIndexer(val project: Project) { +class ScriptClassRootsIndexer(val project: Project) { private var newRootsPresent: Boolean = false private val concurrentTransactions = AtomicInteger() @Synchronized - fun markNewRoot(file: VirtualFile, configuration: ScriptCompilationConfigurationWrapper) { - debug(file) { "new class roots found: $configuration" } + fun markNewRoot() { checkInTransaction() newRootsPresent = true } diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/ScriptClassRootsStorage.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/ScriptClassRootsStorage.kt index 7a88ffb57b2..4f817171ed4 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/ScriptClassRootsStorage.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/ScriptClassRootsStorage.kt @@ -10,57 +10,84 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.ProjectJdkTable import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.roots.OrderRootType +import com.intellij.openapi.vfs.JarFileSystem import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.openapi.vfs.VirtualFile -import com.intellij.util.io.URLUtil -import com.intellij.util.xmlb.XmlSerializerUtil +import org.jdom.Element import org.jetbrains.kotlin.idea.core.script.debug -import java.io.File @State( name = "ScriptClassRootsStorage", storages = [Storage(StoragePathMacros.CACHE_FILE)] ) -class ScriptClassRootsStorage : PersistentStateComponent { - private var classpath: Set = hashSetOf() - private var sources: Set = hashSetOf() - private var sdks: Set = hashSetOf() +class ScriptClassRootsStorage : PersistentStateComponent { - override fun getState(): ScriptClassRootsStorage? { - return this + private var classpath: MutableMap> = hashMapOf() + private var sources: MutableMap> = hashMapOf() + private var sdks: MutableMap> = hashMapOf() + + override fun getState(): Element { + val root = Element("ScriptClassRootsStorage") + + storeCollection(root, classpath, "classpath") + storeCollection(root, sources, "sources") + storeCollection(root, sdks, "sdk") + + return root } - override fun loadState(state: ScriptClassRootsStorage) { - XmlSerializerUtil.copyBean(state, this) + private fun storeCollection(root: Element, col: Map>, name: String) { + for ((key, paths) in col) { + for (path in paths) { + val element = Element(name) + element.setAttribute("path", path) + element.setAttribute("key", key) + root.addContent(element) + } + } + } + + override fun loadState(state: Element) { + classpath = readCollection(state, "classpath") + sources = readCollection(state, "sources") + sdks = readCollection(state, "sdks") + } + + private fun readCollection(root: Element, name: String): MutableMap> { + val result: MutableMap> = hashMapOf() + for (it in root.getChildren(name)) { + result.getOrPut(it.getAttributeValue("key")) { + hashSetOf() + }.add(it.getAttributeValue("path")) + } + + @Suppress("UNCHECKED_CAST") + return result as MutableMap> } private fun toStringNames(sdks: Collection): Set { return sdks.map { it.name }.toSet() } - private fun toStringValues(prop: Collection): Set { - return prop.mapNotNull { - when { - it.isDirectory -> it.absolutePath - it.isFile -> it.absolutePath + URLUtil.JAR_SEPARATOR - else -> null - } - }.toSet() - } + private fun toVirtualFiles(prop: Set?, sources: Boolean): List { + if (prop == null) return emptyList() - private fun toVirtualFiles(prop: Set, sources: Boolean): List { val rootType = if (sources) OrderRootType.SOURCES else OrderRootType.CLASSES return prop.mapNotNull { ProjectJdkTable.getInstance().findJdk(it) } .flatMap { it.rootProvider.getFiles(rootType).toList() } } - private fun toVirtualFiles(prop: Collection): List { + private fun toVirtualFiles(prop: Collection?): List { + if (prop == null) return emptyList() + return prop.mapNotNull { - StandardFileSystems.local()?.findFileByPath(it)?.let { - return@mapNotNull it + if (it.endsWith(JarFileSystem.PROTOCOL)) { + StandardFileSystems.jar()?.findFileByPath(it + JarFileSystem.JAR_SEPARATOR)?.let { + return@mapNotNull it + } } - StandardFileSystems.jar()?.findFileByPath(it)?.let { + StandardFileSystems.local()?.findFileByPath(it)?.let { return@mapNotNull it } @@ -70,36 +97,40 @@ class ScriptClassRootsStorage : PersistentStateComponent { - return toVirtualFiles(sdks, false) + toVirtualFiles(classpath) + fun loadClasspathRoots(key: Key): List { + return toVirtualFiles(sdks[key.value], false) + toVirtualFiles(classpath[key.value]) } - fun loadSourcesRoots(): List { - return toVirtualFiles(sdks, true) + toVirtualFiles(sources) + fun loadSourcesRoots(key: Key): List { + return toVirtualFiles(sdks[key.value], true) + toVirtualFiles(sources[key.value]) } companion object { @@ -107,9 +138,13 @@ class ScriptClassRootsStorage : PersistentStateComponent, - val sourcesFiles: List, - val sdks: List + val classpathFiles: Set, + val sourcesFiles: Set, + val sdks: Set ) + + val EMPTY = ScriptClassRoots(emptySet(), emptySet(), emptySet()) + + data class Key(val value: String) } } \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleClassRootsCache.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleClassRootsCache.kt new file mode 100644 index 00000000000..4b6ac6491a6 --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleClassRootsCache.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2010-2020 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.scripting.gradle + +import com.intellij.openapi.project.Project +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache +import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsStorage +import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsStorage.Companion.ScriptClassRoots +import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper + +internal class GradleClassRootsCache( + project: Project, + configuration: Configuration?, + override val fileToConfiguration: (VirtualFile) -> ScriptCompilationConfigurationWrapper? +) : ScriptClassRootsCache(project, extractRoots(configuration)) { + + override val rootsCacheKey = ScriptClassRootsStorage.Companion.Key("gradle") + + override fun getScriptSdk(file: VirtualFile): Sdk? { + return firstScriptSdk + } + + override val firstScriptSdk: Sdk? = configuration?.let { getScriptSdk(configuration.context.javaHome) } + + // TODO what should we do if no configuration is loaded yet + override fun contains(file: VirtualFile): Boolean = true + + companion object { + fun extractRoots(configuration: Configuration?): ScriptClassRoots { + if (configuration == null) { + return ScriptClassRootsStorage.EMPTY + } + val scriptSdk = getScriptSdk(configuration.context.javaHome) + if (scriptSdk != null && !scriptSdk.isAlreadyIndexed(configuration.context.project)) { + return ScriptClassRootsStorage.Companion.ScriptClassRoots( + configuration.classFilePath, + configuration.sourcePath, + setOf(scriptSdk) + ) + } + return ScriptClassRoots( + configuration.classFilePath, + configuration.sourcePath, + getScriptSdk(configuration.context.javaHome)?.let { setOf(it) } ?: setOf() + ) + } + } +} \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleImportHelper.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleImportHelper.kt index 3e8057d5280..a3579bccdc5 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleImportHelper.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleImportHelper.kt @@ -34,15 +34,14 @@ fun runPartialGradleImport(project: Project) { ) } -private val kotlinDslNotificationGroupId = "Gradle Kotlin DSL Scripts" +private const val kotlinDslNotificationGroupId = "Gradle Kotlin DSL Scripts" private var Project.notificationPanel: ScriptConfigurationChangedNotification? by UserDataProperty(Key.create("load.script.configuration.panel")) -fun showNotificationForProjectImport(project: Project, callback: () -> Unit) { +fun showNotificationForProjectImport(project: Project) { val existingPanel = project.notificationPanel if (existingPanel != null) { - existingPanel.update(callback) return } @@ -53,7 +52,7 @@ fun showNotificationForProjectImport(project: Project, callback: () -> Unit) { ) } - val notification = ScriptConfigurationChangedNotification(project, callback) + val notification = ScriptConfigurationChangedNotification(project) project.notificationPanel = notification notification.notify(project) } @@ -64,7 +63,7 @@ fun hideNotificationForProjectImport(project: Project): Boolean { return true } -private class ScriptConfigurationChangedNotification(val project: Project, onClick: () -> Unit) : +private class ScriptConfigurationChangedNotification(val project: Project) : Notification( kotlinDslNotificationGroupId, KotlinIcons.LOAD_SCRIPT_CONFIGURATION, @@ -76,9 +75,7 @@ private class ScriptConfigurationChangedNotification(val project: Project, onCli ) { init { - //NotificationGroup.findRegisteredGroup(s) ?: NotificationGroup.balloonGroup(s) - - addAction(LoadConfigurationAction(onClick)) + addAction(LoadConfigurationAction()) addAction(NotificationAction.createSimple(KotlinIdeaGradleBundle.message("action.label.enable.auto.import")) { val gradleSettings = ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID) val projectSettings = gradleSettings.getLinkedProjectsSettings() @@ -87,22 +84,20 @@ private class ScriptConfigurationChangedNotification(val project: Project, onCli if (projectSettings != null) { projectSettings.isUseAutoImport = true } + runPartialGradleImport(project) }) } - fun update(onClick: () -> Unit) { - actions.filterIsInstance().forEach { it.onClick = onClick } - } - override fun expire() { super.expire() project.notificationPanel = null } - private class LoadConfigurationAction(var onClick: () -> Unit) : AnAction(KotlinIdeaGradleBundle.message("action.label.import.project")) { + private class LoadConfigurationAction : AnAction(KotlinIdeaGradleBundle.message("action.label.import.project")) { override fun actionPerformed(e: AnActionEvent) { - onClick() + val project = e.project ?: return + runPartialGradleImport(project) } } } \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleImportHelper.kt.201 b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleImportHelper.kt.201 index a51bdfe7140..97b7984ad2a 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleImportHelper.kt.201 +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleImportHelper.kt.201 @@ -8,7 +8,6 @@ package org.jetbrains.kotlin.idea.scripting.gradle import com.intellij.diff.util.DiffUtil -import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys @@ -46,17 +45,16 @@ fun runPartialGradleImport(project: Project) { ) } -private var Project.notificationPanel: Boolean? - by UserDataProperty(Key.create("load.script.configuration.panel")) +private var Project.shouldShowLoadConfiguraionsAction: Boolean? + by UserDataProperty(Key.create("load.script.configuration.action")) -fun showNotificationForProjectImport(project: Project, callback: () -> Unit) { - project.notificationPanel = true - (ActionManager.getInstance().getAction("LoadConfigurationAction") as LoadConfigurationAction).onClick = callback +fun showNotificationForProjectImport(project: Project) { + project.shouldShowLoadConfiguraionsAction = true } fun hideNotificationForProjectImport(project: Project): Boolean { - project.notificationPanel = false + project.shouldShowLoadConfiguraionsAction = false return true } @@ -65,11 +63,9 @@ class LoadConfigurationAction : AnAction( KotlinIdeaGradleBundle.message("action.description.load.script.configurations"), KotlinIcons.LOAD_SCRIPT_CONFIGURATION ) { - - var onClick = {} - override fun actionPerformed(e: AnActionEvent) { - onClick() + val project = e.project ?: return + runPartialGradleImport(project) } override fun update(e: AnActionEvent) { @@ -79,24 +75,28 @@ class LoadConfigurationAction : AnAction( } private fun ensureValidActionVisibility(e: AnActionEvent) { - if (e.project?.notificationPanel != true) { + if (e.project?.shouldShowLoadConfiguraionsAction != true) { e.presentation.isVisible = false return } val editor = e.getData(CommonDataKeys.EDITOR) ?: return - when { - DiffUtil.isDiffEditor(editor) -> e.presentation.isVisible = false - !editor.isScriptEditor() -> e.presentation.isVisible = false + if (DiffUtil.isDiffEditor(editor)) { + e.presentation.isVisible = false + return } + e.presentation.isVisible = editor.isScriptEditor() } private fun Editor.isScriptEditor(): Boolean { + val project = project ?: return false + val documentManager = FileDocumentManager.getInstance() val virtualFile = documentManager.getFile(document) if (virtualFile is LightVirtualFileBase) return false if (virtualFile == null || !virtualFile.isValid) return false - val project = project ?: return false + + // todo only for gradle script return virtualFile.findScriptDefinition(project) != null } } \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptConfigurationLoader.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptConfigurationLoader.kt index 9ec3fe09562..811e414f71a 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptConfigurationLoader.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptConfigurationLoader.kt @@ -78,12 +78,6 @@ class GradleScriptConfigurationLoader(project: Project) : DefaultScriptConfigura } } - val gradleVersion = getGradleVersion(project) - if (gradleVersion != null && kotlinDslScriptsModelImportSupported(gradleVersion)) { - runPartialGradleImport(project) - return true - } - val result = getConfigurationThroughScriptingApi(ktFile, vFile, scriptDefinition) context.saveNewConfiguration(vFile, result) return true diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptConfigurationManager.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptConfigurationManager.kt new file mode 100644 index 00000000000..d7848c271a7 --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptConfigurationManager.kt @@ -0,0 +1,178 @@ +/* + * 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.scripting.gradle + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.vfs.VfsUtil +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport +import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupportHelper +import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater +import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache +import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsIndexer +import org.jetbrains.kotlin.idea.scripting.gradle.importing.GradleKtsContext +import org.jetbrains.kotlin.idea.scripting.gradle.importing.createGradleKtsContextIfPossible +import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel +import org.jetbrains.kotlin.idea.scripting.gradle.importing.toScriptConfiguration +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition +import org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotatedTemplate +import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance +import java.nio.file.Paths + + +internal data class ConfigurationData( + val templateClasspath: List, + val models: List +) + +internal class Configuration(val context: GradleKtsContext, data: ConfigurationData) { + val scripts = data.models.associateBy { it.file } + val sourcePath = data.models.flatMapTo(mutableSetOf()) { it.sourcePath } + + val classFilePath: MutableSet = mutableSetOf() + + init { + classFilePath.addAll(data.templateClasspath) + data.models.flatMapTo(classFilePath) { it.classPath } + } + + fun scriptModel(file: VirtualFile): KotlinDslScriptModel? { + return scripts[FileUtil.toSystemDependentName(file.path)] + } +} + +class GradleScriptingSupport(val project: Project) : ScriptingSupport() { + @Volatile + private var configuration: Configuration? = null + + private val rootsIndexer = ScriptClassRootsIndexer(project) + + init { + if (isKotlinDslScriptsModelImportSupported(project)) { + ApplicationManager.getApplication().executeOnPooledThread { + val data = KotlinDslScriptModels.read(project) + val gradleKtsContext = createGradleKtsContextIfPossible(project) + if (data != null && gradleKtsContext != null) { + val newConfiguration = Configuration( + gradleKtsContext, + data + ) + + configuration = newConfiguration + configurationChangedCallback(newConfiguration) + } + } + } + } + + override fun recreateRootsCache(): ScriptClassRootsCache { + return GradleClassRootsCache(project, configuration) { + configuration?.let { conf -> + val model = conf.scriptModel(it) + model?.toScriptConfiguration(conf.context) + } + } + } + + fun replace(context: GradleKtsContext, models: List) { + if (models.isEmpty()) return + + val anyScript = VfsUtil.findFile(Paths.get(models.first().file), true)!! + + val definition = anyScript.findScriptDefinition(context.project) ?: return + val templateClasspath = definition.asLegacyOrNull() + ?.templateClasspath?.map { it.path } ?: return + + val data = ConfigurationData(templateClasspath, models) + KotlinDslScriptModels.write(project, data) + + val newConfiguration = Configuration(context, data) + configuration = newConfiguration + + configurationChangedCallback(newConfiguration) + } + + private fun configurationChangedCallback(newConfiguration: Configuration) { + rootsIndexer.transaction { + if (classpathRoots.hasNotCachedRoots(GradleClassRootsCache.extractRoots(newConfiguration))) { + rootsIndexer.markNewRoot() + } + + clearClassRootsCaches(project) + + ScriptingSupportHelper.updateHighlighting(project) { + configuration?.scriptModel(it) != null + } + } + + hideNotificationForProjectImport(project) + } + + fun updateNotification(file: KtFile) { + val vFile = file.originalFile.virtualFile + val scriptModel = configuration?.scriptModel(vFile) ?: return + + if (scriptModel.inputs.isUpToDate(project, vFile)) { + hideNotificationForProjectImport(project) + } else { + showNotificationForProjectImport(project) + } + } + + override fun isRelated(file: VirtualFile): Boolean { + if (isGradleKotlinScript(file)) { + return isKotlinDslScriptsModelImportSupported(project) + } + + return false + } + + private fun isKotlinDslScriptsModelImportSupported(project: Project): Boolean { + val gradleVersion = getGradleVersion(project) + return gradleVersion != null && kotlinDslScriptsModelImportSupported(gradleVersion) + } + + override fun clearCaches() { + // todo should clear up to date + } + + override fun hasCachedConfiguration(file: KtFile): Boolean = + configuration?.scriptModel(file.originalFile.virtualFile) != null + + override fun getOrLoadConfiguration(virtualFile: VirtualFile, preloadedKtFile: KtFile?): ScriptCompilationConfigurationWrapper? { + val configuration = configuration + if (configuration == null) { + // todo: show notification "Import gradle project" + return null + } else { + return classpathRoots.getScriptConfiguration(virtualFile) + } + } + + override val updater: ScriptConfigurationUpdater + get() = object : ScriptConfigurationUpdater { + override fun ensureUpToDatedConfigurationSuggested(file: KtFile) { + // do nothing for gradle scripts + } + + // unused symbol inspection should not initiate loading + override fun ensureConfigurationUpToDate(files: List): Boolean = true + + override fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile) { + updateNotification(file) + } + } + + companion object { + fun getInstance(project: Project): GradleScriptingSupport { + return SCRIPTING_SUPPORT.getPoint(project).extensionList.firstIsInstance() + } + } +} \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptConfigurationNotificationFactory.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptConfigurationNotificationFactory.kt deleted file mode 100644 index 12341d68a42..00000000000 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptConfigurationNotificationFactory.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2010-2020 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.scripting.gradle - -import com.intellij.openapi.project.Project -import com.intellij.openapi.vfs.VirtualFile -import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationNotificationFactory - -class GradleScriptConfigurationNotificationFactory : ScriptConfigurationNotificationFactory { - override fun showNotification(file: VirtualFile, project: Project, onClick: () -> Unit): Boolean { - if (isGradleKotlinScript(file) && isGradleImportCanBeUsed(project)) { - showNotificationForProjectImport(project, onClick) - return true - } - return false - } - - override fun hideNotification(file: VirtualFile, project: Project): Boolean { - return hideNotificationForProjectImport(project) - } - - private fun isGradleImportCanBeUsed(project: Project): Boolean { - val gradleVersion = getGradleVersion(project) - if (gradleVersion != null && kotlinDslScriptsModelImportSupported(gradleVersion)) { - return true - } - return false - } -} \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScriptModel.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScriptModel.kt index e6eb72ca21a..e3e84613228 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScriptModel.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScriptModel.kt @@ -5,9 +5,11 @@ package org.jetbrains.kotlin.idea.scripting.gradle.importing +import org.jetbrains.kotlin.idea.scripting.gradle.GradleKotlinScriptConfigurationInputs + data class KotlinDslScriptModel( val file: String, - val inputsTimeStamp: Long, + val inputs: GradleKotlinScriptConfigurationInputs, val classPath: List, val sourcePath: List, val imports: List, diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScrtiptModelProcessor.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScrtiptModelProcessor.kt index bffdedcd50c..127f5cc6197 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScrtiptModelProcessor.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScrtiptModelProcessor.kt @@ -7,16 +7,14 @@ package org.jetbrains.kotlin.idea.scripting.gradle.importing import com.intellij.openapi.components.service import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VfsUtil -import com.intellij.openapi.vfs.VirtualFile import org.gradle.tooling.model.kotlin.dsl.EditorReportSeverity import org.gradle.tooling.model.kotlin.dsl.KotlinDslScriptsModel import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle -import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager -import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigurationInputs -import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationSnapshot import org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptInputsWatcher +import org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptingSupport import org.jetbrains.kotlin.idea.scripting.gradle.getGradleScriptInputsStamp import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper @@ -24,6 +22,8 @@ import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource import org.jetbrains.kotlin.scripting.resolve.adjustByDefinition import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext import org.jetbrains.plugins.gradle.settings.GradleExecutionSettings +import org.jetbrains.plugins.gradle.settings.GradleProjectSettings +import org.jetbrains.plugins.gradle.util.GradleConstants import java.io.File import kotlin.script.experimental.api.* import kotlin.script.experimental.jvm.JvmDependency @@ -40,8 +40,9 @@ fun processScriptModel( "Couldn't get KotlinDslScriptsModel for $projectName:\n${model.message}\n${model.stackTrace}" ) } else { - val models = model.toListOfScriptModels() - resolverCtx.externalSystemTaskId.findProject()?.kotlinDslModels?.addAll( + val project = resolverCtx.externalSystemTaskId.findProject() ?: return + val models = model.toListOfScriptModels(project) + project.kotlinDslModels.addAll( models ) if (models.containsErrors()) { @@ -58,7 +59,7 @@ private fun Collection.containsErrors(): Boolean { return any { it.messages.any { it.severity == KotlinDslScriptModel.Severity.ERROR } } } -private fun KotlinDslScriptsModel.toListOfScriptModels(): List = +private fun KotlinDslScriptsModel.toListOfScriptModels(project: Project): List = scriptModels.map { (file, model) -> val messages = mutableListOf() @@ -91,10 +92,14 @@ private fun KotlinDslScriptsModel.toListOfScriptModels(): List().firstOrNull() ?: return null + + val gradleExeSettings = ExternalSystemApiUtil.getExecutionSettings( + project, + gradleProjectSettings.externalProjectPath, + GradleConstants.SYSTEM_ID + ) + + val javaHome = gradleExeSettings.javaHome?.let { File(it)} + + return GradleKtsContext(project, javaHome) +} + +class GradleKtsContext(val project: Project, val javaHome: File?) + +fun KotlinDslScriptModel.toScriptConfiguration(context: GradleKtsContext): ScriptCompilationConfigurationWrapper? { + val scriptFile = File(file) + val virtualFile = VfsUtil.findFile(scriptFile.toPath(), true)!! + + val definition = virtualFile.findScriptDefinition(context.project) ?: return null + + return ScriptCompilationConfigurationWrapper.FromCompilationConfiguration( + VirtualFileScriptSource(virtualFile), + definition.compilationConfiguration.with { + if (context.javaHome != null) { + jvm.jdkHome(context.javaHome) + } + defaultImports(imports) + dependencies(JvmDependency(classPath.map { File(it) })) + ide.dependenciesSources(JvmDependency(sourcePath.map { File(it) })) + }.adjustByDefinition(definition) + ) +} + fun saveScriptModels( project: Project, task: ExternalSystemTaskId, javaHomeStr: String?, models: List ) { - val scriptConfigurations = mutableListOf>() - val errorReporter = KotlinGradleDslErrorReporter(project, task) val javaHome = javaHomeStr?.let { File(it) } + val context = GradleKtsContext(project, javaHome) + models.forEach { model -> - val scriptFile = File(model.file) - val virtualFile = VfsUtil.findFile(scriptFile.toPath(), true)!! - - val inputs = getGradleScriptInputsStamp( - project, - virtualFile, - givenTimeStamp = model.inputsTimeStamp - ) - - val definition = virtualFile.findScriptDefinition(project) ?: return@forEach - - val configuration = - definition.compilationConfiguration.with { - if (javaHome != null) { - jvm.jdkHome(javaHome) - } - defaultImports(model.imports) - dependencies(JvmDependency(model.classPath.map { - File( - it - ) - })) - ide.dependenciesSources(JvmDependency(model.sourcePath.map { - File( - it - ) - })) - }.adjustByDefinition(definition) - - scriptConfigurations.add( - Pair( - virtualFile, - ScriptConfigurationSnapshot( - inputs - ?: CachedConfigurationInputs.OutOfDate, - listOf(), - ScriptCompilationConfigurationWrapper.FromCompilationConfiguration( - VirtualFileScriptSource(virtualFile), - configuration, - ), - ), - ), - ) - - errorReporter.reportError(scriptFile, model) + errorReporter.reportError(File(model.file), model) } project.service().saveGradleProjectRootsAfterImport( - scriptConfigurations.map { it.first.parent.path }.toSet() + models.map { File(it.file).parent }.toSet() ) - project.service().saveCompilationConfigurationAfterImport(scriptConfigurations) + GradleScriptingSupport.getInstance(project).replace(context, models) project.service().clearState() } \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/serizliation.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/serizliation.kt new file mode 100644 index 00000000000..4b24838d229 --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/serizliation.kt @@ -0,0 +1,143 @@ +/* + * Copyright 2010-2020 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.scripting.gradle + +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.newvfs.FileAttribute +import org.jetbrains.kotlin.idea.core.util.readString +import org.jetbrains.kotlin.idea.core.util.writeString +import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel +import java.io.DataInputStream +import java.io.DataOutputStream + +internal object KotlinDslScriptModels { + private val attribute = FileAttribute("kotlin-dsl-script-models", 2, false) + + fun read(project: Project): ConfigurationData? { + return attribute.readAttribute(project.projectFile ?: return null)?.use { + readKotlinDslScriptModels(it) + } + } + + fun write(project: Project, configuration: ConfigurationData) { + attribute.writeAttribute(project.projectFile ?: return).use { + writeKotlinDslScriptModels(it, configuration) + } + } +} + +internal fun writeKotlinDslScriptModels(output: DataOutputStream, data: ConfigurationData) { + val strings = StringsPool.writer(output) + strings.addStrings(data.templateClasspath) + val scriptModels = data.models + scriptModels.forEach { + strings.addString(it.file) + strings.addStrings(it.classPath) + strings.addStrings(it.sourcePath) + strings.addStrings(it.imports) + } + strings.writeHeader() + strings.writeStringIds(data.templateClasspath) + output.writeList(scriptModels) { + strings.writeStringId(it.file) + output.writeString(it.inputs.sections) + output.writeLong(it.inputs.inputsTS) + strings.writeStringIds(it.classPath) + strings.writeStringIds(it.sourcePath) + strings.writeStringIds(it.imports) + } +} + +internal fun readKotlinDslScriptModels(input: DataInputStream): ConfigurationData { + val strings = StringsPool.reader(input) + + val templateClasspath = strings.readStrings() + + val models = input.readList { + KotlinDslScriptModel( + strings.readString(), + GradleKotlinScriptConfigurationInputs(input.readString(), input.readLong()), + strings.readStrings(), + strings.readStrings(), + strings.readStrings(), + listOf() + ) + } + + return ConfigurationData(templateClasspath, models) +} + +private object StringsPool { + fun writer(output: DataOutputStream) = Writer(output) + + class Writer(val output: DataOutputStream) { + var freeze = false + val ids = mutableMapOf() + + fun getStringId(string: String) = ids.getOrPut(string) { + check(!freeze) + ids.size + } + + fun addString(string: String) { + getStringId(string) + } + + fun addStrings(list: Collection) { + list.forEach { addString(it) } + } + + fun writeHeader() { + freeze = true + + output.writeInt(ids.size) + + // sort for optimal performance and compression + ids.keys.sorted().forEachIndexed { index, s -> + ids[s] = index + output.writeString(s) + } + } + + fun writeStringId(it: String) { + output.writeInt(getStringId(it)) + } + + fun writeStringIds(strings: List) { + output.writeInt(strings.size) + strings.forEach { + writeStringId(it) + } + } + } + + fun reader(input: DataInputStream): Reader { + val strings = input.readList { input.readString() } + return Reader(input, strings) + } + + class Reader(val input: DataInputStream, val strings: List) { + fun getString(id: Int) = strings[id] + + fun readString() = getString(input.readInt()) + + fun readStrings(): List = input.readList { readString() } + } +} + +private inline fun DataOutputStream.writeList(list: Collection, write: (T) -> Unit) { + writeInt(list.size) + list.forEach { write(it) } +} + +private inline fun DataInputStream.readList(read: () -> T): List { + val n = readInt() + val result = ArrayList(n) + repeat(n) { + result.add(read()) + } + return result +} \ No newline at end of file diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/KotlinDslScriptModelsTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/KotlinDslScriptModelsTest.kt new file mode 100644 index 00000000000..89c75a2a001 --- /dev/null +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/KotlinDslScriptModelsTest.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2010-2020 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.scripting.gradle + +import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel +import org.junit.Test +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.DataInputStream +import java.io.DataOutputStream +import kotlin.test.assertEquals + +class KotlinDslScriptModelsTest { + @Test + fun write() { + val buffer = ByteArrayOutputStream() + + val conf = ConfigurationData( + listOf("a", "b", "c"), + listOf( + KotlinDslScriptModel( + "a", + GradleKotlinScriptConfigurationInputs("b", 1), + listOf("c", "a", "b"), + listOf("b", "c", "a"), + listOf("i", "c", "b"), + listOf() + ), + KotlinDslScriptModel( + "a", + GradleKotlinScriptConfigurationInputs("b", 1), + listOf("c", "a", "b"), + listOf("b", "c", "a"), + listOf("i", "c", "b"), + listOf() + ) + ) + ) + + writeKotlinDslScriptModels(DataOutputStream(buffer), conf) + + val restored = readKotlinDslScriptModels(DataInputStream(ByteArrayInputStream(buffer.toByteArray()))) + + assertEquals(conf.toString(), restored.toString()) + } +} \ No newline at end of file diff --git a/idea/resources/META-INF/extensions/ide.xml b/idea/resources/META-INF/extensions/ide.xml index a1998d0a649..0a023d83e2e 100644 --- a/idea/resources/META-INF/extensions/ide.xml +++ b/idea/resources/META-INF/extensions/ide.xml @@ -68,10 +68,6 @@ interface="org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener" area="IDEA_PROJECT"/> - - @@ -105,11 +101,6 @@ - - diff --git a/idea/resources/META-INF/gradle.xml b/idea/resources/META-INF/gradle.xml index 7151437ef44..cd8fb13c113 100644 --- a/idea/resources/META-INF/gradle.xml +++ b/idea/resources/META-INF/gradle.xml @@ -15,8 +15,8 @@ - diff --git a/idea/resources/META-INF/gradle.xml.201 b/idea/resources/META-INF/gradle.xml.201 index 7be649b16cc..ba6cabe4ae8 100644 --- a/idea/resources/META-INF/gradle.xml.201 +++ b/idea/resources/META-INF/gradle.xml.201 @@ -15,8 +15,8 @@ - diff --git a/idea/resources/META-INF/plugin-common.xml b/idea/resources/META-INF/plugin-common.xml index 4cc19631437..e3899aec1a0 100644 --- a/idea/resources/META-INF/plugin-common.xml +++ b/idea/resources/META-INF/plugin-common.xml @@ -231,7 +231,7 @@ serviceImplementation="org.jetbrains.kotlin.idea.core.script.IdeScriptDependenciesProvider"/> + serviceImplementation="org.jetbrains.kotlin.idea.core.script.configuration.CompositeManager"/> diff --git a/idea/resources/META-INF/scripting-support.xml b/idea/resources/META-INF/scripting-support.xml index 1be40949391..764f2c73725 100644 --- a/idea/resources/META-INF/scripting-support.xml +++ b/idea/resources/META-INF/scripting-support.xml @@ -1,7 +1,20 @@ + + + + + + + + \ No newline at end of file