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 cc43997c72d..41228fd2687 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,8 +27,6 @@ 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 @@ -69,12 +67,6 @@ 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 @@ -97,15 +89,10 @@ interface ScriptConfigurationManager { */ fun isConfigurationLoadingInProgress(file: KtFile): Boolean - /** - * See [ScriptConfigurationUpdater]. - */ - val updater: ScriptConfigurationUpdater - /** * Clear all caches and re-highlighting opened scripts */ - fun clearConfigurationCachesAndRehighlight() + fun updateScriptDefinitions() /////////////// // classpath roots info: @@ -126,14 +113,14 @@ interface ScriptConfigurationManager { ServiceManager.getService(project, ScriptConfigurationManager::class.java) fun toVfsRoots(roots: Iterable): List { - return roots.mapNotNull { it.classpathEntryToVfs() } + return roots.mapNotNull { classpathEntryToVfs(it) } } - private fun File.classpathEntryToVfs(): VirtualFile? { + fun classpathEntryToVfs(file: File): VirtualFile? { val res = when { - !exists() -> null - isDirectory -> StandardFileSystems.local()?.findFileByPath(this.canonicalPath) - isFile -> StandardFileSystems.jar()?.findFileByPath(this.canonicalPath + URLUtil.JAR_SEPARATOR) + !file.exists() -> null + file.isDirectory -> StandardFileSystems.local()?.findFileByPath(file.canonicalPath) + file.isFile -> StandardFileSystems.jar()?.findFileByPath(file.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" }) @@ -150,7 +137,7 @@ interface ScriptConfigurationManager { @TestOnly fun clearCaches(project: Project) { (getInstance(project) as CompositeScriptConfigurationManager).default - .clearCaches() + .updateScriptDefinitions() } 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 788e471f491..7f682f43c5a 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 @@ -215,7 +215,7 @@ class ScriptDefinitionsManager(private val project: Project) : LazyScriptDefinit scriptDefinitionsCacheLock.withLock { scriptDefinitionsCache.clear() } // TODO: clear by script type/definition - ScriptConfigurationManager.getInstance(project).clearConfigurationCachesAndRehighlight() + ScriptConfigurationManager.getInstance(project).updateScriptDefinitions() } 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 ea27044d957..bf546d4d03f 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 @@ -15,69 +15,87 @@ 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.isNonScript import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper -import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult class CompositeScriptConfigurationManager(val project: Project) : ScriptConfigurationManager { @Suppress("unused") - private val notifier = ScriptChangesNotifier(project, updater) + private val notifier = ScriptChangesNotifier(project) - private val providers = ScriptingSupport.Provider.EPN.getPoint(project).extensionList + val updater = ScriptClassRootsUpdater(project, this) - val default = DefaultScriptingSupport(project) + private val plugins = ScriptingSupport.Provider.EPN.getPoint(project).extensionList - private val managers - get() = mutableListOf().also { managers -> - managers.add(default) - providers.forEach { managers.addAll(it.all) } - } - - private fun getRelatedManager(file: VirtualFile): ScriptingSupport = - providers.firstNotNullResult { it.getSupport(file) } ?: default - - private fun getRelatedManager(file: KtFile): ScriptingSupport = - getRelatedManager(file.originalFile.virtualFile) + val default = DefaultScriptingSupport(this) private fun getOrLoadConfiguration( virtualFile: VirtualFile, preloadedKtFile: KtFile? = null - ): ScriptCompilationConfigurationWrapper? = - getRelatedManager(virtualFile).getOrLoadConfiguration(virtualFile, preloadedKtFile) + ): ScriptCompilationConfigurationWrapper? { + val scriptConfiguration = classpathRoots.getScriptConfiguration(virtualFile) + if (scriptConfiguration != null) return scriptConfiguration - override fun getConfiguration(file: KtFile) = getOrLoadConfiguration(file.originalFile.virtualFile, file) + // check that this script should be loaded later in special way (e.g. gradle project import) + 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 hasConfiguration(file: KtFile): Boolean = - getRelatedManager(file).hasCachedConfiguration(file) + classpathRoots.contains(file.originalFile.virtualFile) override fun isConfigurationLoadingInProgress(file: KtFile): Boolean = - getRelatedManager(file).isConfigurationLoadingInProgress(file) + plugins.firstOrNull { it.isApplicable(file.originalFile.virtualFile) }?.isConfigurationLoadingInProgress(file) + ?: default.isConfigurationLoadingInProgress(file) - override val updater: ScriptConfigurationUpdater - get() = object : ScriptConfigurationUpdater { - override fun ensureUpToDatedConfigurationSuggested(file: KtFile) = - getRelatedManager(file).updater.ensureUpToDatedConfigurationSuggested(file) + @Volatile + private var classpathRoots: ScriptClassRootsCache = recreateRootsCache() - override fun ensureConfigurationUpToDate(files: List): Boolean = - files.groupBy { getRelatedManager(it) }.all { (manager, files) -> - manager.updater.ensureConfigurationUpToDate(files) - } + fun getLightScriptInfo(file: String): ScriptClassRootsCache.LightScriptInfo? = + classpathRoots.getLightScriptInfo(file) - override fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile) = - getRelatedManager(file).updater.suggestToUpdateConfigurationIfOutOfDate(file) + 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 updateScriptDefinitions() { + ScriptDependenciesModificationTracker.getInstance(project).incModificationCount() + + default.updateScriptDefinitions() + + if (classpathRoots.customDefinitionsUsed) { + plugins.forEach { + it.updateScriptDefinitions() + } + + updater.ensureUpdateScheduled() } + } init { val connection = project.messageBus.connect(project) connection.subscribe(ProjectTopics.PROJECT_ROOTS, object : ModuleRootListener { override fun rootsChanged(event: ModuleRootEvent) { - managers.forEach { - it.clearClassRootsCaches(project) + if (event.isCausedByFileTypesChange) return + + if (classpathRoots.hasInvalidSdk(project)) { + updater.ensureUpdateScheduled() } } }) @@ -88,49 +106,36 @@ class CompositeScriptConfigurationManager(val project: Project) : ScriptConfigur * Loads script configuration if classpath roots don't contain [file] yet */ private fun getActualClasspathRoots(file: VirtualFile): ScriptClassRootsCache { - val manager = getRelatedManager(file) - - val classpathRoots = manager.classpathRoots + val classpathRoots = classpathRoots if (classpathRoots.contains(file)) { return classpathRoots } getOrLoadConfiguration(file) - return manager.classpathRoots + return this.classpathRoots } override fun getScriptSdk(file: VirtualFile): Sdk? = getActualClasspathRoots(file).getScriptSdk(file) - override fun getFirstScriptsSdk(): Sdk? { - managers.forEach { - it.classpathRoots.firstScriptSdk?.let { sdk -> return sdk } - } - - return null - } + override fun getFirstScriptsSdk(): Sdk? = + classpathRoots.firstScriptSdk override fun getScriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope = - getActualClasspathRoots(file).getScriptDependenciesClassFilesScope(file) + classpathRoots.getScriptDependenciesClassFilesScope(file) override fun getAllScriptsDependenciesClassFilesScope(): GlobalSearchScope = - GlobalSearchScope.union(managers.map { it.classpathRoots.allDependenciesClassFilesScope }) + classpathRoots.allDependenciesClassFilesScope override fun getAllScriptDependenciesSourcesScope(): GlobalSearchScope = - GlobalSearchScope.union(managers.map { it.classpathRoots.allDependenciesSourcesScope }) + classpathRoots.allDependenciesSourcesScope override fun getAllScriptsDependenciesClassFiles(): List = - managers.flatMap { it.classpathRoots.allDependenciesClassFiles } + classpathRoots.allDependenciesClassFiles override fun getAllScriptDependenciesSources(): List = - managers.flatMap { it.classpathRoots.allDependenciesSources } - - override fun forceReloadConfiguration(file: VirtualFile, loader: ScriptConfigurationLoader): ScriptCompilationConfigurationWrapper? { - val ktFile = project.getKtFile(file, null) ?: return null - - return default.forceReloadConfiguration(ktFile, loader) - } + classpathRoots.allDependenciesSources /////////////////// // Adapters for deprecated API @@ -144,20 +149,4 @@ 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 988df2f7cf8..4b007100f6b 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,7 +20,6 @@ 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 @@ -37,21 +36,21 @@ import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock import kotlin.script.experimental.api.ScriptDiagnostic -class DefaultScriptingSupport(project: Project) : DefaultScriptingSupportBase(project) { +class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : DefaultScriptingSupportBase(manager) { // TODO public for tests val backgroundExecutor: BackgroundExecutor = - if (ApplicationManager.getApplication().isUnitTestMode) TestingBackgroundExecutor(rootsIndexer) - else DefaultBackgroundExecutor(project, rootsIndexer) + if (ApplicationManager.getApplication().isUnitTestMode) TestingBackgroundExecutor(manager.updater) + else DefaultBackgroundExecutor(project, manager.updater) private val outsiderLoader = ScriptOutsiderFileConfigurationLoader(project) private val fileAttributeCache = ScriptConfigurationFileAttributeCache(project) private val defaultLoader = DefaultScriptConfigurationLoader(project) - private val loaders: Sequence - get() = sequence { - yield(outsiderLoader) - yield(fileAttributeCache) - yieldAll(LOADER.getPoint(project).extensionList) - yield(defaultLoader) + private val loaders: List + get() = mutableListOf().apply { + add(outsiderLoader) + add(fileAttributeCache) + addAll(LOADER.getPoint(project).extensionList) + add(defaultLoader) } private val saveLock = ReentrantLock() @@ -166,7 +165,7 @@ class DefaultScriptingSupport(project: Project) : DefaultScriptingSupportBase(pr if (!ScriptDefinitionsManager.getInstance(project).isReady()) return null val scriptDefinition = file.findScriptDefinition() ?: return null - rootsIndexer.transaction { + manager.updater.update { if (!loader.shouldRunInBackground(scriptDefinition)) { loader.loadDependencies(false, file, scriptDefinition, loadingContext) } else { @@ -240,7 +239,7 @@ class DefaultScriptingSupport(project: Project) : DefaultScriptingSupportBase(pr onClick = { saveReports(file, newResult.reports) file.removeScriptDependenciesNotificationPanel(project) - rootsIndexer.transaction { + manager.updater.update { setAppliedConfiguration(file, newResult) } } @@ -272,10 +271,16 @@ class DefaultScriptingSupport(project: Project) : DefaultScriptingSupportBase(pr } } } + + companion object { + fun getInstance(project: Project) = + (ScriptConfigurationManager.getInstance(project) as CompositeScriptConfigurationManager).default + } } -abstract class DefaultScriptingSupportBase(val project: Project) : ScriptingSupport() { - protected val rootsIndexer = ScriptClassRootsIndexer(project) +abstract class DefaultScriptingSupportBase(val manager: CompositeScriptConfigurationManager) : ScriptingSupport() { + val project: Project + get() = manager.project @Suppress("LeakingThis") protected val cache: ScriptConfigurationCache = createCache() @@ -311,14 +316,14 @@ abstract class DefaultScriptingSupportBase(val project: Project) : ScriptingSupp fun getAppliedConfiguration(file: VirtualFile?): ScriptConfigurationSnapshot? = getCachedConfigurationState(file)?.applied - override fun hasCachedConfiguration(file: KtFile): Boolean = + fun hasCachedConfiguration(file: KtFile): Boolean = getAppliedConfiguration(file.originalFile.virtualFile) != null - override fun isConfigurationLoadingInProgress(file: KtFile): Boolean { + fun isConfigurationLoadingInProgress(file: KtFile): Boolean { return !hasCachedConfiguration(file) && !ScriptConfigurationManager.isManualConfigurationLoading(file.originalFile.virtualFile) } - override fun getOrLoadConfiguration( + fun getOrLoadConfiguration( virtualFile: VirtualFile, preloadedKtFile: KtFile? ): ScriptCompilationConfigurationWrapper? { @@ -326,32 +331,30 @@ abstract class DefaultScriptingSupportBase(val project: Project) : ScriptingSupp if (cached != null) return cached.configuration val ktFile = project.getKtFile(virtualFile, preloadedKtFile) ?: return null - rootsIndexer.transaction { + manager.updater.update { reloadOutOfDateConfiguration(ktFile, isFirstLoad = true) } return getAppliedConfiguration(virtualFile)?.configuration } - override val updater: ScriptConfigurationUpdater = object : ScriptConfigurationUpdater { - override fun ensureUpToDatedConfigurationSuggested(file: KtFile) { - reloadIfOutOfDate(listOf(file), loadEvenWillNotBeApplied = true, isPostponedLoad = false) - } + fun ensureUpToDatedConfigurationSuggested(file: KtFile) { + reloadIfOutOfDate(listOf(file), loadEvenWillNotBeApplied = true, isPostponedLoad = false) + } - override fun ensureConfigurationUpToDate(files: List): Boolean { - return reloadIfOutOfDate(files, loadEvenWillNotBeApplied = false, isPostponedLoad = false) - } + fun ensureConfigurationUpToDate(files: List): Boolean { + return reloadIfOutOfDate(files, loadEvenWillNotBeApplied = false, isPostponedLoad = false) + } - override fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile) { - reloadIfOutOfDate(listOf(file), loadEvenWillNotBeApplied = true, isPostponedLoad = true) - } + fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile) { + reloadIfOutOfDate(listOf(file), loadEvenWillNotBeApplied = true, isPostponedLoad = true) } private fun reloadIfOutOfDate(files: List, loadEvenWillNotBeApplied: Boolean, isPostponedLoad: Boolean): Boolean { if (!ScriptDefinitionsManager.getInstance(project).isReady()) return false var upToDate = true - rootsIndexer.transaction { + manager.updater.update { files.forEach { file -> val virtualFile = file.originalFile.virtualFile if (virtualFile != null) { @@ -376,23 +379,13 @@ abstract class DefaultScriptingSupportBase(val project: Project) : ScriptingSupp file: VirtualFile, newConfigurationSnapshot: ScriptConfigurationSnapshot? ) { - rootsIndexer.checkInTransaction() + manager.updater.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() - } - + manager.updater.invalidate(file) cache.setApplied(file, newConfigurationSnapshot) - - clearClassRootsCaches(project) - } - - ScriptingSupportHelper.updateHighlighting(project) { - it == file } } @@ -403,10 +396,6 @@ abstract class DefaultScriptingSupportBase(val project: Project) : ScriptingSupp 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 @@ -416,20 +405,29 @@ abstract class DefaultScriptingSupportBase(val project: Project) : ScriptingSupp val virtualFile = file.virtualFile if (cache[virtualFile]?.isUpToDate(project, virtualFile, file) == true) return - rootsIndexer.transaction { + manager.updater.update { reloadOutOfDateConfiguration(file, forceSync = true, loadEvenWillNotBeApplied = true) } } - override fun clearCaches() { + fun updateScriptDefinitions() { cache.clear() } - override fun recreateRootsCache(): ScriptClassRootsCache { - return DefaultClassRootsCache( - project, - cache.allApplied() - ) + 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) } } 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 499a014d865..3b2b55b4b30 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,76 +6,20 @@ 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.debug import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper -import java.util.concurrent.locks.ReentrantLock -import kotlin.concurrent.withLock abstract class ScriptingSupport { abstract class Provider { - abstract val all: Collection - - abstract fun getSupport(file: VirtualFile): ScriptingSupport? + abstract fun updateScriptDefinitions() + abstract fun isApplicable(file: VirtualFile): Boolean + abstract fun isConfigurationLoadingInProgress(file: KtFile): Boolean + abstract fun collectConfigurations(builder: ScriptClassRootsCache.Builder) 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/cache/ScriptConfigurationCache.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationCache.kt index 9a5877d5e5b..14eabb2b5e9 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(): Map + fun allApplied(): List> fun clear() fun getAnyLoadedScript(): ScriptCompilationConfigurationWrapper? 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 56ce446feb3..b5a0469ac0f 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(): Map { - val result = hashMapOf() + override fun allApplied(): List> { + val result = mutableListOf>() for ((file, configuration) in memoryCache.entrySet()) { if (configuration.applied?.configuration != null) { - result[file] = configuration.applied.configuration + result.add(Pair(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 3240a8e53a2..95fdc34ae53 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, updater: ScriptConfigurationUpdater) { + override fun editorActivated(vFile: VirtualFile) { val file = getAnalyzableKtFileForScript(vFile) ?: return - updater.ensureUpToDatedConfigurationSuggested(file) + default.ensureUpToDatedConfigurationSuggested(file) } - override fun documentChanged(vFile: VirtualFile, updater: ScriptConfigurationUpdater) { + override fun documentChanged(vFile: VirtualFile) { val file = getAnalyzableKtFileForScript(vFile) ?: return - updater.ensureUpToDatedConfigurationSuggested(file) + default.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 2f36addd61c..e8be01fb80d 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,6 +9,7 @@ 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 @@ -21,8 +22,11 @@ import org.jetbrains.kotlin.psi.KtFile * @see DefaultScriptConfigurationManager for more details. */ abstract class ScriptChangeListener(protected val project: Project) { - abstract fun editorActivated(vFile: VirtualFile, updater: ScriptConfigurationUpdater) - abstract fun documentChanged(vFile: VirtualFile, updater: ScriptConfigurationUpdater) + val default: DefaultScriptingSupport + get() = DefaultScriptingSupport.getInstance(project) + + abstract fun editorActivated(vFile: VirtualFile) + abstract fun documentChanged(vFile: VirtualFile) 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 043c4cf42e2..ee96808e39d 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,8 +22,7 @@ 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 updater: ScriptConfigurationUpdater, + private val project: Project ) { private val scriptsQueue = Alarm(Alarm.ThreadToUse.POOLED_THREAD, project) private val scriptChangesListenerDelay = 1400 @@ -46,10 +45,10 @@ internal class ScriptChangesNotifier( private fun runScriptDependenciesUpdateIfNeeded(file: VirtualFile) { if (ApplicationManager.getApplication().isUnitTestMode) { - getListener(project, file)?.editorActivated(file, updater) + getListener(project, file)?.editorActivated(file) } else { AppExecutorUtil.getAppExecutorService().submit { - getListener(project, file)?.editorActivated(file, updater) + getListener(project, file)?.editorActivated(file) } } } @@ -68,11 +67,11 @@ internal class ScriptChangesNotifier( } if (ApplicationManager.getApplication().isUnitTestMode) { - getListener(project, file)?.documentChanged(file, updater) + getListener(project, file)?.documentChanged(file) } else { scriptsQueue.cancelAllRequests() scriptsQueue.addRequest( - { getListener(project, file)?.documentChanged(file, updater) }, + { getListener(project, file)?.documentChanged(file) }, scriptChangesListenerDelay, true, ) @@ -84,11 +83,10 @@ internal class ScriptChangesNotifier( } private val defaultListener = DefaultScriptChangeListener(project) - private val listeners: Sequence - get() = sequence { - yieldAll(LISTENER.getPoint(project).extensionList) - yield(defaultListener) - } + private val listeners: Collection = mutableListOf().apply { + addAll(LISTENER.getPoint(project).extensionList) + add(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/utils/DefaultBackgroundExecutor.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/DefaultBackgroundExecutor.kt index 7e1a7298fa5..d6ca4347e0c 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 @@ -32,7 +32,7 @@ import java.util.* */ internal class DefaultBackgroundExecutor( val project: Project, - val rootsManager: ScriptClassRootsIndexer + val rootsManager: ScriptClassRootsUpdater ) : BackgroundExecutor { companion object { const val PROGRESS_INDICATOR_DELAY = 1000 @@ -137,7 +137,7 @@ internal class DefaultBackgroundExecutor( private fun ensureInTransaction() { if (inTransaction) return inTransaction = true - rootsManager.startTransaction() + rootsManager.beginUpdating() } @Synchronized 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 deleted file mode 100644 index 8e3b7b16b41..00000000000 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/DefaultClassRootsCache.kt +++ /dev/null @@ -1,86 +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.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 a0f73423e01..5ef10d0ec89 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,100 +12,99 @@ 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.vfs.* +import com.intellij.openapi.vfs.VfsUtil +import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.search.GlobalSearchScope -import com.intellij.psi.search.NonClasspathDirectoriesScope -import com.intellij.util.containers.ConcurrentFactoryMap +import com.intellij.psi.search.NonClasspathDirectoriesScope.compose import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks import org.jetbrains.kotlin.idea.core.script.LOG -import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager +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.util.getProjectJdkTableSafe import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper import java.io.File +import java.lang.ref.Reference +import java.lang.ref.SoftReference -abstract class ScriptClassRootsCache( - private val project: Project, - private val rootsCacheKey: ScriptClassRootsStorage.Companion.Key, - private val roots: ScriptClassRootsStorage.Companion.ScriptClassRoots +class ScriptClassRootsCache( + val scripts: Map, + val classes: Set, + val sources: Set, + val sdks: Map, + private val nonModulesSdks: Collection, + val customDefinitionsUsed: Boolean ) { - protected abstract fun getConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? + abstract class LightScriptInfo { + @Volatile + var heavyCache: Reference? = null - abstract val firstScriptSdk: Sdk? + abstract fun buildConfiguration(): ScriptCompilationConfigurationWrapper? + } - abstract fun getScriptSdk(file: VirtualFile): Sdk? + class DirectScriptInfo(val result: ScriptCompilationConfigurationWrapper) : LightScriptInfo() { + override fun buildConfiguration(): ScriptCompilationConfigurationWrapper = result + } - abstract fun contains(file: VirtualFile): Boolean - - private class Fat( + class HeavyScriptInfo( val scriptConfiguration: ScriptCompilationConfigurationWrapper, - val classFilesScope: GlobalSearchScope + val classFilesScope: GlobalSearchScope, + val sdk: Sdk? ) - val allDependenciesClassFiles by lazy { - ScriptClassRootsStorage.getInstance(project).loadClasspathRoots(rootsCacheKey) - } + class Builder(val project: Project) { + val scripts = mutableMapOf() - val allDependenciesSources by lazy { - ScriptClassRootsStorage.getInstance(project).loadSourcesRoots(rootsCacheKey) - } + private val defaultSdk = getScriptDefaultSdk() + val sdks = mutableMapOf(null to defaultSdk) - val allDependenciesClassFilesScope by lazy { - NonClasspathDirectoriesScope.compose(allDependenciesClassFiles) - } + val classes = mutableSetOf() + val sources = mutableSetOf() + private var customDefinitionsUsed: Boolean = false - val allDependenciesSourcesScope by lazy { - NonClasspathDirectoriesScope.compose(allDependenciesSources) - } + fun build(): ScriptClassRootsCache { + val nonIndexedSdks = collectNonIndexedSdks() + return ScriptClassRootsCache(scripts, classes, sources, sdks, nonIndexedSdks, customDefinitionsUsed) + } - 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)) - ) + fun collectNonIndexedSdks(): MutableSet { + val nonIndexedSdks = sdks.values.filterNotNullTo(mutableSetOf()) + ModuleManager.getInstance(project).modules.map { + nonIndexedSdks.remove(ModuleRootManager.getInstance(it).sdk) } - - return@createWeakMap Fat( - configuration, - NonClasspathDirectoriesScope.compose( - sdk.rootProvider.getFiles(OrderRootType.CLASSES).toList() + ScriptConfigurationManager.toVfsRoots(roots) - ) - ) + return nonIndexedSdks } - 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 useCustomScriptDefinition() { + customDefinitionsUsed = true } - fun getScriptSdkOrDefault(javaHome: File?, project: Project): Sdk? { - return javaHome?.let { getScriptSdkByJavaHome(it) } ?: getScriptDefaultSdk(project) + 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 = javaHome.canonicalPath + return sdks.getOrPut(canonicalPath) { + getScriptSdkByJavaHome(javaHome) ?: defaultSdk + } } private fun getScriptSdkByJavaHome(javaHome: File): Sdk? { @@ -119,7 +118,13 @@ abstract class ScriptClassRootsCache( return getProjectJdkTableSafe().allJdks.find { it.homeDirectory == javaHomeVF } } - private fun getScriptDefaultSdk(project: Project): Sdk? { + 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? { val projectSdk = ProjectRootManager.getInstance(project).projectSdk?.takeIf { it.canBeUsedForScript() } if (projectSdk != null) return projectSdk @@ -133,32 +138,118 @@ abstract class ScriptClassRootsCache( "projectSdk = ${ProjectRootManager.getInstance(project).projectSdk}, " + "all sdks = ${getAllProjectSdks().joinToString("\n")}" ) + return null } private fun Sdk.canBeUsedForScript() = sdkType is JavaSdkType + } - fun Sdk.isAlreadyIndexed(project: Project): Boolean { - return ModuleManager.getInstance(project).modules.any { ModuleRootManager.getInstance(it).sdk == this } + 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 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 + old.scripts.forEach { + if (it.key !in scripts) { + changed.add(it.key) + } } + + 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 deleted file mode 100644 index efe5be39fc6..00000000000 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/ScriptClassRootsIndexer.kt +++ /dev/null @@ -1,80 +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.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 4f817171ed4..deb243235f4 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,144 +7,46 @@ package org.jetbrains.kotlin.idea.core.script.configuration.utils import com.intellij.openapi.components.* import com.intellij.openapi.project.Project -import com.intellij.openapi.projectRoots.ProjectJdkTable -import com.intellij.openapi.projectRoots.Sdk -import com.intellij.openapi.roots.OrderRootType -import com.intellij.openapi.vfs.JarFileSystem -import com.intellij.openapi.vfs.StandardFileSystems -import com.intellij.openapi.vfs.VirtualFile -import org.jdom.Element -import org.jetbrains.kotlin.idea.core.script.debug +import com.intellij.util.xmlb.XmlSerializerUtil +/** + * Actually, this storage is required only for before Gradle 6.0. + * For projects starting from Gradle 6.0, [org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptingSupportProvider] + * will also store all roots in FS, and will overwrite this one shrotly after start. + * + * Btw, this is still useful for projects without custom scripting support. + */ @State( name = "ScriptClassRootsStorage", storages = [Storage(StoragePathMacros.CACHE_FILE)] ) -class ScriptClassRootsStorage : PersistentStateComponent { +class ScriptClassRootsStorage(val project: Project) : PersistentStateComponent { + private var classpath: Set = hashSetOf() + private var sources: Set = hashSetOf() + private var sdks: Set = hashSetOf() - 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 getState(): ScriptClassRootsStorage? { + return this } - private fun storeCollection(root: Element, col: Map>, name: String) { - for ((key, paths) in col) { - for (path in paths) { - val element = Element(name) - element.setAttribute("path", path) - element.setAttribute("key", key) - root.addContent(element) - } - } + override fun loadState(state: ScriptClassRootsStorage) { + XmlSerializerUtil.copyBean(state, this) } - override fun loadState(state: Element) { - classpath = readCollection(state, "classpath") - sources = readCollection(state, "sources") - sdks = readCollection(state, "sdks") + fun save(roots: ScriptClassRootsCache.Builder) { + classpath = roots.classes + sources = roots.sources + sdks = roots.sdks.values.mapNotNullTo(mutableSetOf()) { it?.name } } - 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]) + fun load(builder: ScriptClassRootsCache.Builder) { + builder.sources.addAll(sources) + builder.classes.addAll(classpath) + sdks.forEach(builder::addSdkByName) } 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 new file mode 100644 index 00000000000..f529db87874 --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/utils/ScriptClassRootsUpdater.kt @@ -0,0 +1,136 @@ +/* + * 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.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 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.configuration.ScriptingSupportHelper +import org.jetbrains.kotlin.idea.core.script.debug +import org.jetbrains.kotlin.idea.util.application.runWriteAction +import java.util.concurrent.atomic.AtomicInteger + +/** + * 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 val concurrentUpdates = AtomicInteger() + + @Synchronized + @Suppress("UNUSED_PARAMETER") + fun invalidate(file: VirtualFile) { + // todo: record invalided files for some optimisations in update + invalidate() + } + + @Synchronized + fun invalidate() { + checkInTransaction() + invalidated = 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() + } + + private fun scheduleUpdateIfInvalid() { + if (!invalidated) return + invalidated = false + + ensureUpdateScheduled() + } + + private var scheduledUpdate: ProgressIndicator? = null + + @Synchronized + fun ensureUpdateScheduled() { + scheduledUpdate?.cancel() + scheduledUpdate = BackgroundTaskUtil.executeOnPooledThread(project) { + doUpdate() + } + } + + fun doUpdate() { + val updates = manager.collectRootsAndCheckNew() + + if (!updates.changed) return + + ProgressManager.checkCanceled() + + if (updates.hasNewRoots) { + notifyRootsChanged() + } + + PsiElementFinder.EP.findExtensionOrFail(KotlinScriptDependenciesClassFinder::class.java, project) + .clearCache() + + ScriptDependenciesModificationTracker.getInstance(project).incModificationCount() + + if (updates.updatedScripts.isNotEmpty()) { + ScriptingSupportHelper.updateHighlighting(project) { + it.path in updates.updatedScripts + } + } + + 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) + } + } +} \ 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 7a15e63bcab..608b4042563 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: ScriptClassRootsIndexer + private val rootsManager: ScriptClassRootsUpdater ) : BackgroundExecutor { val backgroundQueue = HashSetQueue() @@ -42,7 +42,7 @@ class TestingBackgroundExecutor internal constructor( actions() - rootsManager.transaction { + rootsManager.update { copy.forEach { it.actions() } 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 index 2a43c7c4f7e..9a0513839f6 100644 --- 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 @@ -5,33 +5,9 @@ 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( +data class GradleImportedBuildRootData( 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 +) \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleBuildRoot.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleBuildRoot.kt new file mode 100644 index 00000000000..309bfdf50c3 --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleBuildRoot.kt @@ -0,0 +1,114 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.scripting.gradle + +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.LocalFileSystem +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.scripting.gradle.importing.KotlinDslScriptModel +import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition +import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition +import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper +import org.jetbrains.kotlin.scripting.resolve.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 + +sealed class GradleBuildRoot { + abstract val pathPrefix: String + + abstract class Unlinked : GradleBuildRoot() + + class UnlinkedUnknown(override val pathPrefix: String) : Unlinked() + + class UnlinkedLegacy(override val pathPrefix: String) : Unlinked() + + class UnlinkedSupported(override val pathPrefix: String) : Unlinked() + + abstract class Linked : GradleBuildRoot() { + @Volatile + var importing = false + } + + class Legacy(override val pathPrefix: String) : Linked() + + class New(override val pathPrefix: String) : Linked() + + class Imported( + val project: Project, + val dir: VirtualFile, + val context: GradleKtsContext, + val data: GradleImportedBuildRootData + ) : Linked() { + override val pathPrefix: String = dir.path + + fun collectConfigurations(builder: ScriptClassRootsCache.Builder) { + val javaHome = context.javaHome + javaHome?.let { builder.addSdk(it) } + + val anyScript = data.models.firstOrNull()?.let { + LocalFileSystem.getInstance().findFileByPath(it.file) + } + + val scriptDefinition: ScriptDefinition? = anyScript?.findScriptDefinition(project) + + if (scriptDefinition != null) { + builder.classes.addAll(data.templateClasspath) + } + + val root = ScriptInfoRoot(this, scriptDefinition) + + data.models.forEach { + builder.scripts[it.file] = ScriptInfo(root, it) + + builder.classes.addAll(it.classPath) + builder.sources.addAll(it.sourcePath) + } + } + + /** + * Common info between scripts (to save bits inside ScriptInfo) + */ + data class ScriptInfoRoot( + val buildRoot: Imported, + val scriptDefinition: ScriptDefinition? + ) + + data class ScriptInfo( + val root: ScriptInfoRoot, + val model: KotlinDslScriptModel + ) : ScriptClassRootsCache.LightScriptInfo() { + val buildRoot get() = root.buildRoot + + override fun buildConfiguration(): ScriptCompilationConfigurationWrapper? { + val javaHome = buildRoot.context.javaHome + val scriptDefinition = root.scriptDefinition + + val scriptFile = File(model.file) + val virtualFile = VfsUtil.findFile(scriptFile.toPath(), true)!! + + if (scriptDefinition == null) return null + + return ScriptCompilationConfigurationWrapper.FromCompilationConfiguration( + VirtualFileScriptSource(virtualFile), + scriptDefinition.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(scriptDefinition) + ) + } + } + } +} \ 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 deleted file mode 100644 index c08827db02e..00000000000 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleClassRootsCache.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package org.jetbrains.kotlin.idea.scripting.gradle - -import com.intellij.openapi.project.Project -import com.intellij.openapi.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/GradleScriptListener.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptListener.kt index 035e3f82f97..76cd963f621 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,5 +1,5 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ @@ -9,36 +9,23 @@ 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.core.script.configuration.listener.ScriptConfigurationUpdater -open class GradleScriptListener(project: Project) : ScriptChangeListener(project) { +class GradleScriptListener(project: Project) : ScriptChangeListener(project) { init { // start GradleScriptInputsWatcher to track changes in gradle-configuration related files project.service().startWatching() } - override fun editorActivated(vFile: VirtualFile, updater: ScriptConfigurationUpdater) { - if (!isInAffectedGradleProjectFiles(project, vFile.path)) return - - if (useScriptConfigurationFromImportOnly()) { - // do nothing - } else { - val file = getAnalyzableKtFileForScript(vFile) ?: return - updater.suggestToUpdateConfigurationIfOutOfDate(file) - } - } - - override fun documentChanged(vFile: VirtualFile, updater: ScriptConfigurationUpdater) { - if (!isInAffectedGradleProjectFiles(project, vFile.path)) return - - val file = getAnalyzableKtFileForScript(vFile) - if (file != null) { - // *.gradle.kts file was changed - updater.suggestToUpdateConfigurationIfOutOfDate(file) - } - } - override fun isApplicable(vFile: VirtualFile): Boolean { - return isGradleKotlinScript(vFile) + return GradleScriptingSupportProvider.getInstance(project).isApplicable(vFile) + } + + override fun editorActivated(vFile: VirtualFile) { + // do nothing + } + + override fun documentChanged(vFile: VirtualFile) { + + GradleScriptingSupportProvider.getInstance(project).getScriptInfo(vFile)?.model?.inputs } } \ 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 deleted file mode 100644 index 3a464bb93b6..00000000000 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptingSupport.kt +++ /dev/null @@ -1,62 +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.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 index 64fd3f4ed1d..c027b8688e1 100644 --- 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 @@ -5,82 +5,146 @@ 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.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.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.settings.* 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]. + * Manages [roots] gradle build roots: + * - populated after Gradle project sync, by calling [update] + * - stored in FS and loaded in [init] */ class GradleScriptingSupportProvider(val project: Project) : ScriptingSupport.Provider() { - private val rootsIndexer = ScriptClassRootsIndexer(project) + val manager: CompositeScriptConfigurationManager + get() = ScriptConfigurationManager.getInstance(project) as CompositeScriptConfigurationManager - val roots = RootsIndex() - override val all get() = roots.values + private val updater + get() = manager.updater + + val roots = RootsIndex() + + //////////// + /// ScriptingSupport.Provider implementation: + + override fun updateScriptDefinitions() { + // nothing related to script definition and project roots are cached + } + + override fun isApplicable(file: VirtualFile): Boolean { + val buildRoot = findScriptBuildRoot(file) ?: return false + if (buildRoot is GradleBuildRoot.Legacy) return false + if (buildRoot is GradleBuildRoot.UnlinkedLegacy) return false + return true + } + + override fun isConfigurationLoadingInProgress(file: KtFile): Boolean { + return when (val root = findScriptBuildRoot(file.originalFile.virtualFile)) { + is GradleBuildRoot.Linked -> root.importing + else -> false + } + } + + override fun collectConfigurations(builder: ScriptClassRootsCache.Builder) { + roots.values.forEach { root -> + if (root is GradleBuildRoot.Imported) { + root.collectConfigurations(builder) + } + } + } + + ////////////////// private val VirtualFile.localPath get() = path - private fun findRoot(file: VirtualFile) = roots.findRoot(file.localPath) + fun getScriptInfo(file: VirtualFile): GradleBuildRoot.Imported.ScriptInfo? = + manager.getLightScriptInfo(file.localPath) as? GradleBuildRoot.Imported.ScriptInfo - override fun getSupport(file: VirtualFile): ScriptingSupport? { - if (isGradleKotlinScript(file)) { - findRoot(file)?.let { return it } + fun findScriptBuildRoot(file: VirtualFile): GradleBuildRoot? { + if (!isGradleKotlinScript(file)) return null - val externalProjectSettings = findExternalProjectSettings(file) ?: return null - if (kotlinDslScriptsModelImportSupported(getGradleVersion(project, externalProjectSettings))) { - return unlinkedFilesSupport - } - } - return null + val path = file.localPath + val found = roots.findNearestRoot(path) + if (found != null) return found + + return findNewScriptBuildRoot(path) } - private fun findExternalProjectSettings(file: VirtualFile): GradleProjectSettings? { + private fun findNewScriptBuildRoot(scriptPath: String): GradleBuildRoot { + val newRoot = updater.update { + loadScriptBuildRoot(scriptPath) + } + roots[newRoot.pathPrefix] = newRoot + return newRoot + } + + private fun updateBuildRoot(rootPath: String) { + val settings = getGradleProjectSettings(rootPath) ?: return + val newRoot = loadLinkedRoot(settings) + roots[newRoot.pathPrefix] = newRoot + if (newRoot is GradleBuildRoot.Imported) { + updater.ensureUpdateScheduled() + } + } + + private fun loadScriptBuildRoot(scriptPath: String): GradleBuildRoot { + val settings = findNearestGradleProjectSettings(scriptPath) ?: return detectUnlinkedGradleBuildRoot(scriptPath) + return loadLinkedRoot(settings) + } + + private fun loadLinkedRoot(settings: GradleProjectSettings) = + tryLoadFromFsCache(settings) ?: createOtherLinkedRoot(settings) + + private fun createOtherLinkedRoot(settings: GradleProjectSettings): GradleBuildRoot.Linked { + val supported = kotlinDslScriptsModelImportSupported(settings.resolveGradleVersion().version) + return when { + supported -> GradleBuildRoot.New(settings.externalProjectPath) + else -> GradleBuildRoot.Legacy(settings.externalProjectPath) + } + } + + private fun getGradleProjectSettings(workingDir: String): GradleProjectSettings? { + return (ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID) as GradleSettings) + .getLinkedProjectSettings(workingDir) + } + + private fun findNearestGradleProjectSettings(localPath: String): GradleProjectSettings? { return getGradleProjectSettings(project) - .filter { file.localPath.startsWith(it.externalProjectPath) } + .filter { 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) - } + private fun detectUnlinkedGradleBuildRoot(file: String): GradleBuildRoot.Unlinked = + GradleBuildRoot.UnlinkedUnknown(file) - if (support != null) { - set(support.buildRoot.localPath, support) - } - } - } - } + init { + getGradleProjectSettings(project).forEach { + roots[it.externalProjectPath] = loadLinkedRoot(it) } // subscribe to gradle build unlink val listener = object : GradleSettingsListenerAdapter() { + override fun onProjectsLinked(settings: MutableCollection) { + settings.forEach { + loadLinkedRoot(it) + } + } + override fun onProjectsUnlinked(linkedProjectPaths: MutableSet) { linkedProjectPaths.forEach { val buildRoot = VfsUtil.findFile(Paths.get(it), false) @@ -91,30 +155,68 @@ class GradleScriptingSupportProvider(val project: Project) : ScriptingSupport.Pr } } } + + override fun onGradleHomeChange(oldPath: String?, newPath: String?, linkedProjectPath: String) { + updateBuildRoot(linkedProjectPath) + manager.updater.ensureUpdateScheduled() + } + + override fun onGradleDistributionTypeChange(currentValue: DistributionType?, linkedProjectPath: String) { + updateBuildRoot(linkedProjectPath) + manager.updater.ensureUpdateScheduled() + } } project.messageBus.connect(project).subscribe(GradleSettingsListener.TOPIC, listener) } + private fun tryLoadFromFsCache(settings: GradleProjectSettings) = + tryCreateImportedRoot(settings.externalProjectPath) { + KotlinDslScriptModels.read(it) + } + + fun markImportingInProgress(workingDir: String) { + val linked = roots.getBuildRoot(workingDir) as? GradleBuildRoot.Linked + if (linked != null) { + linked.importing = true + } else { + val settings = getGradleProjectSettings(workingDir) + if (settings != null) { + val root = createOtherLinkedRoot(settings) + root.importing = true + roots[workingDir] = root + } + } + } + fun update(build: KotlinDslGradleBuildSync) { + val settings = getGradleProjectSettings(build.workingDir) ?: return + + (roots.getBuildRoot(build.workingDir) as? GradleBuildRoot.Linked)?.importing = false + + if (!kotlinDslScriptsModelImportSupported(settings.resolveGradleVersion().version)) { + roots[build.workingDir] = GradleBuildRoot.Legacy(build.workingDir) + return + } + // 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 root = roots.findNearestRoot(build.workingDir) ?: return + if (root is GradleBuildRoot.Imported && root.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 + val data = GradleImportedBuildRootData(templateClasspath, build.models) + val newSupport = tryCreateImportedRoot(build.workingDir) { data } ?: return + KotlinDslScriptModels.write(newSupport.dir, data) + roots[build.workingDir] = newSupport + manager.updater.ensureUpdateScheduled() } - private fun createSupport( + private fun tryCreateImportedRoot( externalProjectPath: String, - dataProvider: (buildRoot: VirtualFile) -> ConfigurationData? - ): GradleScriptingSupport? { + dataProvider: (buildRoot: VirtualFile) -> GradleImportedBuildRootData? + ): GradleBuildRoot.Imported? { val gradleExeSettings = ExternalSystemApiUtil.getExecutionSettings( project, @@ -125,28 +227,15 @@ class GradleScriptingSupportProvider(val project: Project) : ScriptingSupport.Pr val buildRoot = VfsUtil.findFile(Paths.get(externalProjectPath), true) ?: return null val data = dataProvider(buildRoot) ?: return null - val newSupport = GradleScriptingSupport( + val newSupport = GradleBuildRoot.Imported( project, buildRoot, GradleKtsContext(gradleExeSettings.javaHome?.let { File(it) }), - Configuration(data) + data ) - val oldSupport = roots.findRoot(externalProjectPath) + val oldSupport = roots.findNearestRoot(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) } @@ -161,44 +250,11 @@ class GradleScriptingSupportProvider(val project: Project) : ScriptingSupport.Pr ?.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) + val script = getScriptInfo(file) ?: return false + return script.model.inputs.isUpToDate(project, file) } companion object { 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 00330d665d6..6dc367a2c85 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,18 +16,20 @@ 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.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 - if (!isInAffectedGradleProjectFiles(project, file.path)) { - return EditorNotificationPanel().apply { + return when (GradleScriptingSupportProvider.getInstance(project).findScriptBuildRoot(file)) { + is GradleBuildRoot.UnlinkedSupported -> EditorNotificationPanel().apply { text(KotlinIdeaGradleBundle.message("text.the.associated.gradle.project.isn.t.imported")) val linkProjectText = KotlinIdeaGradleBundle.message("action.label.text.load.script.configuration") @@ -53,22 +55,20 @@ class MissingGradleScriptConfigurationNotificationProvider(private val project: linkProjectText ) } - } - - if (GradleScriptingSupportProvider.getInstance(project).isMissingConfigurationCanBeLoadedDuringImport(file)) { - return EditorNotificationPanel().apply { + is GradleBuildRoot.Linked -> EditorNotificationPanel().apply { text(getMissingConfigurationNotificationText()) createActionLabel(getMissingConfigurationActionText()) { runPartialGradleImport(project) } } + else -> null } - - return null } private val loaderForOutOfProjectScripts by lazy { - GradleScriptConfigurationLoaderForOutOfProjectScripts(project) + GradleLegacyScriptConfigurationLoaderForOutOfProjectScripts( + project + ) } companion object { 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 f6e78171caf..3906b04c01a 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,6 +9,7 @@ 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.GradleScriptingSupportProvider class KotlinDslSyncListener : ExternalSystemTaskNotificationListenerAdapter() { override fun onStart(id: ExternalSystemTaskId, workingDir: String?) { @@ -18,6 +19,7 @@ class KotlinDslSyncListener : ExternalSystemTaskNotificationListenerAdapter() { if (workingDir == null) return val project = id.findProject() ?: return + GradleScriptingSupportProvider.getInstance(project).markImportingInProgress(workingDir) project.kotlinGradleDslSync[id] = KotlinDslGradleBuildSync(workingDir, id) } diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptConfigurationLoader.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/legacy/GradleLegacyScriptConfigurationLoader.kt similarity index 83% rename from idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptConfigurationLoader.kt rename to idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/legacy/GradleLegacyScriptConfigurationLoader.kt index b74be965796..f4fcffc11db 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptConfigurationLoader.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/legacy/GradleLegacyScriptConfigurationLoader.kt @@ -1,9 +1,9 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ -package org.jetbrains.kotlin.idea.scripting.gradle +package org.jetbrains.kotlin.idea.scripting.gradle.legacy import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.Project @@ -15,6 +15,10 @@ 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.isInAffectedGradleProjectFiles +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 @@ -24,7 +28,7 @@ import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition * * TODO(gradle6): remove */ -class GradleScriptConfigurationLoaderForOutOfProjectScripts(project: Project) : DefaultScriptConfigurationLoader(project) { +class GradleLegacyScriptConfigurationLoaderForOutOfProjectScripts(project: Project) : DefaultScriptConfigurationLoader(project) { override fun loadDependencies( isFirstLoad: Boolean, ktFile: KtFile, @@ -53,7 +57,7 @@ class GradleScriptConfigurationLoaderForOutOfProjectScripts(project: Project) : * * TODO(gradle6): remove */ -class GradleScriptConfigurationLoader(project: Project) : DefaultScriptConfigurationLoader(project) { +class GradleLegacyScriptConfigurationLoader(project: Project) : DefaultScriptConfigurationLoader(project) { override fun shouldRunInBackground(scriptDefinition: ScriptDefinition): Boolean { return if (useScriptConfigurationFromImportOnly()) false else super.shouldRunInBackground(scriptDefinition) } 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 new file mode 100644 index 00000000000..347b6ec70fd --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/legacy/GradleLegacyScriptListener.kt @@ -0,0 +1,39 @@ +/* + * 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.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.* + +class GradleLegacyScriptListener(project: Project) : ScriptChangeListener(project) { + override fun isApplicable(vFile: VirtualFile): Boolean { + return isGradleKotlinScript(vFile) + } + + override fun editorActivated(vFile: VirtualFile) { + if (!isInAffectedGradleProjectFiles(project, vFile.path)) return + + if (useScriptConfigurationFromImportOnly()) { + // do nothing + } else { + val file = getAnalyzableKtFileForScript(vFile) ?: return + default.suggestToUpdateConfigurationIfOutOfDate(file) + } + } + + override fun documentChanged(vFile: VirtualFile) { + if (!isInAffectedGradleProjectFiles(project, 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/serizliation.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/serizliation.kt index 3e144fe1d25..9787b8b52b3 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/serizliation.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/serizliation.kt @@ -19,7 +19,7 @@ import java.io.DataOutput internal object KotlinDslScriptModels { private val attribute = FileAttribute("kotlin-dsl-script-models", 3, false) - fun read(buildRoot: VirtualFile): ConfigurationData? { + fun read(buildRoot: VirtualFile): GradleImportedBuildRootData? { return attribute.readAttribute(buildRoot)?.use { it.readNullable { readKotlinDslScriptModels(it) @@ -27,9 +27,9 @@ internal object KotlinDslScriptModels { } } - fun write(buildRoot: VirtualFile, configuration: ConfigurationData?) { + fun write(buildRoot: VirtualFile, data: GradleImportedBuildRootData?) { attribute.writeAttribute(buildRoot).use { - it.writeNullable(configuration) { + it.writeNullable(data) { writeKotlinDslScriptModels(this, it) } } @@ -40,7 +40,7 @@ internal object KotlinDslScriptModels { } } -internal fun writeKotlinDslScriptModels(output: DataOutput, data: ConfigurationData) { +internal fun writeKotlinDslScriptModels(output: DataOutput, data: GradleImportedBuildRootData) { val strings = StringsPool.writer(output) strings.addStrings(data.templateClasspath) data.models.forEach { @@ -61,7 +61,7 @@ internal fun writeKotlinDslScriptModels(output: DataOutput, data: ConfigurationD } } -internal fun readKotlinDslScriptModels(input: DataInputStream): ConfigurationData { +internal fun readKotlinDslScriptModels(input: DataInputStream): GradleImportedBuildRootData { val strings = StringsPool.reader(input) val templateClasspath = strings.readStrings() @@ -77,7 +77,7 @@ internal fun readKotlinDslScriptModels(input: DataInputStream): ConfigurationDat ) } - return ConfigurationData(templateClasspath, models) + return GradleImportedBuildRootData(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 67de35abe76..2852b2869b2 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 @@ -24,7 +24,8 @@ 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.* +import org.slf4j.LoggerFactory +import kotlin.collections.HashMap private val sections = arrayListOf("buildscript", "plugins", "initscript", "pluginManagement") @@ -108,42 +109,48 @@ fun getGradleProjectSettings(project: Project): Collection { - internal val tree = TreeMap() - var values: Collection = listOf() - internal set +class RootsIndex { + private val log = LoggerFactory.getLogger(RootsIndex::class.java) - 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 - } + private val buildRoots = HashMap() + private val scripts = HashMap() + val values: Collection + get() = buildRoots.values - internal inline fun update(updater: (insert: (prefix: String, value: T) -> Unit) -> Unit) { - synchronized(this) { - updater { prefix, value -> tree[prefix] = value } - values = tree.values - } + @Synchronized + fun rebuildProjectRoots() { +// values.forEach { +// it. +// } } @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`" - } + fun getBuildRoot(dir: String) = buildRoots[dir] - tree[prefix] = value - values = tree.values + @Synchronized + fun findNearestRoot(path: String): GradleBuildRoot? { + var max: Pair? = null + buildRoots.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 remove(prefix: String) = tree.remove(prefix) + operator fun set(prefix: String, value: GradleBuildRoot) { + val old = buildRoots.put(prefix, value) + rebuildProjectRoots() + log.info("{}: {} -> {}", prefix, old, value) + } + + @Synchronized + fun remove(prefix: String) = buildRoots.remove(prefix)?.also { + rebuildProjectRoots() + log.info("{}: removed", prefix) + } } - private val logger = Logger.getInstance("#org.jetbrains.kotlin.idea.scripting.gradle") fun scriptingDebugLog(message: () -> String) { 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 6f31466cd00..4159e2a880b 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,6 +7,7 @@ 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 @@ -34,7 +35,9 @@ class GradleScriptOutOfProjectTest : AbstractScriptConfigurationLoadingTest() { } private val loaderForOutOfProjectScripts by lazy { - GradleScriptConfigurationLoaderForOutOfProjectScripts(project) + GradleLegacyScriptConfigurationLoaderForOutOfProjectScripts( + project + ) } fun testManualLoadingForUpToDate() { diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/KotlinDslScriptModelsTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/KotlinDslScriptModelsTest.kt index 78bffda78eb..d7e7ee0154e 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/KotlinDslScriptModelsTest.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/KotlinDslScriptModelsTest.kt @@ -16,7 +16,7 @@ import kotlin.test.assertEquals class KotlinDslScriptModelsTest { @Test fun write() { - val data = ConfigurationData( + val data = GradleImportedBuildRootData( listOf("a", "b", "c"), listOf( KotlinDslScriptModel( 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 index c713df03f79..47be40910df 100644 --- 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 @@ -18,16 +18,16 @@ class RootsIndexTest { 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")) + assertEquals(1, roots.findNearestRoot("a/b/c/x")) + assertEquals(1, roots.findNearestRoot("a/b/c")) + assertEquals(3, roots.findNearestRoot("a/c/x")) + assertEquals(3, roots.findNearestRoot("a/c")) + assertEquals(4, roots.findNearestRoot("d")) + assertEquals(4, roots.findNearestRoot("d/x")) + assertEquals(null, roots.findNearestRoot("e")) + assertEquals(null, roots.findNearestRoot("e/f")) roots["d"] = 5 - assertEquals(5, roots.findRoot("d")) + assertEquals(5, roots.findNearestRoot("d")) } } \ No newline at end of file diff --git a/idea/resources/META-INF/gradle.xml b/idea/resources/META-INF/gradle.xml index e58df94d702..f7bfdd46818 100644 --- a/idea/resources/META-INF/gradle.xml +++ b/idea/resources/META-INF/gradle.xml @@ -14,10 +14,10 @@ - - + + + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt index 1f6570e1890..0b7d848d69b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt @@ -145,12 +145,12 @@ class UnusedSymbolInspection : AbstractKotlinInspection() { val project = declaration.project val psiSearchHelper = PsiSearchHelper.getInstance(project) - val usedScripts = findScriptsWithUsages(declaration) - if (usedScripts.isNotEmpty()) { - if (!ScriptConfigurationManager.getInstance(declaration.project).updater.ensureConfigurationUpToDate(usedScripts)) { - return TOO_MANY_OCCURRENCES - } - } +// val usedScripts = findScriptsWithUsages(declaration) +// if (usedScripts.isNotEmpty()) { +// if (!ScriptConfigurationManager.getInstance(declaration.project).updater.ensureConfigurationUpToDate(usedScripts)) { +// return TOO_MANY_OCCURRENCES +// } +// } val useScope = psiSearchHelper.getUseScope(declaration) if (useScope is GlobalSearchScope) {