GradleBuildRoot: remove classes nesting
This commit is contained in:
+2
-2
@@ -10,14 +10,14 @@ 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.idea.scripting.gradle.roots.GradleBuildRoot
|
||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.*
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
/**
|
||||
* Up to date of gradle script depends on following factors:
|
||||
* 1. It is out of date when essential [sections] are changed. See [getGradleScriptInputsStamp].
|
||||
* 2. When some related file is changed (other gradle script, gradle.properties file)
|
||||
* See [GradleBuildRoot.Linked.areRelatedFilesChangedBefore].
|
||||
* See [GradleBuildRoot.areRelatedFilesChangedBefore].
|
||||
*
|
||||
* [lastModifiedTs] is needed to check if some related file was changed since last update
|
||||
*/
|
||||
|
||||
+49
-17
@@ -14,6 +14,7 @@ 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.roots.GradleBuildRootsLocator.NotificationKind.*
|
||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
|
||||
|
||||
class MissingGradleScriptConfigurationNotificationProvider(private val project: Project) :
|
||||
@@ -24,32 +25,63 @@ class MissingGradleScriptConfigurationNotificationProvider(private val project:
|
||||
if (!isGradleKotlinScript(file)) return null
|
||||
if (file.fileType != KotlinFileType.INSTANCE) return null
|
||||
|
||||
val scriptUnderRoot = GradleBuildRootsManager.getInstance(project).findScriptBuildRoot(file) ?: return null
|
||||
return when {
|
||||
scriptUnderRoot.isUnrelatedScript -> EditorNotificationPanel().apply {
|
||||
text(KotlinIdeaGradleBundle.message("text.the.associated.gradle.project.isn.t.imported"))
|
||||
|
||||
createActionLabel(KotlinIdeaGradleBundle.message("action.text.standalone")) {
|
||||
GradleBuildRootsManager.getInstance(project).addStandaloneScript(file)
|
||||
val rootsManager = GradleBuildRootsManager.getInstance(project)
|
||||
val scriptUnderRoot = rootsManager.findScriptBuildRoot(file) ?: return null
|
||||
return when (scriptUnderRoot.notificationKind) {
|
||||
dontCare -> null
|
||||
outsideAnyting -> EditorNotificationPanel().apply {
|
||||
text("Code insight unavailable (related Gradle project not linked)")
|
||||
// todo: Code insight unavailable (cannot find related Gradle project)
|
||||
createActionLabel("Link related Gradle project") {
|
||||
runPartialGradleImport(project)
|
||||
}
|
||||
|
||||
val helpIcon = createActionLabel("") {}
|
||||
helpIcon.setIcon(AllIcons.General.ContextHelp)
|
||||
helpIcon.setUseIconAsLink(true)
|
||||
helpIcon.toolTipText = KotlinIdeaGradleBundle.message(
|
||||
"tool.tip.text.the.external.gradle.project.needs.to.be.imported.to.get.this.script.analyzed"
|
||||
)
|
||||
}
|
||||
scriptUnderRoot.importRequired -> EditorNotificationPanel().apply {
|
||||
text(getMissingConfigurationNotificationText())
|
||||
wasNotImportedAfterCreation -> EditorNotificationPanel().apply {
|
||||
text("Code insight unavailable (Gradle project Sync required)")
|
||||
createActionLabel(getMissingConfigurationActionText()) {
|
||||
runPartialGradleImport(project)
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
notEvaluatedInLastImport -> EditorNotificationPanel().apply {
|
||||
text(KotlinIdeaGradleBundle.message("text.the.associated.gradle.project.isn.t.imported"))
|
||||
|
||||
createActionLabel(KotlinIdeaGradleBundle.message("action.text.standalone")) {
|
||||
rootsManager.updateStandaloneScripts {
|
||||
removeStandaloneScript(file.path)
|
||||
}
|
||||
}
|
||||
|
||||
contextHelp(
|
||||
KotlinIdeaGradleBundle.message("tool.tip.text.the.external.gradle.project.needs.to.be.imported.to.get.this.script.analyzed")
|
||||
)
|
||||
}
|
||||
standalone -> EditorNotificationPanel().apply {
|
||||
text("Standalone script")
|
||||
createActionLabel("Disable script") {
|
||||
rootsManager.updateStandaloneScripts {
|
||||
addStandaloneScript(file.path)
|
||||
}
|
||||
}
|
||||
contextHelp(
|
||||
"""
|
||||
<div width=400>\
|
||||
<p>Configuration for this script will be loaded separately from Gradle project Sync. \
|
||||
<br/>
|
||||
<p>This can be expensive for large Gradle projects.</p>\
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun EditorNotificationPanel.contextHelp(text: String) {
|
||||
val helpIcon = createActionLabel("") {}
|
||||
helpIcon.setIcon(AllIcons.General.ContextHelp)
|
||||
helpIcon.setUseIconAsLink(true)
|
||||
helpIcon.toolTipText = text
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val KEY = Key.create<EditorNotificationPanel>("GradleScriptOutOfSourceNotification")
|
||||
}
|
||||
|
||||
+99
-114
@@ -5,7 +5,6 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.scripting.gradle.roots
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.LocalFileSystem
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
@@ -18,6 +17,10 @@ import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource
|
||||
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
|
||||
import java.io.File
|
||||
|
||||
@set:TestOnly
|
||||
@get:TestOnly
|
||||
internal var skipLastModifiedFilesLoading = false
|
||||
|
||||
/**
|
||||
* [GradleBuildRoot] is a linked gradle build (don't confuse with gradle project and included build).
|
||||
* Each [GradleBuildRoot] may have it's own Gradle version, Java home and other settings.
|
||||
@@ -27,132 +30,114 @@ import java.io.File
|
||||
* See [GradleBuildRootsManager] for more details.
|
||||
*/
|
||||
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()
|
||||
@Volatile
|
||||
var importing = false
|
||||
|
||||
/**
|
||||
* Linked project, that may be itself: [Legacy], [New] or [Imported].
|
||||
*/
|
||||
abstract class Linked : GradleBuildRoot() {
|
||||
@Volatile
|
||||
var importing = false
|
||||
abstract val pathPrefix: String
|
||||
|
||||
abstract val pathPrefix: String
|
||||
abstract val projectRoots: Collection<String>
|
||||
|
||||
abstract val projectRoots: Collection<String>
|
||||
val dir: VirtualFile?
|
||||
get() = LocalFileSystem.getInstance().findFileByPath(pathPrefix)
|
||||
|
||||
val dir: VirtualFile?
|
||||
get() = LocalFileSystem.getInstance().findFileByPath(pathPrefix)
|
||||
private lateinit var lastModifiedFiles: LastModifiedFiles
|
||||
|
||||
private lateinit var lastModifiedFiles: LastModifiedFiles
|
||||
fun loadLastModifiedFiles() {
|
||||
val loaded = if (!skipLastModifiedFilesLoading) {
|
||||
val dir = dir
|
||||
if (dir != null) LastModifiedFiles.read(dir)
|
||||
else null
|
||||
} else null
|
||||
|
||||
fun loadLastModifiedFiles() {
|
||||
val loaded = if (!skipLastModifiedFilesLoading) {
|
||||
val dir = dir
|
||||
if (dir != null) LastModifiedFiles.read(dir)
|
||||
else null
|
||||
} else null
|
||||
lastModifiedFiles = loaded ?: LastModifiedFiles()
|
||||
}
|
||||
|
||||
lastModifiedFiles = loaded ?: LastModifiedFiles()
|
||||
fun saveLastModifiedFiles() {
|
||||
LastModifiedFiles.write(dir ?: return, lastModifiedFiles)
|
||||
}
|
||||
|
||||
fun areRelatedFilesChangedBefore(file: VirtualFile, lastModified: Long): Boolean =
|
||||
lastModifiedFiles.lastModifiedTimeStampExcept(file.path) < lastModified
|
||||
|
||||
fun fileChanged(filePath: String, ts: Long) {
|
||||
lastModifiedFiles.fileChanged(ts, filePath)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class WithoutScriptModels(settings: GradleProjectSettings) : GradleBuildRoot() {
|
||||
final override val pathPrefix = settings.externalProjectPath!!
|
||||
final override val projectRoots = settings.modules.takeIf { it.isNotEmpty() } ?: listOf(pathPrefix)
|
||||
|
||||
init {
|
||||
loadLastModifiedFiles()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gradle build with old Gradle version (<6.0)
|
||||
*/
|
||||
class Legacy(
|
||||
settings: GradleProjectSettings
|
||||
) : WithoutScriptModels(settings) {
|
||||
init {
|
||||
loadLastModifiedFiles()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Linked but not yet imported Gradle build.
|
||||
*/
|
||||
class New(
|
||||
settings: GradleProjectSettings
|
||||
) : WithoutScriptModels(settings) {
|
||||
init {
|
||||
loadLastModifiedFiles()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Imported Gradle build.
|
||||
* Each imported build have info about all of it's Kotlin Build Scripts.
|
||||
*/
|
||||
class Imported(
|
||||
override val pathPrefix: String,
|
||||
val javaHome: File?,
|
||||
val data: GradleBuildRootData
|
||||
) : GradleBuildRoot() {
|
||||
override val projectRoots: Collection<String>
|
||||
get() = data.projectRoots
|
||||
|
||||
init {
|
||||
loadLastModifiedFiles()
|
||||
}
|
||||
|
||||
fun collectConfigurations(builder: ScriptClassRootsBuilder) {
|
||||
if (javaHome != null) {
|
||||
builder.sdks.addSdk(javaHome)
|
||||
}
|
||||
|
||||
fun saveLastModifiedFiles() {
|
||||
LastModifiedFiles.write(dir ?: return, lastModifiedFiles)
|
||||
}
|
||||
val definitions = GradleScriptDefinitionsContributor.getDefinitions(builder.project)
|
||||
|
||||
fun areRelatedFilesChangedBefore(file: VirtualFile, lastModified: Long): Boolean =
|
||||
lastModifiedFiles.lastModifiedTimeStampExcept(file.path) < lastModified
|
||||
builder.addTemplateClassesRoots(data.templateClasspath)
|
||||
|
||||
fun fileChanged(filePath: String, ts: Long) {
|
||||
lastModifiedFiles.fileChanged(ts, filePath)
|
||||
data.models.forEach { script ->
|
||||
val definition = selectScriptDefinition(script, definitions)
|
||||
|
||||
builder.addCustom(
|
||||
script.file,
|
||||
script.classPath,
|
||||
script.sourcePath,
|
||||
GradleScriptInfo(this, definition, script)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class WithoutScriptModels(settings: GradleProjectSettings) : Linked() {
|
||||
final override val pathPrefix = settings.externalProjectPath!!
|
||||
final override val projectRoots = settings.modules.takeIf { it.isNotEmpty() } ?: listOf(pathPrefix)
|
||||
|
||||
init {
|
||||
loadLastModifiedFiles()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gradle build with old Gradle version (<6.0)
|
||||
*/
|
||||
class Legacy(
|
||||
settings: GradleProjectSettings
|
||||
) : WithoutScriptModels(settings) {
|
||||
init {
|
||||
loadLastModifiedFiles()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Linked but not yet imported Gradle build.
|
||||
*/
|
||||
class New(
|
||||
settings: GradleProjectSettings
|
||||
) : WithoutScriptModels(settings) {
|
||||
init {
|
||||
loadLastModifiedFiles()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Imported Gradle build.
|
||||
* Each imported build have info about all of it's Kotlin Build Scripts.
|
||||
*/
|
||||
class Imported(
|
||||
override val pathPrefix: String,
|
||||
val javaHome: File?,
|
||||
val data: GradleBuildRootData
|
||||
) : Linked() {
|
||||
override val projectRoots: Collection<String>
|
||||
get() = data.projectRoots
|
||||
|
||||
init {
|
||||
loadLastModifiedFiles()
|
||||
}
|
||||
|
||||
fun collectConfigurations(builder: ScriptClassRootsBuilder) {
|
||||
if (javaHome != null) {
|
||||
builder.sdks.addSdk(javaHome)
|
||||
}
|
||||
|
||||
val definitions = GradleScriptDefinitionsContributor.getDefinitions(builder.project)
|
||||
|
||||
builder.addTemplateClassesRoots(data.templateClasspath)
|
||||
|
||||
data.models.forEach { script ->
|
||||
val definition = selectScriptDefinition(script, definitions)
|
||||
|
||||
builder.addCustom(
|
||||
script.file,
|
||||
script.classPath,
|
||||
script.sourcePath,
|
||||
GradleScriptInfo(this, definition, script)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectScriptDefinition(
|
||||
script: KotlinDslScriptModel,
|
||||
definitions: List<ScriptDefinition>
|
||||
): ScriptDefinition? {
|
||||
val file = LocalFileSystem.getInstance().findFileByPath(script.file) ?: return null
|
||||
val scriptSource = VirtualFileScriptSource(file)
|
||||
return definitions.firstOrNull { it.isScript(scriptSource) }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@set:TestOnly
|
||||
@get:TestOnly
|
||||
internal var skipLastModifiedFilesLoading = false
|
||||
private fun selectScriptDefinition(
|
||||
script: KotlinDslScriptModel,
|
||||
definitions: List<ScriptDefinition>
|
||||
): ScriptDefinition? {
|
||||
val file = LocalFileSystem.getInstance().findFileByPath(script.file) ?: return null
|
||||
val scriptSource = VirtualFileScriptSource(file)
|
||||
return definitions.firstOrNull { it.isScript(scriptSource) }
|
||||
}
|
||||
}
|
||||
+14
-10
@@ -7,15 +7,15 @@ package org.jetbrains.kotlin.idea.scripting.gradle.roots
|
||||
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
|
||||
class GradleBuildRootIndex {
|
||||
class GradleBuildRootIndex : StandaloneScriptsUpdater {
|
||||
private val log = logger<GradleBuildRootIndex>()
|
||||
|
||||
private val standaloneScriptRoots = mutableMapOf<String, GradleBuildRoot.Linked?>()
|
||||
private val standaloneScriptRoots = mutableMapOf<String, GradleBuildRoot?>()
|
||||
|
||||
private val byWorkingDir = HashMap<String, GradleBuildRoot.Linked>()
|
||||
private val byProjectDir = HashMap<String, GradleBuildRoot.Linked>()
|
||||
private val byWorkingDir = HashMap<String, GradleBuildRoot>()
|
||||
private val byProjectDir = HashMap<String, GradleBuildRoot>()
|
||||
|
||||
val list: Collection<GradleBuildRoot.Linked>
|
||||
val list: Collection<GradleBuildRoot>
|
||||
get() = byWorkingDir.values
|
||||
|
||||
@Synchronized
|
||||
@@ -34,8 +34,8 @@ class GradleBuildRootIndex {
|
||||
fun getBuildByRootDir(dir: String) = byWorkingDir[dir]
|
||||
|
||||
@Synchronized
|
||||
fun findNearestRoot(path: String): GradleBuildRoot.Linked? {
|
||||
var max: Pair<String, GradleBuildRoot.Linked>? = null
|
||||
fun findNearestRoot(path: String): GradleBuildRoot? {
|
||||
var max: Pair<String, GradleBuildRoot>? = null
|
||||
byWorkingDir.entries.forEach {
|
||||
if (path.startsWith(it.key) && (max == null || it.key.length > max!!.first.length)) {
|
||||
max = it.key to it.value
|
||||
@@ -54,7 +54,7 @@ class GradleBuildRootIndex {
|
||||
fun getStandaloneScriptRoot(path: String) = standaloneScriptRoots[path]
|
||||
|
||||
@Synchronized
|
||||
fun add(value: GradleBuildRoot.Linked): GradleBuildRoot.Linked? {
|
||||
fun add(value: GradleBuildRoot): GradleBuildRoot? {
|
||||
val prefix = value.pathPrefix
|
||||
val old = byWorkingDir.put(prefix, value)
|
||||
rebuildProjectRoots()
|
||||
@@ -69,11 +69,15 @@ class GradleBuildRootIndex {
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun addStandaloneScript(path: String) {
|
||||
override fun addStandaloneScript(path: String) {
|
||||
computeStandaloneScriptRoot(path)
|
||||
}
|
||||
|
||||
var standaloneScripts: Collection<String>
|
||||
@Synchronized
|
||||
override fun removeStandaloneScript(path: String) =
|
||||
standaloneScriptRoots.remove(path)
|
||||
|
||||
override var standaloneScripts: Collection<String>
|
||||
@Synchronized get() = standaloneScriptRoots.keys
|
||||
@Synchronized set(value) {
|
||||
standaloneScriptRoots.clear()
|
||||
|
||||
+35
-16
@@ -38,7 +38,7 @@ abstract class GradleBuildRootsLocator {
|
||||
findAffectedFileRoot(filePath) != null ||
|
||||
roots.isStandaloneScript(filePath)
|
||||
|
||||
fun findAffectedFileRoot(filePath: String): GradleBuildRoot.Linked? {
|
||||
fun findAffectedFileRoot(filePath: String): GradleBuildRoot? {
|
||||
if (filePath.endsWith("/gradle.properties") ||
|
||||
filePath.endsWith("/gradle.local")
|
||||
) {
|
||||
@@ -50,25 +50,42 @@ abstract class GradleBuildRootsLocator {
|
||||
return roots.getBuildByRootDir(filePath.substring(0, buildDirIndex))
|
||||
}
|
||||
|
||||
return findScriptBuildRoot(filePath, searchNearestLegacy = false)?.root as? GradleBuildRoot.Linked
|
||||
return findScriptBuildRoot(filePath, searchNearestLegacy = false)?.root as? GradleBuildRoot
|
||||
}
|
||||
|
||||
enum class NotificationKind {
|
||||
dontCare, // one of: imported, inside linked legacy gradle build
|
||||
outsideAnyting, // suggest link related gradle build or just say that there is no one
|
||||
wasNotImportedAfterCreation, // project not yet imported after this file was created
|
||||
notEvaluatedInLastImport, // all other scripts, suggest to sync or mark as standalone
|
||||
standalone
|
||||
}
|
||||
|
||||
class ScriptUnderRoot(
|
||||
val root: GradleBuildRoot?,
|
||||
val script: GradleScriptInfo? = null,
|
||||
val standalone: Boolean = false
|
||||
val standalone: Boolean = false,
|
||||
val nearest: GradleBuildRoot? = null
|
||||
) {
|
||||
val isUnrelatedScript: Boolean
|
||||
get() = root is GradleBuildRoot.Unlinked
|
||||
|
||||
val importRequired: Boolean
|
||||
get() = root is GradleBuildRoot.Linked &&
|
||||
!isImported &&
|
||||
!root.importing &&
|
||||
!standalone
|
||||
val notificationKind: NotificationKind
|
||||
get() = when {
|
||||
isImported -> NotificationKind.dontCare
|
||||
standalone -> NotificationKind.standalone
|
||||
nearest == null -> NotificationKind.outsideAnyting
|
||||
nearest.importing -> NotificationKind.dontCare
|
||||
else -> when (nearest) {
|
||||
is Legacy -> NotificationKind.dontCare
|
||||
is New -> NotificationKind.wasNotImportedAfterCreation
|
||||
is Imported -> NotificationKind.notEvaluatedInLastImport // todo: wasNotImportedAfterCreation
|
||||
}
|
||||
}
|
||||
|
||||
private val isImported: Boolean
|
||||
get() = script != null
|
||||
|
||||
override fun toString(): String {
|
||||
return "ScriptUnderRoot(root=$root, script=$script, standalone=$standalone, nearest=$nearest)"
|
||||
}
|
||||
}
|
||||
|
||||
fun findScriptBuildRoot(gradleKtsFile: VirtualFile): ScriptUnderRoot? =
|
||||
@@ -98,12 +115,14 @@ abstract class GradleBuildRootsLocator {
|
||||
return ScriptUnderRoot(standaloneScriptRoot, standalone = true)
|
||||
}
|
||||
|
||||
// todo(gradle6): remove, it is required only for projects with old gradle
|
||||
if (searchNearestLegacy) {
|
||||
val found = roots.findNearestRoot(filePath)
|
||||
if (found is GradleBuildRoot.Legacy) return ScriptUnderRoot(found)
|
||||
val nearest = roots.findNearestRoot(filePath)
|
||||
return when (nearest) {
|
||||
is Legacy -> ScriptUnderRoot(nearest)
|
||||
else -> ScriptUnderRoot(null, nearest = nearest)
|
||||
}
|
||||
} else {
|
||||
return ScriptUnderRoot(null)
|
||||
}
|
||||
|
||||
return ScriptUnderRoot(GradleBuildRoot.Unlinked())
|
||||
}
|
||||
}
|
||||
+47
-39
@@ -21,11 +21,11 @@ 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.ScriptingSupport.Companion.EPN
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.utils.getKtFile
|
||||
import org.jetbrains.kotlin.idea.core.script.ucache.ScriptClassRootsBuilder
|
||||
import org.jetbrains.kotlin.idea.core.util.EDT
|
||||
import org.jetbrains.kotlin.idea.scripting.gradle.*
|
||||
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslGradleBuildSync
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
|
||||
import org.jetbrains.plugins.gradle.config.GradleSettingsListenerAdapter
|
||||
@@ -50,12 +50,10 @@ import java.util.concurrent.atomic.AtomicBoolean
|
||||
* It also used to show related notification and floating actions depending on root kind, state and script state itself.
|
||||
*
|
||||
* Roots may be:
|
||||
* - [GradleBuildRoot.Unlinked] - The script not related to any Gradle build that is linked to IntelliJ Project,
|
||||
* or we cannot known what is it
|
||||
* - [GradleBuildRoot.Linked] - Linked project, that may be itself:
|
||||
* - [GradleBuildRoot.Legacy] - Gradle build with old Gradle version (<6.0)
|
||||
* - [GradleBuildRoot.New] - not yet imported
|
||||
* - [GradleBuildRoot.Imported] - imported
|
||||
* - [GradleBuildRoot] - Linked project, that may be itself:
|
||||
* - [Legacy] - Gradle build with old Gradle version (<6.0)
|
||||
* - [New] - not yet imported
|
||||
* - [Imported] - imported
|
||||
*/
|
||||
class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(), ScriptingSupport {
|
||||
private val manager: CompositeScriptConfigurationManager
|
||||
@@ -69,14 +67,14 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(),
|
||||
|
||||
override fun isApplicable(file: VirtualFile): Boolean {
|
||||
val scriptUnderRoot = findScriptBuildRoot(file) ?: return false
|
||||
if (scriptUnderRoot.root is GradleBuildRoot.Legacy) return false
|
||||
if (scriptUnderRoot.root is Legacy) return false
|
||||
if (roots.isStandaloneScript(file.path)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun isConfigurationLoadingInProgress(file: KtFile): Boolean {
|
||||
return when (val root = findScriptBuildRoot(file.originalFile.virtualFile)?.root) {
|
||||
is GradleBuildRoot.Linked -> root.importing
|
||||
is GradleBuildRoot -> root.importing
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@@ -98,7 +96,7 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(),
|
||||
|
||||
override fun collectConfigurations(builder: ScriptClassRootsBuilder) {
|
||||
roots.list.forEach { root ->
|
||||
if (root is GradleBuildRoot.Imported) {
|
||||
if (root is Imported) {
|
||||
root.collectConfigurations(builder)
|
||||
}
|
||||
}
|
||||
@@ -116,32 +114,32 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(),
|
||||
|
||||
fun markImportingInProgress(workingDir: String, inProgress: Boolean = true) {
|
||||
actualizeBuildRoot(workingDir)?.importing = inProgress
|
||||
updateNotifications(workingDir)
|
||||
scriptConfigurationsAreUpToDate(project)
|
||||
updateNotifications { it.startsWith(workingDir) }
|
||||
hideNotificationForProjectImport(project)
|
||||
}
|
||||
|
||||
fun update(build: KotlinDslGradleBuildSync) {
|
||||
// fast path for linked gradle builds without .gradle.kts support
|
||||
if (build.models.isEmpty()) {
|
||||
val root = getBuildRootByWorkingDir(build.workingDir) ?: return
|
||||
if (root is GradleBuildRoot.Imported && root.data.models.isEmpty()) return
|
||||
if (root is Imported && root.data.models.isEmpty()) return
|
||||
}
|
||||
|
||||
val root = actualizeBuildRoot(build.workingDir) ?: return
|
||||
root.importing = false
|
||||
|
||||
if (root is GradleBuildRoot.Legacy) return
|
||||
if (root is Legacy) return
|
||||
|
||||
val templateClasspath = GradleScriptDefinitionsContributor.getDefinitionsTemplateClasspath(project)
|
||||
val newData = GradleBuildRootData(build.projectRoots, templateClasspath, build.models)
|
||||
val mergedData = if (build.failed && root is GradleBuildRoot.Imported) merge(root.data, newData) else newData
|
||||
val mergedData = if (build.failed && root is Imported) merge(root.data, newData) else newData
|
||||
|
||||
val newSupport = tryCreateImportedRoot(build.workingDir) { mergedData } ?: return
|
||||
GradleBuildRootDataSerializer.write(newSupport.dir ?: return, mergedData)
|
||||
|
||||
add(newSupport)
|
||||
|
||||
scriptConfigurationsAreUpToDate(project)
|
||||
hideNotificationForProjectImport(project)
|
||||
}
|
||||
|
||||
private fun merge(old: GradleBuildRootData, new: GradleBuildRootData): GradleBuildRootData {
|
||||
@@ -168,12 +166,22 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(),
|
||||
}
|
||||
}
|
||||
|
||||
fun addStandaloneScript(file: VirtualFile) {
|
||||
roots.addStandaloneScript(file.path)
|
||||
roots.rebuildProjectRoots()
|
||||
updateNotifications(file.path)
|
||||
val ktFile = project.getKtFile(file) ?: return
|
||||
ScriptConfigurationManager.getInstance(project).getConfiguration(ktFile)
|
||||
fun updateStandaloneScripts(update: StandaloneScriptsUpdater.() -> Unit) {
|
||||
val changes = StandaloneScriptsUpdater.collectChanges(delegate = roots, update)
|
||||
|
||||
updateNotifications { it in changes.new || it in changes.removed }
|
||||
|
||||
runReadAction {
|
||||
changes.new.forEach {
|
||||
val virtualFile = LocalFileSystem.getInstance().findFileByPath(it)
|
||||
if (virtualFile != null) {
|
||||
val ktFile = PsiManager.getInstance(project).findFile(virtualFile) as? KtFile
|
||||
if (ktFile != null) {
|
||||
ScriptConfigurationManager.getInstance(project).getConfiguration(ktFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
@@ -218,7 +226,7 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(),
|
||||
* Actually this should be true, but we may miss some change events.
|
||||
* For that cases we are rechecking this on each Gradle Project sync (importing/reimporting)
|
||||
*/
|
||||
private fun actualizeBuildRoot(workingDir: String): GradleBuildRoot.Linked? {
|
||||
private fun actualizeBuildRoot(workingDir: String): GradleBuildRoot? {
|
||||
val actualSettings = getGradleProjectSettings(workingDir)
|
||||
val buildRoot = getBuildRootByWorkingDir(workingDir)
|
||||
|
||||
@@ -232,15 +240,15 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(),
|
||||
}
|
||||
}
|
||||
|
||||
private fun GradleBuildRoot.Linked.checkActual(actualSettings: GradleProjectSettings?): Boolean {
|
||||
private fun GradleBuildRoot.checkActual(actualSettings: GradleProjectSettings?): Boolean {
|
||||
if (actualSettings == null) return false
|
||||
|
||||
val knownAsSupported = this !is GradleBuildRoot.Legacy
|
||||
val knownAsSupported = this !is Legacy
|
||||
val shouldBeSupported = kotlinDslScriptsModelImportSupported(actualSettings.resolveGradleVersion().version)
|
||||
return knownAsSupported == shouldBeSupported
|
||||
}
|
||||
|
||||
private fun reloadBuildRoot(rootPath: String): GradleBuildRoot.Linked? {
|
||||
private fun reloadBuildRoot(rootPath: String): GradleBuildRoot? {
|
||||
val settings = getGradleProjectSettings(rootPath)
|
||||
if (settings == null) {
|
||||
remove(rootPath)
|
||||
@@ -267,45 +275,45 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(),
|
||||
settings: GradleProjectSettings
|
||||
) = data.copy(projectRoots = data.projectRoots.toSet() + settings.modules)
|
||||
|
||||
private fun createOtherLinkedRoot(settings: GradleProjectSettings): GradleBuildRoot.Linked {
|
||||
private fun createOtherLinkedRoot(settings: GradleProjectSettings): GradleBuildRoot {
|
||||
val supported = kotlinDslScriptsModelImportSupported(settings.resolveGradleVersion().version)
|
||||
return when {
|
||||
supported -> GradleBuildRoot.New(settings)
|
||||
else -> GradleBuildRoot.Legacy(settings)
|
||||
supported -> New(settings)
|
||||
else -> Legacy(settings)
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryCreateImportedRoot(
|
||||
externalProjectPath: String,
|
||||
dataProvider: (buildRoot: VirtualFile) -> GradleBuildRootData?
|
||||
): GradleBuildRoot.Imported? {
|
||||
): Imported? {
|
||||
val buildRoot = VfsUtil.findFile(Paths.get(externalProjectPath), true) ?: return null
|
||||
val data = dataProvider(buildRoot) ?: return null
|
||||
val javaHome = ExternalSystemApiUtil
|
||||
.getExecutionSettings<GradleExecutionSettings>(project, externalProjectPath, GradleConstants.SYSTEM_ID)
|
||||
.javaHome?.let { File(it) }
|
||||
|
||||
return GradleBuildRoot.Imported(externalProjectPath, javaHome, data)
|
||||
return Imported(externalProjectPath, javaHome, data)
|
||||
}
|
||||
|
||||
private fun add(newRoot: GradleBuildRoot.Linked) {
|
||||
private fun add(newRoot: GradleBuildRoot) {
|
||||
val old = roots.add(newRoot)
|
||||
if (old is GradleBuildRoot.Imported && newRoot !is GradleBuildRoot.Imported) removeData(old.pathPrefix)
|
||||
if (old is GradleBuildRoot.Imported || newRoot is GradleBuildRoot.Imported) {
|
||||
if (old is Imported && newRoot !is Imported) removeData(old.pathPrefix)
|
||||
if (old is Imported || newRoot is Imported) {
|
||||
updater.invalidateAndCommit()
|
||||
}
|
||||
|
||||
updateNotifications(newRoot.pathPrefix)
|
||||
updateNotifications { it.startsWith(newRoot.pathPrefix) }
|
||||
}
|
||||
|
||||
private fun remove(rootPath: String) {
|
||||
val removed = roots.remove(rootPath)
|
||||
if (removed is GradleBuildRoot.Imported) {
|
||||
if (removed is Imported) {
|
||||
removeData(rootPath)
|
||||
updater.invalidateAndCommit()
|
||||
}
|
||||
|
||||
updateNotifications(rootPath)
|
||||
updateNotifications { it.startsWith(rootPath) }
|
||||
}
|
||||
|
||||
private fun removeData(rootPath: String) {
|
||||
@@ -317,11 +325,11 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(),
|
||||
}
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
private fun updateNotifications(dir: String) {
|
||||
private fun updateNotifications(shouldUpdatePath: (String) -> Boolean) {
|
||||
if (!project.isOpen) return
|
||||
|
||||
val openedScripts = FileEditorManager.getInstance(project).openFiles.filter {
|
||||
it.path.startsWith(dir) && maybeAffectedGradleProjectFile(it.path)
|
||||
shouldUpdatePath(it.path) && maybeAffectedGradleProjectFile(it.path)
|
||||
}
|
||||
|
||||
if (openedScripts.isEmpty()) return
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ import kotlin.script.experimental.jvm.jdkHome
|
||||
import kotlin.script.experimental.jvm.jvm
|
||||
|
||||
class GradleScriptInfo(
|
||||
val buildRoot: GradleBuildRoot.Imported,
|
||||
val buildRoot: Imported,
|
||||
scriptDefinition: ScriptDefinition?,
|
||||
val model: KotlinDslScriptModel
|
||||
) : ScriptClassRootsCache.LightScriptInfo(scriptDefinition) {
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.roots
|
||||
|
||||
interface StandaloneScriptsUpdater {
|
||||
var standaloneScripts: Collection<String>
|
||||
|
||||
fun addStandaloneScript(path: String)
|
||||
|
||||
fun removeStandaloneScript(path: String): GradleBuildRoot?
|
||||
|
||||
class Changes {
|
||||
val new = mutableSetOf<String>()
|
||||
val removed = mutableSetOf<String>()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun collectChanges(
|
||||
delegate: StandaloneScriptsUpdater,
|
||||
update: StandaloneScriptsUpdater.() -> Unit
|
||||
): Changes {
|
||||
synchronized(delegate) {
|
||||
val old = delegate.standaloneScripts.toSet()
|
||||
delegate.update()
|
||||
val changes = Changes()
|
||||
val new = delegate.standaloneScripts
|
||||
|
||||
new.forEach {
|
||||
if (it !in old) changes.new.add(it)
|
||||
}
|
||||
|
||||
old.forEach {
|
||||
if (it !in new) changes.removed.add(it)
|
||||
}
|
||||
|
||||
return changes
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ package org.jetbrains.kotlin.idea.scripting.gradle
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.impl.source.tree.LeafPsiElement
|
||||
@@ -64,7 +63,7 @@ fun getGradleScriptInputsStamp(
|
||||
}
|
||||
}
|
||||
|
||||
val buildRoot = GradleBuildRootsManager.getInstance(project).findScriptBuildRoot(file)?.root as? GradleBuildRoot.Linked
|
||||
val buildRoot = GradleBuildRootsManager.getInstance(project).findScriptBuildRoot(file)?.root as? GradleBuildRoot
|
||||
GradleKotlinScriptConfigurationInputs(result.toString(), givenTimeStamp, buildRoot?.pathPrefix)
|
||||
} else null
|
||||
}
|
||||
|
||||
+8
-3
@@ -7,10 +7,11 @@ package org.jetbrains.kotlin.idea.scripting.gradle.roots
|
||||
|
||||
import org.jetbrains.kotlin.idea.scripting.gradle.GradleKotlinScriptConfigurationInputs
|
||||
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
open class AbstractGradleBuildRootsLocatorTest {
|
||||
init {
|
||||
GradleBuildRoot.skipLastModifiedFilesLoading = true
|
||||
skipLastModifiedFilesLoading = true
|
||||
}
|
||||
|
||||
private val scripts = mutableMapOf<String, GradleScriptInfo>()
|
||||
@@ -21,7 +22,7 @@ open class AbstractGradleBuildRootsLocatorTest {
|
||||
fun accessRoots() = roots
|
||||
}
|
||||
|
||||
private fun add(root: GradleBuildRoot.Linked) {
|
||||
private fun add(root: GradleBuildRoot) {
|
||||
locator.accessRoots().add(root)
|
||||
}
|
||||
|
||||
@@ -31,7 +32,7 @@ open class AbstractGradleBuildRootsLocatorTest {
|
||||
relativeScripts: List<String> = listOf("build.gradle.kts")
|
||||
) {
|
||||
val pathPrefix = "$dir/"
|
||||
val root = GradleBuildRoot.Imported(
|
||||
val root = Imported(
|
||||
dir,
|
||||
null,
|
||||
GradleBuildRootData(
|
||||
@@ -62,4 +63,8 @@ open class AbstractGradleBuildRootsLocatorTest {
|
||||
|
||||
fun findScriptBuildRoot(filePath: String, searchNearestLegacy: Boolean = true) =
|
||||
locator.findScriptBuildRoot(filePath, searchNearestLegacy)
|
||||
|
||||
fun assertNotificationKind(filePath: String, notificationKind: GradleBuildRootsLocator.NotificationKind) {
|
||||
assertEquals(notificationKind, findScriptBuildRoot(filePath)?.notificationKind)
|
||||
}
|
||||
}
|
||||
+7
-8
@@ -5,7 +5,10 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.scripting.gradle.roots
|
||||
|
||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsLocator.NotificationKind.dontCare
|
||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsLocator.NotificationKind.wasNotImportedAfterCreation
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
@@ -16,18 +19,14 @@ class GradleBuildRootsLocatorTest : AbstractGradleBuildRootsLocatorTest() {
|
||||
// the build.gradle.kts under the project root will be definitive import at next import
|
||||
// so, we should not treat it as unlinked
|
||||
newImportedGradleProject("imported", relativeScripts = listOf())
|
||||
val scriptUnderRoot = findScriptBuildRoot("imported/build.gradle.kts")
|
||||
assertNotNull(scriptUnderRoot)
|
||||
assertTrue(scriptUnderRoot.importRequired)
|
||||
assertFalse(scriptUnderRoot.isUnrelatedScript)
|
||||
|
||||
assertNotificationKind("imported/build.gradle.kts", wasNotImportedAfterCreation)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBuildGradleKtsNearProjectRoot() {
|
||||
newImportedGradleProject("imported", relativeScripts = listOf("build.gradle.kts"))
|
||||
val scriptUnderRoot = findScriptBuildRoot("imported/build.gradle.kts")
|
||||
assertNotNull(scriptUnderRoot)
|
||||
assertFalse(scriptUnderRoot.importRequired)
|
||||
assertFalse(scriptUnderRoot.isUnrelatedScript)
|
||||
|
||||
assertNotificationKind("imported/build.gradle.kts", dontCare)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user