.gradle.kts, listener: support multiple gradle projects linked to one IntelliJ project

GradleScriptInputsWatcher moved to GradleScriptListener.kt and GradleBuildRootsManager.kt
This commit is contained in:
Sergey Rostov
2020-05-06 16:51:57 +03:00
parent 258064d82d
commit cde53285c6
16 changed files with 175 additions and 150 deletions
@@ -16,12 +16,9 @@ import org.jetbrains.kotlin.psi.KtFile
/**
* [ScriptChangesNotifier] will call first applicable [ScriptChangeListener] when editor is activated or document changed.
*
* Listener may call [ScriptConfigurationUpdater] to invalidate configuration and schedule reloading.
*
* @see DefaultScriptConfigurationManager for more details.
* Listener should do something to invalidate configuration and schedule reloading.
*/
abstract class ScriptChangeListener(protected val project: Project) {
abstract class ScriptChangeListener(val project: Project) {
val default: DefaultScriptingSupport
get() = DefaultScriptingSupport.getInstance(project)
@@ -10,7 +10,7 @@ import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
fun addVfsListener(watcher: GradleScriptInputsWatcher) {
fun addVfsListener(watcher: GradleScriptListener) {
VirtualFileManager.getInstance().addAsyncFileListener(
object : AsyncFileChangeListenerBase() {
override fun isRelevant(path: String): Boolean {
@@ -10,7 +10,7 @@ import com.intellij.openapi.vfs.newvfs.BulkFileListener
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
import com.intellij.util.concurrency.SequentialTaskExecutor
fun addVfsListener(watcher: GradleScriptInputsWatcher) {
fun addVfsListener(watcher: GradleScriptListener) {
watcher.project.messageBus.connect().subscribe(
VirtualFileManager.VFS_CHANGES,
object : BulkFileListener {
@@ -10,7 +10,7 @@ import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
fun addVfsListener(watcher: GradleScriptInputsWatcher) {
fun addVfsListener(watcher: GradleScriptListener) {
VirtualFileManager.getInstance().addAsyncFileListener(
object : AsyncFileChangeListenerBase() {
override fun isRelevant(path: String): Boolean {
@@ -5,11 +5,11 @@
package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.openapi.components.service
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigurationInputs
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
import org.jetbrains.kotlin.psi.KtFile
/**
@@ -19,11 +19,12 @@ import org.jetbrains.kotlin.psi.KtFile
* 2. When some related file is changed (other gradle script, gradle.properties file)
* @see GradleScriptInputsWatcher.areRelatedFilesUpToDate
*
* [inputsTS] is needed to check if some related file was changed since last update
* [lastModifiedTs] is needed to check if some related file was changed since last update
*/
data class GradleKotlinScriptConfigurationInputs(
val sections: String,
val inputsTS: Long
val lastModifiedTs: Long,
val buildRoot: String? = null
) : CachedConfigurationInputs {
override fun isUpToDate(project: Project, file: VirtualFile, ktFile: KtFile?): Boolean {
try {
@@ -31,7 +32,10 @@ data class GradleKotlinScriptConfigurationInputs(
if (actualStamp.sections != this.sections) return false
return project.service<GradleScriptInputsWatcher>().areRelatedFilesUpToDate(file, inputsTS)
return buildRoot == null ||
GradleBuildRootsManager.getInstance(project)
.getBuildRoot(buildRoot)
?.areRelatedFilesUpToDate(file, lastModifiedTs) ?: false
} catch (cancel: ProcessCanceledException) {
return false
}
@@ -1,91 +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.components.*
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.annotations.TestOnly
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
import org.jetbrains.plugins.gradle.util.GradleConstants
@State(
name = "KotlinBuildScriptsModificationInfo",
storages = [Storage(StoragePathMacros.CACHE_FILE)]
)
class GradleScriptInputsWatcher(val project: Project) : PersistentStateComponent<GradleScriptInputsWatcher.Storage> {
private var storage = Storage()
private var cachedGradleProjectsRoots: Set<String>? = null
fun getGradleProjectsRoots(): Set<String> {
if (cachedGradleProjectsRoots == null) {
cachedGradleProjectsRoots = computeGradleProjectRoots(project)
}
return cachedGradleProjectsRoots ?: emptySet()
}
fun saveGradleProjectRootsAfterImport(roots: Set<String>) {
val oldRoots = cachedGradleProjectsRoots
if (oldRoots != null && oldRoots.isNotEmpty()) {
cachedGradleProjectsRoots = oldRoots + roots
} else {
cachedGradleProjectsRoots = roots
}
}
private fun computeGradleProjectRoots(project: Project): Set<String> {
val gradleSettings = ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID)
if (gradleSettings.getLinkedProjectsSettings().isEmpty()) return setOf()
val projectSettings = gradleSettings.getLinkedProjectsSettings().filterIsInstance<GradleProjectSettings>().firstOrNull()
?: return setOf()
return projectSettings.modules.takeIf { it.isNotEmpty() } ?: setOf(projectSettings.externalProjectPath)
}
fun startWatching() {
addVfsListener(this)
}
fun areRelatedFilesUpToDate(file: VirtualFile, timeStamp: Long): Boolean {
return storage.lastModifiedTimeStampExcept(file.path) < timeStamp
}
class Storage {
private val lastModifiedFiles = LastModifiedFiles()
fun lastModifiedTimeStampExcept(filePath: String): Long {
return lastModifiedFiles.lastModifiedTimeStampExcept(filePath)
}
fun fileChanged(filePath: String, ts: Long) {
lastModifiedFiles.fileChanged(ts, filePath)
}
}
override fun getState(): Storage {
return storage
}
override fun loadState(state: Storage) {
this.storage = state
}
fun fileChanged(filePath: String, ts: Long) {
storage.fileChanged(filePath, ts)
}
fun clearState() {
storage = Storage()
}
@TestOnly
fun clearAndRefillState() {
loadState(project.service<GradleScriptInputsWatcher>().state)
}
}
@@ -5,16 +5,25 @@
package org.jetbrains.kotlin.idea.scripting.gradle
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.roots.GradleBuildRoot
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
class GradleScriptListener(project: Project) : ScriptChangeListener(project) {
init {
// start GradleScriptInputsWatcher to track changes in gradle-configuration related files
project.service<GradleScriptInputsWatcher>().startWatching()
// listen changes using VFS events, including gradle-configuration related files
addVfsListener(this)
}
fun fileChanged(filePath: String, ts: Long) {
// NOTE: this is required for legacy roots too
val root = GradleBuildRootsManager.getInstance(project)
.findScriptBuildRoot(filePath, searchNearestLegacy = false)
?.root as? GradleBuildRoot.Linked
root?.fileChanged(filePath, ts)
}
override fun isApplicable(vFile: VirtualFile) = GradleBuildRootsManager.getInstance(project).isApplicable(vFile)
@@ -5,6 +5,19 @@
package org.jetbrains.kotlin.idea.scripting.gradle
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.readStringList
import org.jetbrains.kotlin.idea.core.util.writeNullable
import org.jetbrains.kotlin.idea.core.util.writeStringList
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootData
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootDataSerializer
import org.jetbrains.kotlin.idea.scripting.gradle.roots.readKotlinDslScriptModels
import org.jetbrains.kotlin.idea.scripting.gradle.roots.writeKotlinDslScriptModels
import java.io.DataInputStream
import java.io.DataOutput
/**
* Optimized collection for storing last modified files with ability to
* get time of last modified file expect given one ([lastModifiedTimeStampExcept]).
@@ -14,10 +27,10 @@ package org.jetbrains.kotlin.idea.scripting.gradle
*
* Actually works by storing two last timestamps with the set of files modified at this times.
*/
class LastModifiedFiles {
private var last: SimultaneouslyChangedFiles = SimultaneouslyChangedFiles()
class LastModifiedFiles(
private var last: SimultaneouslyChangedFiles = SimultaneouslyChangedFiles(),
private var previous: SimultaneouslyChangedFiles = SimultaneouslyChangedFiles()
) {
class SimultaneouslyChangedFiles(
val ts: Long = Long.MIN_VALUE,
val fileIds: MutableSet<String> = mutableSetOf()
@@ -40,4 +53,36 @@ class LastModifiedFiles {
last.fileIds.size == 1 && last.fileIds.contains(fileId) -> previous.ts
else -> last.ts
}
companion object {
private val fileAttribute = FileAttribute("last-modified-files", 1, false)
fun read(buildRoot: VirtualFile): LastModifiedFiles? {
return fileAttribute.readAttribute(buildRoot)?.use {
it.readNullable {
LastModifiedFiles(readSCF(it), readSCF(it))
}
}
}
fun write(buildRoot: VirtualFile, data: LastModifiedFiles?) {
fileAttribute.writeAttribute(buildRoot).use {
it.writeNullable(data) { data ->
writeSCF(data.last)
writeSCF(data.previous)
}
}
}
fun remove(buildRoot: VirtualFile) {
write(buildRoot, null)
}
private fun readSCF(it: DataInputStream) = SimultaneouslyChangedFiles(it.readLong(), it.readStringList().toMutableSet())
private fun DataOutput.writeSCF(last: SimultaneouslyChangedFiles) {
writeLong(last.ts)
writeStringList(last.fileIds)
}
}
}
@@ -56,9 +56,9 @@ Note that we can provide custom scripting support only for projects that using G
To have consistent sate of scripts, we should also be aware of external script changes. This is achieved by watching files using the IntelliJ VFS events. [GradleScriptInputsWatcher] is responsible for that.
The first tricky part is that scripts are depending on each other: so, when one script is changed, we actually should invalidate all other scripts as we don't know dependencies between them (Gradle will provide this information later, but it is not yet implemented). Actually, we should know the last modified timestamp of all scripts excepting a particular one. This can be achieved by storing timestamps of two last modified files. [LastModifiedFiles] utility is responsible for that. This is not multiple linked Gradle builds aware yet (currently there is only one global state), but it will be fixed shortly.
The first tricky part is that scripts are depending on each other: so, when one script is changed, we actually should invalidate all other scripts as we don't know dependencies between them (Gradle will provide this information later, but it is not yet implemented). Actually, we should know the last modified timestamp of all scripts excepting a particular one. This can be achieved by storing timestamps of two last modified files. [LastModifiedFiles] utility is responsible for that.
Another tricky part is that we should track only scripts belong the Gradle project and should ignore all other `*.gradle.kts` files (in `testData` for example). This is achieved by storing Gradle project roots, as scripts can be exactly near Gradle project roots (excepting included and precompiled scripts which are not fully supported yet). This can be gathered from the Gradle project import information or from GradleProjectSettings when the import has not occurred yet. `GradleScriptInputsWatcher.cachedGradleProjectsRoots` is responsible for that.
Another tricky part is that we should track only scripts belong the Gradle project and should ignore all other `*.gradle.kts` files (in `testData` for example). This is achieved by storing Gradle project roots, as scripts can be exactly near Gradle project roots (excepting included and precompiled scripts which are not fully supported yet). This can be gathered from the Gradle project import information or from GradleProjectSettings when the import has not occurred yet. `GradleBuildRootsManager` does it.
## Out of project Gradle scripts
@@ -68,7 +68,7 @@ There are scripts that are not linked to any import Gradle project. In `testData
For Gradle versions before 6.0, we are still falling back to the [DefaultScriptingSupport] with some extensions.
We are showing notification before loading as we knew what changes will case it. This is done by:
We are showing notification before loading as we knew what changes will cause it. This is done by:
- implementing `org.jetbrains.kotlin.scripting.idea.listener` extension point and calling `suggestToUpdateConfigurationIfOutOfDate` instead of `ensureUpToDatedConfigurationSuggested` on document changes.
- implementing `org.jetbrains.kotlin.scripting.idea.loader` extension point and overriding `getInputsStamp`
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.idea.scripting.gradle.importing
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
@@ -15,9 +14,8 @@ import org.gradle.tooling.model.kotlin.dsl.EditorReportSeverity
import org.gradle.tooling.model.kotlin.dsl.KotlinDslScriptsModel
import org.jetbrains.kotlin.gradle.BrokenKotlinDslScriptsModel
import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle
import org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptInputsWatcher
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
import org.jetbrains.kotlin.idea.scripting.gradle.getGradleScriptInputsStamp
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
import org.jetbrains.kotlin.psi.NotNullableUserDataProperty
import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext
import java.io.File
@@ -115,14 +113,8 @@ fun saveScriptModels(project: Project, build: KotlinDslGradleBuildSync) {
errorReporter.reportError(File(model.file), model)
}
project.service<GradleScriptInputsWatcher>().saveGradleProjectRootsAfterImport(
build.models.map { FileUtil.toSystemIndependentName(File(it.file).parent) }.toSet()
)
// todo: use real info about projects
build.projectRoots.addAll(build.models.map { FileUtil.toSystemIndependentName(File(it.file).parent) })
GradleBuildRootsManager.getInstance(project).update(build)
project.service<GradleScriptInputsWatcher>().clearState()
}
@@ -10,9 +10,11 @@ import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache
import org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptDefinitionsContributor
import org.jetbrains.kotlin.idea.scripting.gradle.LastModifiedFiles
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
import java.io.File
/**
@@ -28,6 +30,7 @@ sealed class GradleBuildRoot {
* The script not related to any Gradle build that is linked to IntelliJ Project,
* or we cannot known what is it
*/
@Suppress("CanSealedSubClassBeObject")
class Unlinked : GradleBuildRoot()
/**
@@ -37,31 +40,65 @@ sealed class GradleBuildRoot {
@Volatile
var importing = false
abstract val manager: GradleBuildRootsManager
abstract val pathPrefix: String
open val projectRoots: Collection<String> get() = listOf()
abstract val projectRoots: Collection<String>
private val dir: VirtualFile?
get() = LocalFileSystem.getInstance().findFileByPath(pathPrefix)
private val lastModifiedFiles =
dir?.let { LastModifiedFiles.read(it) } ?: LastModifiedFiles()
fun areRelatedFilesUpToDate(file: VirtualFile, lastModified: Long): Boolean =
lastModifiedFiles.lastModifiedTimeStampExcept(file.path) < lastModified
fun fileChanged(filePath: String, ts: Long) {
lastModifiedFiles.fileChanged(ts, filePath)
manager.scheduleLastModifiedFilesSave()
}
fun saveLastModifiedFiles() {
LastModifiedFiles.write(dir ?: return, lastModifiedFiles)
}
}
abstract class WithoutScriptModels(settings: GradleProjectSettings) : Linked() {
final override val pathPrefix = settings.externalProjectPath!!
final override val projectRoots = settings.modules.takeIf { it.isNotEmpty() } ?: listOf(pathPrefix)
}
/**
* Gradle build with old Gradle version (<6.0)
*/
class Legacy(override val pathPrefix: String) : Linked()
class Legacy(
override val manager: GradleBuildRootsManager,
settings: GradleProjectSettings
) : WithoutScriptModels(settings)
/**
* Linked but not yet imported Gradle build.
*/
class New(override val pathPrefix: String) : Linked()
class New(
override val manager: GradleBuildRootsManager,
settings: GradleProjectSettings
) : WithoutScriptModels(settings)
/**
* Imported Gradle build.
* Each imported build have info about all of it's Kotlin Build Scripts.
*/
class Imported(
val project: Project,
override val manager: GradleBuildRootsManager,
val dir: VirtualFile,
val javaHome: File?,
val data: GradleBuildRootData
) : Linked() {
val project: Project
get() = manager.project
override val pathPrefix: String = dir.path
override val projectRoots: Collection<String>
@@ -12,6 +12,7 @@ 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.GradleKotlinScriptConfigurationInputs
import org.jetbrains.kotlin.idea.scripting.gradle.LastModifiedFiles
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel
import java.io.DataInput
import java.io.DataInputStream
@@ -23,7 +24,7 @@ internal object GradleBuildRootDataSerializer {
fun read(buildRoot: VirtualFile): GradleBuildRootData? {
return attribute.readAttribute(buildRoot)?.use {
it.readNullable {
readKotlinDslScriptModels(it)
readKotlinDslScriptModels(it, buildRoot.path)
}
}
}
@@ -38,6 +39,7 @@ internal object GradleBuildRootDataSerializer {
fun remove(buildRoot: VirtualFile) {
write(buildRoot, null)
LastModifiedFiles.remove(buildRoot)
}
}
@@ -57,14 +59,14 @@ internal fun writeKotlinDslScriptModels(output: DataOutput, data: GradleBuildRoo
output.writeList(data.models) {
strings.writeStringId(it.file)
output.writeString(it.inputs.sections)
output.writeLong(it.inputs.inputsTS)
output.writeLong(it.inputs.lastModifiedTs)
strings.writeStringIds(it.classPath)
strings.writeStringIds(it.sourcePath)
strings.writeStringIds(it.imports)
}
}
internal fun readKotlinDslScriptModels(input: DataInputStream): GradleBuildRootData {
internal fun readKotlinDslScriptModels(input: DataInputStream, buildRoot: String): GradleBuildRootData {
val strings = StringsPool.reader(input)
val projectRoots = strings.readStrings()
@@ -73,7 +75,7 @@ internal fun readKotlinDslScriptModels(input: DataInputStream): GradleBuildRootD
val models = input.readList {
KotlinDslScriptModel(
strings.readString(),
GradleKotlinScriptConfigurationInputs(input.readString(), input.readLong()),
GradleKotlinScriptConfigurationInputs(input.readString(), input.readLong(), buildRoot),
strings.readStrings(),
strings.readStrings(),
strings.readStrings(),
@@ -13,7 +13,7 @@ class GradleBuildRootIndex {
private val byWorkingDir = HashMap<String, GradleBuildRoot.Linked>()
private val byProjectDir = HashMap<String, GradleBuildRoot.Linked>()
val list: Collection<GradleBuildRoot>
val list: Collection<GradleBuildRoot.Linked>
get() = byWorkingDir.values
@Synchronized
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.idea.scripting.gradle.roots
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.progress.util.BackgroundTaskUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VfsUtil
@@ -30,6 +31,7 @@ import org.jetbrains.plugins.gradle.settings.*
import org.jetbrains.plugins.gradle.util.GradleConstants
import java.io.File
import java.nio.file.Paths
import java.util.concurrent.atomic.AtomicBoolean
/**
* [GradleBuildRoot] is a linked gradle build (don't confuse with gradle project and included build).
@@ -103,40 +105,52 @@ class GradleBuildRootsManager(val project: Project) : ScriptingSupport.Provider(
get() = path
fun getScriptInfo(file: VirtualFile): GradleScriptInfo? =
manager.getLightScriptInfo(file.localPath) as? GradleScriptInfo
getScriptInfo(file.localPath)
fun getScriptInfo(localPath: String): GradleScriptInfo? =
manager.getLightScriptInfo(localPath) as? GradleScriptInfo
class ScriptUnderRoot(
val root: GradleBuildRoot?,
val script: GradleScriptInfo? = null
)
fun findScriptBuildRoot(gradleKtsFile: VirtualFile): ScriptUnderRoot? {
if (!isGradleKotlinScript(gradleKtsFile)) return null
fun findScriptBuildRoot(gradleKtsFile: VirtualFile): ScriptUnderRoot? =
findScriptBuildRoot(gradleKtsFile.path)
val filePath = gradleKtsFile.path
val scriptInfo = getScriptInfo(gradleKtsFile)
fun findScriptBuildRoot(filePath: String, searchNearestLegacy: Boolean = true): ScriptUnderRoot? {
if (!filePath.endsWith(".gradle.kts")) return null
val scriptInfo = getScriptInfo(filePath)
val imported = scriptInfo?.buildRoot
if (imported != null) return ScriptUnderRoot(imported, scriptInfo)
if (gradleKtsFile.name == "build.gradle.kts" ||
gradleKtsFile.name == "settings.gradle.kts" ||
gradleKtsFile.name == "init.gradle.kts"
if (filePath.endsWith("/build.gradle.kts") ||
filePath.endsWith("/settings.gradle.kts") ||
filePath.endsWith("/init.gradle.kts")
) {
// build|settings|init.gradle.kts scripts should be located near gradle project root only
val gradleBuild = roots.getBuildByProjectDir(gradleKtsFile.parent.localPath)
val gradleBuild = roots.getBuildByProjectDir(filePath.substringBeforeLast("/"))
if (gradleBuild != null) return ScriptUnderRoot(gradleBuild)
}
// other scripts: "included", "precompiled" scripts, scripts in unlinked projects,
// or just random files with ".gradle.kts" ending
// try find nearest linked legacy gradle project settings first
val found = roots.findNearestRoot(filePath)
if (found is GradleBuildRoot.Legacy) return ScriptUnderRoot(found)
if (searchNearestLegacy) {
val found = roots.findNearestRoot(filePath)
if (found is GradleBuildRoot.Legacy) return ScriptUnderRoot(found)
}
return ScriptUnderRoot(GradleBuildRoot.Unlinked())
}
fun isUnderProjectDir(localPath: String) =
roots.getBuildByProjectDir(localPath) != null
fun getBuildRoot(gradleWorkingDir: String) =
roots.getBuildRoot(gradleWorkingDir)
fun markImportingInProgress(workingDir: String, inProgress: Boolean = true) {
actualizeBuildRoot(workingDir)?.importing = inProgress
updateNotifications(workingDir)
@@ -177,6 +191,19 @@ class GradleBuildRootsManager(val project: Project) : ScriptingSupport.Provider(
return GradleBuildRootData(roots, new.templateClasspath, models.values)
}
private val lastModifiedFilesSaveScheduled = AtomicBoolean()
fun scheduleLastModifiedFilesSave() {
if (lastModifiedFilesSaveScheduled.compareAndSet(false, true)) {
BackgroundTaskUtil.executeOnPooledThread(project) {
lastModifiedFilesSaveScheduled.set(false)
roots.list.forEach {
it.saveLastModifiedFiles()
}
}
}
}
init {
getGradleProjectSettings(project).forEach {
// don't call this.add, as we are inside scripting manager initialization
@@ -269,8 +296,8 @@ class GradleBuildRootsManager(val project: Project) : ScriptingSupport.Provider(
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)
supported -> GradleBuildRoot.New(this, settings)
else -> GradleBuildRoot.Legacy(this, settings)
}
}
@@ -284,7 +311,7 @@ class GradleBuildRootsManager(val project: Project) : ScriptingSupport.Provider(
.getExecutionSettings<GradleExecutionSettings>(project, externalProjectPath, GradleConstants.SYSTEM_ID)
.javaHome?.let { File(it) }
return GradleBuildRoot.Imported(project, buildRoot, javaHome, data)
return GradleBuildRoot.Imported(this, buildRoot, javaHome, data)
}
private fun add(newRoot: GradleBuildRoot.Linked) {
@@ -14,6 +14,8 @@ import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.*
import com.intellij.psi.impl.source.tree.LeafPsiElement
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRoot
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtFile
@@ -36,7 +38,7 @@ fun isInAffectedGradleProjectFiles(project: Project, filePath: String): Boolean
return true
}
return filePath.substringBeforeLast("/") in project.service<GradleScriptInputsWatcher>().getGradleProjectsRoots()
return GradleBuildRootsManager.getInstance(project).isUnderProjectDir(filePath.substringBeforeLast("/"))
}
return false
@@ -78,7 +80,8 @@ fun getGradleScriptInputsStamp(
}
}
GradleKotlinScriptConfigurationInputs(result.toString(), givenTimeStamp)
val buildRoot = GradleBuildRootsManager.getInstance(project).findScriptBuildRoot(file)?.root as? GradleBuildRoot.Linked
GradleKotlinScriptConfigurationInputs(result.toString(), givenTimeStamp, buildRoot?.pathPrefix)
} else null
}
}
@@ -25,7 +25,7 @@ class GradleBuildRootDataSerializerTest {
listOf(
KotlinDslScriptModel(
"a",
GradleKotlinScriptConfigurationInputs("b", 1),
GradleKotlinScriptConfigurationInputs("b", 1, "a"),
listOf("c", "a", "b"),
listOf("b", "c", "a"),
listOf("i", "c", "b"),
@@ -33,7 +33,7 @@ class GradleBuildRootDataSerializerTest {
),
KotlinDslScriptModel(
"a",
GradleKotlinScriptConfigurationInputs("b", 1),
GradleKotlinScriptConfigurationInputs("b", 1, "a"),
listOf("c", "a", "b"),
listOf("b", "c", "a"),
listOf("i", "c", "b"),
@@ -45,7 +45,7 @@ class GradleBuildRootDataSerializerTest {
val buffer = ByteArrayOutputStream()
writeKotlinDslScriptModels(DataOutputStream(buffer), data)
val restored = readKotlinDslScriptModels(DataInputStream(ByteArrayInputStream(buffer.toByteArray())))
val restored = readKotlinDslScriptModels(DataInputStream(ByteArrayInputStream(buffer.toByteArray())), "a")
assertEquals(data.toString(), restored.toString())
}