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.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<DefaultScriptingSupport>()
(getInstance(file.project) as CompositeScriptConfigurationManager).default
.updateScriptDependenciesSynchronously(file)
}
@TestOnly
fun clearCaches(project: Project) {
(getInstance(project) as CompositeScriptConfigurationManager).managers
.firstIsInstance<DefaultScriptingSupport>()
(getInstance(project) as CompositeScriptConfigurationManager).default
.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.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<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 =
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<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.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<DefaultScriptingSupport>()
get() = (this as CompositeScriptConfigurationManager).default
.backgroundExecutor as TestingBackgroundExecutor
@@ -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<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 hasCachedConfiguration(file: KtFile): Boolean
@@ -68,9 +77,4 @@ abstract class ScriptingSupport {
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(
project: Project,
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?
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<VirtualFile, Sdk?> =
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()
)
}
@@ -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<VirtualFile, Fat> =
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
}
}
}
@@ -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()
)
}
}
@@ -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
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<String>,
val models: List<KotlinDslScriptModel>
)
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<String, KotlinDslScriptModel>
val sourcePath: MutableSet<String>
val classFilePath: MutableSet<String> = 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<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 {
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()
}
}
}
@@ -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.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<KotlinDslScriptModel>
by NotNullableUserDataProperty<Project, ArrayList<KotlinDslScriptModel>>(
Key("Kotlin DSL Scripts Models"), arrayListOf()
var Project.kotlinGradleDslSync: MutableMap<ExternalSystemTaskId, KotlinDslGradleBuildSync>
by NotNullableUserDataProperty<Project, MutableMap<ExternalSystemTaskId, KotlinDslGradleBuildSync>>(
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<K
)
}
fun createGradleKtsContextIfPossible(project: Project): GradleKtsContext? {
val javaHome = getJavaHomeForGradleProject(project)?.let { File(it)}
return GradleKtsContext(project, javaHome)
class KotlinDslGradleBuildSync(val workingDir: String, val taskId: ExternalSystemTaskId) {
val models = mutableListOf<KotlinDslScriptModel>()
}
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<KotlinDslScriptModel>
) {
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<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()
}
@@ -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<KotlinDslScriptModel> = 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)
}
}
@@ -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<String, Int>()
@@ -106,7 +116,7 @@ private object StringsPool {
output.writeInt(getStringId(it))
}
fun writeStringIds(strings: List<String>) {
fun writeStringIds(strings: Collection<String>) {
output.writeInt(strings.size)
strings.forEach {
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)
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 result = ArrayList<T>(n)
repeat(n) {
@@ -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<GradleExecutionSettings>(
project,
projectSettings.externalProjectPath,
GradleConstants.SYSTEM_ID
)
return gradleExeSettings.javaHome
return settings.resolveGradleVersion().version
}
fun getGradleProjectSettings(project: Project): Collection<GradleProjectSettings> {
val gradleSettings = ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID) as GradleSettings
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 {
@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())
}
}
@@ -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">
<listener order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptListener"/>
<loader order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptConfigurationLoader"/>
<scriptingSupport
implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptingSupport"
<scriptingSupportProvider
implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptingSupportProvider"
/>
</extensions>
+2 -2
View File
@@ -15,8 +15,8 @@
<extensions defaultExtensionNs="org.jetbrains.kotlin.scripting.idea">
<listener order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptListener"/>
<loader order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptConfigurationLoader"/>
<scriptingSupport
implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptingSupport"
<scriptingSupportProvider
implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptingSupportProvider"
/>
</extensions>
@@ -1,7 +1,7 @@
<idea-plugin>
<extensionPoints>
<extensionPoint qualifiedName="org.jetbrains.kotlin.scripting.idea.scriptingSupport"
interface="org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport"
<extensionPoint qualifiedName="org.jetbrains.kotlin.scripting.idea.scriptingSupportProvider"
interface="org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport$Provider"
area="IDEA_PROJECT"/>
</extensionPoints>
@@ -10,11 +10,4 @@
<trafficLightRendererContributor implementation="org.jetbrains.kotlin.idea.core.script.ScriptTrafficLightRendererContributor"/>
</extensions>
<extensions defaultExtensionNs="org.jetbrains.kotlin.scripting.idea">
<scriptingSupport
implementation="org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptingSupport"
order="last"
/>
</extensions>
</idea-plugin>