gradle.kts: standalone scripts support (without ui and persistence)

This commit is contained in:
Sergey Rostov
2020-05-28 23:30:37 +03:00
parent 5ed7abd15d
commit d8892ced9d
8 changed files with 84 additions and 55 deletions
@@ -1,3 +1,4 @@
action.text.standalone=Add as standalone script
action.label.text.load.script.configuration=Load related Gradle project action.label.text.load.script.configuration=Load related Gradle project
action.text.install=Install action.text.install=Install
action.text.show.kotlin.gradle.dsl.logs.in=Show Kotlin Gradle DSL Logs in {0} action.text.show.kotlin.gradle.dsl.logs.in=Show Kotlin Gradle DSL Logs in {0}
@@ -37,7 +38,7 @@ text.couldn.t.configure.kotlin.gradle.plugin.automatically=Couldn't configure ko
text.default.kotlin.gradle.script=Default Kotlin Gradle Script text.default.kotlin.gradle.script=Default Kotlin Gradle Script
text.gradle.dsl.logs.cannot.be.found.automatically.see.how.to.find.logs=Gradle DSL Logs cannot be found automatically.<br/>See how to find logs <a href="{0}">here</a>. text.gradle.dsl.logs.cannot.be.found.automatically.see.how.to.find.logs=Gradle DSL Logs cannot be found automatically.<br/>See how to find logs <a href="{0}">here</a>.
text.see.manual.installation.instructions=See manual installation instructions <a href="https://kotlinlang.org/docs/reference/using-gradle.html">here</a>. text.see.manual.installation.instructions=See manual installation instructions <a href="https://kotlinlang.org/docs/reference/using-gradle.html">here</a>.
text.the.associated.gradle.project.isn.t.imported=The associated Gradle Project isn't imported. text.the.associated.gradle.project.isn.t.imported=Cannot find related Gradle project. Kotlin DSL Code insight unavailable.
text.was.modified={0} was modified text.was.modified={0} was modified
title.configure.kotlin.gradle.plugin=Configure Kotlin-Gradle Plugin title.configure.kotlin.gradle.plugin=Configure Kotlin-Gradle Plugin
title.kotlin.build.script=Gradle Kotlin DSL Scripts errors title.kotlin.build.script=Gradle Kotlin DSL Scripts errors
@@ -21,6 +21,8 @@ import org.jetbrains.kotlin.psi.UserDataProperty
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
import org.jetbrains.plugins.gradle.util.GradleConstants import org.jetbrains.plugins.gradle.util.GradleConstants
const val disableNotificationForProjectImport = true
fun runPartialGradleImport(project: Project) { fun runPartialGradleImport(project: Project) {
getGradleProjectSettings(project).forEach { getGradleProjectSettings(project).forEach {
ExternalSystemUtil.refreshProject( ExternalSystemUtil.refreshProject(
@@ -51,6 +53,8 @@ private var Project.notificationPanel: ScriptConfigurationChangedNotification?
by UserDataProperty<Project, ScriptConfigurationChangedNotification>(Key.create("load.script.configuration.panel")) by UserDataProperty<Project, ScriptConfigurationChangedNotification>(Key.create("load.script.configuration.panel"))
fun scriptConfigurationsNeedToBeUpdated(project: Project) { fun scriptConfigurationsNeedToBeUpdated(project: Project) {
if (disableNotificationForProjectImport) return
if (autoReloadScriptConfigurations(project)) { if (autoReloadScriptConfigurations(project)) {
// import should be run automatically by Gradle plugin // import should be run automatically by Gradle plugin
return return
@@ -50,13 +50,6 @@ class GradleScriptListener(project: Project) : ScriptChangeListener(project) {
} }
private fun checkUpToDate(vFile: VirtualFile) { private fun checkUpToDate(vFile: VirtualFile) {
val upToDate = GradleBuildRootsManager.getInstance(project) GradleBuildRootsManager.getInstance(project).checkUpToDate(vFile)
.getScriptInfo(vFile)?.model?.inputs?.isUpToDate(project, vFile) ?: return
if (upToDate) {
scriptConfigurationsAreUpToDate(project)
} else {
scriptConfigurationsNeedToBeUpdated(project)
}
} }
} }
@@ -6,8 +6,6 @@
package org.jetbrains.kotlin.idea.scripting.gradle package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.icons.AllIcons import com.intellij.icons.AllIcons
import com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode
import com.intellij.openapi.externalSystem.util.ExternalSystemUtil
import com.intellij.openapi.fileEditor.FileEditor import com.intellij.openapi.fileEditor.FileEditor
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
@@ -16,11 +14,7 @@ import com.intellij.ui.EditorNotificationPanel
import com.intellij.ui.EditorNotifications import com.intellij.ui.EditorNotifications
import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle
import org.jetbrains.kotlin.idea.scripting.gradle.legacy.GradleLegacyScriptConfigurationLoaderForOutOfProjectScripts
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRoot
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
import org.jetbrains.plugins.gradle.util.GradleConstants
class MissingGradleScriptConfigurationNotificationProvider(private val project: Project) : class MissingGradleScriptConfigurationNotificationProvider(private val project: Project) :
EditorNotifications.Provider<EditorNotificationPanel>() { EditorNotifications.Provider<EditorNotificationPanel>() {
@@ -35,30 +29,18 @@ class MissingGradleScriptConfigurationNotificationProvider(private val project:
scriptUnderRoot.isUnrelatedScript -> EditorNotificationPanel().apply { scriptUnderRoot.isUnrelatedScript -> EditorNotificationPanel().apply {
text(KotlinIdeaGradleBundle.message("text.the.associated.gradle.project.isn.t.imported")) text(KotlinIdeaGradleBundle.message("text.the.associated.gradle.project.isn.t.imported"))
val linkProjectText = KotlinIdeaGradleBundle.message("action.label.text.load.script.configuration") createActionLabel(KotlinIdeaGradleBundle.message("action.text.standalone")) {
createActionLabel(linkProjectText) { GradleBuildRootsManager.getInstance(project).addStandaloneScript(file)
val newProjectSettings = GradleProjectSettings()
newProjectSettings.externalProjectPath = file.parent.path
ExternalSystemUtil.linkExternalProject(
GradleConstants.SYSTEM_ID,
newProjectSettings,
project,
{
},
false,
ProgressExecutionMode.IN_BACKGROUND_ASYNC
)
} }
val link = createActionLabel("") {}
link.setIcon(AllIcons.General.ContextHelp) val helpIcon = createActionLabel("") {}
link.setUseIconAsLink(true) helpIcon.setIcon(AllIcons.General.ContextHelp)
link.toolTipText = KotlinIdeaGradleBundle.message( helpIcon.setUseIconAsLink(true)
"tool.tip.text.the.external.gradle.project.needs.to.be.imported.to.get.this.script.analyzed", helpIcon.toolTipText = KotlinIdeaGradleBundle.message(
linkProjectText "tool.tip.text.the.external.gradle.project.needs.to.be.imported.to.get.this.script.analyzed"
) )
} }
scriptUnderRoot.isNewScript -> EditorNotificationPanel().apply { scriptUnderRoot.importRequired -> EditorNotificationPanel().apply {
text(getMissingConfigurationNotificationText()) text(getMissingConfigurationNotificationText())
createActionLabel(getMissingConfigurationActionText()) { createActionLabel(getMissingConfigurationActionText()) {
runPartialGradleImport(project) runPartialGradleImport(project)
@@ -68,12 +50,6 @@ class MissingGradleScriptConfigurationNotificationProvider(private val project:
} }
} }
private val loaderForOutOfProjectScripts by lazy {
GradleLegacyScriptConfigurationLoaderForOutOfProjectScripts(
project
)
}
companion object { companion object {
private val KEY = Key.create<EditorNotificationPanel>("GradleScriptOutOfSourceNotification") private val KEY = Key.create<EditorNotificationPanel>("GradleScriptOutOfSourceNotification")
} }
@@ -10,6 +10,8 @@ import com.intellij.openapi.diagnostic.logger
class GradleBuildRootIndex { class GradleBuildRootIndex {
private val log = logger<GradleBuildRootIndex>() private val log = logger<GradleBuildRootIndex>()
private val standaloneScriptRoots = mutableMapOf<String, GradleBuildRoot.Linked?>()
private val byWorkingDir = HashMap<String, GradleBuildRoot.Linked>() private val byWorkingDir = HashMap<String, GradleBuildRoot.Linked>()
private val byProjectDir = HashMap<String, GradleBuildRoot.Linked>() private val byProjectDir = HashMap<String, GradleBuildRoot.Linked>()
@@ -24,6 +26,8 @@ class GradleBuildRootIndex {
byProjectDir[it] = buildRoot byProjectDir[it] = buildRoot
} }
} }
standaloneScriptRoots.keys.forEach(::computeStandaloneScriptRoot)
} }
@Synchronized @Synchronized
@@ -43,6 +47,12 @@ class GradleBuildRootIndex {
@Synchronized @Synchronized
fun getBuildByProjectDir(projectDir: String) = byProjectDir[projectDir] fun getBuildByProjectDir(projectDir: String) = byProjectDir[projectDir]
@Synchronized
fun isStandaloneScript(path: String) = path in standaloneScriptRoots
@Synchronized
fun getStandaloneScriptRoot(path: String) = standaloneScriptRoots[path]
@Synchronized @Synchronized
fun add(value: GradleBuildRoot.Linked): GradleBuildRoot.Linked? { fun add(value: GradleBuildRoot.Linked): GradleBuildRoot.Linked? {
val prefix = value.pathPrefix val prefix = value.pathPrefix
@@ -57,4 +67,20 @@ class GradleBuildRootIndex {
rebuildProjectRoots() rebuildProjectRoots()
log.info("$prefix: removed") log.info("$prefix: removed")
} }
@Synchronized
fun addStandaloneScript(path: String) {
computeStandaloneScriptRoot(path)
}
var standaloneScripts: Collection<String>
@Synchronized get() = standaloneScriptRoots.keys
@Synchronized set(value) {
standaloneScriptRoots.clear()
value.forEach(::computeStandaloneScriptRoot)
}
private fun computeStandaloneScriptRoot(path: String) {
standaloneScriptRoots[path] = findNearestRoot(path)
}
} }
@@ -32,12 +32,11 @@ abstract class GradleBuildRootsLocator {
filePath.endsWith("/gradle.properties") || filePath.endsWith("/gradle.properties") ||
filePath.endsWith("/gradle.local") || filePath.endsWith("/gradle.local") ||
filePath.endsWith("/gradle-wrapper.properties") || filePath.endsWith("/gradle-wrapper.properties") ||
filePath.endsWith("/build.gradle.kts") || filePath.endsWith(".gradle.kts")
filePath.endsWith("/settings.gradle.kts") ||
filePath.endsWith("/init.gradle.kts")
fun isAffectedGradleProjectFile(filePath: String): Boolean = fun isAffectedGradleProjectFile(filePath: String): Boolean =
findAffectedFileRoot(filePath) != null findAffectedFileRoot(filePath) != null ||
roots.isStandaloneScript(filePath)
fun findAffectedFileRoot(filePath: String): GradleBuildRoot.Linked? { fun findAffectedFileRoot(filePath: String): GradleBuildRoot.Linked? {
if (filePath.endsWith("/gradle.properties") || if (filePath.endsWith("/gradle.properties") ||
@@ -56,15 +55,17 @@ abstract class GradleBuildRootsLocator {
class ScriptUnderRoot( class ScriptUnderRoot(
val root: GradleBuildRoot?, val root: GradleBuildRoot?,
val script: GradleScriptInfo? = null val script: GradleScriptInfo? = null,
val standalone: Boolean = false
) { ) {
val isUnrelatedScript: Boolean val isUnrelatedScript: Boolean
get() = root is GradleBuildRoot.Unlinked get() = root is GradleBuildRoot.Unlinked
val isNewScript: Boolean val importRequired: Boolean
get() = root is GradleBuildRoot.Linked && get() = root is GradleBuildRoot.Linked &&
!isImported && !isImported &&
!root.importing !root.importing &&
!standalone
private val isImported: Boolean private val isImported: Boolean
get() = script != null get() = script != null
@@ -92,6 +93,11 @@ abstract class GradleBuildRootsLocator {
// other scripts: "included", "precompiled" scripts, scripts in unlinked projects, // other scripts: "included", "precompiled" scripts, scripts in unlinked projects,
// or just random files with ".gradle.kts" ending // or just random files with ".gradle.kts" ending
val standaloneScriptRoot = roots.getStandaloneScriptRoot(filePath)
if (standaloneScriptRoot != null) {
return ScriptUnderRoot(standaloneScriptRoot, standalone = true)
}
// todo(gradle6): remove, it is required only for projects with old gradle // todo(gradle6): remove, it is required only for projects with old gradle
if (searchNearestLegacy) { if (searchNearestLegacy) {
val found = roots.findNearestRoot(filePath) val found = roots.findNearestRoot(filePath)
@@ -21,6 +21,7 @@ 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.CompositeScriptConfigurationManager
import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport 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.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.script.ucache.ScriptClassRootsBuilder
import org.jetbrains.kotlin.idea.core.util.EDT import org.jetbrains.kotlin.idea.core.util.EDT
import org.jetbrains.kotlin.idea.scripting.gradle.* import org.jetbrains.kotlin.idea.scripting.gradle.*
@@ -69,6 +70,7 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(),
override fun isApplicable(file: VirtualFile): Boolean { override fun isApplicable(file: VirtualFile): Boolean {
val scriptUnderRoot = findScriptBuildRoot(file) ?: return false val scriptUnderRoot = findScriptBuildRoot(file) ?: return false
if (scriptUnderRoot.root is GradleBuildRoot.Legacy) return false if (scriptUnderRoot.root is GradleBuildRoot.Legacy) return false
if (roots.isStandaloneScript(file.path)) return false
return true return true
} }
@@ -79,8 +81,16 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(),
} }
} }
// used in 201 @Suppress("MemberVisibilityCanBePrivate") // used in GradleImportHelper.kt.193
@Suppress("UNUSED") fun checkUpToDate(file: VirtualFile) {
if (isConfigurationOutOfDate(file)) {
showNotificationForProjectImport(project)
} else {
hideNotificationForProjectImport(project)
}
}
@Suppress("MemberVisibilityCanBePrivate") // used in GradleImportHelper.kt.201
fun isConfigurationOutOfDate(file: VirtualFile): Boolean { fun isConfigurationOutOfDate(file: VirtualFile): Boolean {
val script = getScriptInfo(file) ?: return false val script = getScriptInfo(file) ?: return false
return !script.model.inputs.isUpToDate(project, file) return !script.model.inputs.isUpToDate(project, file)
@@ -158,6 +168,14 @@ 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)
}
init { init {
getGradleProjectSettings(project).forEach { getGradleProjectSettings(project).forEach {
// don't call this.add, as we are inside scripting manager initialization // don't call this.add, as we are inside scripting manager initialization
@@ -298,15 +316,20 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(),
} }
} }
private fun updateNotifications(dir1: String) { @Suppress("MemberVisibilityCanBePrivate")
private fun updateNotifications(dir: String) {
if (!project.isOpen) return if (!project.isOpen) return
val openedScripts = FileEditorManager.getInstance(project).openFiles.filter { val openedScripts = FileEditorManager.getInstance(project).openFiles.filter {
it.path.startsWith(dir1) && isGradleKotlinScript(it) it.path.startsWith(dir) && maybeAffectedGradleProjectFile(it.path)
} }
if (openedScripts.isEmpty()) return if (openedScripts.isEmpty()) return
openedScripts.forEach {
checkUpToDate(it)
}
GlobalScope.launch(EDT(project)) { GlobalScope.launch(EDT(project)) {
if (project.isDisposed) return@launch if (project.isDisposed) return@launch
@@ -18,7 +18,7 @@ class GradleBuildRootsLocatorTest : AbstractGradleBuildRootsLocatorTest() {
newImportedGradleProject("imported", relativeScripts = listOf()) newImportedGradleProject("imported", relativeScripts = listOf())
val scriptUnderRoot = findScriptBuildRoot("imported/build.gradle.kts") val scriptUnderRoot = findScriptBuildRoot("imported/build.gradle.kts")
assertNotNull(scriptUnderRoot) assertNotNull(scriptUnderRoot)
assertTrue(scriptUnderRoot.isNewScript) assertTrue(scriptUnderRoot.importRequired)
assertFalse(scriptUnderRoot.isUnrelatedScript) assertFalse(scriptUnderRoot.isUnrelatedScript)
} }
@@ -27,7 +27,7 @@ class GradleBuildRootsLocatorTest : AbstractGradleBuildRootsLocatorTest() {
newImportedGradleProject("imported", relativeScripts = listOf("build.gradle.kts")) newImportedGradleProject("imported", relativeScripts = listOf("build.gradle.kts"))
val scriptUnderRoot = findScriptBuildRoot("imported/build.gradle.kts") val scriptUnderRoot = findScriptBuildRoot("imported/build.gradle.kts")
assertNotNull(scriptUnderRoot) assertNotNull(scriptUnderRoot)
assertFalse(scriptUnderRoot.isNewScript) assertFalse(scriptUnderRoot.importRequired)
assertFalse(scriptUnderRoot.isUnrelatedScript) assertFalse(scriptUnderRoot.isUnrelatedScript)
} }
} }