GradleBuildRoot: remove classes nesting

This commit is contained in:
Sergey Rostov
2020-05-29 12:21:44 +03:00
parent d8892ced9d
commit 2ed68643cb
11 changed files with 306 additions and 212 deletions
@@ -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
*/
@@ -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")
}
@@ -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) }
}
}
@@ -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()
@@ -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())
}
}
@@ -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
@@ -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) {
@@ -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
}
@@ -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)
}
}
@@ -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)
}
}