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
This commit is contained in:
Sergey Rostov
2020-04-22 16:13:00 +03:00
committed by Natalia Selezneva
parent 34df3066b3
commit 73760effee
19 changed files with 458 additions and 286 deletions
@@ -21,6 +21,7 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.JavaSdkType import com.intellij.openapi.projectRoots.JavaSdkType
import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ProjectRootManager import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.roots.ProjectRootManager.*
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
@@ -30,7 +31,6 @@ import com.intellij.util.io.URLUtil
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks 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.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.listener.ScriptConfigurationUpdater
import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoader import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoader
import org.jetbrains.kotlin.psi.KtFile 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.definitions.ScriptDependenciesProvider
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import java.io.File import java.io.File
import kotlin.script.experimental.api.asSuccess import kotlin.script.experimental.api.asSuccess
import kotlin.script.experimental.api.makeFailureResult import kotlin.script.experimental.api.makeFailureResult
@@ -162,15 +161,13 @@ interface ScriptConfigurationManager {
@TestOnly @TestOnly
fun updateScriptDependenciesSynchronously(file: PsiFile) { fun updateScriptDependenciesSynchronously(file: PsiFile) {
// TODO: review the usages of this method // TODO: review the usages of this method
(getInstance(file.project) as CompositeScriptConfigurationManager).managers (getInstance(file.project) as CompositeScriptConfigurationManager).default
.firstIsInstance<DefaultScriptingSupport>()
.updateScriptDependenciesSynchronously(file) .updateScriptDependenciesSynchronously(file)
} }
@TestOnly @TestOnly
fun clearCaches(project: Project) { fun clearCaches(project: Project) {
(getInstance(project) as CompositeScriptConfigurationManager).managers (getInstance(project) as CompositeScriptConfigurationManager).default
.firstIsInstance<DefaultScriptingSupport>()
.clearCaches() .clearCaches()
} }
@@ -22,16 +22,25 @@ import org.jetbrains.kotlin.idea.core.script.configuration.utils.getKtFile
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.isNonScript import org.jetbrains.kotlin.scripting.definitions.isNonScript
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper 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 { class CompositeScriptConfigurationManager(val project: Project) : ScriptConfigurationManager {
@Suppress("unused") @Suppress("unused")
private val notifier = ScriptChangesNotifier(project, updater) private val notifier = ScriptChangesNotifier(project, updater)
// todo public for tests private val providers = ScriptingSupport.Provider.EPN.getPoint(project).extensionList
val managers = ScriptingSupport.SCRIPTING_SUPPORT.getPoint(project).extensionList
val default = DefaultScriptingSupport(project)
private val managers
get() = mutableListOf<ScriptingSupport>().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 = private fun getRelatedManager(file: KtFile): ScriptingSupport =
getRelatedManager(file.originalFile.virtualFile) 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 * Loads script configuration if classpath roots don't contain [file] yet
*/ */
private fun getActualClasspathRoots(file: VirtualFile): ScriptClassRootsCache { private fun getActualClasspathRoots(file: VirtualFile): ScriptClassRootsCache {
val classpathRoots = getRelatedManager(file).classpathRoots val manager = getRelatedManager(file)
val classpathRoots = manager.classpathRoots
if (classpathRoots.contains(file)) { if (classpathRoots.contains(file)) {
return classpathRoots return classpathRoots
} }
getOrLoadConfiguration(file) getOrLoadConfiguration(file)
return getRelatedManager(file).classpathRoots return manager.classpathRoots
} }
override fun getScriptSdk(file: VirtualFile): Sdk? = override fun getScriptSdk(file: VirtualFile): Sdk? =
@@ -115,7 +126,7 @@ class CompositeScriptConfigurationManager(val project: Project) : ScriptConfigur
override fun forceReloadConfiguration(file: VirtualFile, loader: ScriptConfigurationLoader): ScriptCompilationConfigurationWrapper? { override fun forceReloadConfiguration(file: VirtualFile, loader: ScriptConfigurationLoader): ScriptCompilationConfigurationWrapper? {
val ktFile = project.getKtFile(file, null) ?: return null val ktFile = project.getKtFile(file, null) ?: return null
return managers.firstIsInstanceOrNull<DefaultScriptingSupport>()?.forceReloadConfiguration(ktFile, loader) return default.forceReloadConfiguration(ktFile, loader)
} }
/////////////////// ///////////////////
@@ -33,7 +33,6 @@ import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock import kotlin.concurrent.withLock
import kotlin.script.experimental.api.ScriptDiagnostic import kotlin.script.experimental.api.ScriptDiagnostic
@@ -312,8 +311,6 @@ abstract class DefaultScriptingSupportBase(val project: Project) : ScriptingSupp
fun getAppliedConfiguration(file: VirtualFile?): ScriptConfigurationSnapshot? = fun getAppliedConfiguration(file: VirtualFile?): ScriptConfigurationSnapshot? =
getCachedConfigurationState(file)?.applied getCachedConfigurationState(file)?.applied
override fun isRelated(file: VirtualFile): Boolean = true
override fun hasCachedConfiguration(file: KtFile): Boolean = override fun hasCachedConfiguration(file: KtFile): Boolean =
getAppliedConfiguration(file.originalFile.virtualFile) != null getAppliedConfiguration(file.originalFile.virtualFile) != null
@@ -438,6 +435,5 @@ object DefaultScriptConfigurationManagerExtensions {
} }
val ScriptConfigurationManager.testingBackgroundExecutor val ScriptConfigurationManager.testingBackgroundExecutor
get() = (this as CompositeScriptConfigurationManager).managers get() = (this as CompositeScriptConfigurationManager).default
.firstIsInstance<DefaultScriptingSupport>()
.backgroundExecutor as TestingBackgroundExecutor .backgroundExecutor as TestingBackgroundExecutor
@@ -21,7 +21,16 @@ import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock import kotlin.concurrent.withLock
abstract class ScriptingSupport { abstract class ScriptingSupport {
abstract fun isRelated(file: VirtualFile): Boolean abstract class Provider {
abstract val all: Collection<ScriptingSupport>
abstract fun getSupport(file: VirtualFile): ScriptingSupport?
companion object {
val EPN: ExtensionPointName<Provider> =
ExtensionPointName.create("org.jetbrains.kotlin.scripting.idea.scriptingSupportProvider")
}
}
abstract fun clearCaches() abstract fun clearCaches()
abstract fun hasCachedConfiguration(file: KtFile): Boolean abstract fun hasCachedConfiguration(file: KtFile): Boolean
@@ -68,9 +77,4 @@ abstract class ScriptingSupport {
ScriptDependenciesModificationTracker.getInstance(project).incModificationCount() ScriptDependenciesModificationTracker.getInstance(project).incModificationCount()
} }
companion object {
val SCRIPTING_SUPPORT: ExtensionPointName<ScriptingSupport> =
ExtensionPointName.create("org.jetbrains.kotlin.scripting.idea.scriptingSupport")
}
} }
@@ -16,15 +16,16 @@ import java.io.File
internal class DefaultClassRootsCache( internal class DefaultClassRootsCache(
project: Project, project: Project,
private val all: Map<VirtualFile, ScriptCompilationConfigurationWrapper> private val all: Map<VirtualFile, ScriptCompilationConfigurationWrapper>
) : ScriptClassRootsCache(project, extractRoots(all, project)) { ) : ScriptClassRootsCache(
project,
ScriptClassRootsStorage.Companion.Key("default"),
extractRoots(all, project)
) {
override val fileToConfiguration: (VirtualFile) -> ScriptCompilationConfigurationWrapper? override fun getConfiguration(file: VirtualFile) = all[file]
get() = { all[it] }
override fun contains(file: VirtualFile): Boolean = file in all override fun contains(file: VirtualFile): Boolean = file in all
override val rootsCacheKey = ScriptClassRootsStorage.Companion.Key("default")
private val scriptsSdksCache: Map<VirtualFile, Sdk?> = private val scriptsSdksCache: Map<VirtualFile, Sdk?> =
ConcurrentFactoryMap.createWeakMap { file -> ConcurrentFactoryMap.createWeakMap { file ->
return@createWeakMap getScriptSdk( return@createWeakMap getScriptSdk(
@@ -44,30 +45,18 @@ internal class DefaultClassRootsCache(
project: Project, project: Project,
configuration: ScriptCompilationConfigurationWrapper configuration: ScriptCompilationConfigurationWrapper
): ScriptClassRootsStorage.Companion.ScriptClassRoots { ): ScriptClassRootsStorage.Companion.ScriptClassRoots {
val scriptSdk = val scriptSdk = getScriptSdk(configuration.javaHome)
getScriptSdkOfDefault(
configuration.javaHome,
project
)
if (scriptSdk != null && !scriptSdk.isAlreadyIndexed(project)) { if (scriptSdk != null && !scriptSdk.isAlreadyIndexed(project)) {
return ScriptClassRootsStorage.Companion.ScriptClassRoots( return ScriptClassRootsStorage.Companion.ScriptClassRoots(
toStringValues( toStringValues(configuration.dependenciesClassPath),
configuration.dependenciesClassPath toStringValues(configuration.dependenciesSources),
),
toStringValues(
configuration.dependenciesSources
),
setOf(scriptSdk) setOf(scriptSdk)
) )
} }
return ScriptClassRootsStorage.Companion.ScriptClassRoots( return ScriptClassRootsStorage.Companion.ScriptClassRoots(
toStringValues( toStringValues(configuration.dependenciesClassPath),
configuration.dependenciesClassPath toStringValues(configuration.dependenciesSources),
),
toStringValues(
configuration.dependenciesSources
),
emptySet() emptySet()
) )
} }
@@ -18,15 +18,13 @@ import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
import org.jetbrains.kotlin.idea.util.getProjectJdkTableSafe import org.jetbrains.kotlin.idea.util.getProjectJdkTableSafe
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import java.io.File import java.io.File
import java.nio.file.FileSystems
abstract class ScriptClassRootsCache( abstract class ScriptClassRootsCache(
private val project: Project, private val project: Project,
private val rootsCacheKey: ScriptClassRootsStorage.Companion.Key,
private val roots: ScriptClassRootsStorage.Companion.ScriptClassRoots private val roots: ScriptClassRootsStorage.Companion.ScriptClassRoots
) { ) {
protected abstract val fileToConfiguration: (VirtualFile) -> ScriptCompilationConfigurationWrapper? protected abstract fun getConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper?
protected abstract val rootsCacheKey: ScriptClassRootsStorage.Companion.Key
abstract val firstScriptSdk: Sdk? abstract val firstScriptSdk: Sdk?
@@ -34,7 +32,7 @@ abstract class ScriptClassRootsCache(
abstract fun contains(file: VirtualFile): Boolean abstract fun contains(file: VirtualFile): Boolean
class Fat( private class Fat(
val scriptConfiguration: ScriptCompilationConfigurationWrapper, val scriptConfiguration: ScriptCompilationConfigurationWrapper,
val classFilesScope: GlobalSearchScope val classFilesScope: GlobalSearchScope
) )
@@ -57,7 +55,7 @@ abstract class ScriptClassRootsCache(
private val scriptsDependenciesCache: MutableMap<VirtualFile, Fat> = private val scriptsDependenciesCache: MutableMap<VirtualFile, Fat> =
ConcurrentFactoryMap.createWeakMap { file -> ConcurrentFactoryMap.createWeakMap { file ->
val configuration = fileToConfiguration(file) ?: return@createWeakMap null val configuration = getConfiguration(file) ?: return@createWeakMap null
val roots = configuration.dependenciesClassPath val roots = configuration.dependenciesClassPath
val sdk = getScriptSdk(file) val sdk = getScriptSdk(file)
@@ -120,6 +118,24 @@ abstract class ScriptClassRootsCache(
fun Sdk.isAlreadyIndexed(project: Project): Boolean { fun Sdk.isAlreadyIndexed(project: Project): Boolean {
return ModuleManager.getInstance(project).modules.any { ModuleRootManager.getInstance(it).sdk == this } 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
}
} }
} }
@@ -7,31 +7,65 @@ package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile 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
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsStorage 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.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.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( class GradleClassRootsCache(
project: Project, val project: Project,
configuration: Configuration?, val context: GradleKtsContext,
override val fileToConfiguration: (VirtualFile) -> ScriptCompilationConfigurationWrapper? val configuration: Configuration
) : ScriptClassRootsCache(project, extractRoots(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? { override fun getScriptSdk(file: VirtualFile): Sdk? {
return firstScriptSdk 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 override fun contains(file: VirtualFile): Boolean = true
companion object { companion object {
fun extractRoots(configuration: Configuration?): ScriptClassRoots { fun extractRoots(context: GradleKtsContext, configuration: Configuration?, project: Project): ScriptClassRoots {
if (configuration == null) { if (configuration == null) {
return ScriptClassRootsStorage.EMPTY return ScriptClassRootsStorage.EMPTY
} }
@@ -46,7 +80,7 @@ internal class GradleClassRootsCache(
return ScriptClassRoots( return ScriptClassRoots(
configuration.classFilePath, configuration.classFilePath,
configuration.sourcePath, configuration.sourcePath,
getScriptSdk(configuration.context.javaHome)?.let { setOf(it) } ?: setOf() getScriptSdk(context.javaHome)?.let { setOf(it) } ?: setOf()
) )
} }
} }
@@ -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?)
@@ -5,42 +5,37 @@
package org.jetbrains.kotlin.idea.scripting.gradle package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport 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.ScriptingSupportHelper
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater 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.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.KotlinDslScriptModel
import org.jetbrains.kotlin.idea.scripting.gradle.importing.toScriptConfiguration
import org.jetbrains.kotlin.psi.KtFile 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.scripting.resolve.ScriptCompilationConfigurationWrapper
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import java.nio.file.Paths
data class ConfigurationData(
internal data class ConfigurationData(
val templateClasspath: List<String>, val templateClasspath: List<String>,
val models: List<KotlinDslScriptModel> val models: List<KotlinDslScriptModel>
) )
internal class Configuration(val context: GradleKtsContext, data: ConfigurationData) { class Configuration(val data: ConfigurationData) {
val scripts = data.models.associateBy { it.file } private val scripts: Map<String, KotlinDslScriptModel>
val sourcePath = data.models.flatMapTo(mutableSetOf()) { it.sourcePath }
val sourcePath: MutableSet<String>
val classFilePath: MutableSet<String> = mutableSetOf() val classFilePath: MutableSet<String> = mutableSetOf()
init { init {
val allModels = data.models
scripts = allModels.associateBy { it.file }
sourcePath = allModels.flatMapTo(mutableSetOf()) { it.sourcePath }
classFilePath.addAll(data.templateClasspath) classFilePath.addAll(data.templateClasspath)
data.models.flatMapTo(classFilePath) { it.classPath } allModels.flatMapTo(classFilePath) { it.classPath }
} }
fun scriptModel(file: VirtualFile): KotlinDslScriptModel? { fun scriptModel(file: VirtualFile): KotlinDslScriptModel? {
@@ -48,74 +43,33 @@ internal class Configuration(val context: GradleKtsContext, data: ConfigurationD
} }
} }
class GradleScriptingSupport(val project: Project) : ScriptingSupport() { class GradleScriptingSupport(
@Volatile private val rootsIndexer: ScriptClassRootsIndexer,
private var configuration: Configuration? = null val project: Project,
val buildRoot: VirtualFile,
private val rootsIndexer = ScriptClassRootsIndexer(project) val context: GradleKtsContext,
val configuration: Configuration
) : ScriptingSupport() {
init { 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<KotlinDslScriptModel>) {
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<KotlinScriptDefinitionFromAnnotatedTemplate>()
?.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 { rootsIndexer.transaction {
if (classpathRoots.hasNotCachedRoots(GradleClassRootsCache.extractRoots(newConfiguration))) { if (classpathRoots.hasNotCachedRoots(GradleClassRootsCache.extractRoots(context, configuration, project))) {
rootsIndexer.markNewRoot() rootsIndexer.markNewRoot()
} }
clearClassRootsCaches(project) clearClassRootsCaches(project)
ScriptingSupportHelper.updateHighlighting(project) { ScriptingSupportHelper.updateHighlighting(project) {
configuration?.scriptModel(it) != null configuration.scriptModel(it) != null
} }
} }
hideNotificationForProjectImport(project) hideNotificationForProjectImport(project)
} }
fun updateNotification(file: KtFile) { override fun recreateRootsCache() = GradleClassRootsCache(project, context, configuration)
private fun updateNotification(file: KtFile) {
val vFile = file.originalFile.virtualFile val vFile = file.originalFile.virtualFile
val scriptModel = configuration?.scriptModel(vFile) ?: return 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() { override fun clearCaches() {
// todo should clear up to date // todo: should clear up to date
} }
override fun hasCachedConfiguration(file: KtFile): Boolean = 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? { override fun getOrLoadConfiguration(virtualFile: VirtualFile, preloadedKtFile: KtFile?): ScriptCompilationConfigurationWrapper? {
val configuration = configuration return classpathRoots.getScriptConfiguration(virtualFile)
if (configuration == null) {
// todo: show notification "Import gradle project"
return null
} else {
return classpathRoots.getScriptConfiguration(virtualFile)
}
} }
override val updater: ScriptConfigurationUpdater override val updater: ScriptConfigurationUpdater
@@ -169,10 +104,4 @@ class GradleScriptingSupport(val project: Project) : ScriptingSupport() {
updateNotification(file) updateNotification(file)
} }
} }
companion object {
fun getInstance(project: Project): GradleScriptingSupport {
return SCRIPTING_SUPPORT.getPoint(project).extensionList.firstIsInstance()
}
}
} }
@@ -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<GradleScriptingSupport>()
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<String>) {
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<GradleExecutionSettings>(
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<String>? {
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<KotlinScriptDefinitionFromAnnotatedTemplate>()
?.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<KtFile>): 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()
}
}
@@ -9,29 +9,22 @@ import com.intellij.openapi.components.service
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VfsUtil
import org.gradle.tooling.model.kotlin.dsl.EditorReportSeverity import org.gradle.tooling.model.kotlin.dsl.EditorReportSeverity
import org.gradle.tooling.model.kotlin.dsl.KotlinDslScriptsModel import org.gradle.tooling.model.kotlin.dsl.KotlinDslScriptsModel
import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle
import org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptInputsWatcher 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.getGradleScriptInputsStamp
import org.jetbrains.kotlin.idea.scripting.gradle.getJavaHomeForGradleProject
import org.jetbrains.kotlin.psi.NotNullableUserDataProperty 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 org.jetbrains.plugins.gradle.service.project.ProjectResolverContext
import java.io.File import java.io.File
import kotlin.script.experimental.api.* import java.util.concurrent.ConcurrentHashMap
import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.jdkHome
import kotlin.script.experimental.jvm.jvm
var Project.kotlinDslModels: ArrayList<KotlinDslScriptModel> var Project.kotlinGradleDslSync: MutableMap<ExternalSystemTaskId, KotlinDslGradleBuildSync>
by NotNullableUserDataProperty<Project, ArrayList<KotlinDslScriptModel>>( by NotNullableUserDataProperty<Project, MutableMap<ExternalSystemTaskId, KotlinDslGradleBuildSync>>(
Key("Kotlin DSL Scripts Models"), arrayListOf() Key("Kotlin DSL Scripts Models"), ConcurrentHashMap()
) )
fun processScriptModel( fun processScriptModel(
@@ -44,14 +37,11 @@ fun processScriptModel(
"Couldn't get KotlinDslScriptsModel for $projectName:\n${model.message}\n${model.stackTrace}" "Couldn't get KotlinDslScriptsModel for $projectName:\n${model.message}\n${model.stackTrace}"
) )
} else { } else {
val project = resolverCtx.externalSystemTaskId.findProject() ?: return val task = resolverCtx.externalSystemTaskId
val project = task.findProject() ?: return
val models = model.toListOfScriptModels(project) val models = model.toListOfScriptModels(project)
project.kotlinDslModels.addAll(
models project.kotlinGradleDslSync[task]?.models?.addAll(models)
)
if (models.containsErrors()) {
throw IllegalStateException(KotlinIdeaGradleBundle.message("title.kotlin.build.script"))
}
if (models.containsErrors()) { if (models.containsErrors()) {
throw IllegalStateException(KotlinIdeaGradleBundle.message("title.kotlin.build.script")) throw IllegalStateException(KotlinIdeaGradleBundle.message("title.kotlin.build.script"))
@@ -111,52 +101,22 @@ private fun KotlinDslScriptsModel.toListOfScriptModels(project: Project): List<K
) )
} }
fun createGradleKtsContextIfPossible(project: Project): GradleKtsContext? { class KotlinDslGradleBuildSync(val workingDir: String, val taskId: ExternalSystemTaskId) {
val javaHome = getJavaHomeForGradleProject(project)?.let { File(it)} val models = mutableListOf<KotlinDslScriptModel>()
return GradleKtsContext(project, javaHome)
} }
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? { build.models.forEach { model ->
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<KotlinDslScriptModel>
) {
val errorReporter = KotlinGradleDslErrorReporter(project, task)
val javaHome = javaHomeStr?.let { File(it) }
val context = GradleKtsContext(project, javaHome)
models.forEach { model ->
errorReporter.reportError(File(model.file), model) errorReporter.reportError(File(model.file), model)
} }
project.service<GradleScriptInputsWatcher>().saveGradleProjectRootsAfterImport( project.service<GradleScriptInputsWatcher>().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<GradleScriptInputsWatcher>().clearState() project.service<GradleScriptInputsWatcher>().clearState()
} }
@@ -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.ExternalSystemTaskNotificationListenerAdapter
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType
import org.jetbrains.kotlin.idea.framework.GRADLE_SYSTEM_ID import org.jetbrains.kotlin.idea.framework.GRADLE_SYSTEM_ID
import org.jetbrains.kotlin.idea.scripting.gradle.getJavaHomeForGradleProject
class KotlinDslSyncListener : ExternalSystemTaskNotificationListenerAdapter() { class KotlinDslSyncListener : ExternalSystemTaskNotificationListenerAdapter() {
override fun onEnd(id: ExternalSystemTaskId) { override fun onStart(id: ExternalSystemTaskId, workingDir: String?) {
if (id.type != ExternalSystemTaskType.RESOLVE_PROJECT || id.projectSystemId != GRADLE_SYSTEM_ID) { if (id.type != ExternalSystemTaskType.RESOLVE_PROJECT) return
return if (id.projectSystemId != GRADLE_SYSTEM_ID) return
}
if (workingDir == null) return
val project = id.findProject() ?: return val project = id.findProject() ?: return
val models: List<KotlinDslScriptModel> = project.kotlinDslModels project.kotlinGradleDslSync[id] = KotlinDslGradleBuildSync(workingDir, id)
project.kotlinDslModels = arrayListOf() }
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)
} }
} }
@@ -5,35 +5,45 @@
package org.jetbrains.kotlin.idea.scripting.gradle 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 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.readString
import org.jetbrains.kotlin.idea.core.util.writeNullable
import org.jetbrains.kotlin.idea.core.util.writeString import org.jetbrains.kotlin.idea.core.util.writeString
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel
import java.io.DataInput
import java.io.DataInputStream import java.io.DataInputStream
import java.io.DataOutputStream import java.io.DataOutput
internal object KotlinDslScriptModels { 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? { fun read(buildRoot: VirtualFile): ConfigurationData? {
return attribute.readAttribute(project.projectFile ?: return null)?.use { return attribute.readAttribute(buildRoot)?.use {
readKotlinDslScriptModels(it) it.readNullable {
readKotlinDslScriptModels(it)
}
} }
} }
fun write(project: Project, configuration: ConfigurationData) { fun write(buildRoot: VirtualFile, configuration: ConfigurationData?) {
attribute.writeAttribute(project.projectFile ?: return).use { attribute.writeAttribute(buildRoot).use {
writeKotlinDslScriptModels(it, configuration) 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) val strings = StringsPool.writer(output)
strings.addStrings(data.templateClasspath) strings.addStrings(data.templateClasspath)
val scriptModels = data.models data.models.forEach {
scriptModels.forEach {
strings.addString(it.file) strings.addString(it.file)
strings.addStrings(it.classPath) strings.addStrings(it.classPath)
strings.addStrings(it.sourcePath) strings.addStrings(it.sourcePath)
@@ -41,7 +51,7 @@ internal fun writeKotlinDslScriptModels(output: DataOutputStream, data: Configur
} }
strings.writeHeader() strings.writeHeader()
strings.writeStringIds(data.templateClasspath) strings.writeStringIds(data.templateClasspath)
output.writeList(scriptModels) { output.writeList(data.models) {
strings.writeStringId(it.file) strings.writeStringId(it.file)
output.writeString(it.inputs.sections) output.writeString(it.inputs.sections)
output.writeLong(it.inputs.inputsTS) output.writeLong(it.inputs.inputsTS)
@@ -71,9 +81,9 @@ internal fun readKotlinDslScriptModels(input: DataInputStream): ConfigurationDat
} }
private object StringsPool { 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 var freeze = false
val ids = mutableMapOf<String, Int>() val ids = mutableMapOf<String, Int>()
@@ -106,7 +116,7 @@ private object StringsPool {
output.writeInt(getStringId(it)) output.writeInt(getStringId(it))
} }
fun writeStringIds(strings: List<String>) { fun writeStringIds(strings: Collection<String>) {
output.writeInt(strings.size) output.writeInt(strings.size)
strings.forEach { strings.forEach {
writeStringId(it) writeStringId(it)
@@ -128,12 +138,12 @@ private object StringsPool {
} }
} }
private inline fun <T> DataOutputStream.writeList(list: Collection<T>, write: (T) -> Unit) { private inline fun <T> DataOutput.writeList(list: Collection<T>, write: (T) -> Unit) {
writeInt(list.size) writeInt(list.size)
list.forEach { write(it) } list.forEach { write(it) }
} }
private inline fun <T> DataInputStream.readList(read: () -> T): List<T> { private inline fun <T> DataInput.readList(read: () -> T): List<T> {
val n = readInt() val n = readInt()
val result = ArrayList<T>(n) val result = ArrayList<T>(n)
repeat(n) { repeat(n) {
@@ -20,10 +20,10 @@ import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtScriptInitializer import org.jetbrains.kotlin.psi.KtScriptInitializer
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
import org.jetbrains.plugins.gradle.settings.GradleLocalSettings 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.GradleProjectSettings
import org.jetbrains.plugins.gradle.settings.GradleSettings import org.jetbrains.plugins.gradle.settings.GradleSettings
import org.jetbrains.plugins.gradle.util.GradleConstants import org.jetbrains.plugins.gradle.util.GradleConstants
import java.util.*
private val sections = arrayListOf("buildscript", "plugins", "initscript", "pluginManagement") private val sections = arrayListOf("buildscript", "plugins", "initscript", "pluginManagement")
@@ -95,34 +95,50 @@ fun useScriptConfigurationFromImportOnly(): Boolean {
return Registry.`is`("kotlin.gradle.scripts.useIdeaProjectImport", false) return Registry.`is`("kotlin.gradle.scripts.useIdeaProjectImport", false)
} }
fun getGradleVersion(project: Project, externalProjectPath: String): String? { fun getGradleVersion(project: Project, settings: GradleProjectSettings): String {
val localVersion = GradleLocalSettings.getInstance(project).getGradleVersion(externalProjectPath) val localVersion = GradleLocalSettings.getInstance(project).getGradleVersion(settings.externalProjectPath)
if (localVersion != null) return localVersion if (localVersion != null) return localVersion
return GradleSettings.getInstance(project).getLinkedProjectSettings(externalProjectPath)?.resolveGradleVersion()?.version return settings.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<GradleExecutionSettings>(
project,
projectSettings.externalProjectPath,
GradleConstants.SYSTEM_ID
)
return gradleExeSettings.javaHome
} }
fun getGradleProjectSettings(project: Project): Collection<GradleProjectSettings> { fun getGradleProjectSettings(project: Project): Collection<GradleProjectSettings> {
val gradleSettings = ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID) as GradleSettings val gradleSettings = ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID) as GradleSettings
return gradleSettings.getLinkedProjectsSettings() return gradleSettings.getLinkedProjectsSettings()
} }
class RootsIndex<T : Any> {
internal val tree = TreeMap<String, T>()
var values: Collection<T> = 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)
}
@@ -16,9 +16,7 @@ import kotlin.test.assertEquals
class KotlinDslScriptModelsTest { class KotlinDslScriptModelsTest {
@Test @Test
fun write() { fun write() {
val buffer = ByteArrayOutputStream() val data = ConfigurationData(
val conf = ConfigurationData(
listOf("a", "b", "c"), listOf("a", "b", "c"),
listOf( listOf(
KotlinDslScriptModel( 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()))) val restored = readKotlinDslScriptModels(DataInputStream(ByteArrayInputStream(buffer.toByteArray())))
assertEquals(conf.toString(), restored.toString()) assertEquals(data.toString(), restored.toString())
} }
} }
@@ -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<Int>()
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"))
}
}
+2 -2
View File
@@ -15,8 +15,8 @@
<extensions defaultExtensionNs="org.jetbrains.kotlin.scripting.idea"> <extensions defaultExtensionNs="org.jetbrains.kotlin.scripting.idea">
<listener order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptListener"/> <listener order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptListener"/>
<loader order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptConfigurationLoader"/> <loader order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptConfigurationLoader"/>
<scriptingSupport <scriptingSupportProvider
implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptingSupport" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptingSupportProvider"
/> />
</extensions> </extensions>
+2 -2
View File
@@ -15,8 +15,8 @@
<extensions defaultExtensionNs="org.jetbrains.kotlin.scripting.idea"> <extensions defaultExtensionNs="org.jetbrains.kotlin.scripting.idea">
<listener order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptListener"/> <listener order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptListener"/>
<loader order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptConfigurationLoader"/> <loader order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptConfigurationLoader"/>
<scriptingSupport <scriptingSupportProvider
implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptingSupport" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptingSupportProvider"
/> />
</extensions> </extensions>
@@ -1,7 +1,7 @@
<idea-plugin> <idea-plugin>
<extensionPoints> <extensionPoints>
<extensionPoint qualifiedName="org.jetbrains.kotlin.scripting.idea.scriptingSupport" <extensionPoint qualifiedName="org.jetbrains.kotlin.scripting.idea.scriptingSupportProvider"
interface="org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport" interface="org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport$Provider"
area="IDEA_PROJECT"/> area="IDEA_PROJECT"/>
</extensionPoints> </extensionPoints>
@@ -10,11 +10,4 @@
<trafficLightRendererContributor implementation="org.jetbrains.kotlin.idea.core.script.ScriptTrafficLightRendererContributor"/> <trafficLightRendererContributor implementation="org.jetbrains.kotlin.idea.core.script.ScriptTrafficLightRendererContributor"/>
</extensions> </extensions>
<extensions defaultExtensionNs="org.jetbrains.kotlin.scripting.idea">
<scriptingSupport
implementation="org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptingSupport"
order="last"
/>
</extensions>
</idea-plugin> </idea-plugin>