From 14edc26cf18eedb54bc58cbd5d6e57a1b8e11d01 Mon Sep 17 00:00:00 2001 From: Sergey Rostov Date: Fri, 8 May 2020 10:12:18 +0300 Subject: [PATCH] Revert due to test failing in 201 --- .../org/jetbrains/kotlin/utils/addToStdlib.kt | 2 +- .../core/script/ScriptConfigurationManager.kt | 29 +- .../core/script/ScriptDefinitionsManager.kt | 9 +- .../CompositeScriptConfigurationManager.kt | 166 ++++---- .../configuration/DefaultScriptingSupport.kt | 285 +++++-------- .../script/configuration/ScriptingSupport.kt | 91 ++-- .../configuration/ScriptingSupportHelper.kt | 36 ++ .../cache/ScriptConfigurationCache.kt | 6 +- .../cache/ScriptConfigurationMemoryCache.kt | 6 +- .../listener/DefaultScriptChangeListener.kt | 8 +- .../listener/ScriptChangeListener.kt | 15 +- .../listener/ScriptChangesNotifier.kt | 20 +- .../listener/ScriptConfigurationUpdater.kt | 31 ++ .../utils/DefaultBackgroundExecutor.kt | 14 +- .../utils/DefaultClassRootsCache.kt | 86 ++++ .../utils/ScriptClassRootsCache.kt | 273 ++++-------- .../utils/ScriptClassRootsIndexer.kt | 80 ++++ .../utils/ScriptClassRootsStorage.kt | 144 ++++++- .../utils/ScriptClassRootsUpdater.kt | 194 --------- .../utils/TestingBackgroundExecutor.kt | 4 +- .../gradle/AsyncFileChangeListenerHelper.kt | 8 +- .../AsyncFileChangeListenerHelper.kt.191 | 8 +- .../AsyncFileChangeListenerHelper.kt.201 | 8 +- .../idea/scripting/gradle/Configuration.kt | 37 ++ .../scripting/gradle/GradleClassRootsCache.kt | 88 ++++ .../scripting/gradle/GradleImportHelper.kt | 1 + .../gradle/GradleImportHelper.kt.201 | 12 +- .../GradleKotlinScriptConfigurationInputs.kt | 24 +- .../idea/scripting/gradle/GradleKtsContext.kt | 10 + ....kt => GradleScriptConfigurationLoader.kt} | 26 +- .../gradle/GradleScriptDefinitionsProvider.kt | 20 +- .../gradle/GradleScriptInputsWatcher.kt | 91 ++++ .../scripting/gradle/GradleScriptListener.kt | 58 +-- .../gradle/GradleScriptingSupport.kt | 62 +++ .../gradle/GradleScriptingSupportProvider.kt | 208 +++++++++ .../scripting/gradle/LastModifiedFiles.kt | 53 +-- ...ScriptConfigurationNotificationProvider.kt | 24 +- .../kotlin/idea/scripting/gradle/README.md | 75 ---- .../KotlinDslScrtiptModelProcessor.kt | 26 +- .../gradle/importing/KotlinDslSyncListener.kt | 25 -- .../legacy/GradleLegacyScriptListener.kt | 37 -- .../scripting/gradle/roots/GradleBuildRoot.kt | 153 ------- .../gradle/roots/GradleBuildRootData.kt | 14 - .../gradle/roots/GradleBuildRootIndex.kt | 60 --- .../gradle/roots/GradleBuildRootsManager.kt | 393 ------------------ .../gradle/roots/GradleScriptInfo.kt | 66 --- ...dRootDataSerializer.kt => serizliation.kt} | 30 +- .../kotlin/idea/scripting/gradle/utils.kt | 73 +++- .../codeInsight/gradle/GradleKtsImportTest.kt | 7 +- ...st.kt => GradleScriptInputsWatcherTest.kt} | 69 +-- .../gradle/GradleScriptOutOfProjectTest.kt | 18 +- ...erTest.kt => KotlinDslScriptModelsTest.kt} | 14 +- .../scripting/gradle/LastModifiedFilesTest.kt | 77 ---- .../idea/scripting/gradle/RootsIndexTest.kt | 33 ++ idea/resources/META-INF/gradle.xml | 8 +- idea/resources/META-INF/gradle.xml.201 | 9 +- idea/resources/META-INF/scripting-support.xml | 4 +- .../inspections/UnusedSymbolInspection.kt | 6 +- .../AbstractScriptConfigurationLoadingTest.kt | 7 +- .../script/ScriptConfigurationLoadingTest.kt | 8 +- .../resolve/refineCompilationConfiguration.kt | 1 - 61 files changed, 1473 insertions(+), 1977 deletions(-) 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/listener/ScriptConfigurationUpdater.kt create mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/DefaultClassRootsCache.kt create mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/ScriptClassRootsIndexer.kt delete mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/ScriptClassRootsUpdater.kt create mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/Configuration.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/GradleKtsContext.kt rename idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/{legacy/GradleLegacyScriptConfigurationLoader.kt => GradleScriptConfigurationLoader.kt} (73%) create mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcher.kt create mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptingSupport.kt create mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptingSupportProvider.kt delete mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/README.md delete mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/legacy/GradleLegacyScriptListener.kt delete mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRoot.kt delete mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootData.kt delete mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootIndex.kt delete mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootsManager.kt delete mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleScriptInfo.kt rename idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/{roots/GradleBuildRootDataSerializer.kt => serizliation.kt} (80%) rename idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/{GradleScriptListenerTest.kt => GradleScriptInputsWatcherTest.kt} (78%) rename idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/{GradleBuildRootDataSerializerTest.kt => KotlinDslScriptModelsTest.kt} (78%) create mode 100644 idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/RootsIndexTest.kt diff --git a/core/util.runtime/src/org/jetbrains/kotlin/utils/addToStdlib.kt b/core/util.runtime/src/org/jetbrains/kotlin/utils/addToStdlib.kt index 6f5c160e83b..9fc250e2696 100644 --- a/core/util.runtime/src/org/jetbrains/kotlin/utils/addToStdlib.kt +++ b/core/util.runtime/src/org/jetbrains/kotlin/utils/addToStdlib.kt @@ -95,7 +95,7 @@ private val constantMap = ConcurrentHashMap, Any>() fun String.indexOfOrNull(char: Char, startIndex: Int = 0, ignoreCase: Boolean = false): Int? = indexOf(char, startIndex, ignoreCase).takeIf { it >= 0 } -fun String.lastIndexOfOrNull(char: Char, startIndex: Int = lastIndex, ignoreCase: Boolean = false): Int? = +fun String.lastIndexOfOrNull(char: Char, startIndex: Int = 0, ignoreCase: Boolean = false): Int? = lastIndexOf(char, startIndex, ignoreCase).takeIf { it >= 0 } inline fun Iterable.firstNotNullResult(transform: (T) -> R?): R? { 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 038ea740c6f..cc43997c72d 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 @@ -27,6 +27,8 @@ import com.intellij.psi.search.GlobalSearchScope import com.intellij.util.io.URLUtil import org.jetbrains.annotations.TestOnly import org.jetbrains.kotlin.idea.core.script.configuration.CompositeScriptConfigurationManager +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 import org.jetbrains.kotlin.psi.UserDataProperty import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider @@ -67,6 +69,12 @@ interface ScriptConfigurationManager { */ fun getConfiguration(file: KtFile): ScriptCompilationConfigurationWrapper? + /** + * Reload the configuration for [file] even it is already loaded. + * [loader] is used to load configuration. Other loaders aren't taken into account. + */ + fun forceReloadConfiguration(file: VirtualFile, loader: ScriptConfigurationLoader): ScriptCompilationConfigurationWrapper? + @Deprecated("Use getScriptClasspath(KtFile) instead") fun getScriptClasspath(file: VirtualFile): List @@ -90,9 +98,14 @@ interface ScriptConfigurationManager { fun isConfigurationLoadingInProgress(file: KtFile): Boolean /** - * Update caches that depends on script definitions and do update if necessary + * See [ScriptConfigurationUpdater]. */ - fun updateScriptDefinitionReferences() + val updater: ScriptConfigurationUpdater + + /** + * Clear all caches and re-highlighting opened scripts + */ + fun clearConfigurationCachesAndRehighlight() /////////////// // classpath roots info: @@ -113,14 +126,14 @@ interface ScriptConfigurationManager { ServiceManager.getService(project, ScriptConfigurationManager::class.java) fun toVfsRoots(roots: Iterable): List { - return roots.mapNotNull { classpathEntryToVfs(it) } + return roots.mapNotNull { it.classpathEntryToVfs() } } - fun classpathEntryToVfs(file: File): VirtualFile? { + private fun File.classpathEntryToVfs(): VirtualFile? { val res = when { - !file.exists() -> null - file.isDirectory -> StandardFileSystems.local()?.findFileByPath(file.canonicalPath) - file.isFile -> StandardFileSystems.jar()?.findFileByPath(file.canonicalPath + URLUtil.JAR_SEPARATOR) + !exists() -> null + isDirectory -> StandardFileSystems.local()?.findFileByPath(this.canonicalPath) + isFile -> StandardFileSystems.jar()?.findFileByPath(this.canonicalPath + URLUtil.JAR_SEPARATOR) else -> null } // TODO: report this somewhere, but do not throw: assert(res != null, { "Invalid classpath entry '$this': exists: ${exists()}, is directory: $isDirectory, is file: $isFile" }) @@ -137,7 +150,7 @@ interface ScriptConfigurationManager { @TestOnly fun clearCaches(project: Project) { (getInstance(project) as CompositeScriptConfigurationManager).default - .updateScriptDefinitions() + .clearCaches() } fun clearManualConfigurationLoadingIfNeeded(file: VirtualFile) { diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDefinitionsManager.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDefinitionsManager.kt index d72331903db..4bc1937f85a 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDefinitionsManager.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDefinitionsManager.kt @@ -27,7 +27,6 @@ import com.intellij.util.containers.SLRUMap import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.caches.project.SdkInfo import org.jetbrains.kotlin.idea.caches.project.getScriptRelatedModuleInfo -import org.jetbrains.kotlin.idea.core.script.configuration.CompositeScriptConfigurationManager import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings import org.jetbrains.kotlin.idea.util.application.getServiceSafe import org.jetbrains.kotlin.idea.util.getProjectJdkTableSafe @@ -65,15 +64,9 @@ class ScriptDefinitionsManager(private val project: Project) : LazyScriptDefinit private val scriptDefinitionsCacheLock = ReentrantLock() private val scriptDefinitionsCache = SLRUMap(10, 10) - val configurations = (ScriptConfigurationManager.getInstance(project) as CompositeScriptConfigurationManager) - override fun findDefinition(script: SourceCode): ScriptDefinition? { val locationId = script.locationId ?: return null if (nonScriptId(locationId)) return null - - val fastPath = configurations.tryGetScriptDefinitionFast(locationId) - if (fastPath != null) return fastPath - if (!isReady()) return null val cached = scriptDefinitionsCacheLock.withLock { scriptDefinitionsCache.get(locationId) } @@ -197,7 +190,7 @@ class ScriptDefinitionsManager(private val project: Project) : LazyScriptDefinit scriptDefinitionsCacheLock.withLock { scriptDefinitionsCache.clear() } // TODO: clear by script type/definition - ScriptConfigurationManager.getInstance(project).updateScriptDefinitionReferences() + ScriptConfigurationManager.getInstance(project).clearConfigurationCachesAndRehighlight() } private fun ScriptDefinitionsSource.safeGetDefinitions(): List { diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/CompositeScriptConfigurationManager.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/CompositeScriptConfigurationManager.kt index 607bbe7586f..ea27044d957 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/CompositeScriptConfigurationManager.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/CompositeScriptConfigurationManager.kt @@ -6,9 +6,6 @@ package org.jetbrains.kotlin.idea.core.script.configuration import com.intellij.ProjectTopics -import com.intellij.codeInsight.daemon.OutsidersPsiFileSupport -import com.intellij.openapi.diagnostic.logger -import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.roots.ModuleRootEvent @@ -18,106 +15,69 @@ import com.intellij.psi.search.GlobalSearchScope 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.ScriptClassRootsUpdater import org.jetbrains.kotlin.idea.core.script.configuration.utils.getKtFile import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition +import org.jetbrains.kotlin.scripting.definitions.isNonScript import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper +import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult -/** - * The [CompositeScriptConfigurationManager] will provide redirection of [ScriptConfigurationManager] calls to the - * custom [ScriptingSupport] or [DefaultScriptingSupport] if that script lack of custom [ScriptingSupport]. - * - * The [ScriptConfigurationManager] is implemented by caching all scripts using the [ScriptClassRootsCache]. - * The [ScriptClassRootsCache] is always available and never blacked by the writer, as all writes occurs - * using the copy-on-write strategy. - * - * This cache are loaded on start and will be updating asynchronously using the [updater]. - * Sync updates still my be occurred from the [DefaultScriptingSupport]. - * - * We are also watching all script documents: - * [notifier] will call first applicable [ScriptChangesNotifier.listeners] when editor is activated or document changed. - * Listener should do something to invalidate configuration and schedule reloading. - */ class CompositeScriptConfigurationManager(val project: Project) : ScriptConfigurationManager { @Suppress("unused") - private val notifier = ScriptChangesNotifier(project) + private val notifier = ScriptChangesNotifier(project, updater) - val updater = ScriptClassRootsUpdater(project, this) + private val providers = ScriptingSupport.Provider.EPN.getPoint(project).extensionList - private val plugins = ScriptingSupport.EPN.getPoint(project).extensionList + val default = DefaultScriptingSupport(project) - val default = DefaultScriptingSupport(this) + private val managers + get() = mutableListOf().also { managers -> + managers.add(default) + providers.forEach { managers.addAll(it.all) } + } - fun tryGetScriptDefinitionFast(locationId: String): ScriptDefinition? { - return classpathRoots.getLightScriptInfo(locationId)?.definition - } + private fun getRelatedManager(file: VirtualFile): ScriptingSupport = + providers.firstNotNullResult { it.getSupport(file) } ?: default + + private fun getRelatedManager(file: KtFile): ScriptingSupport = + getRelatedManager(file.originalFile.virtualFile) private fun getOrLoadConfiguration( virtualFile: VirtualFile, preloadedKtFile: KtFile? = null - ): ScriptCompilationConfigurationWrapper? { - val scriptConfiguration = classpathRoots.getScriptConfiguration(virtualFile) - if (scriptConfiguration != null) return scriptConfiguration + ): ScriptCompilationConfigurationWrapper? = + getRelatedManager(virtualFile).getOrLoadConfiguration(virtualFile, preloadedKtFile) - // check that this script should be loaded later in special way (e.g. gradle project import) - // (and not for syntactic diff files) - if (!OutsidersPsiFileSupport.isOutsiderFile(virtualFile)) { - if (plugins.any { it.isApplicable(virtualFile) }) return null - } - - return default.getOrLoadConfiguration(virtualFile, preloadedKtFile) - } - - override fun getConfiguration(file: KtFile) = - getOrLoadConfiguration(file.originalFile.virtualFile, file) + override fun getConfiguration(file: KtFile) = getOrLoadConfiguration(file.originalFile.virtualFile, file) override fun hasConfiguration(file: KtFile): Boolean = - classpathRoots.contains(file.originalFile.virtualFile) + getRelatedManager(file).hasCachedConfiguration(file) override fun isConfigurationLoadingInProgress(file: KtFile): Boolean = - plugins.firstOrNull { it.isApplicable(file.originalFile.virtualFile) }?.isConfigurationLoadingInProgress(file) - ?: default.isConfigurationLoadingInProgress(file) + getRelatedManager(file).isConfigurationLoadingInProgress(file) - @Volatile - private var classpathRoots: ScriptClassRootsCache = recreateRootsCache() + override val updater: ScriptConfigurationUpdater + get() = object : ScriptConfigurationUpdater { + override fun ensureUpToDatedConfigurationSuggested(file: KtFile) = + getRelatedManager(file).updater.ensureUpToDatedConfigurationSuggested(file) - fun getLightScriptInfo(file: String): ScriptClassRootsCache.LightScriptInfo? = - classpathRoots.getLightScriptInfo(file) + override fun ensureConfigurationUpToDate(files: List): Boolean = + files.groupBy { getRelatedManager(it) }.all { (manager, files) -> + manager.updater.ensureConfigurationUpToDate(files) + } - private fun recreateRootsCache(): ScriptClassRootsCache { - val builder = ScriptClassRootsCache.Builder(project) - default.collectConfigurations(builder) - plugins.forEach { it.collectConfigurations(builder) } - return builder.build() - } - - fun collectRootsAndCheckNew(): ScriptClassRootsCache.Updates { - val old = classpathRoots - val new = recreateRootsCache() - classpathRoots = new - return new.diff(old) - } - - override fun updateScriptDefinitionReferences() { - ScriptDependenciesModificationTracker.getInstance(project).incModificationCount() - - default.updateScriptDefinitions() - - if (classpathRoots.customDefinitionsUsed) { - updater.ensureUpdateScheduled() + 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) { - if (event.isCausedByFileTypesChange) return - - if (classpathRoots.hasInvalidSdk(project)) { - updater.ensureUpdateScheduled() + managers.forEach { + it.clearClassRootsCaches(project) } } }) @@ -128,39 +88,49 @@ class CompositeScriptConfigurationManager(val project: Project) : ScriptConfigur * Loads script configuration if classpath roots don't contain [file] yet */ private fun getActualClasspathRoots(file: VirtualFile): ScriptClassRootsCache { - try { - // we should run default loader if this [file] is not cached in [classpathRoots] - // and it is not supported by any of [plugins] - // getOrLoadConfiguration will do this - // (despite that it's result becomes unused, it still may populate [classpathRoots]) - getOrLoadConfiguration(file, null) - } catch (cancelled: ProcessCanceledException) { - // read actions may be cancelled if we are called by impatient reader + val manager = getRelatedManager(file) + + val classpathRoots = manager.classpathRoots + if (classpathRoots.contains(file)) { + return classpathRoots } - return this.classpathRoots + getOrLoadConfiguration(file) + + return manager.classpathRoots } 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 = - classpathRoots.getScriptDependenciesClassFilesScope(file) + getActualClasspathRoots(file).getScriptDependenciesClassFilesScope(file) override fun getAllScriptsDependenciesClassFilesScope(): GlobalSearchScope = - classpathRoots.allDependenciesClassFilesScope + GlobalSearchScope.union(managers.map { it.classpathRoots.allDependenciesClassFilesScope }) override fun getAllScriptDependenciesSourcesScope(): GlobalSearchScope = - classpathRoots.allDependenciesSourcesScope + GlobalSearchScope.union(managers.map { it.classpathRoots.allDependenciesSourcesScope }) override fun getAllScriptsDependenciesClassFiles(): List = - classpathRoots.allDependenciesClassFiles + managers.flatMap { it.classpathRoots.allDependenciesClassFiles } override fun getAllScriptDependenciesSources(): List = - classpathRoots.allDependenciesSources + managers.flatMap { it.classpathRoots.allDependenciesSources } + + override fun forceReloadConfiguration(file: VirtualFile, loader: ScriptConfigurationLoader): ScriptCompilationConfigurationWrapper? { + val ktFile = project.getKtFile(file, null) ?: return null + + return default.forceReloadConfiguration(ktFile, loader) + } /////////////////// // Adapters for deprecated API @@ -174,4 +144,20 @@ class CompositeScriptConfigurationManager(val project: Project) : ScriptConfigur 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() + } + } } \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/DefaultScriptingSupport.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/DefaultScriptingSupport.kt index 1f052f9ad32..988df2f7cf8 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/DefaultScriptingSupport.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/DefaultScriptingSupport.kt @@ -20,6 +20,7 @@ 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.* +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 @@ -36,73 +37,21 @@ import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock import kotlin.script.experimental.api.ScriptDiagnostic -/** - * Standard implementation of scripts configuration loading and caching. - * - * ## Loading initiation - * - * [getOrLoadConfiguration] will be called when we need to show or analyze some script file. - * - * As described in [DefaultScriptingSupportBase], 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. - * - * Also, [ensureLoadedFromCache] 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. - * - * ## 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. - */ -class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : DefaultScriptingSupportBase(manager) { +class DefaultScriptingSupport(project: Project) : DefaultScriptingSupportBase(project) { // TODO public for tests val backgroundExecutor: BackgroundExecutor = - if (ApplicationManager.getApplication().isUnitTestMode) TestingBackgroundExecutor(manager.updater) - else DefaultBackgroundExecutor(project, manager.updater) + if (ApplicationManager.getApplication().isUnitTestMode) TestingBackgroundExecutor(rootsIndexer) + else DefaultBackgroundExecutor(project, rootsIndexer) private val outsiderLoader = ScriptOutsiderFileConfigurationLoader(project) private val fileAttributeCache = ScriptConfigurationFileAttributeCache(project) private val defaultLoader = DefaultScriptConfigurationLoader(project) - private val loaders: List - get() = mutableListOf().apply { - add(outsiderLoader) - add(fileAttributeCache) - addAll(LOADER.getPoint(project).extensionList) - add(defaultLoader) + private val loaders: Sequence + get() = sequence { + yield(outsiderLoader) + yield(fileAttributeCache) + yieldAll(LOADER.getPoint(project).extensionList) + yield(defaultLoader) } private val saveLock = ReentrantLock() @@ -121,7 +70,7 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De * * Each files may be in on of the states described below: * - scriptDefinition is not ready. `ScriptDefinitionsManager.getInstance(project).isReady() == false`. - * [updateScriptDefinitions] will be called when [ScriptDefinitionsManager] will be ready + * [clearConfigurationCachesAndRehighlight] will be called when [ScriptDefinitionsManager] will be ready * which will call [reloadOutOfDateConfiguration] for opened editors. * - unknown. When [isFirstLoad] true (`cache[file] == null`). * - up-to-date. `cache[file]?.upToDate == true`. @@ -155,39 +104,36 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De override fun reloadOutOfDateConfiguration( file: KtFile, isFirstLoad: Boolean, + loadEvenWillNotBeApplied: Boolean, forceSync: Boolean, - isPostponedLoad: Boolean, - fromCacheOnly: Boolean - ): Boolean { - val virtualFile = file.originalFile.virtualFile ?: return false + isPostponedLoad: Boolean + ) { + val virtualFile = file.originalFile.virtualFile ?: return - if (!ScriptDefinitionsManager.getInstance(project).isReady()) return false - val scriptDefinition = file.findScriptDefinition() ?: return false + val autoReloadEnabled = KotlinScriptingSettings.getInstance(project).isAutoReloadEnabled + val shouldLoad = isFirstLoad || loadEvenWillNotBeApplied || autoReloadEnabled + if (!shouldLoad) return + + val postponeLoading = isPostponedLoad && !autoReloadEnabled && !isFirstLoad + + if (!ScriptDefinitionsManager.getInstance(project).isReady()) return + val scriptDefinition = file.findScriptDefinition() ?: return val (async, sync) = loaders.partition { it.shouldRunInBackground(scriptDefinition) } val syncLoader = sync.firstOrNull { it.loadDependencies(isFirstLoad, file, scriptDefinition, loadingContext) } if (syncLoader == null) { - if (!fromCacheOnly) { - if (forceSync) { - loaders.firstOrNull { it.loadDependencies(isFirstLoad, file, scriptDefinition, loadingContext) } - } else { - val autoReloadEnabled = KotlinScriptingSettings.getInstance(project).isAutoReloadEnabled - val postponeLoading = isPostponedLoad && !autoReloadEnabled - - if (postponeLoading) { - LoadScriptConfigurationNotificationFactory.showNotification(virtualFile, project) { - runAsyncLoaders(file, virtualFile, scriptDefinition, async, isLoadingPostponed = true) - } - } else { - runAsyncLoaders(file, virtualFile, scriptDefinition, async, isLoadingPostponed = false) + if (forceSync) { + loaders.firstOrNull { it.loadDependencies(isFirstLoad, file, scriptDefinition, loadingContext) } + } else { + if (postponeLoading) { + LoadScriptConfigurationNotificationFactory.showNotification(virtualFile, project) { + runAsyncLoaders(file, virtualFile, scriptDefinition, async, isLoadingPostponed = true) } + } else { + runAsyncLoaders(file, virtualFile, scriptDefinition, async, isLoadingPostponed = false) } } - - return false - } else { - return true } } @@ -204,7 +150,7 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De val applied = cached?.applied if (applied != null && applied.inputs.isUpToDate(project, virtualFile)) { // in case user reverted to applied configuration - suggestOrSaveConfiguration(virtualFile, applied, skipNotification = isLoadingPostponed) + suggestOrSaveConfiguration(virtualFile, applied, isLoadingPostponed) } else if (cached == null || !cached.isUpToDate(project, virtualFile)) { // don't start loading if nothing was changed // (in case we checking for up-to-date and loading concurrently) @@ -220,7 +166,7 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De if (!ScriptDefinitionsManager.getInstance(project).isReady()) return null val scriptDefinition = file.findScriptDefinition() ?: return null - manager.updater.update { + rootsIndexer.transaction { if (!loader.shouldRunInBackground(scriptDefinition)) { loader.loadDependencies(false, file, scriptDefinition, loadingContext) } else { @@ -234,16 +180,8 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De } private val loadingContext = object : ScriptConfigurationLoadingContext { - /** - * Used from [ScriptOutsiderFileConfigurationLoader] only. - */ override fun getCachedConfiguration(file: VirtualFile): ScriptConfigurationSnapshot? = - getAppliedConfiguration(file) ?: getFromGlobalCache(file) - - private fun getFromGlobalCache(file: VirtualFile): ScriptConfigurationSnapshot? { - val info = manager.getLightScriptInfo(file.path) ?: return null - return ScriptConfigurationSnapshot(CachedConfigurationInputs.UpToDate, listOf(), info.buildConfiguration()) - } + getAppliedConfiguration(file) override fun suggestNewConfiguration(file: VirtualFile, newResult: ScriptConfigurationSnapshot) { suggestOrSaveConfiguration(file, newResult, false) @@ -286,7 +224,7 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De file.removeScriptDependenciesNotificationPanel(project) } saveReports(file, newResult.reports) - setAppliedConfiguration(file, newResult, syncUpdate = true) + setAppliedConfiguration(file, newResult) } else { debug(file) { "configuration changed, notification is shown: old = $oldConfiguration, new = $newConfiguration" @@ -302,7 +240,7 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De onClick = { saveReports(file, newResult.reports) file.removeScriptDependenciesNotificationPanel(project) - manager.updater.update { + rootsIndexer.transaction { setAppliedConfiguration(file, newResult) } } @@ -334,28 +272,10 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De } } } - - companion object { - fun getInstance(project: Project) = - (ScriptConfigurationManager.getInstance(project) as CompositeScriptConfigurationManager).default - } } -/** - * Abstraction for [DefaultScriptingSupportBase] based [cache] and [reloadOutOfDateConfiguration]. - * Among this two methods concrete implementation should provide script changes listening. - * - * 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). - * - * [ScriptClassRootsCache] will be calculated based on [cache]d configurations. - * Every change in [cache] will invalidate [ScriptClassRootsCache] cache. - */ -abstract class DefaultScriptingSupportBase(val manager: CompositeScriptConfigurationManager) { - val project: Project - get() = manager.project +abstract class DefaultScriptingSupportBase(val project: Project) : ScriptingSupport() { + protected val rootsIndexer = ScriptClassRootsIndexer(project) @Suppress("LeakingThis") protected val cache: ScriptConfigurationCache = createCache() @@ -369,18 +289,19 @@ abstract class DefaultScriptingSupportBase(val manager: CompositeScriptConfigura * (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 - * @param fromCacheOnly load only when builtin fast synchronous loaders are available - * @return true, if configuration loaded in sync */ protected abstract fun reloadOutOfDateConfiguration( file: KtFile, isFirstLoad: Boolean = getAppliedConfiguration(file.originalFile.virtualFile) == null, + loadEvenWillNotBeApplied: Boolean = false, forceSync: Boolean = false, - isPostponedLoad: Boolean = false, - fromCacheOnly: Boolean = false - ): Boolean + isPostponedLoad: Boolean = false + ) fun getCachedConfigurationState(file: VirtualFile?): ScriptConfigurationState? { if (file == null) return null @@ -390,14 +311,14 @@ abstract class DefaultScriptingSupportBase(val manager: CompositeScriptConfigura fun getAppliedConfiguration(file: VirtualFile?): ScriptConfigurationSnapshot? = getCachedConfigurationState(file)?.applied - private fun hasCachedConfiguration(file: KtFile): Boolean = + override fun hasCachedConfiguration(file: KtFile): Boolean = getAppliedConfiguration(file.originalFile.virtualFile) != null - fun isConfigurationLoadingInProgress(file: KtFile): Boolean { + override fun isConfigurationLoadingInProgress(file: KtFile): Boolean { return !hasCachedConfiguration(file) && !ScriptConfigurationManager.isManualConfigurationLoading(file.originalFile.virtualFile) } - fun getOrLoadConfiguration( + override fun getOrLoadConfiguration( virtualFile: VirtualFile, preloadedKtFile: KtFile? ): ScriptCompilationConfigurationWrapper? { @@ -405,86 +326,73 @@ abstract class DefaultScriptingSupportBase(val manager: CompositeScriptConfigura if (cached != null) return cached.configuration val ktFile = project.getKtFile(virtualFile, preloadedKtFile) ?: return null - manager.updater.update { + rootsIndexer.transaction { reloadOutOfDateConfiguration(ktFile, isFirstLoad = true) } return getAppliedConfiguration(virtualFile)?.configuration } - /** - * Load new configuration and suggest to apply it (only if it is changed) - */ - fun ensureUpToDatedConfigurationSuggested(file: KtFile) { - reloadIfOutOfDate(file) - } + override val updater: ScriptConfigurationUpdater = object : ScriptConfigurationUpdater { + override fun ensureUpToDatedConfigurationSuggested(file: KtFile) { + reloadIfOutOfDate(listOf(file), loadEvenWillNotBeApplied = true, isPostponedLoad = false) + } - /** - * Show notification about changed script configuration with action to start loading it - */ - fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile) { - reloadIfOutOfDate(file, isPostponedLoad = true) - } + override fun ensureConfigurationUpToDate(files: List): Boolean { + return reloadIfOutOfDate(files, loadEvenWillNotBeApplied = false, isPostponedLoad = false) + } - private fun reloadIfOutOfDate(file: KtFile, isPostponedLoad: Boolean = false) { - if (!ScriptDefinitionsManager.getInstance(project).isReady()) return - - manager.updater.update { - val virtualFile = file.originalFile.virtualFile - if (virtualFile != null) { - val state = cache[virtualFile] - if (state == null || !state.isUpToDate(project, virtualFile, file)) { - reloadOutOfDateConfiguration( - file, - isFirstLoad = state == null, - isPostponedLoad = isPostponedLoad - ) - } - } + override fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile) { + reloadIfOutOfDate(listOf(file), loadEvenWillNotBeApplied = true, isPostponedLoad = true) } } - /** - * Ensure that any configuration for [files] is loaded from cache - */ - fun ensureLoadedFromCache(files: List): Boolean { + private fun reloadIfOutOfDate(files: List, loadEvenWillNotBeApplied: Boolean, isPostponedLoad: Boolean): Boolean { if (!ScriptDefinitionsManager.getInstance(project).isReady()) return false - var allLoaded = true - manager.updater.update { + var upToDate = true + rootsIndexer.transaction { files.forEach { file -> val virtualFile = file.originalFile.virtualFile if (virtualFile != null) { val state = cache[virtualFile] - if (state == null) { - if (!reloadOutOfDateConfiguration( - file, - isFirstLoad = true, - fromCacheOnly = true - ) - ) { - allLoaded = false - } + if (state == null || !state.isUpToDate(project, virtualFile, file)) { + upToDate = false + reloadOutOfDateConfiguration( + file, + isFirstLoad = state == null, + loadEvenWillNotBeApplied = loadEvenWillNotBeApplied, + isPostponedLoad = isPostponedLoad + ) } } } } - return allLoaded + return upToDate } protected open fun setAppliedConfiguration( file: VirtualFile, - newConfigurationSnapshot: ScriptConfigurationSnapshot?, - syncUpdate: Boolean = false + newConfigurationSnapshot: ScriptConfigurationSnapshot? ) { - manager.updater.checkInTransaction() + 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) - manager.updater.invalidate(file, synchronous = syncUpdate) + + clearClassRootsCaches(project) + } + + ScriptingSupportHelper.updateHighlighting(project) { + it == file } } @@ -495,6 +403,10 @@ abstract class DefaultScriptingSupportBase(val manager: CompositeScriptConfigura 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 @@ -504,29 +416,20 @@ abstract class DefaultScriptingSupportBase(val manager: CompositeScriptConfigura val virtualFile = file.virtualFile if (cache[virtualFile]?.isUpToDate(project, virtualFile, file) == true) return - manager.updater.update { - reloadOutOfDateConfiguration(file, forceSync = true) + rootsIndexer.transaction { + reloadOutOfDateConfiguration(file, forceSync = true, loadEvenWillNotBeApplied = true) } } - fun updateScriptDefinitions() { + override fun clearCaches() { cache.clear() } - fun collectConfigurations(builder: ScriptClassRootsCache.Builder) { - // todo: drop the hell below - // keep this one only: - // cache.allApplied().forEach { (vFile, configuration) -> builder.add(vFile, configuration) } - - // own builder for saving to storage - val ownBuilder = ScriptClassRootsCache.Builder(project) - val rootsStorage = ScriptClassRootsStorage.getInstance(project) - - rootsStorage.load(ownBuilder) - cache.allApplied().forEach { (vFile, configuration) -> ownBuilder.add(vFile, configuration) } - rootsStorage.save(ownBuilder) - - builder.add(ownBuilder) + override fun recreateRootsCache(): ScriptClassRootsCache { + return DefaultClassRootsCache( + project, + cache.allApplied() + ) } } diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/ScriptingSupport.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/ScriptingSupport.kt index e3bb67f1ce6..499a014d865 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/ScriptingSupport.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/ScriptingSupport.kt @@ -6,37 +6,76 @@ package org.jetbrains.kotlin.idea.core.script.configuration import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.extensions.Extensions +import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiElementFinder +import org.jetbrains.kotlin.idea.core.script.KotlinScriptDependenciesClassFinder +import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker +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.listener.ScriptChangeListener +import org.jetbrains.kotlin.idea.core.script.debug import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock -/** - * Extension point for overriding default Kotlin scripting support. - * - * Implementation should store script configuration internally (in memory and/or fs), - * and provide it inside [collectConfigurations] using the [ScriptClassRootsCache.LightScriptInfo]. - * Custom data can be attached to [ScriptClassRootsCache.LightScriptInfo] and retrieved - * by calling [ScriptClassRootsCache.getLightScriptInfo]. - * - * [ScriptChangeListener] can be used to listen script changes. - * [CompositeScriptConfigurationManager.updater] should be used to schedule configuration reloading. - * - * [isApplicable] should return true for files that is covered by that support. - * - * [isConfigurationLoadingInProgress] is used to pause analyzing. - * - * Long read: [idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/README.md]. - * - * @sample GradleBuildRootsManager - */ abstract class ScriptingSupport { - abstract fun isApplicable(file: VirtualFile): Boolean - abstract fun isConfigurationLoadingInProgress(file: KtFile): Boolean - abstract fun collectConfigurations(builder: ScriptClassRootsCache.Builder) + abstract class Provider { + abstract val all: Collection - companion object { - val EPN: ExtensionPointName = - ExtensionPointName.create("org.jetbrains.kotlin.scripting.idea.scriptingSupport") + abstract fun getSupport(file: VirtualFile): ScriptingSupport? + + companion object { + val EPN: ExtensionPointName = + ExtensionPointName.create("org.jetbrains.kotlin.scripting.idea.scriptingSupportProvider") + } + } + + abstract fun clearCaches() + abstract fun hasCachedConfiguration(file: KtFile): Boolean + abstract fun isConfigurationLoadingInProgress(file: KtFile): Boolean + abstract fun getOrLoadConfiguration(virtualFile: VirtualFile, preloadedKtFile: KtFile? = null): ScriptCompilationConfigurationWrapper? + + abstract val updater: ScriptConfigurationUpdater + + private val classpathRootsLock = ReentrantLock() + + @Volatile + private var _classpathRoots: ScriptClassRootsCache? = null + val classpathRoots: ScriptClassRootsCache + get() { + val value1 = _classpathRoots + if (value1 != null) return value1 + + classpathRootsLock.withLock { + val value2 = _classpathRoots + if (value2 != null) return value2 + + val value3 = recreateRootsCache() + value3.saveClassRootsToStorage() + _classpathRoots = value3 + return value3 + } + } + + protected abstract fun recreateRootsCache(): ScriptClassRootsCache + + fun clearClassRootsCaches(project: Project) { + debug { "class roots caches cleared" } + + classpathRootsLock.withLock { + _classpathRoots = null + } + + val kotlinScriptDependenciesClassFinder = + Extensions.getArea(project) + .getExtensionPoint(PsiElementFinder.EP_NAME).extensions + .filterIsInstance() + .single() + + kotlinScriptDependenciesClassFinder.clearCache() + + ScriptDependenciesModificationTracker.getInstance(project).incModificationCount() } } \ 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 21497661754..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 @@ -31,7 +31,7 @@ interface ScriptConfigurationCache { fun setApplied(file: VirtualFile, configurationSnapshot: ScriptConfigurationSnapshot) fun setLoaded(file: VirtualFile, configurationSnapshot: ScriptConfigurationSnapshot) - fun allApplied(): List> + fun allApplied(): Map fun clear() fun getAnyLoadedScript(): ScriptCompilationConfigurationWrapper? @@ -63,10 +63,6 @@ interface CachedConfigurationInputs: Serializable { override fun isUpToDate(project: Project, file: VirtualFile, ktFile: KtFile?): Boolean = false } - object UpToDate: CachedConfigurationInputs { - override fun isUpToDate(project: Project, file: VirtualFile, ktFile: KtFile?): Boolean = true - } - data class PsiModificationStamp( val fileModificationStamp: Long, val psiModificationStamp: Long 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 b5a0469ac0f..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 @@ -41,11 +41,11 @@ open class ScriptConfigurationMemoryCache( @Synchronized @Suppress("UNCHECKED_CAST") - override fun allApplied(): List> { - val result = mutableListOf>() + override fun allApplied(): Map { + val result = hashMapOf() for ((file, configuration) in memoryCache.entrySet()) { if (configuration.applied?.configuration != null) { - result.add(Pair(file, configuration.applied.configuration)) + result[file] = configuration.applied.configuration } } return result diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/DefaultScriptChangeListener.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/DefaultScriptChangeListener.kt index 95fdc34ae53..3240a8e53a2 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/DefaultScriptChangeListener.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/DefaultScriptChangeListener.kt @@ -9,14 +9,14 @@ import com.intellij.openapi.vfs.VirtualFile import org.jetbrains.kotlin.scripting.definitions.isNonScript open class DefaultScriptChangeListener(project: Project) : ScriptChangeListener(project) { - override fun editorActivated(vFile: VirtualFile) { + override fun editorActivated(vFile: VirtualFile, updater: ScriptConfigurationUpdater) { val file = getAnalyzableKtFileForScript(vFile) ?: return - default.ensureUpToDatedConfigurationSuggested(file) + updater.ensureUpToDatedConfigurationSuggested(file) } - override fun documentChanged(vFile: VirtualFile) { + override fun documentChanged(vFile: VirtualFile, updater: ScriptConfigurationUpdater) { val file = getAnalyzableKtFileForScript(vFile) ?: return - default.ensureUpToDatedConfigurationSuggested(file) + updater.ensureUpToDatedConfigurationSuggested(file) } override fun isApplicable(vFile: VirtualFile): Boolean { 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 d97641f6878..2f36addd61c 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,21 +9,20 @@ 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.DefaultScriptingSupport import org.jetbrains.kotlin.idea.util.ProjectRootsUtil import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.psi.KtFile /** * [ScriptChangesNotifier] will call first applicable [ScriptChangeListener] when editor is activated or document changed. - * Listener should do something to invalidate configuration and schedule reloading. + * + * Listener may call [ScriptConfigurationUpdater] to invalidate configuration and schedule reloading. + * + * @see DefaultScriptConfigurationManager for more details. */ -abstract class ScriptChangeListener(val project: Project) { - val default: DefaultScriptingSupport - get() = DefaultScriptingSupport.getInstance(project) - - abstract fun editorActivated(vFile: VirtualFile) - abstract fun documentChanged(vFile: VirtualFile) +abstract class ScriptChangeListener(protected val project: Project) { + abstract fun editorActivated(vFile: VirtualFile, updater: ScriptConfigurationUpdater) + abstract fun documentChanged(vFile: VirtualFile, updater: ScriptConfigurationUpdater) abstract fun isApplicable(vFile: VirtualFile): Boolean diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptChangesNotifier.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptChangesNotifier.kt index ee96808e39d..043c4cf42e2 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptChangesNotifier.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptChangesNotifier.kt @@ -22,7 +22,8 @@ import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChange import org.jetbrains.kotlin.idea.core.script.isScriptChangesNotifierDisabled internal class ScriptChangesNotifier( - private val project: Project + private val project: Project, + private val updater: ScriptConfigurationUpdater, ) { private val scriptsQueue = Alarm(Alarm.ThreadToUse.POOLED_THREAD, project) private val scriptChangesListenerDelay = 1400 @@ -45,10 +46,10 @@ internal class ScriptChangesNotifier( private fun runScriptDependenciesUpdateIfNeeded(file: VirtualFile) { if (ApplicationManager.getApplication().isUnitTestMode) { - getListener(project, file)?.editorActivated(file) + getListener(project, file)?.editorActivated(file, updater) } else { AppExecutorUtil.getAppExecutorService().submit { - getListener(project, file)?.editorActivated(file) + getListener(project, file)?.editorActivated(file, updater) } } } @@ -67,11 +68,11 @@ internal class ScriptChangesNotifier( } if (ApplicationManager.getApplication().isUnitTestMode) { - getListener(project, file)?.documentChanged(file) + getListener(project, file)?.documentChanged(file, updater) } else { scriptsQueue.cancelAllRequests() scriptsQueue.addRequest( - { getListener(project, file)?.documentChanged(file) }, + { getListener(project, file)?.documentChanged(file, updater) }, scriptChangesListenerDelay, true, ) @@ -83,10 +84,11 @@ internal class ScriptChangesNotifier( } private val defaultListener = DefaultScriptChangeListener(project) - private val listeners: Collection = mutableListOf().apply { - addAll(LISTENER.getPoint(project).extensionList) - add(defaultListener) - } + private val listeners: Sequence + get() = sequence { + yieldAll(LISTENER.getPoint(project).extensionList) + yield(defaultListener) + } private fun getListener(project: Project, file: VirtualFile): ScriptChangeListener? { if (project.isDisposed || areListenersDisabled()) return null diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptConfigurationUpdater.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptConfigurationUpdater.kt new file mode 100644 index 00000000000..020d11f6232 --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptConfigurationUpdater.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.core.script.configuration.listener + +import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationCacheScope +import org.jetbrains.kotlin.psi.KtFile + +interface ScriptConfigurationUpdater { + /** + * Ensure that configuration for [file] is up-to-date, + * or new configuration loading is started + */ + fun ensureUpToDatedConfigurationSuggested(file: KtFile) + + /** + * Check that configurations for given [files] is up-to-date or try synchronously update + * them. Synchronous update will be occurred only if auto apply is enabled and + * configuration for given file will not be loaded in background. + * + * @return true, if all files are already up-to-date or updated during this call + */ + fun ensureConfigurationUpToDate(files: List): Boolean + + /** + * Suggest to update script configuration if configuration for [file] is out of date + */ + fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile) +} \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/DefaultBackgroundExecutor.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/DefaultBackgroundExecutor.kt index 5bac5408a61..7e1a7298fa5 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/DefaultBackgroundExecutor.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/DefaultBackgroundExecutor.kt @@ -15,7 +15,6 @@ import com.intellij.util.containers.HashSetQueue import org.jetbrains.kotlin.idea.core.script.debug import org.jetbrains.kotlin.idea.core.util.KotlinIdeaCoreBundle import java.util.* -import javax.swing.SwingUtilities /** * Sequentially loads script configuration in background. @@ -33,7 +32,7 @@ import javax.swing.SwingUtilities */ internal class DefaultBackgroundExecutor( val project: Project, - val rootsManager: ScriptClassRootsUpdater + val rootsManager: ScriptClassRootsIndexer ) : BackgroundExecutor { companion object { const val PROGRESS_INDICATOR_DELAY = 1000 @@ -138,7 +137,7 @@ internal class DefaultBackgroundExecutor( private fun ensureInTransaction() { if (inTransaction) return inTransaction = true - rootsManager.beginUpdating() + rootsManager.startTransaction() } @Synchronized @@ -228,12 +227,9 @@ internal class DefaultBackgroundExecutor( override fun start() { super.start() - // executeOnPooledThread requires read lock, and we may fail to acquire it - SwingUtilities.invokeLater { - BackgroundTaskUtil.executeOnPooledThread(project, { - run() - }) - } + BackgroundTaskUtil.executeOnPooledThread(project, Runnable { + run() + }) } override fun close() { 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..8e3b7b16b41 --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/DefaultClassRootsCache.kt @@ -0,0 +1,86 @@ +/* + * 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, + ScriptClassRootsStorage.Companion.Key("default"), + extractRoots(all, project) +) { + + override fun getConfiguration(file: VirtualFile) = all[file] + + override fun contains(file: VirtualFile): Boolean = file in all + + private val scriptsSdksCache: Map = + ConcurrentFactoryMap.createWeakMap { file -> + return@createWeakMap getScriptSdkOrDefault(all[file]?.javaHome, project) + } + + override fun getScriptSdk(file: VirtualFile): Sdk? = scriptsSdksCache[file] + + override val firstScriptSdk: Sdk? by lazy { + getScriptSdkOrDefault(all.values.firstOrNull()?.javaHome, project) + } + + companion object { + fun extractRoots( + project: Project, + configuration: ScriptCompilationConfigurationWrapper + ): ScriptClassRootsStorage.Companion.ScriptClassRoots { + val scriptSdk = getScriptSdkOrDefault(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 = getScriptSdkOrDefault(configuration.javaHome, project) + 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 bb822abfb65..a0f73423e01 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 @@ -12,101 +12,100 @@ import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.roots.ModuleRootManager import com.intellij.openapi.roots.OrderRootType import com.intellij.openapi.roots.ProjectRootManager -import com.intellij.openapi.util.io.FileUtil.toSystemIndependentName -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.compose +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.LOG -import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager.Companion.classpathEntryToVfs -import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager.Companion.toVfsRoots +import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager import org.jetbrains.kotlin.idea.util.getProjectJdkTableSafe -import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper import java.io.File -import java.lang.ref.Reference -import java.lang.ref.SoftReference -class ScriptClassRootsCache( - val scripts: Map, - val classes: Set, - val sources: Set, - val sdks: Map, - private val nonModulesSdks: Collection, - val customDefinitionsUsed: Boolean +abstract class ScriptClassRootsCache( + private val project: Project, + private val rootsCacheKey: ScriptClassRootsStorage.Companion.Key, + private val roots: ScriptClassRootsStorage.Companion.ScriptClassRoots ) { - abstract class LightScriptInfo(val definition: ScriptDefinition?) { - @Volatile - var heavyCache: Reference? = null + protected abstract fun getConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? - abstract fun buildConfiguration(): ScriptCompilationConfigurationWrapper? - } + abstract val firstScriptSdk: Sdk? - class DirectScriptInfo(val result: ScriptCompilationConfigurationWrapper) : LightScriptInfo(null) { - override fun buildConfiguration(): ScriptCompilationConfigurationWrapper = result - } + abstract fun getScriptSdk(file: VirtualFile): Sdk? - class HeavyScriptInfo( + abstract fun contains(file: VirtualFile): Boolean + + private class Fat( val scriptConfiguration: ScriptCompilationConfigurationWrapper, - val classFilesScope: GlobalSearchScope, - val sdk: Sdk? + val classFilesScope: GlobalSearchScope ) - class Builder(val project: Project) { - val scripts = mutableMapOf() + val allDependenciesClassFiles by lazy { + ScriptClassRootsStorage.getInstance(project).loadClasspathRoots(rootsCacheKey) + } - private val defaultSdk = getScriptDefaultSdk() - val sdks = mutableMapOf(null to defaultSdk) + val allDependenciesSources by lazy { + ScriptClassRootsStorage.getInstance(project).loadSourcesRoots(rootsCacheKey) + } - val classes = mutableSetOf() - val sources = mutableSetOf() - private var customDefinitionsUsed: Boolean = false + val allDependenciesClassFilesScope by lazy { + NonClasspathDirectoriesScope.compose(allDependenciesClassFiles) + } - fun build(): ScriptClassRootsCache { - val nonIndexedSdks = collectNonIndexedSdks() - return ScriptClassRootsCache(scripts, classes, sources, sdks, nonIndexedSdks, customDefinitionsUsed) - } + val allDependenciesSourcesScope by lazy { + NonClasspathDirectoriesScope.compose(allDependenciesSources) + } - fun collectNonIndexedSdks(): MutableSet { - val nonIndexedSdks = sdks.values.filterNotNullTo(mutableSetOf()) - ModuleManager.getInstance(project).modules.map { - nonIndexedSdks.remove(ModuleRootManager.getInstance(it).sdk) + private val scriptsDependenciesCache: MutableMap = + ConcurrentFactoryMap.createWeakMap { file -> + val configuration = getConfiguration(file) ?: return@createWeakMap null + + val roots = configuration.dependenciesClassPath + val sdk = getScriptSdk(file) + + @Suppress("FoldInitializerAndIfToElvis") + if (sdk == null) { + return@createWeakMap Fat( + configuration, + NonClasspathDirectoriesScope.compose(ScriptConfigurationManager.toVfsRoots(roots)) + ) } - return nonIndexedSdks + + return@createWeakMap Fat( + configuration, + NonClasspathDirectoriesScope.compose( + sdk.rootProvider.getFiles(OrderRootType.CLASSES).toList() + ScriptConfigurationManager.toVfsRoots(roots) + ) + ) } - fun useCustomScriptDefinition() { - customDefinitionsUsed = true + fun getScriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope { + return scriptsDependenciesCache[file]?.classFilesScope ?: GlobalSearchScope.EMPTY_SCOPE + } + + fun getScriptConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? { + return scriptsDependenciesCache[file]?.scriptConfiguration + } + + fun hasNotCachedRoots(roots: ScriptClassRootsStorage.Companion.ScriptClassRoots): Boolean { + return !ScriptClassRootsStorage.getInstance(project).containsAll(rootsCacheKey, roots) + } + + fun saveClassRootsToStorage() { + val rootsStorage = ScriptClassRootsStorage.getInstance(project) + if (roots.classpathFiles.isNotEmpty() || roots.sourcesFiles.isNotEmpty() || roots.sdks.isNotEmpty()) { + rootsStorage.save(rootsCacheKey, roots) + } + } + + companion object { + fun toStringValues(prop: Collection): Set { + return prop.mapNotNull { it.absolutePath }.toSet() } - fun add( - vFile: VirtualFile, - configuration: ScriptCompilationConfigurationWrapper - ) { - addSdk(configuration.javaHome) - - configuration.dependenciesClassPath.forEach { classes.add(it.absolutePath) } - configuration.dependenciesSources.forEach { sources.add(it.absolutePath) } - - scripts[vFile.path] = DirectScriptInfo(configuration) - - useCustomScriptDefinition() - } - - fun add(other: Builder) { - classes.addAll(other.classes) - sources.addAll(other.sources) - sdks.putAll(other.sdks) - scripts.putAll(other.scripts) - } - - fun addSdk(javaHome: File?): Sdk? { - if (javaHome == null) return defaultSdk - val canonicalPath = toSystemIndependentName(javaHome.canonicalPath) - return sdks.getOrPut(canonicalPath) { - getScriptSdkByJavaHome(javaHome) ?: defaultSdk - } + fun getScriptSdkOrDefault(javaHome: File?, project: Project): Sdk? { + return javaHome?.let { getScriptSdkByJavaHome(it) } ?: getScriptDefaultSdk(project) } private fun getScriptSdkByJavaHome(javaHome: File): Sdk? { @@ -120,13 +119,7 @@ class ScriptClassRootsCache( return getProjectJdkTableSafe().allJdks.find { it.homeDirectory == javaHomeVF } } - fun addSdkByName(sdkName: String) { - val sdk = getProjectJdkTableSafe().allJdks.find { it.name == sdkName } ?: defaultSdk ?: return - val homePath = sdk.homePath ?: return - sdks[homePath] = sdk - } - - private fun getScriptDefaultSdk(): Sdk? { + private fun getScriptDefaultSdk(project: Project): Sdk? { val projectSdk = ProjectRootManager.getInstance(project).projectSdk?.takeIf { it.canBeUsedForScript() } if (projectSdk != null) return projectSdk @@ -140,118 +133,32 @@ class ScriptClassRootsCache( "projectSdk = ${ProjectRootManager.getInstance(project).projectSdk}, " + "all sdks = ${getAllProjectSdks().joinToString("\n")}" ) - return null } private fun Sdk.canBeUsedForScript() = sdkType is JavaSdkType - } - fun getLightScriptInfo(file: String) = scripts[file] - - fun contains(file: VirtualFile): Boolean = file.path in scripts - - private fun getHeavyScriptInfo(file: String): HeavyScriptInfo? { - val lightScriptInfo = getLightScriptInfo(file) ?: return null - val heavy0 = lightScriptInfo.heavyCache?.get() - if (heavy0 != null) return heavy0 - synchronized(lightScriptInfo) { - val heavy1 = lightScriptInfo.heavyCache?.get() - if (heavy1 != null) return heavy1 - val heavy2 = computeHeavy(lightScriptInfo) - lightScriptInfo.heavyCache = SoftReference(heavy2) - return heavy2 - } - } - - private fun computeHeavy(lightScriptInfo: LightScriptInfo): HeavyScriptInfo? { - val configuration = lightScriptInfo.buildConfiguration() ?: return null - - val roots = configuration.dependenciesClassPath - val sdk = sdks[configuration.javaHome?.canonicalPath] - - return if (sdk == null) { - HeavyScriptInfo(configuration, compose(toVfsRoots(roots)), null) - } else { - val sdkClasses = sdk.rootProvider.getFiles(OrderRootType.CLASSES).toList() - HeavyScriptInfo(configuration, compose(sdkClasses + toVfsRoots(roots)), sdk) - } - } - - val firstScriptSdk: Sdk? = sdks.values.firstOrNull() - - val allDependenciesClassFiles: List - - val allDependenciesSources: List - - init { - allDependenciesClassFiles = mutableSetOf().also { result -> - nonModulesSdks.forEach { result.addAll(it.rootProvider.getFiles(OrderRootType.CLASSES)) } - classes.mapNotNullTo(result) { classpathEntryToVfs(File(it)) } - }.toList() - - allDependenciesSources = mutableSetOf().also { result -> - nonModulesSdks.forEach { result.addAll(it.rootProvider.getFiles(OrderRootType.SOURCES)) } - sources.mapNotNullTo(result) { classpathEntryToVfs(File(it)) } - }.toList() - } - - val allDependenciesClassFilesScope = compose(allDependenciesClassFiles) - - val allDependenciesSourcesScope = compose(allDependenciesSources) - - fun getScriptConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? = - getHeavyScriptInfo(file.path)?.scriptConfiguration - - fun getScriptSdk(file: VirtualFile): Sdk? = - getHeavyScriptInfo(file.path)?.sdk - - fun getScriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope = - getHeavyScriptInfo(file.path)?.classFilesScope ?: GlobalSearchScope.EMPTY_SCOPE - - fun hasInvalidSdk(project: Project): Boolean { - val builder = Builder(project) - if (sdks.any { (home, sdk) -> builder.addSdk(home?.let(::File)) != sdk }) return true - if (builder.collectNonIndexedSdks() != nonModulesSdks) return true - return false - } - - fun diff(old: ScriptClassRootsCache): Updates = - Updates( - hasNewRoots(old), - getChangedScripts(old) - ) - - private fun hasNewRoots(old: ScriptClassRootsCache): Boolean { - return classes.any { it !in old.classes } - || sources.any { it !in old.sources } - || sdks.any { it.key !in old.sdks } - } - - private fun getChangedScripts(old: ScriptClassRootsCache): Set { - val changed = mutableSetOf() - - scripts.forEach { - if (old.scripts[it.key] != it.value) { - changed.add(it.key) - } + fun Sdk.isAlreadyIndexed(project: Project): Boolean { + return ModuleManager.getInstance(project).modules.any { ModuleRootManager.getInstance(it).sdk == this } } - old.scripts.forEach { - if (it.key !in scripts) { - changed.add(it.key) - } + fun empty(project: Project) = object : ScriptClassRootsCache( + project, + ScriptClassRootsStorage.Companion.Key("empty"), + ScriptClassRootsStorage.Companion.ScriptClassRoots( + setOf(), + setOf(), + setOf() + ) + ) { + override fun getConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? = null + + override val firstScriptSdk: Sdk? = null + + override fun getScriptSdk(file: VirtualFile): Sdk? = null + + override fun contains(file: VirtualFile): Boolean = true } - - return changed - } - - class Updates( - val hasNewRoots: Boolean, - val updatedScripts: Set - ) { - val changed: Boolean - get() = hasNewRoots || updatedScripts.isNotEmpty() } } 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 new file mode 100644 index 00000000000..efe5be39fc6 --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/ScriptClassRootsIndexer.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.core.script.configuration.utils + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.TransactionGuard +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ex.ProjectRootManagerEx +import com.intellij.openapi.util.EmptyRunnable +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker +import org.jetbrains.kotlin.idea.core.script.debug +import org.jetbrains.kotlin.idea.util.application.runWriteAction +import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper +import java.util.concurrent.atomic.AtomicInteger + +/** + * Utility for postponing indexing of new roots to the end of some bulk operation. + */ +class ScriptClassRootsIndexer(val project: Project) { + private var newRootsPresent: Boolean = false + private val concurrentTransactions = AtomicInteger() + + @Synchronized + fun markNewRoot() { + checkInTransaction() + newRootsPresent = true + } + + fun checkInTransaction() { + check(concurrentTransactions.get() > 0) + } + + inline fun transaction(body: () -> T): T { + startTransaction() + return try { + body() + } finally { + commit() + } + } + + fun startTransaction() { + concurrentTransactions.incrementAndGet() + } + + fun commit() { + concurrentTransactions.decrementAndGet() + + // run indexing even in inner transaction + // (outer transaction may be async, so it would be better to not wait it) + startIndexingIfNeeded() + } + + @Synchronized + private fun startIndexingIfNeeded() { + if (!newRootsPresent) return + newRootsPresent = false + + val doNotifyRootsChanged = Runnable { + runWriteAction { + if (project.isDisposed) return@runWriteAction + + debug { "roots change event" } + + ProjectRootManagerEx.getInstanceEx(project)?.makeRootsChange(EmptyRunnable.getInstance(), false, true) + ScriptDependenciesModificationTracker.getInstance(project).incModificationCount() + } + } + + if (ApplicationManager.getApplication().isUnitTestMode) { + TransactionGuard.submitTransaction(project, doNotifyRootsChanged) + } else { + TransactionGuard.getInstance().submitTransactionLater(project, doNotifyRootsChanged) + } + } +} \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/ScriptClassRootsStorage.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/ScriptClassRootsStorage.kt index b6329034ea5..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 @@ -7,44 +7,144 @@ package org.jetbrains.kotlin.idea.core.script.configuration.utils import com.intellij.openapi.components.* import com.intellij.openapi.project.Project -import com.intellij.util.xmlb.XmlSerializerUtil +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 org.jdom.Element +import org.jetbrains.kotlin.idea.core.script.debug -/** - * This cache is used by [org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptingSupport] only. - * @see org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptingSupport.collectConfigurations - * - */ @State( name = "ScriptClassRootsStorage", storages = [Storage(StoragePathMacros.CACHE_FILE)] ) -class ScriptClassRootsStorage(val project: Project) : 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) + } + } } - fun save(roots: ScriptClassRootsCache.Builder) { - classpath = roots.classes - sources = roots.sources - sdks = roots.sdks.values.mapNotNullTo(mutableSetOf()) { it?.name } + override fun loadState(state: Element) { + classpath = readCollection(state, "classpath") + sources = readCollection(state, "sources") + sdks = readCollection(state, "sdks") } - fun load(builder: ScriptClassRootsCache.Builder) { - builder.sources.addAll(sources) - builder.classes.addAll(classpath) - sdks.forEach(builder::addSdkByName) + 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 toVirtualFiles(prop: Set?, sources: Boolean): List { + if (prop == null) return emptyList() + + 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 { + if (prop == null) return emptyList() + + return prop.mapNotNull { + if (it.endsWith(JarFileSystem.PROTOCOL)) { + StandardFileSystems.jar()?.findFileByPath(it + JarFileSystem.JAR_SEPARATOR)?.let { + return@mapNotNull it + } + } + + StandardFileSystems.local()?.findFileByPath(it)?.let { + return@mapNotNull it + } + + // TODO: report this somewhere, but do not throw: assert(res != null, { "Invalid classpath entry '$this': exists: ${exists()}, is directory: $isDirectory, is file: $isFile" }) + + null + }.distinct() + } + + fun containsAll(key: Key, configuration: ScriptClassRoots): Boolean { + if (configuration.classpathFiles.isNotEmpty() && classpath[key.value]?.containsAll(configuration.classpathFiles) != true) { + debug { "class roots were changed: old = $classpath, new = ${configuration.classpathFiles}" } + return false + } + if (configuration.sourcesFiles.isNotEmpty() && sources[key.value]?.containsAll(configuration.sourcesFiles) != true) { + debug { "source roots were changed: old = $sources, new = ${configuration.sourcesFiles}" } + return false + } + if (configuration.sdks.isNotEmpty() && sdks[key.value]?.containsAll(toStringNames(configuration.sdks)) != true) { + debug { "sdk classes were changed: old = $sdks, new = ${configuration.sdks.map { it.homePath }}" } + return false + } + return true + } + + fun save(key: Key, configuration: ScriptClassRoots) { + // TODO: do not drop all storage on save: KT-34444 + classpath.getOrPut(key.value, { emptySet() }) + classpath.replace(key.value, configuration.classpathFiles) + + sources.getOrPut(key.value, { emptySet() }) + sources.replace(key.value, configuration.sourcesFiles) + + sdks.getOrPut(key.value, { emptySet() }) + sdks.replace(key.value, toStringNames(configuration.sdks)) + } + + fun loadClasspathRoots(key: Key): List { + return toVirtualFiles(sdks[key.value], false) + toVirtualFiles(classpath[key.value]) + } + + fun loadSourcesRoots(key: Key): List { + return toVirtualFiles(sdks[key.value], true) + toVirtualFiles(sources[key.value]) } companion object { fun getInstance(project: Project): ScriptClassRootsStorage = ServiceManager.getService(project, ScriptClassRootsStorage::class.java) + + data class ScriptClassRoots( + 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-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/ScriptClassRootsUpdater.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/ScriptClassRootsUpdater.kt deleted file mode 100644 index 1f4e13bc602..00000000000 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/ScriptClassRootsUpdater.kt +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package org.jetbrains.kotlin.idea.core.script.configuration.utils - -import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.TransactionGuard -import com.intellij.openapi.fileEditor.FileEditorManager -import com.intellij.openapi.progress.ProcessCanceledException -import com.intellij.openapi.progress.ProgressIndicator -import com.intellij.openapi.progress.ProgressManager -import com.intellij.openapi.progress.util.BackgroundTaskUtil -import com.intellij.openapi.project.Project -import com.intellij.openapi.roots.ex.ProjectRootManagerEx -import com.intellij.openapi.util.EmptyRunnable -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.psi.PsiElementFinder -import com.intellij.psi.PsiManager -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import org.jetbrains.kotlin.idea.core.script.KotlinScriptDependenciesClassFinder -import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker -import org.jetbrains.kotlin.idea.core.script.configuration.CompositeScriptConfigurationManager -import org.jetbrains.kotlin.idea.core.script.debug -import org.jetbrains.kotlin.idea.core.util.EDT -import org.jetbrains.kotlin.idea.util.application.runWriteAction -import java.util.concurrent.atomic.AtomicInteger -import java.util.concurrent.locks.ReentrantLock -import kotlin.concurrent.withLock - -/** - * Utility for postponing indexing of new roots to the end of some bulk operation. - */ -class ScriptClassRootsUpdater( - val project: Project, - val manager: CompositeScriptConfigurationManager -) { - private var invalidated: Boolean = false - private var syncUpdateRequired: Boolean = false - private val concurrentUpdates = AtomicInteger() - - /** - * @param synchronous Used from legacy FS cache only, don't use - */ - @Synchronized - @Suppress("UNUSED_PARAMETER") - fun invalidate(file: VirtualFile, synchronous: Boolean = false) { - // todo: record invalided files for some optimisations in update - invalidate(synchronous) - } - - /** - * @param synchronous Used from legacy FS cache only, don't use - */ - @Synchronized - fun invalidate(synchronous: Boolean = false) { - checkInTransaction() - invalidated = true - if (synchronous) { - syncUpdateRequired = true - } - } - - fun checkInTransaction() { - check(concurrentUpdates.get() > 0) - } - - inline fun update(body: () -> T): T { - beginUpdating() - return try { - body() - } finally { - commit() - } - } - - fun beginUpdating() { - concurrentUpdates.incrementAndGet() - } - - fun commit() { - concurrentUpdates.decrementAndGet() - - // run update even in inner transaction - // (outer transaction may be async, so it would be better to not wait it) - scheduleUpdateIfInvalid() - } - - @Synchronized - private fun scheduleUpdateIfInvalid() { - if (!invalidated) return - invalidated = false - - if (syncUpdateRequired || ApplicationManager.getApplication().isUnitTestMode) { - syncUpdateRequired = false - updateSynchronously() - } else { - ensureUpdateScheduled() - } - } - - private val syncLock = ReentrantLock() - private var scheduledUpdate: ProgressIndicator? = null - - @Synchronized - fun ensureUpdateScheduled() { - scheduledUpdate?.cancel() - scheduledUpdate = BackgroundTaskUtil.executeOnPooledThread(project) { - doUpdate() - } - } - - @Synchronized - private fun updateSynchronously() { - scheduledUpdate?.cancel() - doUpdate(false) - } - - private fun doUpdate(underProgressManager: Boolean = true) { - syncLock.withLock { - try { - val updates = manager.collectRootsAndCheckNew() - - if (!updates.changed) return - - if (underProgressManager) { - ProgressManager.checkCanceled() - } - - if (updates.hasNewRoots) { - notifyRootsChanged() - } - - PsiElementFinder.EP.findExtensionOrFail(KotlinScriptDependenciesClassFinder::class.java, project) - .clearCache() - - ScriptDependenciesModificationTracker.getInstance(project).incModificationCount() - - if (updates.updatedScripts.isNotEmpty()) { - updateHighlighting(project) { - it.path in updates.updatedScripts - } - } - } catch (cancel: ProcessCanceledException) { - if (underProgressManager) throw cancel - } finally { - synchronized(this) { - scheduledUpdate = null - } - } - } - } - - private fun notifyRootsChanged() { - val doNotifyRootsChanged = Runnable { - runWriteAction { - if (project.isDisposed) return@runWriteAction - - debug { "roots change event" } - - ProjectRootManagerEx.getInstanceEx(project)?.makeRootsChange(EmptyRunnable.getInstance(), false, true) - ScriptDependenciesModificationTracker.getInstance(project).incModificationCount() - } - } - - if (ApplicationManager.getApplication().isUnitTestMode) { - TransactionGuard.submitTransaction(project, doNotifyRootsChanged) - } else { - TransactionGuard.getInstance().submitTransactionLater(project, doNotifyRootsChanged) - } - } - - 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/utils/TestingBackgroundExecutor.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/TestingBackgroundExecutor.kt index 608b4042563..7a15e63bcab 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/TestingBackgroundExecutor.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/TestingBackgroundExecutor.kt @@ -10,7 +10,7 @@ import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.containers.HashSetQueue class TestingBackgroundExecutor internal constructor( - private val rootsManager: ScriptClassRootsUpdater + private val rootsManager: ScriptClassRootsIndexer ) : BackgroundExecutor { val backgroundQueue = HashSetQueue() @@ -42,7 +42,7 @@ class TestingBackgroundExecutor internal constructor( actions() - rootsManager.update { + rootsManager.transaction { copy.forEach { it.actions() } diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/AsyncFileChangeListenerHelper.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/AsyncFileChangeListenerHelper.kt index ccc50c982c4..342b8d72c36 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/AsyncFileChangeListenerHelper.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/AsyncFileChangeListenerHelper.kt @@ -9,16 +9,12 @@ import com.intellij.openapi.externalSystem.service.project.autoimport.AsyncFileC import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.openapi.vfs.newvfs.events.VFileEvent -import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager -fun addVfsListener( - watcher: GradleScriptListener, - buildRootsManager: GradleBuildRootsManager -) { +fun addVfsListener(watcher: GradleScriptInputsWatcher) { VirtualFileManager.getInstance().addAsyncFileListener( object : AsyncFileChangeListenerBase() { override fun isRelevant(path: String): Boolean { - return buildRootsManager.maybeAffectedGradleProjectFile(path) + return isInAffectedGradleProjectFiles(watcher.project, path) } override fun updateFile(file: VirtualFile, event: VFileEvent) { diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/AsyncFileChangeListenerHelper.kt.191 b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/AsyncFileChangeListenerHelper.kt.191 index 97e653c6b62..01160790959 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/AsyncFileChangeListenerHelper.kt.191 +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/AsyncFileChangeListenerHelper.kt.191 @@ -9,12 +9,8 @@ import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.openapi.vfs.newvfs.BulkFileListener import com.intellij.openapi.vfs.newvfs.events.VFileEvent import com.intellij.util.concurrency.SequentialTaskExecutor -import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager -fun addVfsListener( - watcher: GradleScriptListener, - buildRootsManager: GradleBuildRootsManager -) { +fun addVfsListener(watcher: GradleScriptInputsWatcher) { watcher.project.messageBus.connect().subscribe( VirtualFileManager.VFS_CHANGES, object : BulkFileListener { @@ -26,7 +22,7 @@ fun addVfsListener( if (watcher.project.isDisposed) return@Runnable for (event in events) { val file = event.file ?: continue - if (buildRootsManager.maybeAffectedGradleProjectFile(event.path)) { + if (isInAffectedGradleProjectFiles(watcher.project, event.path)) { watcher.fileChanged(event.path, file.timeStamp) } } diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/AsyncFileChangeListenerHelper.kt.201 b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/AsyncFileChangeListenerHelper.kt.201 index 1488d07332f..16509e41bfe 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/AsyncFileChangeListenerHelper.kt.201 +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/AsyncFileChangeListenerHelper.kt.201 @@ -9,16 +9,12 @@ import com.intellij.openapi.externalSystem.autoimport.AsyncFileChangeListenerBas import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.openapi.vfs.newvfs.events.VFileEvent -import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager -fun addVfsListener( - watcher: GradleScriptListener, - buildRootsManager: GradleBuildRootsManager -) { +fun addVfsListener(watcher: GradleScriptInputsWatcher) { VirtualFileManager.getInstance().addAsyncFileListener( object : AsyncFileChangeListenerBase() { override fun isRelevant(path: String): Boolean { - return buildRootsManager.maybeAffectedGradleProjectFile(path) + return isInAffectedGradleProjectFiles(watcher.project, path) } override fun updateFile(file: VirtualFile, event: VFileEvent) { diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/Configuration.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/Configuration.kt new file mode 100644 index 00000000000..2a43c7c4f7e --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/Configuration.kt @@ -0,0 +1,37 @@ +/* + * 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.util.io.FileUtil +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel + +data class ConfigurationData( + val templateClasspath: List, + val models: List +) + +class Configuration(val data: ConfigurationData) { + + private val scripts: Map + val sourcePath: MutableSet + + val classFilePath: MutableSet = mutableSetOf() + + init { + val allModels = data.models + + scripts = allModels.associateBy { it.file } + sourcePath = allModels.flatMapTo(mutableSetOf()) { it.sourcePath } + + classFilePath.addAll(data.templateClasspath) + allModels.flatMapTo(classFilePath) { it.classPath } + } + fun scriptModel(file: VirtualFile): KotlinDslScriptModel? { + return scripts[FileUtil.toSystemDependentName(file.path)] + } + +} \ 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..c08827db02e --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleClassRootsCache.kt @@ -0,0 +1,88 @@ +/* + * 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.VfsUtil +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.ScriptClassRootsCache.Companion.getScriptSdkOrDefault +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.idea.scripting.gradle.importing.KotlinDslScriptModel +import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition +import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper +import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource +import org.jetbrains.kotlin.scripting.resolve.adjustByDefinition +import java.io.File +import kotlin.script.experimental.api.* +import kotlin.script.experimental.jvm.JvmDependency +import kotlin.script.experimental.jvm.jdkHome +import kotlin.script.experimental.jvm.jvm + +class GradleClassRootsCache( + val project: Project, + val context: GradleKtsContext, + val configuration: Configuration +) : ScriptClassRootsCache( + project, + ScriptClassRootsStorage.Companion.Key("gradle"), + extractRoots(context, configuration, project) +) { + override fun getConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? = + configuration.scriptModel(file)?.toScriptConfiguration() + + private fun KotlinDslScriptModel.toScriptConfiguration(): ScriptCompilationConfigurationWrapper? { + val scriptFile = File(file) + val virtualFile = VfsUtil.findFile(scriptFile.toPath(), true)!! + + val definition = virtualFile.findScriptDefinition(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) + ) + } + + override fun getScriptSdk(file: VirtualFile): Sdk? { + return firstScriptSdk + } + + override val firstScriptSdk: Sdk? = getScriptSdkOrDefault(context.javaHome, project) + + // called to ensure that configuration for file is loaded + // as we cannot force loading, we always return true + override fun contains(file: VirtualFile): Boolean = true + + companion object { + fun extractRoots(context: GradleKtsContext, configuration: Configuration?, project: Project): ScriptClassRoots { + if (configuration == null) { + return ScriptClassRootsStorage.EMPTY + } + val scriptSdk = getScriptSdkOrDefault(context.javaHome, project) + if (scriptSdk != null && !scriptSdk.isAlreadyIndexed(project)) { + return ScriptClassRootsStorage.Companion.ScriptClassRoots( + configuration.classFilePath, + configuration.sourcePath, + setOf(scriptSdk) + ) + } + return ScriptClassRoots( + configuration.classFilePath, + configuration.sourcePath, + getScriptSdkOrDefault(context.javaHome, project)?.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 06e8967c07a..25dab3b34c9 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 @@ -38,6 +38,7 @@ 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) { val existingPanel = project.notificationPanel if (existingPanel != null) { 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 368c521591a..abe27c57ab4 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 @@ -21,17 +21,19 @@ import com.intellij.testFramework.LightVirtualFileBase import org.jetbrains.kotlin.idea.KotlinIcons import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModelResolver -import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager +import org.jetbrains.kotlin.psi.UserDataProperty import org.jetbrains.plugins.gradle.service.project.GradlePartialResolverPolicy +import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverExtension import org.jetbrains.plugins.gradle.util.GradleConstants +import java.util.function.Predicate fun runPartialGradleImport(project: Project) { - getGradleProjectSettings(project).forEach { gradleProjectSettings -> + getGradleProjectSettings(project).forEach { ExternalSystemUtil.refreshProject( - gradleProjectSettings.externalProjectPath, + it.externalProjectPath, ImportSpecBuilder(project, GradleConstants.SYSTEM_ID) .projectResolverPolicy( - GradlePartialResolverPolicy { it is KotlinDslScriptModelResolver } + GradlePartialResolverPolicy(Predicate { it is KotlinDslScriptModelResolver }) ) ) } @@ -79,6 +81,6 @@ class LoadConfigurationAction : AnAction( if (virtualFile is LightVirtualFileBase) return false if (virtualFile == null || !virtualFile.isValid) return false - return GradleBuildRootsManager.getInstance(project).isConfigurationOutOfDate(virtualFile) + return GradleScriptingSupportProvider.getInstance(project).isConfigurationOutOfDate(virtualFile) } } \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleKotlinScriptConfigurationInputs.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleKotlinScriptConfigurationInputs.kt index fdc9a682d12..ee313aee3d7 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleKotlinScriptConfigurationInputs.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleKotlinScriptConfigurationInputs.kt @@ -5,12 +5,10 @@ package org.jetbrains.kotlin.idea.scripting.gradle -import com.intellij.openapi.progress.ProcessCanceledException +import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigurationInputs -import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager -import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRoot import org.jetbrains.kotlin.psi.KtFile /** @@ -18,27 +16,19 @@ import org.jetbrains.kotlin.psi.KtFile * 1. It is out of date when essential [sections] are changed * @see getGradleScriptInputsStamp * 2. When some related file is changed (other gradle script, gradle.properties file) - * @see GradleBuildRoot.Linked.areRelatedFilesChangedBefore + * @see GradleScriptInputsWatcher.areRelatedFilesUpToDate * - * [lastModifiedTs] is needed to check if some related file was changed since last update + * [inputsTS] is needed to check if some related file was changed since last update */ data class GradleKotlinScriptConfigurationInputs( val sections: String, - val lastModifiedTs: Long, - val buildRoot: String? = null + val inputsTS: Long ) : CachedConfigurationInputs { override fun isUpToDate(project: Project, file: VirtualFile, ktFile: KtFile?): Boolean { - try { - val actualStamp = getGradleScriptInputsStamp(project, file, ktFile) ?: return false + val actualStamp = getGradleScriptInputsStamp(project, file, ktFile) ?: return false - if (actualStamp.sections != this.sections) return false + if (actualStamp.sections != this.sections) return false - return buildRoot == null || - GradleBuildRootsManager.getInstance(project) - .getBuildRoot(buildRoot) - ?.areRelatedFilesChangedBefore(file, lastModifiedTs) ?: false - } catch (cancel: ProcessCanceledException) { - return false - } + return project.service().areRelatedFilesUpToDate(file, inputsTS) } } \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleKtsContext.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleKtsContext.kt new file mode 100644 index 00000000000..e9a143defd5 --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleKtsContext.kt @@ -0,0 +1,10 @@ +/* + * 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 java.io.File + +class GradleKtsContext(val javaHome: File?) \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/legacy/GradleLegacyScriptConfigurationLoader.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptConfigurationLoader.kt similarity index 73% rename from idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/legacy/GradleLegacyScriptConfigurationLoader.kt rename to idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptConfigurationLoader.kt index f3429e70cb0..811e414f71a 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/legacy/GradleLegacyScriptConfigurationLoader.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptConfigurationLoader.kt @@ -1,9 +1,9 @@ /* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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.legacy +package org.jetbrains.kotlin.idea.scripting.gradle import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.Project @@ -15,20 +15,11 @@ import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigura import org.jetbrains.kotlin.idea.core.script.configuration.loader.DefaultScriptConfigurationLoader import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoadingContext import org.jetbrains.kotlin.idea.core.util.EDT -import org.jetbrains.kotlin.idea.scripting.gradle.getGradleScriptInputsStamp -import org.jetbrains.kotlin.idea.scripting.gradle.isGradleKotlinScript -import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager -import org.jetbrains.kotlin.idea.scripting.gradle.useScriptConfigurationFromImportOnly import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition -/** - * Loader that performs loading for .gralde.kts scripts configuration through the [DefaultScriptingSupport] - * - * TODO(gradle6): remove - */ -class GradleLegacyScriptConfigurationLoaderForOutOfProjectScripts(project: Project) : DefaultScriptConfigurationLoader(project) { +class GradleScriptConfigurationLoaderForOutOfProjectScripts(project: Project) : DefaultScriptConfigurationLoader(project) { override fun loadDependencies( isFirstLoad: Boolean, ktFile: KtFile, @@ -52,14 +43,7 @@ class GradleLegacyScriptConfigurationLoaderForOutOfProjectScripts(project: Proje } } -/** - * Loader that performs loading for .gralde.kts scripts configuration through the [DefaultScriptingSupport] - * - * TODO(gradle6): remove - */ -class GradleLegacyScriptConfigurationLoader(project: Project) : DefaultScriptConfigurationLoader(project) { - private val buildRootsManager = GradleBuildRootsManager.getInstance(project) - +class GradleScriptConfigurationLoader(project: Project) : DefaultScriptConfigurationLoader(project) { override fun shouldRunInBackground(scriptDefinition: ScriptDefinition): Boolean { return if (useScriptConfigurationFromImportOnly()) false else super.shouldRunInBackground(scriptDefinition) } @@ -80,7 +64,7 @@ class GradleLegacyScriptConfigurationLoader(project: Project) : DefaultScriptCon return true } - if (!buildRootsManager.isAffectedGradleProjectFile(vFile.path)) { + if (!isInAffectedGradleProjectFiles(ktFile.project, vFile.path)) { ScriptConfigurationManager.markFileWithManualConfigurationLoading(vFile) return true } diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptDefinitionsProvider.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptDefinitionsProvider.kt index de935af6ad8..f606109975d 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptDefinitionsProvider.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptDefinitionsProvider.kt @@ -11,7 +11,6 @@ import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil import com.intellij.openapi.project.Project import com.intellij.util.EnvironmentUtil import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle -import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionContributor import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionSourceAsContributor import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionsManager import org.jetbrains.kotlin.idea.core.script.loadDefinitionsFromTemplates @@ -41,21 +40,6 @@ import kotlin.script.experimental.location.ScriptExpectedLocation import kotlin.script.templates.standard.ScriptTemplateWithArgs class GradleScriptDefinitionsContributor(private val project: Project) : ScriptDefinitionSourceAsContributor { - companion object { - fun getDefinitions(project: Project) = - ScriptDefinitionContributor.EP_NAME.getExtensions(project) - .filterIsInstance() - .single().definitions.toList() - - fun getDefinitionsTemplateClasspath(project: Project): MutableSet = - mutableSetOf().also { result -> - getDefinitions(project).forEach { definition -> - definition.asLegacyOrNull() - ?.templateClasspath - ?.mapNotNullTo(result) { it.path } - } - } - } override val id: String = "Gradle Kotlin DSL" private val failedToLoad = AtomicBoolean(false) @@ -144,8 +128,8 @@ class GradleScriptDefinitionsContributor(private val project: Project) : ScriptD private fun kotlinStdlibAndCompiler(gradleLibDir: File): List { // additionally need compiler jar to load gradle resolver return gradleLibDir.listFiles { file -> - file.name.startsWith("kotlin-compiler-embeddable") || file.name.startsWith("kotlin-stdlib") - } + file.name.startsWith("kotlin-compiler-embeddable") || file.name.startsWith("kotlin-stdlib") + } .firstOrNull()?.let(::listOf).orEmpty() } diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcher.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcher.kt new file mode 100644 index 00000000000..8215b419414 --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcher.kt @@ -0,0 +1,91 @@ +/* + * 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.components.* +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.annotations.TestOnly +import org.jetbrains.plugins.gradle.settings.GradleProjectSettings +import org.jetbrains.plugins.gradle.util.GradleConstants + +@State( + name = "KotlinBuildScriptsModificationInfo", + storages = [Storage(StoragePathMacros.CACHE_FILE)] +) +class GradleScriptInputsWatcher(val project: Project) : PersistentStateComponent { + private var storage = Storage() + + private var cachedGradleProjectsRoots: Set? = null + + fun getGradleProjectsRoots(): Set { + if (cachedGradleProjectsRoots == null) { + cachedGradleProjectsRoots = computeGradleProjectRoots(project) + } + return cachedGradleProjectsRoots ?: emptySet() + } + + fun saveGradleProjectRootsAfterImport(roots: Set) { + val oldRoots = cachedGradleProjectsRoots + if (oldRoots != null && oldRoots.isNotEmpty()) { + cachedGradleProjectsRoots = oldRoots + roots + } else { + cachedGradleProjectsRoots = roots + } + } + + private fun computeGradleProjectRoots(project: Project): Set { + val gradleSettings = ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID) + if (gradleSettings.getLinkedProjectsSettings().isEmpty()) return setOf() + + val projectSettings = gradleSettings.getLinkedProjectsSettings().filterIsInstance().firstOrNull() + ?: return setOf() + + return projectSettings.modules.takeIf { it.isNotEmpty() } ?: setOf(projectSettings.externalProjectPath) + } + + fun startWatching() { + addVfsListener(this) + } + + fun areRelatedFilesUpToDate(file: VirtualFile, timeStamp: Long): Boolean { + return storage.lastModifiedTimeStampExcept(file.path) < timeStamp + } + + class Storage { + private val lastModifiedFiles = LastModifiedFiles() + + fun lastModifiedTimeStampExcept(filePath: String): Long { + return lastModifiedFiles.lastModifiedTimeStampExcept(filePath) + } + + fun fileChanged(filePath: String, ts: Long) { + lastModifiedFiles.fileChanged(ts, filePath) + } + } + + override fun getState(): Storage { + return storage + } + + override fun loadState(state: Storage) { + this.storage = state + } + + fun fileChanged(filePath: String, ts: Long) { + storage.fileChanged(filePath, ts) + } + + fun clearState() { + storage = Storage() + } + + @TestOnly + fun clearAndRefillState() { + loadState(project.service().state) + } +} \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptListener.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptListener.kt index d14921422eb..035e3f82f97 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptListener.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptListener.kt @@ -1,62 +1,44 @@ /* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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.components.service import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener -import org.jetbrains.kotlin.idea.scripting.gradle.legacy.GradleLegacyScriptListener -import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager - -class GradleScriptListener(project: Project) : ScriptChangeListener(project) { - // todo(gradle6): remove - private val legacy = GradleLegacyScriptListener(project) - private val buildRootsManager = GradleBuildRootsManager.getInstance(project) +import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater +open class GradleScriptListener(project: Project) : ScriptChangeListener(project) { init { - // listen changes using VFS events, including gradle-configuration related files - addVfsListener(this, buildRootsManager) + // start GradleScriptInputsWatcher to track changes in gradle-configuration related files + project.service().startWatching() } - fun fileChanged(filePath: String, ts: Long) = - buildRootsManager.fileChanged(filePath, ts) + override fun editorActivated(vFile: VirtualFile, updater: ScriptConfigurationUpdater) { + if (!isInAffectedGradleProjectFiles(project, vFile.path)) return - override fun isApplicable(vFile: VirtualFile) = - // todo(gradle6): replace with `isCustomScriptingSupport(vFile)` - legacy.isApplicable(vFile) - - private fun isCustomScriptingSupport(vFile: VirtualFile) = - buildRootsManager.isApplicable(vFile) - - override fun editorActivated(vFile: VirtualFile) { - if (isCustomScriptingSupport(vFile)) { - checkUpToDate(vFile) + if (useScriptConfigurationFromImportOnly()) { + // do nothing } else { - legacy.editorActivated(vFile) + val file = getAnalyzableKtFileForScript(vFile) ?: return + updater.suggestToUpdateConfigurationIfOutOfDate(file) } } - override fun documentChanged(vFile: VirtualFile) { - fileChanged(vFile.path, System.currentTimeMillis()) + override fun documentChanged(vFile: VirtualFile, updater: ScriptConfigurationUpdater) { + if (!isInAffectedGradleProjectFiles(project, vFile.path)) return - if (isCustomScriptingSupport(vFile)) { - checkUpToDate(vFile) - } else { - legacy.documentChanged(vFile) + val file = getAnalyzableKtFileForScript(vFile) + if (file != null) { + // *.gradle.kts file was changed + updater.suggestToUpdateConfigurationIfOutOfDate(file) } } - private fun checkUpToDate(vFile: VirtualFile) { - val upToDate = GradleBuildRootsManager.getInstance(project) - .getScriptInfo(vFile)?.model?.inputs?.isUpToDate(project, vFile) ?: return - - if (upToDate) { - hideNotificationForProjectImport(project) - } else { - showNotificationForProjectImport(project) - } + override fun isApplicable(vFile: VirtualFile): Boolean { + return isGradleKotlinScript(vFile) } } \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptingSupport.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptingSupport.kt new file mode 100644 index 00000000000..3a464bb93b6 --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptingSupport.kt @@ -0,0 +1,62 @@ +/* + * 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.project.Project +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport +import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper + +class GradleScriptingSupport( + val project: Project, + val buildRoot: VirtualFile, + val context: GradleKtsContext, + val configuration: Configuration +) : ScriptingSupport() { + + override fun recreateRootsCache() = GradleClassRootsCache(project, context, configuration) + + fun isConfigurationOutOfDate(file: VirtualFile): Boolean { + val scriptModel = configuration.scriptModel(file) ?: return false + return !scriptModel.inputs.isUpToDate(project, file) + } + + override fun clearCaches() { + // todo: should clear up to date + } + + override fun hasCachedConfiguration(file: KtFile): Boolean = + configuration.scriptModel(file.originalFile.virtualFile) != null + + // TODO: can be true during import + override fun isConfigurationLoadingInProgress(file: KtFile): Boolean = false + + override fun getOrLoadConfiguration(virtualFile: VirtualFile, preloadedKtFile: KtFile?): ScriptCompilationConfigurationWrapper? { + 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) { + val vFile = file.originalFile.virtualFile + + if (isConfigurationOutOfDate(vFile)) { + showNotificationForProjectImport(project) + } else { + hideNotificationForProjectImport(project) + } + } + } +} \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptingSupportProvider.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptingSupportProvider.kt new file mode 100644 index 00000000000..64fd3f4ed1d --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptingSupportProvider.kt @@ -0,0 +1,208 @@ +/* + * 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.application.ApplicationManager +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.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.KotlinDslGradleBuildSync +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 org.jetbrains.plugins.gradle.config.GradleSettingsListenerAdapter +import org.jetbrains.plugins.gradle.settings.GradleExecutionSettings +import org.jetbrains.plugins.gradle.settings.GradleProjectSettings +import org.jetbrains.plugins.gradle.settings.GradleSettingsListener +import org.jetbrains.plugins.gradle.util.GradleConstants +import java.io.File +import java.nio.file.Paths + +/** + * Creates [GradleScriptingSupport] per each linked Gradle build. + * Gradle project sync should call [update]. + */ +class GradleScriptingSupportProvider(val project: Project) : ScriptingSupport.Provider() { + private val rootsIndexer = ScriptClassRootsIndexer(project) + + val roots = RootsIndex() + override val all get() = roots.values + + private val VirtualFile.localPath + get() = path + + private fun findRoot(file: VirtualFile) = roots.findRoot(file.localPath) + + override fun getSupport(file: VirtualFile): ScriptingSupport? { + if (isGradleKotlinScript(file)) { + findRoot(file)?.let { return it } + + val externalProjectSettings = findExternalProjectSettings(file) ?: return null + if (kotlinDslScriptsModelImportSupported(getGradleVersion(project, externalProjectSettings))) { + return unlinkedFilesSupport + } + } + return null + } + + private fun findExternalProjectSettings(file: VirtualFile): GradleProjectSettings? { + return getGradleProjectSettings(project) + .filter { file.localPath.startsWith(it.externalProjectPath) } + .maxBy { it.externalProjectPath.length } + } + + init { + ApplicationManager.getApplication().executeOnPooledThread { + roots.update { set -> + getGradleProjectSettings(project).forEach { gradleProjectSettings -> + if (kotlinDslScriptsModelImportSupported(getGradleVersion(project, gradleProjectSettings))) { + val support = createSupport(gradleProjectSettings.externalProjectPath) { + KotlinDslScriptModels.read(it) + } + + if (support != null) { + set(support.buildRoot.localPath, support) + } + } + } + } + } + + // subscribe to gradle build unlink + val listener = object : GradleSettingsListenerAdapter() { + override fun onProjectsUnlinked(linkedProjectPaths: MutableSet) { + linkedProjectPaths.forEach { + val buildRoot = VfsUtil.findFile(Paths.get(it), false) + if (buildRoot != null) { + if (roots.remove(buildRoot.localPath) != null) { + KotlinDslScriptModels.remove(buildRoot) + } + } + } + } + } + + project.messageBus.connect(project).subscribe(GradleSettingsListener.TOPIC, listener) + } + + fun update(build: KotlinDslGradleBuildSync) { + // fast path for linked gradle builds without .gradle.kts support + if (build.models.isEmpty()) { + val root = roots.findRoot(build.workingDir) ?: return + if (root.configuration.data.models.isEmpty()) return + } + + val templateClasspath = findTemplateClasspath(build) ?: return + val data = ConfigurationData(templateClasspath, build.models) + val newSupport = createSupport(build.workingDir) { data } ?: return + KotlinDslScriptModels.write(newSupport.buildRoot, data) + + roots[newSupport.buildRoot.localPath] = newSupport + } + + private fun createSupport( + externalProjectPath: String, + dataProvider: (buildRoot: VirtualFile) -> ConfigurationData? + ): GradleScriptingSupport? { + val gradleExeSettings = + ExternalSystemApiUtil.getExecutionSettings( + project, + externalProjectPath, + GradleConstants.SYSTEM_ID + ) + + val buildRoot = VfsUtil.findFile(Paths.get(externalProjectPath), true) ?: return null + val data = dataProvider(buildRoot) ?: return null + + val newSupport = GradleScriptingSupport( + project, + buildRoot, + GradleKtsContext(gradleExeSettings.javaHome?.let { File(it) }), + Configuration(data) + ) + + val oldSupport = roots.findRoot(externalProjectPath) + if (oldSupport != null) { + rootsIndexer.transaction { + val newRoots = GradleClassRootsCache.extractRoots(newSupport.context, newSupport.configuration, project) + if (oldSupport.classpathRoots.hasNotCachedRoots(newRoots)) { + rootsIndexer.markNewRoot() + } + + newSupport.clearClassRootsCaches(project) + + ScriptingSupportHelper.updateHighlighting(project) { + newSupport.configuration.scriptModel(it) != null + } + } + + hideNotificationForProjectImport(project) + } + + return newSupport + } + + private fun findTemplateClasspath(build: KotlinDslGradleBuildSync): List? { + val anyScript = VfsUtil.findFile(Paths.get(build.models.first().file), true)!! + // todo: find definition according to build.workingDir + val definition = anyScript.findScriptDefinition(project) ?: return null + return definition.asLegacyOrNull() + ?.templateClasspath?.map { it.path } + } + + private val unlinkedFilesSupport = object : ScriptingSupport() { + override fun clearCaches() { + } + + override fun hasCachedConfiguration(file: KtFile): Boolean = false + override fun isConfigurationLoadingInProgress(file: KtFile): Boolean = false + + override fun getOrLoadConfiguration( + virtualFile: VirtualFile, + preloadedKtFile: KtFile? + ): ScriptCompilationConfigurationWrapper? = null + + override val updater: ScriptConfigurationUpdater + get() = object : ScriptConfigurationUpdater { + override fun ensureUpToDatedConfigurationSuggested(file: KtFile) { + } + + override fun ensureConfigurationUpToDate(files: List): Boolean = false + + override fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile) { + } + } + + override fun recreateRootsCache(): ScriptClassRootsCache = ScriptClassRootsCache.empty(project) + } + + fun isMissingConfigurationCanBeLoadedDuringImport(file: VirtualFile): Boolean { + findRoot(file)?.let { return false } + + val externalProjectSettings = findExternalProjectSettings(file) ?: return false + return kotlinDslScriptsModelImportSupported(getGradleVersion(project, externalProjectSettings)) + } + + // used in 201 + @Suppress("UNUSED") + fun isConfigurationOutOfDate(file: VirtualFile): Boolean { + val support = findRoot(file) ?: return false + return support.isConfigurationOutOfDate(file) + } + + companion object { + fun getInstance(project: Project): GradleScriptingSupportProvider = + EPN.getPoint(project).extensionList.firstIsInstance() + } +} \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/LastModifiedFiles.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/LastModifiedFiles.kt index 4e3f6c299a3..c4b18e8a559 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/LastModifiedFiles.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/LastModifiedFiles.kt @@ -5,15 +5,6 @@ package org.jetbrains.kotlin.idea.scripting.gradle -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.openapi.vfs.newvfs.FileAttribute -import org.jetbrains.kotlin.idea.core.util.readNullable -import org.jetbrains.kotlin.idea.core.util.readStringList -import org.jetbrains.kotlin.idea.core.util.writeNullable -import org.jetbrains.kotlin.idea.core.util.writeStringList -import java.io.DataInputStream -import java.io.DataOutput - /** * Optimized collection for storing last modified files with ability to * get time of last modified file expect given one ([lastModifiedTimeStampExcept]). @@ -23,14 +14,9 @@ import java.io.DataOutput * * Actually works by storing two last timestamps with the set of files modified at this times. */ -class LastModifiedFiles( - private var last: SimultaneouslyChangedFiles = SimultaneouslyChangedFiles(), +class LastModifiedFiles { + private var last: SimultaneouslyChangedFiles = SimultaneouslyChangedFiles() private var previous: SimultaneouslyChangedFiles = SimultaneouslyChangedFiles() -) { - init { - previous.fileIds.removeAll(last.fileIds) - if (previous.fileIds.isEmpty()) previous = SimultaneouslyChangedFiles() - } class SimultaneouslyChangedFiles( val ts: Long = Long.MIN_VALUE, @@ -41,10 +27,7 @@ class LastModifiedFiles( fun fileChanged(ts: Long, fileId: String) { when { ts > last.ts -> { - val prevPrev = previous previous = last - previous.fileIds.remove(fileId) - if (previous.fileIds.isEmpty()) previous = prevPrev last = SimultaneouslyChangedFiles(ts, hashSetOf(fileId)) } ts == last.ts -> last.fileIds.add(fileId) @@ -57,36 +40,4 @@ class LastModifiedFiles( last.fileIds.size == 1 && last.fileIds.contains(fileId) -> previous.ts else -> last.ts } - - companion object { - private val fileAttribute = FileAttribute("last-modified-files", 1, false) - - fun read(buildRoot: VirtualFile): LastModifiedFiles? { - return fileAttribute.readAttribute(buildRoot)?.use { - it.readNullable { - LastModifiedFiles(readSCF(it), readSCF(it)) - } - } - } - - fun write(buildRoot: VirtualFile, data: LastModifiedFiles?) { - fileAttribute.writeAttribute(buildRoot).use { - it.writeNullable(data) { data -> - writeSCF(data.last) - writeSCF(data.previous) - } - } - } - - fun remove(buildRoot: VirtualFile) { - write(buildRoot, null) - } - - private fun readSCF(it: DataInputStream) = SimultaneouslyChangedFiles(it.readLong(), it.readStringList().toMutableSet()) - - private fun DataOutput.writeSCF(last: SimultaneouslyChangedFiles) { - writeLong(last.ts) - writeStringList(last.fileIds) - } - } } \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/MissingGradleScriptConfigurationNotificationProvider.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/MissingGradleScriptConfigurationNotificationProvider.kt index 9919dffa76f..00330d665d6 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/MissingGradleScriptConfigurationNotificationProvider.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/MissingGradleScriptConfigurationNotificationProvider.kt @@ -16,24 +16,18 @@ import com.intellij.ui.EditorNotificationPanel import com.intellij.ui.EditorNotifications import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle -import org.jetbrains.kotlin.idea.scripting.gradle.legacy.GradleLegacyScriptConfigurationLoaderForOutOfProjectScripts -import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRoot -import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager import org.jetbrains.plugins.gradle.settings.GradleProjectSettings import org.jetbrains.plugins.gradle.util.GradleConstants -class MissingGradleScriptConfigurationNotificationProvider(private val project: Project) : - EditorNotifications.Provider() { +class MissingGradleScriptConfigurationNotificationProvider(private val project: Project) : EditorNotifications.Provider() { override fun getKey(): Key = KEY override fun createNotificationPanel(file: VirtualFile, fileEditor: FileEditor): EditorNotificationPanel? { if (!isGradleKotlinScript(file)) return null if (file.fileType != KotlinFileType.INSTANCE) return null - val scriptUnderRoot = GradleBuildRootsManager.getInstance(project).findScriptBuildRoot(file) - val root = scriptUnderRoot?.root - return when { - root is GradleBuildRoot.Unlinked -> EditorNotificationPanel().apply { + if (!isInAffectedGradleProjectFiles(project, file.path)) { + return EditorNotificationPanel().apply { text(KotlinIdeaGradleBundle.message("text.the.associated.gradle.project.isn.t.imported")) val linkProjectText = KotlinIdeaGradleBundle.message("action.label.text.load.script.configuration") @@ -59,20 +53,22 @@ class MissingGradleScriptConfigurationNotificationProvider(private val project: linkProjectText ) } - root is GradleBuildRoot.New && !root.importing -> EditorNotificationPanel().apply { + } + + if (GradleScriptingSupportProvider.getInstance(project).isMissingConfigurationCanBeLoadedDuringImport(file)) { + return EditorNotificationPanel().apply { text(getMissingConfigurationNotificationText()) createActionLabel(getMissingConfigurationActionText()) { runPartialGradleImport(project) } } - else -> null } + + return null } private val loaderForOutOfProjectScripts by lazy { - GradleLegacyScriptConfigurationLoaderForOutOfProjectScripts( - project - ) + GradleScriptConfigurationLoaderForOutOfProjectScripts(project) } companion object { diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/README.md b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/README.md deleted file mode 100644 index 400d46e2db1..00000000000 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# Kotlin Gradle build scripts support. - -## Why custom Scripting support - -The scripting support for Gradle becomes tricky since the default one was designed for a little bit another case: each script becomes configured individually on first opening/usage and loading supposes to be instant. - -Unfortunately, this is not working well for the Gradle case: the script configuration gathering is long operation since the Gradle configuration phase required to be executed to get it. For large projects, it may take from 5 seconds for hot daemon and to minutes for initial project loading. It may download dependencies, compile and build buildSrc project and all these things will be done on first script usage, even when we need just to know is such word in reference to our symbol when we are calling "Find usages". Among that, Gradle is not able to gather individual script configuration and always provides configuration for all scripts. - -Taking this in mind, it becomes better to have this strategy for Gradle scripts: -- The user performing Gradle project importing/reimporting by executing Gradle configuration phase -- We have all Gradle scripts configured -- We can save this configuration to FS cache which can be persisted between IntelliJ restarts. - -## More details about what we already have - -[ScriptConfigurationManager] service already existed for providing and updating scripts configuration. It was working by loading scripts configuration on the first opening, storing in-memory cache, and FS cache. - -It also reloads it after each typing (with some throttling) and notifies the user if the configuration is changed with a suggestion to apply a new configuration. We may apply this configuration automatically, but it may introduce indexing which breaks UX (some action becomes unavailable during indexing). To leverage this situation, we are showing notification instead and indexing will occur only after an explicit user action. We can do this automatically if no new source and classes are introduced in dependencies, but this is not implemented yet. - -Note: as we don't know in general when some script change causes script configuration change, we always run configuration reloading and comparing new configuration with the old one. If we can know that some particular change will affect script configuration, we can show notification before starting expensive loading, but this requires some knowledge of the script, so should be implemented by the script definition author. [ScriptConfigurationManager] provides ability to implement up-to-date stamp by extension point. It is implemented for the older Gradle versions, see "DefaultScriptingSupport extensions for older Gradle versions" below. - -The manager also creates a list of all classes and sources of script dependencies to properly support analyzing of scripts. All these calculations are cached and invalidated on each change in script configurations. - -Sometimes this loading may occur in the background, sometimes in sync. We also may apply configuration automatically in some cases (first loading, loading in sync, etc). All that introduces complex states intersection which is tricky to support. Adding another layer of complexity to that implementation is not a way to go. - -## Custom scripting support - -After some experiments and several iterations, the final idea is to provide the ability to create custom mechanics for managing the scripts configurations without breaking default scripting support. - -As we already need a cache of all known scripts, the union of its classpath, and source path which is rebuilt from the scratch on any change, we may just provide the ability to populate this cache with custom content on each invalidation. That the main idea behind custom scripting support. - -The other idea is to don't make any blocking work synchronously while getting some configuration. The cache should be always ready for unblocking reading. All updating operation is asynchronous and non-blocking thanks to copy-on-write strategy. - -So, it should work in that way: -- Custom scripting support should manage the scripts configurations on its own (with own in-memory/fs cache). -- It should implement a method that will gather all script configuration providers and populate the classpath/sourcepath union that is used to get files for indexing. -- It may implement ScriptChangeListener extension point to do some invalidation on document change events. -- It should call `updater.update { ... updater.invalidate() ... }` when the new script configurations are required. The manager will schedule an asynchronous update in the background. -- It is not able to load new configurations lazily on the first request (it is still possible to calculate script configuration lazily, but file name should be already registered) - -The [CompositeScriptConfigurationManager] will provide redirection of [ScriptConfigurationManager] calls to the custom [ScriptingSupport] or [DefaultScriptingSupport]. - -## Gradle CustomScriptingSupport - -`GradleBuildRootsManager` implementing `ScriptingSupport` described above. - -There are many Gradle builds can be linked with a single IntelliJ project (don't confuse with the Gradle project and included build). This complicates implementation a bit, as we should manage this builds separately: each of them may have its own script definitions, Gradle version and java home. Typically, the IntelliJ project has no more than one [GradleBuildRoot]. - -`GradleBuildRootsManager` actually managing linked Gradle builds. See it's KDoc for more details. - -The script configuration is stored in FS by using the IntelliJ VFS file attributes. - -Note that we can provide custom scripting support only for projects that using Gradle 6.0 and later, as gathering script models unavailable in older Gradle versions. `GradleBuildRootsManager` falling back to default scripting support for such linked Gradle builds. It is extended through: see "DefaultScriptingSupport extensions for older Gradle versions" for more details. - -## Watching files states across IntelliJ restarts - -To have consistent sate of scripts, we should also be aware of external script changes. This is achieved by watching files using the IntelliJ VFS events. [GradleScriptInputsWatcher] is responsible for that. - -The first tricky part is that scripts are depending on each other: so, when one script is changed, we actually should invalidate all other scripts as we don't know dependencies between them (Gradle will provide this information later, but it is not yet implemented). Actually, we should know the last modified timestamp of all scripts excepting a particular one. This can be achieved by storing timestamps of two last modified files. [LastModifiedFiles] utility is responsible for that. - -Another tricky part is that we should track only scripts belong the Gradle project and should ignore all other `*.gradle.kts` files (in `testData` for example). This is achieved by storing Gradle project roots, as scripts can be exactly near Gradle project roots (excepting included and precompiled scripts which are not fully supported yet). This can be gathered from the Gradle project import information or from GradleProjectSettings when the import has not occurred yet. `GradleBuildRootsManager` does it. - -## Out of project Gradle scripts - -There are scripts that are not linked to any import Gradle project. In `testData` for example. Currently, we are suggesting to import Gradle project for such scripts, but we also may implement standalone Gradle scripts supporting using the `DefaultScriptingSupport` for explicitly listed scripts. We can implement this in the future if it will be valuable for users. - -## DefaultScriptingSupport extensions for older Gradle versions - -For Gradle versions before 6.0, we are still falling back to the [DefaultScriptingSupport] with some extensions. - -We are showing notification before loading as we knew what changes will cause it. This is done by: -- implementing `org.jetbrains.kotlin.scripting.idea.listener` extension point and calling `suggestToUpdateConfigurationIfOutOfDate` instead of `ensureUpToDatedConfigurationSuggested` on document changes. -- implementing `org.jetbrains.kotlin.scripting.idea.loader` extension point and overriding `getInputsStamp` - -We are also managing out of project scripts the same way as it is done in `GradleBuildRootsManager`. \ No newline at end of file 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 6960d9747df..0e78d56a80f 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 @@ -5,17 +5,19 @@ 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.project.Project import com.intellij.openapi.util.Key -import com.intellij.openapi.util.io.FileUtil.toSystemIndependentName +import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vfs.VfsUtil import org.gradle.tooling.model.kotlin.dsl.EditorReportSeverity import org.gradle.tooling.model.kotlin.dsl.KotlinDslScriptsModel import org.jetbrains.kotlin.gradle.BrokenKotlinDslScriptsModel import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle +import org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptInputsWatcher +import org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptingSupportProvider import org.jetbrains.kotlin.idea.scripting.gradle.getGradleScriptInputsStamp -import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager import org.jetbrains.kotlin.psi.NotNullableUserDataProperty import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext import java.io.File @@ -90,19 +92,18 @@ private fun KotlinDslScriptsModel.toListOfScriptModels(project: Project): List() val models = mutableListOf() - var failed = false } fun saveScriptModels(project: Project, build: KotlinDslGradleBuildSync) { @@ -112,8 +113,11 @@ fun saveScriptModels(project: Project, build: KotlinDslGradleBuildSync) { errorReporter.reportError(File(model.file), model) } - // todo: use real info about projects - build.projectRoots.addAll(build.models.map { toSystemIndependentName(File(it.file).parent) }) + project.service().saveGradleProjectRootsAfterImport( + build.models.map { FileUtil.toSystemIndependentName(File(it.file).parent) }.toSet() + ) - GradleBuildRootsManager.getInstance(project).update(build) + GradleScriptingSupportProvider.getInstance(project).update(build) + + project.service().clearState() } \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslSyncListener.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslSyncListener.kt index 1f13b26c437..f6e78171caf 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslSyncListener.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslSyncListener.kt @@ -9,8 +9,6 @@ import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListenerAdapter import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType import org.jetbrains.kotlin.idea.framework.GRADLE_SYSTEM_ID -import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager -import java.lang.Exception class KotlinDslSyncListener : ExternalSystemTaskNotificationListenerAdapter() { override fun onStart(id: ExternalSystemTaskId, workingDir: String?) { @@ -20,7 +18,6 @@ class KotlinDslSyncListener : ExternalSystemTaskNotificationListenerAdapter() { if (workingDir == null) return val project = id.findProject() ?: return - GradleBuildRootsManager.getInstance(project).markImportingInProgress(workingDir) project.kotlinGradleDslSync[id] = KotlinDslGradleBuildSync(workingDir, id) } @@ -33,26 +30,4 @@ class KotlinDslSyncListener : ExternalSystemTaskNotificationListenerAdapter() { saveScriptModels(project, sync) } - - override fun onFailure(id: ExternalSystemTaskId, e: Exception) { - if (id.type != ExternalSystemTaskType.RESOLVE_PROJECT) return - if (id.projectSystemId != GRADLE_SYSTEM_ID) return - - val project = id.findProject() ?: return - val sync = project.kotlinGradleDslSync[id] ?: return - - sync.failed = true - } - - override fun onCancel(id: ExternalSystemTaskId) { - if (id.type != ExternalSystemTaskType.RESOLVE_PROJECT) return - if (id.projectSystemId != GRADLE_SYSTEM_ID) return - - val project = id.findProject() ?: return - - val cancelled = project.kotlinGradleDslSync.remove(id) - if (cancelled != null) { - GradleBuildRootsManager.getInstance(project).markImportingInProgress(cancelled.workingDir, false) - } - } } diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/legacy/GradleLegacyScriptListener.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/legacy/GradleLegacyScriptListener.kt deleted file mode 100644 index dd6a1bbb31b..00000000000 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/legacy/GradleLegacyScriptListener.kt +++ /dev/null @@ -1,37 +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.legacy - -import com.intellij.openapi.project.Project -import com.intellij.openapi.vfs.VirtualFile -import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener -import org.jetbrains.kotlin.idea.scripting.gradle.isGradleKotlinScript -import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager - -// called from GradleScriptListener -// todo(gradle6): remove -class GradleLegacyScriptListener(project: Project) : ScriptChangeListener(project) { - private val buildRootsManager = GradleBuildRootsManager.getInstance(project) - - override fun isApplicable(vFile: VirtualFile) = - isGradleKotlinScript(vFile) - - override fun editorActivated(vFile: VirtualFile) = - checkUpToDate(vFile) - - override fun documentChanged(vFile: VirtualFile) = - checkUpToDate(vFile) - - private fun checkUpToDate(vFile: VirtualFile) { - if (!buildRootsManager.isAffectedGradleProjectFile(vFile.path)) return - - val file = getAnalyzableKtFileForScript(vFile) - if (file != null) { - // *.gradle.kts file was changed - default.suggestToUpdateConfigurationIfOutOfDate(file) - } - } -} \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRoot.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRoot.kt deleted file mode 100644 index fdbe8575da5..00000000000 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRoot.kt +++ /dev/null @@ -1,153 +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.roots - -import com.intellij.openapi.project.Project -import com.intellij.openapi.vfs.LocalFileSystem -import com.intellij.openapi.vfs.VirtualFile -import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache -import org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptDefinitionsContributor -import org.jetbrains.kotlin.idea.scripting.gradle.LastModifiedFiles -import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel -import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition -import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource -import org.jetbrains.plugins.gradle.settings.GradleProjectSettings -import java.io.File - -/** - * [GradleBuildRoot] is a linked gradle build (don't confuse with gradle project and included build). - * Each [GradleBuildRoot] may have it's own Gradle version, Java home and other settings. - * - * Typically, IntelliJ project have no more than one [GradleBuildRoot]. - * - * See [GradleBuildRootsManager] for more details. - */ -sealed class GradleBuildRoot { - /** - * The script not related to any Gradle build that is linked to IntelliJ Project, - * or we cannot known what is it - */ - @Suppress("CanSealedSubClassBeObject") - class Unlinked : GradleBuildRoot() - - /** - * Linked project, that may be itself: [Legacy], [New] or [Imported]. - */ - abstract class Linked : GradleBuildRoot() { - @Volatile - var importing = false - - abstract val manager: GradleBuildRootsManager - - abstract val pathPrefix: String - - abstract val projectRoots: Collection - - private val dir: VirtualFile? - get() = LocalFileSystem.getInstance().findFileByPath(pathPrefix) - - private lateinit var lastModifiedFiles: LastModifiedFiles - - fun loadLastModifiedFiles() { - lastModifiedFiles = dir?.let { LastModifiedFiles.read(it) } ?: LastModifiedFiles() - } - - fun saveLastModifiedFiles() { - LastModifiedFiles.write(dir ?: return, lastModifiedFiles) - } - - fun areRelatedFilesChangedBefore(file: VirtualFile, lastModified: Long): Boolean = - lastModifiedFiles.lastModifiedTimeStampExcept(file.path) < lastModified - - fun fileChanged(filePath: String, ts: Long) { - lastModifiedFiles.fileChanged(ts, filePath) - manager.scheduleLastModifiedFilesSave() - } - } - - abstract class WithoutScriptModels(settings: GradleProjectSettings) : Linked() { - final override val pathPrefix = settings.externalProjectPath!! - final override val projectRoots = settings.modules.takeIf { it.isNotEmpty() } ?: listOf(pathPrefix) - - init { - loadLastModifiedFiles() - } - } - - /** - * Gradle build with old Gradle version (<6.0) - */ - class Legacy( - override val manager: GradleBuildRootsManager, - settings: GradleProjectSettings - ) : WithoutScriptModels(settings) { - init { - loadLastModifiedFiles() - } - } - - /** - * Linked but not yet imported Gradle build. - */ - class New( - override val manager: GradleBuildRootsManager, - settings: GradleProjectSettings - ) : WithoutScriptModels(settings) { - init { - loadLastModifiedFiles() - } - } - - /** - * Imported Gradle build. - * Each imported build have info about all of it's Kotlin Build Scripts. - */ - class Imported( - override val manager: GradleBuildRootsManager, - val dir: VirtualFile, - val javaHome: File?, - val data: GradleBuildRootData - ) : Linked() { - val project: Project - get() = manager.project - - override val pathPrefix: String = dir.path - - override val projectRoots: Collection - get() = data.projectRoots - - init { - loadLastModifiedFiles() - } - - fun collectConfigurations(builder: ScriptClassRootsCache.Builder) { - if (javaHome != null) { - builder.addSdk(javaHome) - } - - val definitions = GradleScriptDefinitionsContributor.getDefinitions(project) - - builder.classes.addAll(data.templateClasspath) - data.models.forEach { script -> - val definition = selectScriptDefinition(script, definitions) - - builder.scripts[script.file] = GradleScriptInfo(this, definition, script) - - builder.classes.addAll(script.classPath) - builder.sources.addAll(script.sourcePath) - } - } - - private fun selectScriptDefinition( - script: KotlinDslScriptModel, - definitions: List - ): ScriptDefinition? { - val file = LocalFileSystem.getInstance().findFileByPath(script.file) ?: return null - val scriptSource = VirtualFileScriptSource(file) - return definitions.firstOrNull { it.isScript(scriptSource) } - } - } -} \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootData.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootData.kt deleted file mode 100644 index 2075893163b..00000000000 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootData.kt +++ /dev/null @@ -1,14 +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.roots - -import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel - -data class GradleBuildRootData( - val projectRoots: Collection, - val templateClasspath: Collection, - val models: Collection -) \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootIndex.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootIndex.kt deleted file mode 100644 index 81a1436d222..00000000000 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootIndex.kt +++ /dev/null @@ -1,60 +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.roots - -import com.intellij.openapi.diagnostic.logger - -class GradleBuildRootIndex { - private val log = logger() - - private val byWorkingDir = HashMap() - private val byProjectDir = HashMap() - - val list: Collection - get() = byWorkingDir.values - - @Synchronized - fun rebuildProjectRoots() { - byProjectDir.clear() - byWorkingDir.values.forEach { buildRoot -> - buildRoot.projectRoots.forEach { - byProjectDir[it] = buildRoot - } - } - } - - @Synchronized - fun getBuildByRootDir(dir: String) = byWorkingDir[dir] - - @Synchronized - fun findNearestRoot(path: String): GradleBuildRoot.Linked? { - var max: Pair? = null - byWorkingDir.entries.forEach { - if (path.startsWith(it.key) && (max == null || it.key.length > max!!.first.length)) { - max = it.key to it.value - } - } - return max?.second - } - - @Synchronized - fun getBuildByProjectDir(projectDir: String) = byProjectDir[projectDir] - - @Synchronized - fun add(value: GradleBuildRoot.Linked): GradleBuildRoot.Linked? { - val prefix = value.pathPrefix - val old = byWorkingDir.put(prefix, value) - rebuildProjectRoots() - log.info("$prefix: $old -> $value") - return old - } - - @Synchronized - fun remove(prefix: String) = byWorkingDir.remove(prefix)?.also { - rebuildProjectRoots() - log.info("$prefix: removed") - } -} \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootsManager.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootsManager.kt deleted file mode 100644 index e01d6e6de3d..00000000000 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootsManager.kt +++ /dev/null @@ -1,393 +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.roots - -import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer -import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil -import com.intellij.openapi.fileEditor.FileEditorManager -import com.intellij.openapi.progress.util.BackgroundTaskUtil -import com.intellij.openapi.project.Project -import com.intellij.openapi.vfs.LocalFileSystem -import com.intellij.openapi.vfs.VfsUtil -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.psi.PsiManager -import com.intellij.ui.EditorNotifications -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager -import org.jetbrains.kotlin.idea.core.script.configuration.CompositeScriptConfigurationManager -import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport -import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache -import org.jetbrains.kotlin.idea.core.util.EDT -import org.jetbrains.kotlin.idea.scripting.gradle.* -import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslGradleBuildSync -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance -import org.jetbrains.kotlin.utils.addToStdlib.lastIndexOfOrNull -import org.jetbrains.plugins.gradle.config.GradleSettingsListenerAdapter -import org.jetbrains.plugins.gradle.settings.* -import org.jetbrains.plugins.gradle.util.GradleConstants -import java.io.File -import java.nio.file.Paths -import java.util.concurrent.atomic.AtomicBoolean - -/** - * [GradleBuildRoot] is a linked gradle build (don't confuse with gradle project and included build). - * Each [GradleBuildRoot] may have it's own Gradle version, Java home and other settings. - * - * Typically, IntelliJ project have no more than one [GradleBuildRoot]. - * - * This manager allows to find related Gradle build by the Gradle Kotlin script file path. - * Each imported build have info about all of it's Kotlin Build Scripts. - * It is populated by calling [update], stored in FS and will be loaded from FS on next project opening - * - * [CompositeScriptConfigurationManager] may ask about known scripts by calling [collectConfigurations]. - * - * It also used to show related notification and floating actions depending on root kind, state and script state itself. - * - * Roots may be: - * - [GradleBuildRoot.Unlinked] - The script not related to any Gradle build that is linked to IntelliJ Project, - * or we cannot known what is it - * - [GradleBuildRoot.Linked] - Linked project, that may be itself: - * - [GradleBuildRoot.Legacy] - Gradle build with old Gradle version (<6.0) - * - [GradleBuildRoot.New] - not yet imported - * - [GradleBuildRoot.Imported] - imported - */ -class GradleBuildRootsManager(val project: Project) : ScriptingSupport() { - private val manager: CompositeScriptConfigurationManager - get() = ScriptConfigurationManager.getInstance(project) as CompositeScriptConfigurationManager - - private val updater - get() = manager.updater - - private val roots = GradleBuildRootIndex() - - //////////// - /// ScriptingSupport.Provider implementation: - - override fun isApplicable(file: VirtualFile): Boolean { - val scriptUnderRoot = findScriptBuildRoot(file) ?: return false - if (scriptUnderRoot.root is GradleBuildRoot.Legacy) return false - return true - } - - override fun isConfigurationLoadingInProgress(file: KtFile): Boolean { - return when (val root = findScriptBuildRoot(file.originalFile.virtualFile)?.root) { - is GradleBuildRoot.Linked -> root.importing - else -> false - } - } - - // used in 201 - @Suppress("UNUSED") - fun isConfigurationOutOfDate(file: VirtualFile): Boolean { - val script = getScriptInfo(file) ?: return false - return !script.model.inputs.isUpToDate(project, file) - } - - override fun collectConfigurations(builder: ScriptClassRootsCache.Builder) { - roots.list.forEach { root -> - if (root is GradleBuildRoot.Imported) { - root.collectConfigurations(builder) - } - } - } - - ////////////////// - - private val VirtualFile.localPath - get() = path - - fun getScriptInfo(file: VirtualFile): GradleScriptInfo? = - getScriptInfo(file.localPath) - - fun getScriptInfo(localPath: String): GradleScriptInfo? = - manager.getLightScriptInfo(localPath) as? GradleScriptInfo - - class ScriptUnderRoot( - val root: GradleBuildRoot?, - val script: GradleScriptInfo? = null - ) - - fun findScriptBuildRoot(gradleKtsFile: VirtualFile): ScriptUnderRoot? = - findScriptBuildRoot(gradleKtsFile.path) - - /** - * Fast method that can return false positive - */ - fun maybeAffectedGradleProjectFile(filePath: String): Boolean = - filePath.endsWith("/gradle.properties") || - filePath.endsWith("/gradle.local") || - filePath.endsWith("/gradle-wrapper.properties") || - filePath.endsWith("/build.gradle.kts") || - filePath.endsWith("/settings.gradle.kts") || - filePath.endsWith("/init.gradle.kts") - - fun isAffectedGradleProjectFile(filePath: String): Boolean = - findAffectedFileRoot(filePath) != null - - fun fileChanged(filePath: String, ts: Long = System.currentTimeMillis()) { - findAffectedFileRoot(filePath)?.fileChanged(filePath, ts) - } - - private fun findAffectedFileRoot(filePath: String): GradleBuildRoot.Linked? { - if (filePath.endsWith("/gradle.properties") || - filePath.endsWith("/gradle.local") - ) { - return roots.getBuildByProjectDir(filePath.substringBeforeLast("/")) - } else if (filePath.endsWith("/gradle-wrapper.properties")) { - val gradleWrapperDirIndex = filePath.lastIndexOfOrNull('/') ?: return null - val gradleDirIndex = filePath.lastIndexOfOrNull('/', gradleWrapperDirIndex - 1) ?: return null - val buildDirIndex = filePath.lastIndexOfOrNull('/', gradleDirIndex - 1) ?: return null - return roots.getBuildByRootDir(filePath.substring(0, buildDirIndex)) - } - - return findScriptBuildRoot(filePath, searchNearestLegacy = false)?.root as? GradleBuildRoot.Linked - } - - private fun findScriptBuildRoot(filePath: String, searchNearestLegacy: Boolean = true): ScriptUnderRoot? { - if (!filePath.endsWith(".gradle.kts")) return null - - val scriptInfo = getScriptInfo(filePath) - val imported = scriptInfo?.buildRoot - if (imported != null) return ScriptUnderRoot(imported, scriptInfo) - - if (filePath.endsWith("/build.gradle.kts") || - filePath.endsWith("/settings.gradle.kts") || - filePath.endsWith("/init.gradle.kts") - ) { - // build|settings|init.gradle.kts scripts should be located near gradle project root only - val gradleBuild = roots.getBuildByProjectDir(filePath.substringBeforeLast("/")) - if (gradleBuild != null) return ScriptUnderRoot(gradleBuild) - } - - // other scripts: "included", "precompiled" scripts, scripts in unlinked projects, - // or just random files with ".gradle.kts" ending - - // todo(gradle6): remove, it is required only for projects with old gradle - if (searchNearestLegacy) { - val found = roots.findNearestRoot(filePath) - if (found is GradleBuildRoot.Legacy) return ScriptUnderRoot(found) - } - - return ScriptUnderRoot(GradleBuildRoot.Unlinked()) - } - - fun getBuildRoot(gradleWorkingDir: String) = - roots.getBuildByRootDir(gradleWorkingDir) - - fun markImportingInProgress(workingDir: String, inProgress: Boolean = true) { - actualizeBuildRoot(workingDir)?.importing = inProgress - updateNotifications(workingDir) - hideNotificationForProjectImport(project) - } - - fun update(build: KotlinDslGradleBuildSync) { - // fast path for linked gradle builds without .gradle.kts support - if (build.models.isEmpty()) { - val root = roots.getBuildByRootDir(build.workingDir) ?: return - if (root is GradleBuildRoot.Imported && root.data.models.isEmpty()) return - } - - val root = actualizeBuildRoot(build.workingDir) ?: return - root.importing = false - - if (root is GradleBuildRoot.Legacy) return - - val templateClasspath = GradleScriptDefinitionsContributor.getDefinitionsTemplateClasspath(project) - val newData = GradleBuildRootData(build.projectRoots, templateClasspath, build.models) - val mergedData = if (build.failed && root is GradleBuildRoot.Imported) merge(root.data, newData) else newData - - val newSupport = tryCreateImportedRoot(build.workingDir) { mergedData } ?: return - GradleBuildRootDataSerializer.write(newSupport.dir, mergedData) - - add(newSupport) - - hideNotificationForProjectImport(project) - } - - private fun merge(old: GradleBuildRootData, new: GradleBuildRootData): GradleBuildRootData { - val roots = old.projectRoots.toMutableSet() - roots.addAll(new.projectRoots) - - val models = old.models.associateByTo(mutableMapOf()) { it.file } - new.models.associateByTo(models) { it.file } - - return GradleBuildRootData(roots, new.templateClasspath, models.values) - } - - private val lastModifiedFilesSaveScheduled = AtomicBoolean() - - fun scheduleLastModifiedFilesSave() { - if (lastModifiedFilesSaveScheduled.compareAndSet(false, true)) { - BackgroundTaskUtil.executeOnPooledThread(project) { - if (lastModifiedFilesSaveScheduled.compareAndSet(true, false)) { - roots.list.forEach { - it.saveLastModifiedFiles() - } - } - } - } - } - - init { - getGradleProjectSettings(project).forEach { - // don't call this.add, as we are inside scripting manager initialization - roots.add(loadLinkedRoot(it)) - } - - // subscribe to linked gradle project modification - val listener = object : GradleSettingsListenerAdapter() { - override fun onProjectsLinked(settings: MutableCollection) { - settings.forEach { - add(loadLinkedRoot(it)) - } - } - - override fun onProjectsUnlinked(linkedProjectPaths: MutableSet) { - linkedProjectPaths.forEach { - remove(it) - } - } - - override fun onGradleHomeChange(oldPath: String?, newPath: String?, linkedProjectPath: String) { - reloadBuildRoot(linkedProjectPath) - } - - override fun onGradleDistributionTypeChange(currentValue: DistributionType?, linkedProjectPath: String) { - reloadBuildRoot(linkedProjectPath) - } - } - - project.messageBus.connect(project).subscribe(GradleSettingsListener.TOPIC, listener) - } - - private fun getGradleProjectSettings(workingDir: String): GradleProjectSettings? { - return (ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID) as GradleSettings) - .getLinkedProjectSettings(workingDir) - } - - /** - * Check that root under [workingDir] in sync with it's [GradleProjectSettings]. - * Actually this should be true, but we may miss some change events. - * For that cases we are rechecking this on each Gradle Project sync (importing/reimporting) - */ - private fun actualizeBuildRoot(workingDir: String): GradleBuildRoot.Linked? { - val actualSettings = getGradleProjectSettings(workingDir) - val buildRoot = roots.getBuildByRootDir(workingDir) - - return when { - buildRoot != null -> when { - !buildRoot.checkActual(actualSettings) -> reloadBuildRoot(workingDir) - else -> buildRoot - } - actualSettings != null -> loadLinkedRoot(actualSettings) - else -> null - } - } - - private fun GradleBuildRoot.Linked.checkActual(actualSettings: GradleProjectSettings?): Boolean { - if (actualSettings == null) return false - - val knownAsSupported = this !is GradleBuildRoot.Legacy - val shouldBeSupported = kotlinDslScriptsModelImportSupported(actualSettings.resolveGradleVersion().version) - return knownAsSupported == shouldBeSupported - } - - private fun reloadBuildRoot(rootPath: String): GradleBuildRoot.Linked? { - val settings = getGradleProjectSettings(rootPath) - if (settings == null) { - remove(rootPath) - return null - } else { - val newRoot = loadLinkedRoot(settings) - add(newRoot) - return newRoot - } - } - - private fun loadLinkedRoot(settings: GradleProjectSettings) = - tryLoadFromFsCache(settings) ?: createOtherLinkedRoot(settings) - - private fun tryLoadFromFsCache(settings: GradleProjectSettings) = - tryCreateImportedRoot(settings.externalProjectPath) { - GradleBuildRootDataSerializer.read(it) - } - - private fun createOtherLinkedRoot(settings: GradleProjectSettings): GradleBuildRoot.Linked { - val supported = kotlinDslScriptsModelImportSupported(settings.resolveGradleVersion().version) - return when { - supported -> GradleBuildRoot.New(this, settings) - else -> GradleBuildRoot.Legacy(this, settings) - } - } - - private fun tryCreateImportedRoot( - externalProjectPath: String, - dataProvider: (buildRoot: VirtualFile) -> GradleBuildRootData? - ): GradleBuildRoot.Imported? { - val buildRoot = VfsUtil.findFile(Paths.get(externalProjectPath), true) ?: return null - val data = dataProvider(buildRoot) ?: return null - val javaHome = ExternalSystemApiUtil - .getExecutionSettings(project, externalProjectPath, GradleConstants.SYSTEM_ID) - .javaHome?.let { File(it) } - - return GradleBuildRoot.Imported(this, buildRoot, javaHome, data) - } - - private fun add(newRoot: GradleBuildRoot.Linked) { - val old = roots.add(newRoot) - if (old is GradleBuildRoot.Imported) removeData(old.pathPrefix) - if (old is GradleBuildRoot.Imported || newRoot is GradleBuildRoot.Imported) { - updater.ensureUpdateScheduled() - } - - updateNotifications(newRoot.pathPrefix) - } - - private fun remove(rootPath: String) { - val removed = roots.remove(rootPath) - if (removed is GradleBuildRoot.Imported) { - removeData(rootPath) - updater.ensureUpdateScheduled() - } - - updateNotifications(rootPath) - } - - private fun removeData(rootPath: String) { - val buildRoot = LocalFileSystem.getInstance().findFileByPath(rootPath) - if (buildRoot != null) { - GradleBuildRootDataSerializer.remove(buildRoot) - LastModifiedFiles.remove(buildRoot) - } - } - - private fun updateNotifications(dir1: String) { - if (!project.isOpen) return - - val openedScripts = FileEditorManager.getInstance(project).openFiles.filter { - it.path.startsWith(dir1) && isGradleKotlinScript(it) - } - - if (openedScripts.isEmpty()) return - - GlobalScope.launch(EDT(project)) { - if (project.isDisposed) return@launch - - openedScripts.forEach { - val ktFile = PsiManager.getInstance(project).findFile(it) - if (ktFile != null) DaemonCodeAnalyzer.getInstance(project).restart(ktFile) - EditorNotifications.getInstance(project).updateAllNotifications() - } - } - } - - companion object { - fun getInstance(project: Project): GradleBuildRootsManager = - EPN.getPoint(project).extensionList.firstIsInstance() - } -} \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleScriptInfo.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleScriptInfo.kt deleted file mode 100644 index 7e20f3baeab..00000000000 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleScriptInfo.kt +++ /dev/null @@ -1,66 +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.roots - -import com.intellij.openapi.vfs.VfsUtil -import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache -import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel -import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition -import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper -import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource -import org.jetbrains.kotlin.scripting.resolve.adjustByDefinition -import java.io.File -import kotlin.script.experimental.api.* -import kotlin.script.experimental.jvm.JvmDependency -import kotlin.script.experimental.jvm.jdkHome -import kotlin.script.experimental.jvm.jvm - -class GradleScriptInfo( - val buildRoot: GradleBuildRoot.Imported, - scriptDefinition: ScriptDefinition?, - val model: KotlinDslScriptModel -) : ScriptClassRootsCache.LightScriptInfo(scriptDefinition) { - override fun buildConfiguration(): ScriptCompilationConfigurationWrapper? { - val javaHome = buildRoot.javaHome - - val scriptFile = File(model.file) - val virtualFile = VfsUtil.findFile(scriptFile.toPath(), true)!! - - if (definition == null) return null - - return ScriptCompilationConfigurationWrapper.FromCompilationConfiguration( - VirtualFileScriptSource(virtualFile), - 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) - ) - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as GradleScriptInfo - - if (buildRoot.pathPrefix != other.buildRoot.pathPrefix) return false - if (model != other.model) return false - if (definition != other.definition) return false - - return true - } - - override fun hashCode(): Int { - var result = buildRoot.pathPrefix.hashCode() - result = 31 * result + model.hashCode() - result = 31 * result + definition.hashCode() - return result - } -} diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootDataSerializer.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/serizliation.kt similarity index 80% rename from idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootDataSerializer.kt rename to idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/serizliation.kt index 133826f3f34..3e144fe1d25 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootDataSerializer.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/serizliation.kt @@ -3,7 +3,7 @@ * 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.roots +package org.jetbrains.kotlin.idea.scripting.gradle import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.newvfs.FileAttribute @@ -11,27 +11,25 @@ import org.jetbrains.kotlin.idea.core.util.readNullable import org.jetbrains.kotlin.idea.core.util.readString import org.jetbrains.kotlin.idea.core.util.writeNullable import org.jetbrains.kotlin.idea.core.util.writeString -import org.jetbrains.kotlin.idea.scripting.gradle.GradleKotlinScriptConfigurationInputs -import org.jetbrains.kotlin.idea.scripting.gradle.LastModifiedFiles import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel import java.io.DataInput import java.io.DataInputStream import java.io.DataOutput -internal object GradleBuildRootDataSerializer { - private val attribute = FileAttribute("kotlin-dsl-script-models", 5, false) +internal object KotlinDslScriptModels { + private val attribute = FileAttribute("kotlin-dsl-script-models", 3, false) - fun read(buildRoot: VirtualFile): GradleBuildRootData? { + fun read(buildRoot: VirtualFile): ConfigurationData? { return attribute.readAttribute(buildRoot)?.use { it.readNullable { - readKotlinDslScriptModels(it, buildRoot.path) + readKotlinDslScriptModels(it) } } } - fun write(buildRoot: VirtualFile, data: GradleBuildRootData?) { + fun write(buildRoot: VirtualFile, configuration: ConfigurationData?) { attribute.writeAttribute(buildRoot).use { - it.writeNullable(data) { + it.writeNullable(configuration) { writeKotlinDslScriptModels(this, it) } } @@ -39,13 +37,11 @@ internal object GradleBuildRootDataSerializer { fun remove(buildRoot: VirtualFile) { write(buildRoot, null) - LastModifiedFiles.remove(buildRoot) } } -internal fun writeKotlinDslScriptModels(output: DataOutput, data: GradleBuildRootData) { +internal fun writeKotlinDslScriptModels(output: DataOutput, data: ConfigurationData) { val strings = StringsPool.writer(output) - strings.addStrings(data.projectRoots) strings.addStrings(data.templateClasspath) data.models.forEach { strings.addString(it.file) @@ -54,28 +50,26 @@ internal fun writeKotlinDslScriptModels(output: DataOutput, data: GradleBuildRoo strings.addStrings(it.imports) } strings.writeHeader() - strings.writeStringIds(data.projectRoots) strings.writeStringIds(data.templateClasspath) output.writeList(data.models) { strings.writeStringId(it.file) output.writeString(it.inputs.sections) - output.writeLong(it.inputs.lastModifiedTs) + output.writeLong(it.inputs.inputsTS) strings.writeStringIds(it.classPath) strings.writeStringIds(it.sourcePath) strings.writeStringIds(it.imports) } } -internal fun readKotlinDslScriptModels(input: DataInputStream, buildRoot: String): GradleBuildRootData { +internal fun readKotlinDslScriptModels(input: DataInputStream): ConfigurationData { val strings = StringsPool.reader(input) - val projectRoots = strings.readStrings() val templateClasspath = strings.readStrings() val models = input.readList { KotlinDslScriptModel( strings.readString(), - GradleKotlinScriptConfigurationInputs(input.readString(), input.readLong(), buildRoot), + GradleKotlinScriptConfigurationInputs(input.readString(), input.readLong()), strings.readStrings(), strings.readStrings(), strings.readStrings(), @@ -83,7 +77,7 @@ internal fun readKotlinDslScriptModels(input: DataInputStream, buildRoot: String ) } - return GradleBuildRootData(projectRoots, templateClasspath, models) + return ConfigurationData(templateClasspath, models) } private object StringsPool { diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/utils.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/utils.kt index 02d579e8ced..b95a9d45b20 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/utils.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/utils.kt @@ -5,6 +5,8 @@ package org.jetbrains.kotlin.idea.scripting.gradle +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.service import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil import com.intellij.openapi.project.Project import com.intellij.openapi.util.registry.Registry @@ -12,21 +14,36 @@ import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.* import com.intellij.psi.impl.source.tree.LeafPsiElement import org.gradle.util.GradleVersion -import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRoot -import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtScriptInitializer import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType +import org.jetbrains.plugins.gradle.settings.GradleLocalSettings import org.jetbrains.plugins.gradle.settings.GradleProjectSettings import org.jetbrains.plugins.gradle.settings.GradleSettings import org.jetbrains.plugins.gradle.util.GradleConstants +import java.util.* private val sections = arrayListOf("buildscript", "plugins", "initscript", "pluginManagement") fun isGradleKotlinScript(virtualFile: VirtualFile) = virtualFile.name.endsWith(".gradle.kts") +fun isInAffectedGradleProjectFiles(project: Project, filePath: String): Boolean { + if (filePath.endsWith("/gradle.properties")) return true + if (filePath.endsWith("/gradle-wrapper.properties")) return true + + if (filePath.endsWith(".gradle") || filePath.endsWith(".gradle.kts")) { + if (ApplicationManager.getApplication().isUnitTestModeWithoutAffectedGradleProjectFilesCheck) { + return true + } + + return filePath.substringBeforeLast("/") in project.service().getGradleProjectsRoots() + } + + return false +} + fun getGradleScriptInputsStamp( project: Project, file: VirtualFile, @@ -63,8 +80,7 @@ fun getGradleScriptInputsStamp( } } - val buildRoot = GradleBuildRootsManager.getInstance(project).findScriptBuildRoot(file)?.root as? GradleBuildRoot.Linked - GradleKotlinScriptConfigurationInputs(result.toString(), givenTimeStamp, buildRoot?.pathPrefix) + GradleKotlinScriptConfigurationInputs(result.toString(), givenTimeStamp) } else null } } @@ -79,5 +95,50 @@ fun useScriptConfigurationFromImportOnly(): Boolean { return Registry.`is`("kotlin.gradle.scripts.useIdeaProjectImport", false) } -fun getGradleProjectSettings(project: Project): Collection = - (ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID) as GradleSettings).linkedProjectsSettings \ No newline at end of file +fun getGradleVersion(project: Project, settings: GradleProjectSettings): String { + val localVersion = GradleLocalSettings.getInstance(project).getGradleVersion(settings.externalProjectPath) + if (localVersion != null) return localVersion + + return settings.resolveGradleVersion().version +} + +fun getGradleProjectSettings(project: Project): Collection { + val gradleSettings = ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID) as GradleSettings + return gradleSettings.getLinkedProjectsSettings() +} + +class RootsIndex { + internal val tree = TreeMap() + var values: Collection = listOf() + internal set + + fun findRoot(path: String): T? { + // race condition can be ignored + val values = values + val size = values.size + if (size == 0) return null + if (size == 1) return values.single() // we can omit prefix check + return tree.floorEntry(path).takeIf { path.startsWith(it.key) }?.value + } + + internal inline fun update(updater: (insert: (prefix: String, value: T) -> Unit) -> Unit) { + synchronized(this) { + updater { prefix, value -> tree[prefix] = value } + values = tree.values + } + } + + @Synchronized + operator fun set(prefix: String, value: T) { + val moreCommon = tree.lowerKey(prefix) + check(moreCommon == null || !prefix.startsWith(moreCommon)) { + "Cannot add root `${prefix}`. More common root already added: `$moreCommon`" + } + + tree[prefix] = value + values = tree.values + } + + @Synchronized + fun remove(prefix: String) = tree.remove(prefix) +} \ No newline at end of file diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleKtsImportTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleKtsImportTest.kt index 11ac5e99cf5..39c4cc6454c 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleKtsImportTest.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleKtsImportTest.kt @@ -10,7 +10,6 @@ import junit.framework.AssertionFailedError import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager import org.jetbrains.kotlin.idea.core.script.applySuggestedScriptConfiguration -import org.jetbrains.kotlin.idea.core.script.configuration.CompositeScriptConfigurationManager import org.jetbrains.kotlin.idea.core.script.configuration.loader.DefaultScriptConfigurationLoader import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoadingContext import org.jetbrains.kotlin.idea.core.script.configuration.utils.areSimilar @@ -38,7 +37,7 @@ class GradleKtsImportTest : GradleImportingTestCase() { fun data(): Collection> = listOf(arrayOf("6.0.1")) } - val scriptConfigurationManager get() = ScriptConfigurationManager.getInstance(myProject) as CompositeScriptConfigurationManager + val scriptConfigurationManager get() = ScriptConfigurationManager.getInstance(myProject) val projectDir get() = File(GradleSettings.getInstance(myProject).linkedProjectsSettings.first().externalProjectPath) override fun testDataDirName(): String { @@ -99,8 +98,8 @@ class GradleKtsImportTest : GradleImportingTestCase() { // reload configuration and check this it is not changed scripts.forEach { - val reloadedConfiguration = scriptConfigurationManager.default.forceReloadConfiguration( - it.psiFile, + val reloadedConfiguration = scriptConfigurationManager.forceReloadConfiguration( + it.virtualFile, object : DefaultScriptConfigurationLoader(it.psiFile.project) { override fun shouldRunInBackground(scriptDefinition: ScriptDefinition) = false override fun loadDependencies( diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptListenerTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcherTest.kt similarity index 78% rename from idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptListenerTest.kt rename to idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcherTest.kt index d6d9f15b050..73e69d9efb3 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptListenerTest.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcherTest.kt @@ -5,24 +5,19 @@ package org.jetbrains.kotlin.idea.scripting.gradle -import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil -import com.intellij.openapi.vfs.LocalFileSystem -import com.intellij.openapi.vfs.VfsUtil +import com.intellij.openapi.components.service import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiFile import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationLoadingTest -import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.test.JUnit3RunnerWithInners -import org.jetbrains.plugins.gradle.settings.DistributionType -import org.jetbrains.plugins.gradle.settings.GradleProjectSettings +import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner import org.jetbrains.plugins.gradle.util.GradleConstants import org.junit.runner.RunWith import java.io.File -@RunWith(JUnit3RunnerWithInners::class) -open class GradleScriptListenerTest : AbstractScriptConfigurationLoadingTest() { +@RunWith(JUnit3WithIdeaConfigurationRunner::class) +open class GradleScriptInputsWatcherTest : AbstractScriptConfigurationLoadingTest() { companion object { internal const val outsidePlaceholder = "// OUTSIDE_SECTIONS" internal const val insidePlaceholder = "// INSIDE_SECTIONS" @@ -30,12 +25,14 @@ open class GradleScriptListenerTest : AbstractScriptConfigurationLoadingTest() { private lateinit var testFiles: TestFiles - data class TestFiles( - val buildKts: KtFile, - val settings: KtFile, - val prop: PsiFile, - val gradleWrapperProperties: VirtualFile - ) + data class TestFiles(val buildKts: KtFile, val settings: KtFile, val prop: PsiFile) + + override fun setUp() { + super.setUp() + + // should be initialized explicitly because we do not have a real Gradle Project in this test + project.service().startWatching() + } override fun setUpTestProject() { val rootDir = "idea/testData/script/definition/loading/gradle/" @@ -43,35 +40,12 @@ open class GradleScriptListenerTest : AbstractScriptConfigurationLoadingTest() { val settings: KtFile = addFileToProject(rootDir + GradleConstants.KOTLIN_DSL_SETTINGS_FILE_NAME) val prop: PsiFile = addFileToProject(rootDir + "gradle.properties") - val gradleWrapperProperties = VfsUtil.virtualToIoFile(settings.virtualFile.parent) - .resolve("gradle/wrapper/gradle-wrapper.properties") - gradleWrapperProperties.parentFile.mkdirs() - gradleWrapperProperties.writeText( - """ - distributionBase=GRADLE_USER_HOME - distributionPath=wrapper/dists - distributionUrl=https\://services.gradle.org/distributions/gradle-1.0.0-bin.zip - zipStoreBase=GRADLE_USER_HOME - zipStorePath=wrapper/dists - """.trimIndent() - ) - val buildGradleKts = File(rootDir).walkTopDown().find { it.name == GradleConstants.KOTLIN_DSL_SCRIPT_NAME } ?: error("Couldn't find main script") configureScriptFile(rootDir, buildGradleKts) val build = (myFile as? KtFile) ?: error("") - val newProjectSettings = GradleProjectSettings() - newProjectSettings.distributionType = DistributionType.DEFAULT_WRAPPED - newProjectSettings.externalProjectPath = settings.virtualFile.parent.path - ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID).linkProject(newProjectSettings) - - testFiles = TestFiles( - build, - settings, - prop, - LocalFileSystem.getInstance().findFileByIoFile(gradleWrapperProperties)!! - ) + testFiles = TestFiles(build, settings, prop) } private inline fun addFileToProject(file: String): T { @@ -211,6 +185,8 @@ open class GradleScriptListenerTest : AbstractScriptConfigurationLoadingTest() { changeSettingsKtsOutsideSections() + project.service().clearAndRefillState() + assertConfigurationUpToDate(testFiles.settings) assertConfigurationUpdateWasDone(testFiles.buildKts) } @@ -244,7 +220,7 @@ open class GradleScriptListenerTest : AbstractScriptConfigurationLoadingTest() { } private fun markFileChanged(virtualFile: VirtualFile, ts: Long) { - GradleBuildRootsManager.getInstance(project).fileChanged(virtualFile.path, ts) + project.service().fileChanged(virtualFile.path, ts) } fun testLoadedConfigurationWhenExternalFileChanged() { @@ -256,23 +232,14 @@ open class GradleScriptListenerTest : AbstractScriptConfigurationLoadingTest() { assertConfigurationUpToDate(testFiles.buildKts) } - fun testChangeGradleWrapperPropertiesFile() { - assertAndLoadInitialConfiguration(testFiles.buildKts) - - markFileChanged(testFiles.gradleWrapperProperties, System.currentTimeMillis()) - - assertConfigurationUpdateWasDone(testFiles.buildKts) - assertConfigurationUpToDate(testFiles.buildKts) - } - private fun assertConfigurationUpToDate(file: KtFile) { - scriptConfigurationManager.default.ensureUpToDatedConfigurationSuggested(file) + scriptConfigurationManager.updater.ensureUpToDatedConfigurationSuggested(file) assertNoBackgroundTasks() assertNoLoading() } private fun assertConfigurationUpdateWasDone(file: KtFile) { - scriptConfigurationManager.default.ensureUpToDatedConfigurationSuggested(file) + scriptConfigurationManager.updater.ensureUpToDatedConfigurationSuggested(file) assertAndDoAllBackgroundTasks() assertSingleLoading() } diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptOutOfProjectTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptOutOfProjectTest.kt index ec2578486b7..6f31466cd00 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptOutOfProjectTest.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptOutOfProjectTest.kt @@ -7,7 +7,6 @@ package org.jetbrains.kotlin.idea.scripting.gradle import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationLoadingTest -import org.jetbrains.kotlin.idea.scripting.gradle.legacy.GradleLegacyScriptConfigurationLoaderForOutOfProjectScripts import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner import org.jetbrains.plugins.gradle.util.GradleConstants @@ -35,15 +34,13 @@ class GradleScriptOutOfProjectTest : AbstractScriptConfigurationLoadingTest() { } private val loaderForOutOfProjectScripts by lazy { - GradleLegacyScriptConfigurationLoaderForOutOfProjectScripts( - project - ) + GradleScriptConfigurationLoaderForOutOfProjectScripts(project) } fun testManualLoadingForUpToDate() { assertConfigurationShouldBeLoadedManually() - scriptConfigurationManager.default.forceReloadConfiguration(myFile as KtFile, loaderForOutOfProjectScripts) + scriptConfigurationManager.forceReloadConfiguration(myFile.virtualFile, loaderForOutOfProjectScripts) assertConfigurationWasLoaded() } @@ -56,7 +53,7 @@ class GradleScriptOutOfProjectTest : AbstractScriptConfigurationLoadingTest() { assertNoBackgroundTasks() assertNoLoading() - scriptConfigurationManager.default.forceReloadConfiguration(myFile as KtFile, loaderForOutOfProjectScripts) + scriptConfigurationManager.forceReloadConfiguration(myFile.virtualFile, loaderForOutOfProjectScripts) assertConfigurationWasLoaded() } @@ -64,7 +61,7 @@ class GradleScriptOutOfProjectTest : AbstractScriptConfigurationLoadingTest() { fun testFileAttributesForManualLoading() { assertConfigurationShouldBeLoadedManually() - scriptConfigurationManager.default.forceReloadConfiguration(myFile as KtFile, loaderForOutOfProjectScripts) + scriptConfigurationManager.forceReloadConfiguration(myFile.virtualFile, loaderForOutOfProjectScripts) assertConfigurationWasLoaded() @@ -76,22 +73,23 @@ class GradleScriptOutOfProjectTest : AbstractScriptConfigurationLoadingTest() { fun testNoNotificationAfterForReload() { assertConfigurationShouldBeLoadedManually() - scriptConfigurationManager.default.forceReloadConfiguration(myFile as KtFile, loaderForOutOfProjectScripts) + scriptConfigurationManager.forceReloadConfiguration(myFile.virtualFile, loaderForOutOfProjectScripts) assertConfigurationWasLoaded() makeChangesInsideSections() - scriptConfigurationManager.default.forceReloadConfiguration(myFile as KtFile, loaderForOutOfProjectScripts) + scriptConfigurationManager.forceReloadConfiguration(myFile.virtualFile, loaderForOutOfProjectScripts) assertConfigurationWasLoaded() } private fun makeChangesInsideSections() { - changeContents(myFile.text.replace(GradleScriptListenerTest.insidePlaceholder, "application"), myFile) + changeContents(myFile.text.replace(GradleScriptInputsWatcherTest.insidePlaceholder, "application"), myFile) } private fun assertConfigurationShouldBeLoadedManually() { assertNull(getConfiguration()) + assertAndDoAllBackgroundTasks() assertNoLoading() } diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleBuildRootDataSerializerTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/KotlinDslScriptModelsTest.kt similarity index 78% rename from idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleBuildRootDataSerializerTest.kt rename to idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/KotlinDslScriptModelsTest.kt index 1e97eed7db5..78bffda78eb 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleBuildRootDataSerializerTest.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/KotlinDslScriptModelsTest.kt @@ -6,9 +6,6 @@ package org.jetbrains.kotlin.idea.scripting.gradle import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel -import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootData -import org.jetbrains.kotlin.idea.scripting.gradle.roots.readKotlinDslScriptModels -import org.jetbrains.kotlin.idea.scripting.gradle.roots.writeKotlinDslScriptModels import org.junit.Test import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream @@ -16,16 +13,15 @@ import java.io.DataInputStream import java.io.DataOutputStream import kotlin.test.assertEquals -class GradleBuildRootDataSerializerTest { +class KotlinDslScriptModelsTest { @Test fun write() { - val data = GradleBuildRootData( + val data = ConfigurationData( listOf("a", "b", "c"), - listOf("a"), listOf( KotlinDslScriptModel( "a", - GradleKotlinScriptConfigurationInputs("b", 1, "a"), + GradleKotlinScriptConfigurationInputs("b", 1), listOf("c", "a", "b"), listOf("b", "c", "a"), listOf("i", "c", "b"), @@ -33,7 +29,7 @@ class GradleBuildRootDataSerializerTest { ), KotlinDslScriptModel( "a", - GradleKotlinScriptConfigurationInputs("b", 1, "a"), + GradleKotlinScriptConfigurationInputs("b", 1), listOf("c", "a", "b"), listOf("b", "c", "a"), listOf("i", "c", "b"), @@ -45,7 +41,7 @@ class GradleBuildRootDataSerializerTest { val buffer = ByteArrayOutputStream() writeKotlinDslScriptModels(DataOutputStream(buffer), data) - val restored = readKotlinDslScriptModels(DataInputStream(ByteArrayInputStream(buffer.toByteArray())), "a") + val restored = readKotlinDslScriptModels(DataInputStream(ByteArrayInputStream(buffer.toByteArray()))) assertEquals(data.toString(), restored.toString()) } diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/LastModifiedFilesTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/LastModifiedFilesTest.kt index 5f8d6e9054f..059394a37e8 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/LastModifiedFilesTest.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/LastModifiedFilesTest.kt @@ -72,81 +72,4 @@ class LastModifiedFilesTest { assertEquals(3, files.lastModifiedTimeStampExcept("a")) assertEquals(3, files.lastModifiedTimeStampExcept("b")) } - - @Test - fun testSameFile() { - files.fileChanged(1, "x") - files.fileChanged(2, "x") - assertEquals(Long.MIN_VALUE, files.lastModifiedTimeStampExcept("x")) - } - - @Test - fun testSameFile1() { - files.fileChanged(1, "z") - files.fileChanged(2, "x") - files.fileChanged(3, "x") - assertEquals(1, files.lastModifiedTimeStampExcept("x")) - assertEquals(3, files.lastModifiedTimeStampExcept("z")) - } - - @Test - fun testSameFile2() { - files.fileChanged(1, "z") - files.fileChanged(1, "x") - files.fileChanged(2, "x") - assertEquals(1, files.lastModifiedTimeStampExcept("x")) - assertEquals(2, files.lastModifiedTimeStampExcept("z")) - } - - @Test - fun testSameFile3() { - files.fileChanged(1, "x") - files.fileChanged(1, "y") - files.fileChanged(2, "x") - assertEquals(1, files.lastModifiedTimeStampExcept("x")) - assertEquals(2, files.lastModifiedTimeStampExcept("y")) - } - - @Test - fun testSameFile4() { - files.fileChanged(1, "z") - files.fileChanged(2, "x") - files.fileChanged(2, "y") - files.fileChanged(3, "x") - assertEquals(2, files.lastModifiedTimeStampExcept("x")) - assertEquals(3, files.lastModifiedTimeStampExcept("y")) - assertEquals(3, files.lastModifiedTimeStampExcept("z")) - } - - @Test - fun testLoadingAfterSameFileBug() { - val restored = LastModifiedFiles( - LastModifiedFiles.SimultaneouslyChangedFiles(2, mutableSetOf("x")), - LastModifiedFiles.SimultaneouslyChangedFiles(1, mutableSetOf("x")) - ) - - assertEquals(Long.MIN_VALUE, restored.lastModifiedTimeStampExcept("x")) - } - - @Test - fun testLoadingAfterSameFileBug2() { - val restored = LastModifiedFiles( - LastModifiedFiles.SimultaneouslyChangedFiles(2, mutableSetOf("x", "y")), - LastModifiedFiles.SimultaneouslyChangedFiles(1, mutableSetOf("x")) - ) - - assertEquals(2, restored.lastModifiedTimeStampExcept("x")) - assertEquals(2, restored.lastModifiedTimeStampExcept("y")) - } - - @Test - fun testLoadingAfterSameFileBug3() { - val restored = LastModifiedFiles( - LastModifiedFiles.SimultaneouslyChangedFiles(2, mutableSetOf("x", "y")), - LastModifiedFiles.SimultaneouslyChangedFiles(1, mutableSetOf("x", "z")) - ) - - assertEquals(2, restored.lastModifiedTimeStampExcept("x")) - assertEquals(2, restored.lastModifiedTimeStampExcept("y")) - } } \ No newline at end of file diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/RootsIndexTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/RootsIndexTest.kt new file mode 100644 index 00000000000..c713df03f79 --- /dev/null +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/RootsIndexTest.kt @@ -0,0 +1,33 @@ +/* + * 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.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertFails + +class RootsIndexTest { + @Test + fun findRoot() { + val roots = RootsIndex() + roots["a/b/c"] = 1 + assertFails { roots["a/b/c/d"] = 2 } + roots["a/c"] = 3 + roots["d"] = 4 + + assertEquals(1, roots.findRoot("a/b/c/x")) + assertEquals(1, roots.findRoot("a/b/c")) + assertEquals(3, roots.findRoot("a/c/x")) + assertEquals(3, roots.findRoot("a/c")) + assertEquals(4, roots.findRoot("d")) + assertEquals(4, roots.findRoot("d/x")) + assertEquals(null, roots.findRoot("e")) + assertEquals(null, roots.findRoot("e/f")) + + roots["d"] = 5 + assertEquals(5, roots.findRoot("d")) + } +} \ No newline at end of file diff --git a/idea/resources/META-INF/gradle.xml b/idea/resources/META-INF/gradle.xml index 5687dd29a3c..e58df94d702 100644 --- a/idea/resources/META-INF/gradle.xml +++ b/idea/resources/META-INF/gradle.xml @@ -14,10 +14,10 @@ - - - - + + diff --git a/idea/resources/META-INF/gradle.xml.201 b/idea/resources/META-INF/gradle.xml.201 index 7038af60150..c747dae6418 100644 --- a/idea/resources/META-INF/gradle.xml.201 +++ b/idea/resources/META-INF/gradle.xml.201 @@ -14,11 +14,10 @@ - - - - - + + diff --git a/idea/resources/META-INF/scripting-support.xml b/idea/resources/META-INF/scripting-support.xml index 8acf103162e..6b84786709f 100644 --- a/idea/resources/META-INF/scripting-support.xml +++ b/idea/resources/META-INF/scripting-support.xml @@ -1,7 +1,7 @@ - diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt index 63a03009824..a1ac7ec101e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.idea.inspections import com.intellij.codeInsight.FileModificationService import com.intellij.codeInsight.daemon.QuickFixBundle +import com.intellij.codeInsight.daemon.impl.HighlightInfoType import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil import com.intellij.codeInspection.* @@ -44,7 +45,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.core.isInheritable -import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptingSupport +import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager import org.jetbrains.kotlin.idea.core.toDescriptor import org.jetbrains.kotlin.idea.findUsages.KotlinFindUsagesHandlerFactory import org.jetbrains.kotlin.idea.findUsages.handlers.KotlinFindClassUsagesHandler @@ -137,8 +138,7 @@ class UnusedSymbolInspection : AbstractKotlinInspection() { val usedScripts = findScriptsWithUsages(declaration) if (usedScripts.isNotEmpty()) { - if (!DefaultScriptingSupport.getInstance(declaration.project).ensureLoadedFromCache(usedScripts)) { - // Not all script configuration are loaded; behave like it is used + if (!ScriptConfigurationManager.getInstance(declaration.project).updater.ensureConfigurationUpToDate(usedScripts)) { return TOO_MANY_OCCURRENCES } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/script/AbstractScriptConfigurationLoadingTest.kt b/idea/tests/org/jetbrains/kotlin/idea/script/AbstractScriptConfigurationLoadingTest.kt index da27468a763..e6bb2b4bc56 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/script/AbstractScriptConfigurationLoadingTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/script/AbstractScriptConfigurationLoadingTest.kt @@ -13,7 +13,6 @@ import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiFile import org.jetbrains.kotlin.idea.core.script.* -import org.jetbrains.kotlin.idea.core.script.configuration.CompositeScriptConfigurationManager import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptConfigurationManagerExtensions import org.jetbrains.kotlin.idea.core.script.configuration.loader.FileContentsDependentConfigurationLoader import org.jetbrains.kotlin.idea.core.script.configuration.testingBackgroundExecutor @@ -22,7 +21,7 @@ import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.psi.KtFile abstract class AbstractScriptConfigurationLoadingTest : AbstractScriptConfigurationTest() { - lateinit var scriptConfigurationManager: CompositeScriptConfigurationManager + lateinit var scriptConfigurationManager: ScriptConfigurationManager companion object { private var occurredLoadings = 0 @@ -42,7 +41,7 @@ abstract class AbstractScriptConfigurationLoadingTest : AbstractScriptConfigurat testScriptConfigurationNotification = true ApplicationManager.getApplication().isScriptChangesNotifierDisabled = false - scriptConfigurationManager = ServiceManager.getService(project, ScriptConfigurationManager::class.java) as CompositeScriptConfigurationManager + scriptConfigurationManager = ServiceManager.getService(project, ScriptConfigurationManager::class.java) } override fun tearDown() { @@ -104,7 +103,7 @@ abstract class AbstractScriptConfigurationLoadingTest : AbstractScriptConfigurat protected fun makeChanges(contents: String, file: KtFile = myFile as KtFile) { changeContents(contents) - scriptConfigurationManager.default.ensureUpToDatedConfigurationSuggested(file) + scriptConfigurationManager.updater.ensureUpToDatedConfigurationSuggested(file) } protected fun changeContents(contents: String, file: PsiFile = myFile) { diff --git a/idea/tests/org/jetbrains/kotlin/idea/script/ScriptConfigurationLoadingTest.kt b/idea/tests/org/jetbrains/kotlin/idea/script/ScriptConfigurationLoadingTest.kt index e2d0dffc3d5..6aa62a30550 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/script/ScriptConfigurationLoadingTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/script/ScriptConfigurationLoadingTest.kt @@ -6,10 +6,10 @@ package org.jetbrains.kotlin.idea.script import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.test.JUnit3RunnerWithInners +import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner import org.junit.runner.RunWith -@RunWith(JUnit3RunnerWithInners::class) +@RunWith(JUnit3WithIdeaConfigurationRunner::class) class ScriptConfigurationLoadingTest : AbstractScriptConfigurationLoadingTest() { fun testSimple() { assertAndLoadInitialConfiguration() @@ -163,10 +163,10 @@ class ScriptConfigurationLoadingTest : AbstractScriptConfigurationLoadingTest() fun testLoadingForUsagesSearch() { assertAndLoadInitialConfiguration() - assertTrue(scriptConfigurationManager.default.ensureLoadedFromCache(listOf(myFile as KtFile))) + assertTrue(scriptConfigurationManager.updater.ensureConfigurationUpToDate(listOf(myFile as KtFile))) changeContents("A") - assertTrue(scriptConfigurationManager.default.ensureLoadedFromCache(listOf(myFile as KtFile))) + assertFalse(scriptConfigurationManager.updater.ensureConfigurationUpToDate(listOf(myFile as KtFile))) } fun testReportsOnAutoApply() { diff --git a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/resolve/refineCompilationConfiguration.kt b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/resolve/refineCompilationConfiguration.kt index a027ba6ef7f..4065e5488b2 100644 --- a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/resolve/refineCompilationConfiguration.kt +++ b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/resolve/refineCompilationConfiguration.kt @@ -135,7 +135,6 @@ abstract class ScriptCompilationConfigurationWrapper(val script: SourceCode) { override val javaHome: File? get() = configuration?.get(ScriptCompilationConfiguration.hostConfiguration)?.get(ScriptingHostConfiguration.jvm.jdkHome) - ?: configuration?.get(ScriptingHostConfiguration.jvm.jdkHome) override val defaultImports: List get() = configuration?.get(ScriptCompilationConfiguration.defaultImports).orEmpty()