CompositeScriptConfigurationManager: unified caching

This commit is contained in:
Sergey Rostov
2020-04-25 00:37:58 +03:00
parent 368e6f93df
commit 6ad0ed7f40
34 changed files with 899 additions and 977 deletions
@@ -5,33 +5,9 @@
package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel
data class ConfigurationData(
data class GradleImportedBuildRootData(
val templateClasspath: List<String>,
val models: List<KotlinDslScriptModel>
)
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)
allModels.flatMapTo(classFilePath) { it.classPath }
}
fun scriptModel(file: VirtualFile): KotlinDslScriptModel? {
return scripts[FileUtil.toSystemDependentName(file.path)]
}
}
)
@@ -0,0 +1,114 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource
import org.jetbrains.kotlin.scripting.resolve.adjustByDefinition
import java.io.File
import kotlin.script.experimental.api.*
import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.jdkHome
import kotlin.script.experimental.jvm.jvm
sealed class GradleBuildRoot {
abstract val pathPrefix: String
abstract class Unlinked : GradleBuildRoot()
class UnlinkedUnknown(override val pathPrefix: String) : Unlinked()
class UnlinkedLegacy(override val pathPrefix: String) : Unlinked()
class UnlinkedSupported(override val pathPrefix: String) : Unlinked()
abstract class Linked : GradleBuildRoot() {
@Volatile
var importing = false
}
class Legacy(override val pathPrefix: String) : Linked()
class New(override val pathPrefix: String) : Linked()
class Imported(
val project: Project,
val dir: VirtualFile,
val context: GradleKtsContext,
val data: GradleImportedBuildRootData
) : Linked() {
override val pathPrefix: String = dir.path
fun collectConfigurations(builder: ScriptClassRootsCache.Builder) {
val javaHome = context.javaHome
javaHome?.let { builder.addSdk(it) }
val anyScript = data.models.firstOrNull()?.let {
LocalFileSystem.getInstance().findFileByPath(it.file)
}
val scriptDefinition: ScriptDefinition? = anyScript?.findScriptDefinition(project)
if (scriptDefinition != null) {
builder.classes.addAll(data.templateClasspath)
}
val root = ScriptInfoRoot(this, scriptDefinition)
data.models.forEach {
builder.scripts[it.file] = ScriptInfo(root, it)
builder.classes.addAll(it.classPath)
builder.sources.addAll(it.sourcePath)
}
}
/**
* Common info between scripts (to save bits inside ScriptInfo)
*/
data class ScriptInfoRoot(
val buildRoot: Imported,
val scriptDefinition: ScriptDefinition?
)
data class ScriptInfo(
val root: ScriptInfoRoot,
val model: KotlinDslScriptModel
) : ScriptClassRootsCache.LightScriptInfo() {
val buildRoot get() = root.buildRoot
override fun buildConfiguration(): ScriptCompilationConfigurationWrapper? {
val javaHome = buildRoot.context.javaHome
val scriptDefinition = root.scriptDefinition
val scriptFile = File(model.file)
val virtualFile = VfsUtil.findFile(scriptFile.toPath(), true)!!
if (scriptDefinition == null) return null
return ScriptCompilationConfigurationWrapper.FromCompilationConfiguration(
VirtualFileScriptSource(virtualFile),
scriptDefinition.compilationConfiguration.with {
if (javaHome != null) {
jvm.jdkHome(javaHome)
}
defaultImports(model.imports)
dependencies(JvmDependency(model.classPath.map { File(it) }))
ide.dependenciesSources(JvmDependency(model.sourcePath.map { File(it) }))
}.adjustByDefinition(scriptDefinition)
)
}
}
}
}
@@ -1,88 +0,0 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache.Companion.getScriptSdkOrDefault
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsStorage
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsStorage.Companion.ScriptClassRoots
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource
import org.jetbrains.kotlin.scripting.resolve.adjustByDefinition
import java.io.File
import kotlin.script.experimental.api.*
import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.jdkHome
import kotlin.script.experimental.jvm.jvm
class GradleClassRootsCache(
val project: Project,
val context: GradleKtsContext,
val configuration: Configuration
) : ScriptClassRootsCache(
project,
ScriptClassRootsStorage.Companion.Key("gradle"),
extractRoots(context, configuration, project)
) {
override fun getConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? =
configuration.scriptModel(file)?.toScriptConfiguration()
private fun KotlinDslScriptModel.toScriptConfiguration(): ScriptCompilationConfigurationWrapper? {
val scriptFile = File(file)
val virtualFile = VfsUtil.findFile(scriptFile.toPath(), true)!!
val definition = virtualFile.findScriptDefinition(project) ?: return null
return ScriptCompilationConfigurationWrapper.FromCompilationConfiguration(
VirtualFileScriptSource(virtualFile),
definition.compilationConfiguration.with {
if (context.javaHome != null) {
jvm.jdkHome(context.javaHome)
}
defaultImports(imports)
dependencies(JvmDependency(classPath.map { File(it) }))
ide.dependenciesSources(JvmDependency(sourcePath.map { File(it) }))
}.adjustByDefinition(definition)
)
}
override fun getScriptSdk(file: VirtualFile): Sdk? {
return firstScriptSdk
}
override val firstScriptSdk: Sdk? = getScriptSdkOrDefault(context.javaHome, project)
// called to ensure that configuration for file is loaded
// as we cannot force loading, we always return true
override fun contains(file: VirtualFile): Boolean = true
companion object {
fun extractRoots(context: GradleKtsContext, configuration: Configuration?, project: Project): ScriptClassRoots {
if (configuration == null) {
return ScriptClassRootsStorage.EMPTY
}
val scriptSdk = getScriptSdkOrDefault(context.javaHome, project)
if (scriptSdk != null && !scriptSdk.isAlreadyIndexed(project)) {
return ScriptClassRootsStorage.Companion.ScriptClassRoots(
configuration.classFilePath,
configuration.sourcePath,
setOf(scriptSdk)
)
}
return ScriptClassRoots(
configuration.classFilePath,
configuration.sourcePath,
getScriptSdkOrDefault(context.javaHome, project)?.let { setOf(it) } ?: setOf()
)
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@@ -9,36 +9,23 @@ import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
open class GradleScriptListener(project: Project) : ScriptChangeListener(project) {
class GradleScriptListener(project: Project) : ScriptChangeListener(project) {
init {
// start GradleScriptInputsWatcher to track changes in gradle-configuration related files
project.service<GradleScriptInputsWatcher>().startWatching()
}
override fun editorActivated(vFile: VirtualFile, updater: ScriptConfigurationUpdater) {
if (!isInAffectedGradleProjectFiles(project, vFile.path)) return
if (useScriptConfigurationFromImportOnly()) {
// do nothing
} else {
val file = getAnalyzableKtFileForScript(vFile) ?: return
updater.suggestToUpdateConfigurationIfOutOfDate(file)
}
}
override fun documentChanged(vFile: VirtualFile, updater: ScriptConfigurationUpdater) {
if (!isInAffectedGradleProjectFiles(project, vFile.path)) return
val file = getAnalyzableKtFileForScript(vFile)
if (file != null) {
// *.gradle.kts file was changed
updater.suggestToUpdateConfigurationIfOutOfDate(file)
}
}
override fun isApplicable(vFile: VirtualFile): Boolean {
return isGradleKotlinScript(vFile)
return GradleScriptingSupportProvider.getInstance(project).isApplicable(vFile)
}
override fun editorActivated(vFile: VirtualFile) {
// do nothing
}
override fun documentChanged(vFile: VirtualFile) {
GradleScriptingSupportProvider.getInstance(project).getScriptInfo(vFile)?.model?.inputs
}
}
@@ -1,62 +0,0 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
class GradleScriptingSupport(
val project: Project,
val buildRoot: VirtualFile,
val context: GradleKtsContext,
val configuration: Configuration
) : ScriptingSupport() {
override fun recreateRootsCache() = GradleClassRootsCache(project, context, configuration)
fun isConfigurationOutOfDate(file: VirtualFile): Boolean {
val scriptModel = configuration.scriptModel(file) ?: return false
return !scriptModel.inputs.isUpToDate(project, file)
}
override fun clearCaches() {
// todo: should clear up to date
}
override fun hasCachedConfiguration(file: KtFile): Boolean =
configuration.scriptModel(file.originalFile.virtualFile) != null
// TODO: can be true during import
override fun isConfigurationLoadingInProgress(file: KtFile): Boolean = false
override fun getOrLoadConfiguration(virtualFile: VirtualFile, preloadedKtFile: KtFile?): ScriptCompilationConfigurationWrapper? {
return classpathRoots.getScriptConfiguration(virtualFile)
}
override val updater: ScriptConfigurationUpdater
get() = object : ScriptConfigurationUpdater {
override fun ensureUpToDatedConfigurationSuggested(file: KtFile) {
// do nothing for gradle scripts
}
// unused symbol inspection should not initiate loading
override fun ensureConfigurationUpToDate(files: List<KtFile>): Boolean = true
override fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile) {
val vFile = file.originalFile.virtualFile
if (isConfigurationOutOfDate(vFile)) {
showNotificationForProjectImport(project)
} else {
hideNotificationForProjectImport(project)
}
}
}
}
@@ -5,82 +5,146 @@
package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
import org.jetbrains.kotlin.idea.core.script.configuration.CompositeScriptConfigurationManager
import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport
import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupportHelper
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsIndexer
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslGradleBuildSync
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotatedTemplate
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import org.jetbrains.plugins.gradle.config.GradleSettingsListenerAdapter
import org.jetbrains.plugins.gradle.settings.GradleExecutionSettings
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
import org.jetbrains.plugins.gradle.settings.GradleSettingsListener
import org.jetbrains.plugins.gradle.settings.*
import org.jetbrains.plugins.gradle.util.GradleConstants
import java.io.File
import java.nio.file.Paths
/**
* Creates [GradleScriptingSupport] per each linked Gradle build.
* Gradle project sync should call [update].
* Manages [roots] gradle build roots:
* - populated after Gradle project sync, by calling [update]
* - stored in FS and loaded in [init]
*/
class GradleScriptingSupportProvider(val project: Project) : ScriptingSupport.Provider() {
private val rootsIndexer = ScriptClassRootsIndexer(project)
val manager: CompositeScriptConfigurationManager
get() = ScriptConfigurationManager.getInstance(project) as CompositeScriptConfigurationManager
val roots = RootsIndex<GradleScriptingSupport>()
override val all get() = roots.values
private val updater
get() = manager.updater
val roots = RootsIndex()
////////////
/// ScriptingSupport.Provider implementation:
override fun updateScriptDefinitions() {
// nothing related to script definition and project roots are cached
}
override fun isApplicable(file: VirtualFile): Boolean {
val buildRoot = findScriptBuildRoot(file) ?: return false
if (buildRoot is GradleBuildRoot.Legacy) return false
if (buildRoot is GradleBuildRoot.UnlinkedLegacy) return false
return true
}
override fun isConfigurationLoadingInProgress(file: KtFile): Boolean {
return when (val root = findScriptBuildRoot(file.originalFile.virtualFile)) {
is GradleBuildRoot.Linked -> root.importing
else -> false
}
}
override fun collectConfigurations(builder: ScriptClassRootsCache.Builder) {
roots.values.forEach { root ->
if (root is GradleBuildRoot.Imported) {
root.collectConfigurations(builder)
}
}
}
//////////////////
private val VirtualFile.localPath
get() = path
private fun findRoot(file: VirtualFile) = roots.findRoot(file.localPath)
fun getScriptInfo(file: VirtualFile): GradleBuildRoot.Imported.ScriptInfo? =
manager.getLightScriptInfo(file.localPath) as? GradleBuildRoot.Imported.ScriptInfo
override fun getSupport(file: VirtualFile): ScriptingSupport? {
if (isGradleKotlinScript(file)) {
findRoot(file)?.let { return it }
fun findScriptBuildRoot(file: VirtualFile): GradleBuildRoot? {
if (!isGradleKotlinScript(file)) return null
val externalProjectSettings = findExternalProjectSettings(file) ?: return null
if (kotlinDslScriptsModelImportSupported(getGradleVersion(project, externalProjectSettings))) {
return unlinkedFilesSupport
}
}
return null
val path = file.localPath
val found = roots.findNearestRoot(path)
if (found != null) return found
return findNewScriptBuildRoot(path)
}
private fun findExternalProjectSettings(file: VirtualFile): GradleProjectSettings? {
private fun findNewScriptBuildRoot(scriptPath: String): GradleBuildRoot {
val newRoot = updater.update {
loadScriptBuildRoot(scriptPath)
}
roots[newRoot.pathPrefix] = newRoot
return newRoot
}
private fun updateBuildRoot(rootPath: String) {
val settings = getGradleProjectSettings(rootPath) ?: return
val newRoot = loadLinkedRoot(settings)
roots[newRoot.pathPrefix] = newRoot
if (newRoot is GradleBuildRoot.Imported) {
updater.ensureUpdateScheduled()
}
}
private fun loadScriptBuildRoot(scriptPath: String): GradleBuildRoot {
val settings = findNearestGradleProjectSettings(scriptPath) ?: return detectUnlinkedGradleBuildRoot(scriptPath)
return loadLinkedRoot(settings)
}
private fun loadLinkedRoot(settings: GradleProjectSettings) =
tryLoadFromFsCache(settings) ?: createOtherLinkedRoot(settings)
private fun createOtherLinkedRoot(settings: GradleProjectSettings): GradleBuildRoot.Linked {
val supported = kotlinDslScriptsModelImportSupported(settings.resolveGradleVersion().version)
return when {
supported -> GradleBuildRoot.New(settings.externalProjectPath)
else -> GradleBuildRoot.Legacy(settings.externalProjectPath)
}
}
private fun getGradleProjectSettings(workingDir: String): GradleProjectSettings? {
return (ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID) as GradleSettings)
.getLinkedProjectSettings(workingDir)
}
private fun findNearestGradleProjectSettings(localPath: String): GradleProjectSettings? {
return getGradleProjectSettings(project)
.filter { file.localPath.startsWith(it.externalProjectPath) }
.filter { localPath.startsWith(it.externalProjectPath) }
.maxBy { it.externalProjectPath.length }
}
init {
ApplicationManager.getApplication().executeOnPooledThread {
roots.update { set ->
getGradleProjectSettings(project).forEach { gradleProjectSettings ->
if (kotlinDslScriptsModelImportSupported(getGradleVersion(project, gradleProjectSettings))) {
val support = createSupport(gradleProjectSettings.externalProjectPath) {
KotlinDslScriptModels.read(it)
}
private fun detectUnlinkedGradleBuildRoot(file: String): GradleBuildRoot.Unlinked =
GradleBuildRoot.UnlinkedUnknown(file)
if (support != null) {
set(support.buildRoot.localPath, support)
}
}
}
}
init {
getGradleProjectSettings(project).forEach {
roots[it.externalProjectPath] = loadLinkedRoot(it)
}
// subscribe to gradle build unlink
val listener = object : GradleSettingsListenerAdapter() {
override fun onProjectsLinked(settings: MutableCollection<GradleProjectSettings>) {
settings.forEach {
loadLinkedRoot(it)
}
}
override fun onProjectsUnlinked(linkedProjectPaths: MutableSet<String>) {
linkedProjectPaths.forEach {
val buildRoot = VfsUtil.findFile(Paths.get(it), false)
@@ -91,30 +155,68 @@ class GradleScriptingSupportProvider(val project: Project) : ScriptingSupport.Pr
}
}
}
override fun onGradleHomeChange(oldPath: String?, newPath: String?, linkedProjectPath: String) {
updateBuildRoot(linkedProjectPath)
manager.updater.ensureUpdateScheduled()
}
override fun onGradleDistributionTypeChange(currentValue: DistributionType?, linkedProjectPath: String) {
updateBuildRoot(linkedProjectPath)
manager.updater.ensureUpdateScheduled()
}
}
project.messageBus.connect(project).subscribe(GradleSettingsListener.TOPIC, listener)
}
private fun tryLoadFromFsCache(settings: GradleProjectSettings) =
tryCreateImportedRoot(settings.externalProjectPath) {
KotlinDslScriptModels.read(it)
}
fun markImportingInProgress(workingDir: String) {
val linked = roots.getBuildRoot(workingDir) as? GradleBuildRoot.Linked
if (linked != null) {
linked.importing = true
} else {
val settings = getGradleProjectSettings(workingDir)
if (settings != null) {
val root = createOtherLinkedRoot(settings)
root.importing = true
roots[workingDir] = root
}
}
}
fun update(build: KotlinDslGradleBuildSync) {
val settings = getGradleProjectSettings(build.workingDir) ?: return
(roots.getBuildRoot(build.workingDir) as? GradleBuildRoot.Linked)?.importing = false
if (!kotlinDslScriptsModelImportSupported(settings.resolveGradleVersion().version)) {
roots[build.workingDir] = GradleBuildRoot.Legacy(build.workingDir)
return
}
// fast path for linked gradle builds without .gradle.kts support
if (build.models.isEmpty()) {
val root = roots.findRoot(build.workingDir) ?: return
if (root.configuration.data.models.isEmpty()) return
val root = roots.findNearestRoot(build.workingDir) ?: return
if (root is GradleBuildRoot.Imported && root.data.models.isEmpty()) return
}
val templateClasspath = findTemplateClasspath(build) ?: return
val data = ConfigurationData(templateClasspath, build.models)
val newSupport = createSupport(build.workingDir) { data } ?: return
KotlinDslScriptModels.write(newSupport.buildRoot, data)
roots[newSupport.buildRoot.localPath] = newSupport
val data = GradleImportedBuildRootData(templateClasspath, build.models)
val newSupport = tryCreateImportedRoot(build.workingDir) { data } ?: return
KotlinDslScriptModels.write(newSupport.dir, data)
roots[build.workingDir] = newSupport
manager.updater.ensureUpdateScheduled()
}
private fun createSupport(
private fun tryCreateImportedRoot(
externalProjectPath: String,
dataProvider: (buildRoot: VirtualFile) -> ConfigurationData?
): GradleScriptingSupport? {
dataProvider: (buildRoot: VirtualFile) -> GradleImportedBuildRootData?
): GradleBuildRoot.Imported? {
val gradleExeSettings =
ExternalSystemApiUtil.getExecutionSettings<GradleExecutionSettings>(
project,
@@ -125,28 +227,15 @@ class GradleScriptingSupportProvider(val project: Project) : ScriptingSupport.Pr
val buildRoot = VfsUtil.findFile(Paths.get(externalProjectPath), true) ?: return null
val data = dataProvider(buildRoot) ?: return null
val newSupport = GradleScriptingSupport(
val newSupport = GradleBuildRoot.Imported(
project,
buildRoot,
GradleKtsContext(gradleExeSettings.javaHome?.let { File(it) }),
Configuration(data)
data
)
val oldSupport = roots.findRoot(externalProjectPath)
val oldSupport = roots.findNearestRoot(externalProjectPath)
if (oldSupport != null) {
rootsIndexer.transaction {
val newRoots = GradleClassRootsCache.extractRoots(newSupport.context, newSupport.configuration, project)
if (oldSupport.classpathRoots.hasNotCachedRoots(newRoots)) {
rootsIndexer.markNewRoot()
}
newSupport.clearClassRootsCaches(project)
ScriptingSupportHelper.updateHighlighting(project) {
newSupport.configuration.scriptModel(it) != null
}
}
hideNotificationForProjectImport(project)
}
@@ -161,44 +250,11 @@ class GradleScriptingSupportProvider(val project: Project) : ScriptingSupport.Pr
?.templateClasspath?.map { it.path }
}
private val unlinkedFilesSupport = object : ScriptingSupport() {
override fun clearCaches() {
}
override fun hasCachedConfiguration(file: KtFile): Boolean = false
override fun isConfigurationLoadingInProgress(file: KtFile): Boolean = false
override fun getOrLoadConfiguration(
virtualFile: VirtualFile,
preloadedKtFile: KtFile?
): ScriptCompilationConfigurationWrapper? = null
override val updater: ScriptConfigurationUpdater
get() = object : ScriptConfigurationUpdater {
override fun ensureUpToDatedConfigurationSuggested(file: KtFile) {
}
override fun ensureConfigurationUpToDate(files: List<KtFile>): Boolean = false
override fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile) {
}
}
override fun recreateRootsCache(): ScriptClassRootsCache = ScriptClassRootsCache.empty(project)
}
fun isMissingConfigurationCanBeLoadedDuringImport(file: VirtualFile): Boolean {
findRoot(file)?.let { return false }
val externalProjectSettings = findExternalProjectSettings(file) ?: return false
return kotlinDslScriptsModelImportSupported(getGradleVersion(project, externalProjectSettings))
}
// used in 201
@Suppress("UNUSED")
fun isConfigurationOutOfDate(file: VirtualFile): Boolean {
val support = findRoot(file) ?: return false
return support.isConfigurationOutOfDate(file)
val script = getScriptInfo(file) ?: return false
return script.model.inputs.isUpToDate(project, file)
}
companion object {
@@ -16,18 +16,20 @@ import com.intellij.ui.EditorNotificationPanel
import com.intellij.ui.EditorNotifications
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle
import org.jetbrains.kotlin.idea.scripting.gradle.legacy.GradleLegacyScriptConfigurationLoaderForOutOfProjectScripts
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
import org.jetbrains.plugins.gradle.util.GradleConstants
class MissingGradleScriptConfigurationNotificationProvider(private val project: Project) : EditorNotifications.Provider<EditorNotificationPanel>() {
class MissingGradleScriptConfigurationNotificationProvider(private val project: Project) :
EditorNotifications.Provider<EditorNotificationPanel>() {
override fun getKey(): Key<EditorNotificationPanel> = KEY
override fun createNotificationPanel(file: VirtualFile, fileEditor: FileEditor): EditorNotificationPanel? {
if (!isGradleKotlinScript(file)) return null
if (file.fileType != KotlinFileType.INSTANCE) return null
if (!isInAffectedGradleProjectFiles(project, file.path)) {
return EditorNotificationPanel().apply {
return when (GradleScriptingSupportProvider.getInstance(project).findScriptBuildRoot(file)) {
is GradleBuildRoot.UnlinkedSupported -> EditorNotificationPanel().apply {
text(KotlinIdeaGradleBundle.message("text.the.associated.gradle.project.isn.t.imported"))
val linkProjectText = KotlinIdeaGradleBundle.message("action.label.text.load.script.configuration")
@@ -53,22 +55,20 @@ class MissingGradleScriptConfigurationNotificationProvider(private val project:
linkProjectText
)
}
}
if (GradleScriptingSupportProvider.getInstance(project).isMissingConfigurationCanBeLoadedDuringImport(file)) {
return EditorNotificationPanel().apply {
is GradleBuildRoot.Linked -> EditorNotificationPanel().apply {
text(getMissingConfigurationNotificationText())
createActionLabel(getMissingConfigurationActionText()) {
runPartialGradleImport(project)
}
}
else -> null
}
return null
}
private val loaderForOutOfProjectScripts by lazy {
GradleScriptConfigurationLoaderForOutOfProjectScripts(project)
GradleLegacyScriptConfigurationLoaderForOutOfProjectScripts(
project
)
}
companion object {
@@ -9,6 +9,7 @@ import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListenerAdapter
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType
import org.jetbrains.kotlin.idea.framework.GRADLE_SYSTEM_ID
import org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptingSupportProvider
class KotlinDslSyncListener : ExternalSystemTaskNotificationListenerAdapter() {
override fun onStart(id: ExternalSystemTaskId, workingDir: String?) {
@@ -18,6 +19,7 @@ class KotlinDslSyncListener : ExternalSystemTaskNotificationListenerAdapter() {
if (workingDir == null) return
val project = id.findProject() ?: return
GradleScriptingSupportProvider.getInstance(project).markImportingInProgress(workingDir)
project.kotlinGradleDslSync[id] = KotlinDslGradleBuildSync(workingDir, id)
}
@@ -1,9 +1,9 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.scripting.gradle
package org.jetbrains.kotlin.idea.scripting.gradle.legacy
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.project.Project
@@ -15,6 +15,10 @@ import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigura
import org.jetbrains.kotlin.idea.core.script.configuration.loader.DefaultScriptConfigurationLoader
import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoadingContext
import org.jetbrains.kotlin.idea.core.util.EDT
import org.jetbrains.kotlin.idea.scripting.gradle.getGradleScriptInputsStamp
import org.jetbrains.kotlin.idea.scripting.gradle.isGradleKotlinScript
import org.jetbrains.kotlin.idea.scripting.gradle.isInAffectedGradleProjectFiles
import org.jetbrains.kotlin.idea.scripting.gradle.useScriptConfigurationFromImportOnly
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
@@ -24,7 +28,7 @@ import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
*
* TODO(gradle6): remove
*/
class GradleScriptConfigurationLoaderForOutOfProjectScripts(project: Project) : DefaultScriptConfigurationLoader(project) {
class GradleLegacyScriptConfigurationLoaderForOutOfProjectScripts(project: Project) : DefaultScriptConfigurationLoader(project) {
override fun loadDependencies(
isFirstLoad: Boolean,
ktFile: KtFile,
@@ -53,7 +57,7 @@ class GradleScriptConfigurationLoaderForOutOfProjectScripts(project: Project) :
*
* TODO(gradle6): remove
*/
class GradleScriptConfigurationLoader(project: Project) : DefaultScriptConfigurationLoader(project) {
class GradleLegacyScriptConfigurationLoader(project: Project) : DefaultScriptConfigurationLoader(project) {
override fun shouldRunInBackground(scriptDefinition: ScriptDefinition): Boolean {
return if (useScriptConfigurationFromImportOnly()) false else super.shouldRunInBackground(scriptDefinition)
}
@@ -0,0 +1,39 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.scripting.gradle.legacy
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener
import org.jetbrains.kotlin.idea.scripting.gradle.*
class GradleLegacyScriptListener(project: Project) : ScriptChangeListener(project) {
override fun isApplicable(vFile: VirtualFile): Boolean {
return isGradleKotlinScript(vFile)
}
override fun editorActivated(vFile: VirtualFile) {
if (!isInAffectedGradleProjectFiles(project, vFile.path)) return
if (useScriptConfigurationFromImportOnly()) {
// do nothing
} else {
val file = getAnalyzableKtFileForScript(vFile) ?: return
default.suggestToUpdateConfigurationIfOutOfDate(file)
}
}
override fun documentChanged(vFile: VirtualFile) {
if (!isInAffectedGradleProjectFiles(project, vFile.path)) return
val file = getAnalyzableKtFileForScript(vFile)
if (file != null) {
// *.gradle.kts file was changed
default.suggestToUpdateConfigurationIfOutOfDate(file)
}
}
}
@@ -19,7 +19,7 @@ import java.io.DataOutput
internal object KotlinDslScriptModels {
private val attribute = FileAttribute("kotlin-dsl-script-models", 3, false)
fun read(buildRoot: VirtualFile): ConfigurationData? {
fun read(buildRoot: VirtualFile): GradleImportedBuildRootData? {
return attribute.readAttribute(buildRoot)?.use {
it.readNullable {
readKotlinDslScriptModels(it)
@@ -27,9 +27,9 @@ internal object KotlinDslScriptModels {
}
}
fun write(buildRoot: VirtualFile, configuration: ConfigurationData?) {
fun write(buildRoot: VirtualFile, data: GradleImportedBuildRootData?) {
attribute.writeAttribute(buildRoot).use {
it.writeNullable(configuration) {
it.writeNullable(data) {
writeKotlinDslScriptModels(this, it)
}
}
@@ -40,7 +40,7 @@ internal object KotlinDslScriptModels {
}
}
internal fun writeKotlinDslScriptModels(output: DataOutput, data: ConfigurationData) {
internal fun writeKotlinDslScriptModels(output: DataOutput, data: GradleImportedBuildRootData) {
val strings = StringsPool.writer(output)
strings.addStrings(data.templateClasspath)
data.models.forEach {
@@ -61,7 +61,7 @@ internal fun writeKotlinDslScriptModels(output: DataOutput, data: ConfigurationD
}
}
internal fun readKotlinDslScriptModels(input: DataInputStream): ConfigurationData {
internal fun readKotlinDslScriptModels(input: DataInputStream): GradleImportedBuildRootData {
val strings = StringsPool.reader(input)
val templateClasspath = strings.readStrings()
@@ -77,7 +77,7 @@ internal fun readKotlinDslScriptModels(input: DataInputStream): ConfigurationDat
)
}
return ConfigurationData(templateClasspath, models)
return GradleImportedBuildRootData(templateClasspath, models)
}
private object StringsPool {
@@ -23,7 +23,8 @@ import org.jetbrains.plugins.gradle.settings.GradleLocalSettings
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
import org.jetbrains.plugins.gradle.settings.GradleSettings
import org.jetbrains.plugins.gradle.util.GradleConstants
import java.util.*
import org.slf4j.LoggerFactory
import kotlin.collections.HashMap
private val sections = arrayListOf("buildscript", "plugins", "initscript", "pluginManagement")
@@ -107,38 +108,45 @@ fun getGradleProjectSettings(project: Project): Collection<GradleProjectSettings
return gradleSettings.getLinkedProjectsSettings()
}
class RootsIndex<T : Any> {
internal val tree = TreeMap<String, T>()
var values: Collection<T> = listOf()
internal set
class RootsIndex {
private val log = LoggerFactory.getLogger(RootsIndex::class.java)
fun findRoot(path: String): T? {
// race condition can be ignored
val values = values
val size = values.size
if (size == 0) return null
if (size == 1) return values.single() // we can omit prefix check
return tree.floorEntry(path).takeIf { path.startsWith(it.key) }?.value
}
private val buildRoots = HashMap<String, GradleBuildRoot>()
private val scripts = HashMap<String, GradleBuildRoot>()
val values: Collection<GradleBuildRoot>
get() = buildRoots.values
internal inline fun update(updater: (insert: (prefix: String, value: T) -> Unit) -> Unit) {
synchronized(this) {
updater { prefix, value -> tree[prefix] = value }
values = tree.values
}
@Synchronized
fun rebuildProjectRoots() {
// values.forEach {
// it.
// }
}
@Synchronized
operator fun set(prefix: String, value: T) {
val moreCommon = tree.lowerKey(prefix)
check(moreCommon == null || !prefix.startsWith(moreCommon)) {
"Cannot add root `${prefix}`. More common root already added: `$moreCommon`"
}
fun getBuildRoot(dir: String) = buildRoots[dir]
tree[prefix] = value
values = tree.values
@Synchronized
fun findNearestRoot(path: String): GradleBuildRoot? {
var max: Pair<String, GradleBuildRoot>? = null
buildRoots.entries.forEach {
if (path.startsWith(it.key) && (max == null || it.key.length > max!!.first.length)) {
max = it.key to it.value
}
}
return max?.second
}
@Synchronized
fun remove(prefix: String) = tree.remove(prefix)
operator fun set(prefix: String, value: GradleBuildRoot) {
val old = buildRoots.put(prefix, value)
rebuildProjectRoots()
log.info("{}: {} -> {}", prefix, old, value)
}
@Synchronized
fun remove(prefix: String) = buildRoots.remove(prefix)?.also {
rebuildProjectRoots()
log.info("{}: removed", prefix)
}
}
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.idea.scripting.gradle
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationLoadingTest
import org.jetbrains.kotlin.idea.scripting.gradle.legacy.GradleLegacyScriptConfigurationLoaderForOutOfProjectScripts
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner
import org.jetbrains.plugins.gradle.util.GradleConstants
@@ -34,7 +35,9 @@ class GradleScriptOutOfProjectTest : AbstractScriptConfigurationLoadingTest() {
}
private val loaderForOutOfProjectScripts by lazy {
GradleScriptConfigurationLoaderForOutOfProjectScripts(project)
GradleLegacyScriptConfigurationLoaderForOutOfProjectScripts(
project
)
}
fun testManualLoadingForUpToDate() {
@@ -16,7 +16,7 @@ import kotlin.test.assertEquals
class KotlinDslScriptModelsTest {
@Test
fun write() {
val data = ConfigurationData(
val data = GradleImportedBuildRootData(
listOf("a", "b", "c"),
listOf(
KotlinDslScriptModel(
@@ -18,16 +18,16 @@ class RootsIndexTest {
roots["a/c"] = 3
roots["d"] = 4
assertEquals(1, roots.findRoot("a/b/c/x"))
assertEquals(1, roots.findRoot("a/b/c"))
assertEquals(3, roots.findRoot("a/c/x"))
assertEquals(3, roots.findRoot("a/c"))
assertEquals(4, roots.findRoot("d"))
assertEquals(4, roots.findRoot("d/x"))
assertEquals(null, roots.findRoot("e"))
assertEquals(null, roots.findRoot("e/f"))
assertEquals(1, roots.findNearestRoot("a/b/c/x"))
assertEquals(1, roots.findNearestRoot("a/b/c"))
assertEquals(3, roots.findNearestRoot("a/c/x"))
assertEquals(3, roots.findNearestRoot("a/c"))
assertEquals(4, roots.findNearestRoot("d"))
assertEquals(4, roots.findNearestRoot("d/x"))
assertEquals(null, roots.findNearestRoot("e"))
assertEquals(null, roots.findNearestRoot("e/f"))
roots["d"] = 5
assertEquals(5, roots.findRoot("d"))
assertEquals(5, roots.findNearestRoot("d"))
}
}