From 73760effeece7fdaf81754820317772f5414edb5 Mon Sep 17 00:00:00 2001 From: Sergey Rostov Date: Wed, 22 Apr 2020 16:13:00 +0300 Subject: [PATCH] Refactor GradleScriptingSupport to support multiple gradle projects linked to one idea project For each externalGradleProject separate GradleScriptingSupport is created. This allows to save script configurations by gradle projects ^KT-37806 Obsolete ^KT-35151 Fixed --- .../core/script/ScriptConfigurationManager.kt | 9 +- .../CompositeScriptConfigurationManager.kt | 25 ++- .../configuration/DefaultScriptingSupport.kt | 6 +- .../script/configuration/ScriptingSupport.kt | 16 +- .../utils/DefaultClassRootsCache.kt | 33 ++-- .../utils/ScriptClassRootsCache.kt | 28 ++- .../scripting/gradle/GradleClassRootsCache.kt | 54 +++++- .../idea/scripting/gradle/GradleKtsContext.kt | 10 + .../GradleScriptConfigurationManager.kt | 121 +++--------- .../gradle/GradleScriptingSupportProvider.kt | 173 ++++++++++++++++++ .../KotlinDslScrtiptModelProcessor.kt | 76 ++------ .../gradle/importing/KotlinDslSyncListener.kt | 22 ++- .../idea/scripting/gradle/serizliation.kt | 46 +++-- .../kotlin/idea/scripting/gradle/utils.kt | 64 ++++--- .../gradle/KotlinDslScriptModelsTest.kt | 9 +- .../idea/scripting/gradle/RootsIndexTest.kt | 33 ++++ idea/resources/META-INF/gradle.xml | 4 +- idea/resources/META-INF/gradle.xml.201 | 4 +- idea/resources/META-INF/scripting-support.xml | 11 +- 19 files changed, 458 insertions(+), 286 deletions(-) create mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleKtsContext.kt create mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptingSupportProvider.kt create mode 100644 idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/RootsIndexTest.kt 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 1658f38b2d5..7d3f634e032 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 @@ -21,6 +21,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.JavaSdkType import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.roots.ProjectRootManager +import com.intellij.openapi.roots.ProjectRootManager.* import com.intellij.openapi.util.Key import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.openapi.vfs.VirtualFile @@ -30,7 +31,6 @@ import com.intellij.util.io.URLUtil import org.jetbrains.annotations.TestOnly import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks import org.jetbrains.kotlin.idea.core.script.configuration.CompositeScriptConfigurationManager -import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptingSupport import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoader import org.jetbrains.kotlin.psi.KtFile @@ -38,7 +38,6 @@ import org.jetbrains.kotlin.psi.UserDataProperty import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance import java.io.File import kotlin.script.experimental.api.asSuccess import kotlin.script.experimental.api.makeFailureResult @@ -162,15 +161,13 @@ interface ScriptConfigurationManager { @TestOnly fun updateScriptDependenciesSynchronously(file: PsiFile) { // TODO: review the usages of this method - (getInstance(file.project) as CompositeScriptConfigurationManager).managers - .firstIsInstance() + (getInstance(file.project) as CompositeScriptConfigurationManager).default .updateScriptDependenciesSynchronously(file) } @TestOnly fun clearCaches(project: Project) { - (getInstance(project) as CompositeScriptConfigurationManager).managers - .firstIsInstance() + (getInstance(project) as CompositeScriptConfigurationManager).default .clearCaches() } 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 1d2d3af706a..675e4fac733 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 @@ -22,16 +22,25 @@ 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.firstIsInstanceOrNull +import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult class CompositeScriptConfigurationManager(val project: Project) : ScriptConfigurationManager { @Suppress("unused") private val notifier = ScriptChangesNotifier(project, updater) - // todo public for tests - val managers = ScriptingSupport.SCRIPTING_SUPPORT.getPoint(project).extensionList + private val providers = ScriptingSupport.Provider.EPN.getPoint(project).extensionList + + val default = DefaultScriptingSupport(project) + + 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: VirtualFile): ScriptingSupport = managers.first { it.isRelated(file) } private fun getRelatedManager(file: KtFile): ScriptingSupport = getRelatedManager(file.originalFile.virtualFile) @@ -76,14 +85,16 @@ class CompositeScriptConfigurationManager(val project: Project) : ScriptConfigur * Loads script configuration if classpath roots don't contain [file] yet */ private fun getActualClasspathRoots(file: VirtualFile): ScriptClassRootsCache { - val classpathRoots = getRelatedManager(file).classpathRoots + val manager = getRelatedManager(file) + + val classpathRoots = manager.classpathRoots if (classpathRoots.contains(file)) { return classpathRoots } getOrLoadConfiguration(file) - return getRelatedManager(file).classpathRoots + return manager.classpathRoots } override fun getScriptSdk(file: VirtualFile): Sdk? = @@ -115,7 +126,7 @@ class CompositeScriptConfigurationManager(val project: Project) : ScriptConfigur override fun forceReloadConfiguration(file: VirtualFile, loader: ScriptConfigurationLoader): ScriptCompilationConfigurationWrapper? { val ktFile = project.getKtFile(file, null) ?: return null - return managers.firstIsInstanceOrNull()?.forceReloadConfiguration(ktFile, loader) + return default.forceReloadConfiguration(ktFile, loader) } /////////////////// 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 2c5cc9dd5cc..49ecc60289d 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 @@ -33,7 +33,6 @@ import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock import kotlin.script.experimental.api.ScriptDiagnostic @@ -312,8 +311,6 @@ abstract class DefaultScriptingSupportBase(val project: Project) : ScriptingSupp fun getAppliedConfiguration(file: VirtualFile?): ScriptConfigurationSnapshot? = getCachedConfigurationState(file)?.applied - override fun isRelated(file: VirtualFile): Boolean = true - override fun hasCachedConfiguration(file: KtFile): Boolean = getAppliedConfiguration(file.originalFile.virtualFile) != null @@ -438,6 +435,5 @@ object DefaultScriptConfigurationManagerExtensions { } val ScriptConfigurationManager.testingBackgroundExecutor - get() = (this as CompositeScriptConfigurationManager).managers - .firstIsInstance() + get() = (this as CompositeScriptConfigurationManager).default .backgroundExecutor as TestingBackgroundExecutor \ No newline at end of file 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 d5637632c3c..88cb11562b9 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 @@ -21,7 +21,16 @@ import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock abstract class ScriptingSupport { - abstract fun isRelated(file: VirtualFile): Boolean + abstract class Provider { + abstract val all: Collection + + abstract fun getSupport(file: VirtualFile): ScriptingSupport? + + companion object { + val EPN: ExtensionPointName = + ExtensionPointName.create("org.jetbrains.kotlin.scripting.idea.scriptingSupportProvider") + } + } abstract fun clearCaches() abstract fun hasCachedConfiguration(file: KtFile): Boolean @@ -68,9 +77,4 @@ abstract class ScriptingSupport { ScriptDependenciesModificationTracker.getInstance(project).incModificationCount() } - - companion object { - val SCRIPTING_SUPPORT: ExtensionPointName = - ExtensionPointName.create("org.jetbrains.kotlin.scripting.idea.scriptingSupport") - } } \ No newline at end of file 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 index 69cd74b1d45..ad24f42799a 100644 --- 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 @@ -16,15 +16,16 @@ import java.io.File internal class DefaultClassRootsCache( project: Project, private val all: Map -) : ScriptClassRootsCache(project, extractRoots(all, project)) { +) : ScriptClassRootsCache( + project, + ScriptClassRootsStorage.Companion.Key("default"), + extractRoots(all, project) +) { - override val fileToConfiguration: (VirtualFile) -> ScriptCompilationConfigurationWrapper? - get() = { all[it] } + override fun getConfiguration(file: VirtualFile) = all[file] override fun contains(file: VirtualFile): Boolean = file in all - override val rootsCacheKey = ScriptClassRootsStorage.Companion.Key("default") - private val scriptsSdksCache: Map = ConcurrentFactoryMap.createWeakMap { file -> return@createWeakMap getScriptSdk( @@ -44,30 +45,18 @@ internal class DefaultClassRootsCache( project: Project, configuration: ScriptCompilationConfigurationWrapper ): ScriptClassRootsStorage.Companion.ScriptClassRoots { - val scriptSdk = - getScriptSdkOfDefault( - configuration.javaHome, - project - ) + val scriptSdk = getScriptSdk(configuration.javaHome) if (scriptSdk != null && !scriptSdk.isAlreadyIndexed(project)) { return ScriptClassRootsStorage.Companion.ScriptClassRoots( - toStringValues( - configuration.dependenciesClassPath - ), - toStringValues( - configuration.dependenciesSources - ), + toStringValues(configuration.dependenciesClassPath), + toStringValues(configuration.dependenciesSources), setOf(scriptSdk) ) } return ScriptClassRootsStorage.Companion.ScriptClassRoots( - toStringValues( - configuration.dependenciesClassPath - ), - toStringValues( - configuration.dependenciesSources - ), + toStringValues(configuration.dependenciesClassPath), + toStringValues(configuration.dependenciesSources), emptySet() ) } 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 a97796d5077..62449af191a 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 @@ -18,15 +18,13 @@ import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager import org.jetbrains.kotlin.idea.util.getProjectJdkTableSafe import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper import java.io.File -import java.nio.file.FileSystems abstract class ScriptClassRootsCache( private val project: Project, + private val rootsCacheKey: ScriptClassRootsStorage.Companion.Key, private val roots: ScriptClassRootsStorage.Companion.ScriptClassRoots ) { - protected abstract val fileToConfiguration: (VirtualFile) -> ScriptCompilationConfigurationWrapper? - - protected abstract val rootsCacheKey: ScriptClassRootsStorage.Companion.Key + protected abstract fun getConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? abstract val firstScriptSdk: Sdk? @@ -34,7 +32,7 @@ abstract class ScriptClassRootsCache( abstract fun contains(file: VirtualFile): Boolean - class Fat( + private class Fat( val scriptConfiguration: ScriptCompilationConfigurationWrapper, val classFilesScope: GlobalSearchScope ) @@ -57,7 +55,7 @@ abstract class ScriptClassRootsCache( private val scriptsDependenciesCache: MutableMap = ConcurrentFactoryMap.createWeakMap { file -> - val configuration = fileToConfiguration(file) ?: return@createWeakMap null + val configuration = getConfiguration(file) ?: return@createWeakMap null val roots = configuration.dependenciesClassPath val sdk = getScriptSdk(file) @@ -120,6 +118,24 @@ abstract class ScriptClassRootsCache( fun Sdk.isAlreadyIndexed(project: Project): Boolean { return ModuleManager.getInstance(project).modules.any { ModuleRootManager.getInstance(it).sdk == this } } + + 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 + } } } 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 index 4b6ac6491a6..6b252a47895 100644 --- 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 @@ -7,31 +7,65 @@ 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.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 -internal class GradleClassRootsCache( - project: Project, - configuration: Configuration?, - override val fileToConfiguration: (VirtualFile) -> ScriptCompilationConfigurationWrapper? -) : ScriptClassRootsCache(project, extractRoots(configuration)) { +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() - override val rootsCacheKey = ScriptClassRootsStorage.Companion.Key("gradle") + 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? = configuration?.let { getScriptSdk(configuration.context.javaHome) } + override val firstScriptSdk: Sdk? = configuration?.let { getScriptSdk(context.javaHome) } - // TODO what should we do if no configuration is loaded yet + // 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(configuration: Configuration?): ScriptClassRoots { + fun extractRoots(context: GradleKtsContext, configuration: Configuration?, project: Project): ScriptClassRoots { if (configuration == null) { return ScriptClassRootsStorage.EMPTY } @@ -46,7 +80,7 @@ internal class GradleClassRootsCache( return ScriptClassRoots( configuration.classFilePath, configuration.sourcePath, - getScriptSdk(configuration.context.javaHome)?.let { setOf(it) } ?: setOf() + getScriptSdk(context.javaHome)?.let { setOf(it) } ?: setOf() ) } } diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleKtsContext.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleKtsContext.kt new file mode 100644 index 00000000000..e9a143defd5 --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleKtsContext.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.scripting.gradle + +import java.io.File + +class GradleKtsContext(val javaHome: File?) \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptConfigurationManager.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptConfigurationManager.kt index d7848c271a7..eedfe94500d 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptConfigurationManager.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptConfigurationManager.kt @@ -5,42 +5,37 @@ package org.jetbrains.kotlin.idea.scripting.gradle -import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.project.Project import com.intellij.openapi.util.io.FileUtil -import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VirtualFile import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupportHelper import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater -import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsIndexer -import org.jetbrains.kotlin.idea.scripting.gradle.importing.GradleKtsContext -import org.jetbrains.kotlin.idea.scripting.gradle.importing.createGradleKtsContextIfPossible import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel -import org.jetbrains.kotlin.idea.scripting.gradle.importing.toScriptConfiguration import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition -import org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotatedTemplate import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance -import java.nio.file.Paths - -internal data class ConfigurationData( +data class ConfigurationData( val templateClasspath: List, val models: List ) -internal class Configuration(val context: GradleKtsContext, data: ConfigurationData) { - val scripts = data.models.associateBy { it.file } - val sourcePath = data.models.flatMapTo(mutableSetOf()) { it.sourcePath } +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) - data.models.flatMapTo(classFilePath) { it.classPath } + allModels.flatMapTo(classFilePath) { it.classPath } } fun scriptModel(file: VirtualFile): KotlinDslScriptModel? { @@ -48,74 +43,33 @@ internal class Configuration(val context: GradleKtsContext, data: ConfigurationD } } -class GradleScriptingSupport(val project: Project) : ScriptingSupport() { - @Volatile - private var configuration: Configuration? = null - - private val rootsIndexer = ScriptClassRootsIndexer(project) +class GradleScriptingSupport( + private val rootsIndexer: ScriptClassRootsIndexer, + val project: Project, + val buildRoot: VirtualFile, + val context: GradleKtsContext, + val configuration: Configuration +) : ScriptingSupport() { init { - if (isKotlinDslScriptsModelImportSupported(project)) { - ApplicationManager.getApplication().executeOnPooledThread { - val data = KotlinDslScriptModels.read(project) - val gradleKtsContext = createGradleKtsContextIfPossible(project) - if (data != null && gradleKtsContext != null) { - val newConfiguration = Configuration( - gradleKtsContext, - data - ) - - configuration = newConfiguration - configurationChangedCallback(newConfiguration) - } - } - } - } - - override fun recreateRootsCache(): ScriptClassRootsCache { - return GradleClassRootsCache(project, configuration) { - configuration?.let { conf -> - val model = conf.scriptModel(it) - model?.toScriptConfiguration(conf.context) - } - } - } - - fun replace(context: GradleKtsContext, models: List) { - if (models.isEmpty()) return - - val anyScript = VfsUtil.findFile(Paths.get(models.first().file), true)!! - - val definition = anyScript.findScriptDefinition(context.project) ?: return - val templateClasspath = definition.asLegacyOrNull() - ?.templateClasspath?.map { it.path } ?: return - - val data = ConfigurationData(templateClasspath, models) - KotlinDslScriptModels.write(project, data) - - val newConfiguration = Configuration(context, data) - configuration = newConfiguration - - configurationChangedCallback(newConfiguration) - } - - private fun configurationChangedCallback(newConfiguration: Configuration) { rootsIndexer.transaction { - if (classpathRoots.hasNotCachedRoots(GradleClassRootsCache.extractRoots(newConfiguration))) { + if (classpathRoots.hasNotCachedRoots(GradleClassRootsCache.extractRoots(context, configuration, project))) { rootsIndexer.markNewRoot() } clearClassRootsCaches(project) ScriptingSupportHelper.updateHighlighting(project) { - configuration?.scriptModel(it) != null + configuration.scriptModel(it) != null } } hideNotificationForProjectImport(project) } - fun updateNotification(file: KtFile) { + override fun recreateRootsCache() = GradleClassRootsCache(project, context, configuration) + + private fun updateNotification(file: KtFile) { val vFile = file.originalFile.virtualFile val scriptModel = configuration?.scriptModel(vFile) ?: return @@ -126,34 +80,15 @@ class GradleScriptingSupport(val project: Project) : ScriptingSupport() { } } - override fun isRelated(file: VirtualFile): Boolean { - if (isGradleKotlinScript(file)) { - return isKotlinDslScriptsModelImportSupported(project) - } - - return false - } - - private fun isKotlinDslScriptsModelImportSupported(project: Project): Boolean { - val gradleVersion = getGradleVersion(project) - return gradleVersion != null && kotlinDslScriptsModelImportSupported(gradleVersion) - } - override fun clearCaches() { - // todo should clear up to date + // todo: should clear up to date } override fun hasCachedConfiguration(file: KtFile): Boolean = - configuration?.scriptModel(file.originalFile.virtualFile) != null + configuration.scriptModel(file.originalFile.virtualFile) != null override fun getOrLoadConfiguration(virtualFile: VirtualFile, preloadedKtFile: KtFile?): ScriptCompilationConfigurationWrapper? { - val configuration = configuration - if (configuration == null) { - // todo: show notification "Import gradle project" - return null - } else { - return classpathRoots.getScriptConfiguration(virtualFile) - } + return classpathRoots.getScriptConfiguration(virtualFile) } override val updater: ScriptConfigurationUpdater @@ -169,10 +104,4 @@ class GradleScriptingSupport(val project: Project) : ScriptingSupport() { updateNotification(file) } } - - companion object { - fun getInstance(project: Project): GradleScriptingSupport { - return SCRIPTING_SUPPORT.getPoint(project).extensionList.firstIsInstance() - } - } } \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptingSupportProvider.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptingSupportProvider.kt new file mode 100644 index 00000000000..1625a57e4aa --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptingSupportProvider.kt @@ -0,0 +1,173 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.scripting.gradle + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VfsUtil +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport +import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater +import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache +import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsIndexer +import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslGradleBuildSync +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition +import org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotatedTemplate +import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance +import org.jetbrains.plugins.gradle.config.GradleSettingsListenerAdapter +import org.jetbrains.plugins.gradle.settings.GradleExecutionSettings +import org.jetbrains.plugins.gradle.settings.GradleProjectSettings +import org.jetbrains.plugins.gradle.settings.GradleSettingsListener +import org.jetbrains.plugins.gradle.util.GradleConstants +import java.io.File +import java.nio.file.Paths + +/** + * Creates [GradleScriptingSupport] per each linked Gradle build. + * Gradle project sync should call [update]. + */ +class GradleScriptingSupportProvider(val project: Project) : ScriptingSupport.Provider() { + private val rootsIndexer = ScriptClassRootsIndexer(project) + + val roots = RootsIndex() + override val all get() = roots.values + + private val VirtualFile.localPath + get() = path + + private fun findRoot(file: VirtualFile) = roots.findRoot(file.localPath) + + override fun getSupport(file: VirtualFile): ScriptingSupport? { + if (isGradleKotlinScript(file)) { + findRoot(file)?.let { return it } + + val externalProjectSettings = findExternalProjectSettings(file) ?: return null + if (kotlinDslScriptsModelImportSupported(getGradleVersion(project, externalProjectSettings))) { + return unlinkedFilesSupport + } + } + return null + } + + private fun findExternalProjectSettings(file: VirtualFile): GradleProjectSettings? { + return getGradleProjectSettings(project) + .filter { file.localPath.startsWith(it.externalProjectPath) } + .maxBy { it.externalProjectPath.length } + } + + init { + ApplicationManager.getApplication().executeOnPooledThread { + roots.update { set -> + getGradleProjectSettings(project).forEach { gradleProjectSettings -> + if (kotlinDslScriptsModelImportSupported(getGradleVersion(project, gradleProjectSettings))) { + val support = createSupport(gradleProjectSettings.externalProjectPath) { + KotlinDslScriptModels.read(it) + } + + if (support != null) { + set(support.buildRoot.localPath, support) + } + } + } + } + } + + // subscribe to gradle build unlink + val listener = object : GradleSettingsListenerAdapter() { + override fun onProjectsUnlinked(linkedProjectPaths: MutableSet) { + linkedProjectPaths.forEach { + val buildRoot = VfsUtil.findFile(Paths.get(it), false) + if (buildRoot != null) { + if (roots.remove(buildRoot.localPath) != null) { + KotlinDslScriptModels.remove(buildRoot) + } + } + } + } + } + + project.messageBus.connect(project).subscribe(GradleSettingsListener.TOPIC, listener) + } + + fun update(build: KotlinDslGradleBuildSync) { + // fast path for linked gradle builds without .gradle.kts support + if (build.models.isEmpty()) { + val root = roots.findRoot(build.workingDir) ?: return + if (root.configuration.data.models.isEmpty()) return + } + + val templateClasspath = findTemplateClasspath(build) ?: return + val data = ConfigurationData(templateClasspath, build.models) + val newSupport = createSupport(build.workingDir) { data } ?: return + KotlinDslScriptModels.write(newSupport.buildRoot, data) + + roots[newSupport.buildRoot.localPath] = newSupport + } + + private fun createSupport( + externalProjectPath: String, + dataProvider: (buildRoot: VirtualFile) -> ConfigurationData? + ): GradleScriptingSupport? { + val gradleExeSettings = + ExternalSystemApiUtil.getExecutionSettings( + project, + externalProjectPath, + GradleConstants.SYSTEM_ID + ) + + val buildRoot = VfsUtil.findFile(Paths.get(externalProjectPath), true) ?: return null + val data = dataProvider(buildRoot) ?: return null + + return GradleScriptingSupport( + rootsIndexer, + project, + buildRoot, + GradleKtsContext(gradleExeSettings.javaHome?.let { File(it) }), + Configuration(data) + ) + } + + private fun findTemplateClasspath(build: KotlinDslGradleBuildSync): List? { + val anyScript = VfsUtil.findFile(Paths.get(build.models.first().file), true)!! + // todo: find definition according to build.workingDir + val definition = anyScript.findScriptDefinition(project) ?: return null + return definition.asLegacyOrNull() + ?.templateClasspath?.map { it.path } + } + + private val unlinkedFilesSupport = object : ScriptingSupport() { + override fun clearCaches() { + } + + override fun hasCachedConfiguration(file: KtFile): Boolean = false + + override fun 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) + } + + companion object { + fun getInstance(project: Project): GradleScriptingSupportProvider = + EPN.getPoint(project).extensionList.firstIsInstance() + } +} \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScrtiptModelProcessor.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScrtiptModelProcessor.kt index 164fb233fa2..d84938314f1 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScrtiptModelProcessor.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScrtiptModelProcessor.kt @@ -9,29 +9,22 @@ import com.intellij.openapi.components.service import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId import com.intellij.openapi.project.Project import com.intellij.openapi.util.Key +import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vfs.VfsUtil import org.gradle.tooling.model.kotlin.dsl.EditorReportSeverity import org.gradle.tooling.model.kotlin.dsl.KotlinDslScriptsModel import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle import org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptInputsWatcher -import org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptingSupport +import org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptingSupportProvider import org.jetbrains.kotlin.idea.scripting.gradle.getGradleScriptInputsStamp -import org.jetbrains.kotlin.idea.scripting.gradle.getJavaHomeForGradleProject import org.jetbrains.kotlin.psi.NotNullableUserDataProperty -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 org.jetbrains.plugins.gradle.service.project.ProjectResolverContext 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 +import java.util.concurrent.ConcurrentHashMap -var Project.kotlinDslModels: ArrayList - by NotNullableUserDataProperty>( - Key("Kotlin DSL Scripts Models"), arrayListOf() +var Project.kotlinGradleDslSync: MutableMap + by NotNullableUserDataProperty>( + Key("Kotlin DSL Scripts Models"), ConcurrentHashMap() ) fun processScriptModel( @@ -44,14 +37,11 @@ fun processScriptModel( "Couldn't get KotlinDslScriptsModel for $projectName:\n${model.message}\n${model.stackTrace}" ) } else { - val project = resolverCtx.externalSystemTaskId.findProject() ?: return + val task = resolverCtx.externalSystemTaskId + val project = task.findProject() ?: return val models = model.toListOfScriptModels(project) - project.kotlinDslModels.addAll( - models - ) - if (models.containsErrors()) { - throw IllegalStateException(KotlinIdeaGradleBundle.message("title.kotlin.build.script")) - } + + project.kotlinGradleDslSync[task]?.models?.addAll(models) if (models.containsErrors()) { throw IllegalStateException(KotlinIdeaGradleBundle.message("title.kotlin.build.script")) @@ -111,52 +101,22 @@ private fun KotlinDslScriptsModel.toListOfScriptModels(project: Project): List() } -class GradleKtsContext(val project: Project, val javaHome: File?) +fun saveScriptModels(project: Project, build: KotlinDslGradleBuildSync) { + val errorReporter = KotlinGradleDslErrorReporter(project, build.taskId) -fun KotlinDslScriptModel.toScriptConfiguration(context: GradleKtsContext): ScriptCompilationConfigurationWrapper? { - val scriptFile = File(file) - val virtualFile = VfsUtil.findFile(scriptFile.toPath(), true)!! - - val definition = virtualFile.findScriptDefinition(context.project) ?: return null - - return ScriptCompilationConfigurationWrapper.FromCompilationConfiguration( - VirtualFileScriptSource(virtualFile), - definition.compilationConfiguration.with { - if (context.javaHome != null) { - jvm.jdkHome(context.javaHome) - } - defaultImports(imports) - dependencies(JvmDependency(classPath.map { File(it) })) - ide.dependenciesSources(JvmDependency(sourcePath.map { File(it) })) - }.adjustByDefinition(definition) - ) -} - -fun saveScriptModels( - project: Project, - task: ExternalSystemTaskId, - javaHomeStr: String?, - models: List -) { - val errorReporter = KotlinGradleDslErrorReporter(project, task) - - val javaHome = javaHomeStr?.let { File(it) } - val context = GradleKtsContext(project, javaHome) - - models.forEach { model -> + build.models.forEach { model -> errorReporter.reportError(File(model.file), model) } project.service().saveGradleProjectRootsAfterImport( - models.map { File(it.file).parent }.toSet() + build.models.map { FileUtil.toSystemIndependentName(File(it.file).parent) }.toSet() ) - GradleScriptingSupport.getInstance(project).replace(context, models) + GradleScriptingSupportProvider.getInstance(project).update(build) + project.service().clearState() } \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslSyncListener.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslSyncListener.kt index c05ef6eaa14..f6e78171caf 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslSyncListener.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslSyncListener.kt @@ -9,23 +9,25 @@ 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.getJavaHomeForGradleProject class KotlinDslSyncListener : ExternalSystemTaskNotificationListenerAdapter() { - override fun onEnd(id: ExternalSystemTaskId) { - if (id.type != ExternalSystemTaskType.RESOLVE_PROJECT || id.projectSystemId != GRADLE_SYSTEM_ID) { - return - } + override fun onStart(id: ExternalSystemTaskId, workingDir: String?) { + if (id.type != ExternalSystemTaskType.RESOLVE_PROJECT) return + if (id.projectSystemId != GRADLE_SYSTEM_ID) return + if (workingDir == null) return val project = id.findProject() ?: return - val models: List = project.kotlinDslModels - project.kotlinDslModels = arrayListOf() + project.kotlinGradleDslSync[id] = KotlinDslGradleBuildSync(workingDir, id) + } - if (models.isEmpty()) return + override fun onEnd(id: ExternalSystemTaskId) { + if (id.type != ExternalSystemTaskType.RESOLVE_PROJECT) return + if (id.projectSystemId != GRADLE_SYSTEM_ID) return - val javaHome = getJavaHomeForGradleProject(project) + val project = id.findProject() ?: return + val sync = project.kotlinGradleDslSync.remove(id) ?: return - saveScriptModels(project, id, javaHome, models) + saveScriptModels(project, sync) } } 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 4b24838d229..3e144fe1d25 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 @@ -5,35 +5,45 @@ package org.jetbrains.kotlin.idea.scripting.gradle -import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.newvfs.FileAttribute +import org.jetbrains.kotlin.idea.core.util.readNullable import org.jetbrains.kotlin.idea.core.util.readString +import org.jetbrains.kotlin.idea.core.util.writeNullable import org.jetbrains.kotlin.idea.core.util.writeString import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel +import java.io.DataInput import java.io.DataInputStream -import java.io.DataOutputStream +import java.io.DataOutput internal object KotlinDslScriptModels { - private val attribute = FileAttribute("kotlin-dsl-script-models", 2, false) + private val attribute = FileAttribute("kotlin-dsl-script-models", 3, false) - fun read(project: Project): ConfigurationData? { - return attribute.readAttribute(project.projectFile ?: return null)?.use { - readKotlinDslScriptModels(it) + fun read(buildRoot: VirtualFile): ConfigurationData? { + return attribute.readAttribute(buildRoot)?.use { + it.readNullable { + readKotlinDslScriptModels(it) + } } } - fun write(project: Project, configuration: ConfigurationData) { - attribute.writeAttribute(project.projectFile ?: return).use { - writeKotlinDslScriptModels(it, configuration) + fun write(buildRoot: VirtualFile, configuration: ConfigurationData?) { + attribute.writeAttribute(buildRoot).use { + it.writeNullable(configuration) { + writeKotlinDslScriptModels(this, it) + } } } + + fun remove(buildRoot: VirtualFile) { + write(buildRoot, null) + } } -internal fun writeKotlinDslScriptModels(output: DataOutputStream, data: ConfigurationData) { +internal fun writeKotlinDslScriptModels(output: DataOutput, data: ConfigurationData) { val strings = StringsPool.writer(output) strings.addStrings(data.templateClasspath) - val scriptModels = data.models - scriptModels.forEach { + data.models.forEach { strings.addString(it.file) strings.addStrings(it.classPath) strings.addStrings(it.sourcePath) @@ -41,7 +51,7 @@ internal fun writeKotlinDslScriptModels(output: DataOutputStream, data: Configur } strings.writeHeader() strings.writeStringIds(data.templateClasspath) - output.writeList(scriptModels) { + output.writeList(data.models) { strings.writeStringId(it.file) output.writeString(it.inputs.sections) output.writeLong(it.inputs.inputsTS) @@ -71,9 +81,9 @@ internal fun readKotlinDslScriptModels(input: DataInputStream): ConfigurationDat } private object StringsPool { - fun writer(output: DataOutputStream) = Writer(output) + fun writer(output: DataOutput) = Writer(output) - class Writer(val output: DataOutputStream) { + class Writer(val output: DataOutput) { var freeze = false val ids = mutableMapOf() @@ -106,7 +116,7 @@ private object StringsPool { output.writeInt(getStringId(it)) } - fun writeStringIds(strings: List) { + fun writeStringIds(strings: Collection) { output.writeInt(strings.size) strings.forEach { writeStringId(it) @@ -128,12 +138,12 @@ private object StringsPool { } } -private inline fun DataOutputStream.writeList(list: Collection, write: (T) -> Unit) { +private inline fun DataOutput.writeList(list: Collection, write: (T) -> Unit) { writeInt(list.size) list.forEach { write(it) } } -private inline fun DataInputStream.readList(read: () -> T): List { +private inline fun DataInput.readList(read: () -> T): List { val n = readInt() val result = ArrayList(n) repeat(n) { 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 68a9c4d3f73..b95a9d45b20 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/utils.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/utils.kt @@ -20,10 +20,10 @@ import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtScriptInitializer import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType import org.jetbrains.plugins.gradle.settings.GradleLocalSettings -import org.jetbrains.plugins.gradle.settings.GradleExecutionSettings import org.jetbrains.plugins.gradle.settings.GradleProjectSettings import org.jetbrains.plugins.gradle.settings.GradleSettings import org.jetbrains.plugins.gradle.util.GradleConstants +import java.util.* private val sections = arrayListOf("buildscript", "plugins", "initscript", "pluginManagement") @@ -95,34 +95,50 @@ fun useScriptConfigurationFromImportOnly(): Boolean { return Registry.`is`("kotlin.gradle.scripts.useIdeaProjectImport", false) } -fun getGradleVersion(project: Project, externalProjectPath: String): String? { - val localVersion = GradleLocalSettings.getInstance(project).getGradleVersion(externalProjectPath) +fun getGradleVersion(project: Project, settings: GradleProjectSettings): String { + val localVersion = GradleLocalSettings.getInstance(project).getGradleVersion(settings.externalProjectPath) if (localVersion != null) return localVersion - return GradleSettings.getInstance(project).getLinkedProjectSettings(externalProjectPath)?.resolveGradleVersion()?.version -} - -fun getGradleVersion(project: Project): String? { - val projectSettings = getGradleProjectSettings(project).firstOrNull() - - if (projectSettings != null) { - return getGradleVersion(projectSettings.externalProjectPath) - } - return null -} - -fun getJavaHomeForGradleProject(project: Project): String? { - val projectSettings = getGradleProjectSettings(project).firstOrNull() ?: return null - - val gradleExeSettings = ExternalSystemApiUtil.getExecutionSettings( - project, - projectSettings.externalProjectPath, - GradleConstants.SYSTEM_ID - ) - return gradleExeSettings.javaHome + return settings.resolveGradleVersion().version } fun getGradleProjectSettings(project: Project): Collection { val gradleSettings = ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID) as GradleSettings return gradleSettings.getLinkedProjectsSettings() } + +class RootsIndex { + internal val tree = TreeMap() + var values: Collection = listOf() + internal set + + fun findRoot(path: String): T? { + // race condition can be ignored + val values = values + val size = values.size + if (size == 0) return null + if (size == 1) return values.single() // we can omit prefix check + return tree.floorEntry(path).takeIf { path.startsWith(it.key) }?.value + } + + internal inline fun update(updater: (insert: (prefix: String, value: T) -> Unit) -> Unit) { + synchronized(this) { + updater { prefix, value -> tree[prefix] = value } + values = tree.values + } + } + + @Synchronized + operator fun set(prefix: String, value: T) { + val moreCommon = tree.lowerKey(prefix) + check(moreCommon == null || !prefix.startsWith(moreCommon)) { + "Cannot add root `${prefix}`. More common root already added: `$moreCommon`" + } + + tree[prefix] = value + values = tree.values + } + + @Synchronized + fun remove(prefix: String) = tree.remove(prefix) +} \ No newline at end of file diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/KotlinDslScriptModelsTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/KotlinDslScriptModelsTest.kt index 89c75a2a001..78bffda78eb 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,9 +16,7 @@ import kotlin.test.assertEquals class KotlinDslScriptModelsTest { @Test fun write() { - val buffer = ByteArrayOutputStream() - - val conf = ConfigurationData( + val data = ConfigurationData( listOf("a", "b", "c"), listOf( KotlinDslScriptModel( @@ -40,10 +38,11 @@ class KotlinDslScriptModelsTest { ) ) - writeKotlinDslScriptModels(DataOutputStream(buffer), conf) + val buffer = ByteArrayOutputStream() + writeKotlinDslScriptModels(DataOutputStream(buffer), data) val restored = readKotlinDslScriptModels(DataInputStream(ByteArrayInputStream(buffer.toByteArray()))) - assertEquals(conf.toString(), restored.toString()) + assertEquals(data.toString(), restored.toString()) } } \ No newline at end of file diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/RootsIndexTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/RootsIndexTest.kt new file mode 100644 index 00000000000..c713df03f79 --- /dev/null +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/RootsIndexTest.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.scripting.gradle + +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertFails + +class RootsIndexTest { + @Test + fun findRoot() { + val roots = RootsIndex() + roots["a/b/c"] = 1 + assertFails { roots["a/b/c/d"] = 2 } + roots["a/c"] = 3 + roots["d"] = 4 + + assertEquals(1, roots.findRoot("a/b/c/x")) + assertEquals(1, roots.findRoot("a/b/c")) + assertEquals(3, roots.findRoot("a/c/x")) + assertEquals(3, roots.findRoot("a/c")) + assertEquals(4, roots.findRoot("d")) + assertEquals(4, roots.findRoot("d/x")) + assertEquals(null, roots.findRoot("e")) + assertEquals(null, roots.findRoot("e/f")) + + roots["d"] = 5 + assertEquals(5, roots.findRoot("d")) + } +} \ No newline at end of file diff --git a/idea/resources/META-INF/gradle.xml b/idea/resources/META-INF/gradle.xml index cd8fb13c113..e58df94d702 100644 --- a/idea/resources/META-INF/gradle.xml +++ b/idea/resources/META-INF/gradle.xml @@ -15,8 +15,8 @@ - diff --git a/idea/resources/META-INF/gradle.xml.201 b/idea/resources/META-INF/gradle.xml.201 index ba6cabe4ae8..c747dae6418 100644 --- a/idea/resources/META-INF/gradle.xml.201 +++ b/idea/resources/META-INF/gradle.xml.201 @@ -15,8 +15,8 @@ - diff --git a/idea/resources/META-INF/scripting-support.xml b/idea/resources/META-INF/scripting-support.xml index 764f2c73725..6b84786709f 100644 --- a/idea/resources/META-INF/scripting-support.xml +++ b/idea/resources/META-INF/scripting-support.xml @@ -1,7 +1,7 @@ - @@ -10,11 +10,4 @@ - - - - \ No newline at end of file