Implement Gradle specific behavior for script configuration update logic
We have plans to extract separate implementation for Gradle scripts, but it would be better to support something at this point. For now, we have custom listener, loader and up-to-date check for default configuration manager: - listener will do forced reload on editor activation, even it is already up-to-date this is required for Gradle scripts, since it's classpath may depend on other files (`.properties` for example) - loader will force save failures before running loader. also it will skip loading if `kotlin.gradle.scripts.useIdeaProjectImport` registry key is enabled - loader will return inputs with overridden up-to-date checks: file will be considered out-of-date only when `buildscript` or `plugins` blocks are changed
This commit is contained in:
committed by
Natalia Selezneva
parent
548e8e27dc
commit
19d35d7f27
+7
-5
@@ -15,16 +15,15 @@ import com.intellij.ui.EditorNotifications
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.kotlin.idea.core.script.*
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigurationSnapshot
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationCache
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.*
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationFileAttributeCache
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationMemoryCache
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.DefaultScriptChangeListener
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangesNotifier
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.*
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.DefaultScriptConfigurationLoader
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptOutsiderFileConfigurationLoader
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.utils.BackgroundExecutor
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangesNotifier
|
||||
import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings
|
||||
import org.jetbrains.kotlin.idea.core.util.EDT
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
@@ -34,6 +33,7 @@ import java.util.*
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
import kotlin.script.experimental.api.ScriptDiagnostic
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
|
||||
|
||||
/**
|
||||
* Standard implementation of scripts configuration loading and caching
|
||||
@@ -100,10 +100,12 @@ internal class DefaultScriptConfigurationManager(project: Project) :
|
||||
private val loaders: List<ScriptConfigurationLoader> = listOf(
|
||||
ScriptOutsiderFileConfigurationLoader(project),
|
||||
ScriptConfigurationFileAttributeCache(project),
|
||||
GradleScriptConfigurationLoader(project),
|
||||
DefaultScriptConfigurationLoader(project)
|
||||
)
|
||||
|
||||
private val listeners: List<ScriptChangeListener> = listOf(
|
||||
GradleScriptListener(),
|
||||
DefaultScriptChangeListener()
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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.core.script.configuration
|
||||
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.PsiRecursiveElementVisitor
|
||||
import com.intellij.psi.PsiWhiteSpace
|
||||
import com.intellij.psi.impl.source.tree.LeafPsiElement
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigurationInputs
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.DefaultScriptChangeListener
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.DefaultScriptConfigurationLoader
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoadingContext
|
||||
import org.jetbrains.kotlin.idea.core.util.EDT
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtScriptInitializer
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
|
||||
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
|
||||
|
||||
class GradleScriptListener : DefaultScriptChangeListener() {
|
||||
private val listenGradleRelatedFiles = false // todo
|
||||
|
||||
override fun editorActivated(file: KtFile, updater: ScriptConfigurationUpdater): Boolean {
|
||||
if (!isGradleKotlinScript(file.virtualFile)) return false
|
||||
if (listenGradleRelatedFiles) {
|
||||
updater.ensureUpToDatedConfigurationSuggested(file)
|
||||
// todo: force reload if related files changed
|
||||
} else {
|
||||
// configuration will be reloaded after editor activation, even it is already up-to-date
|
||||
// this is required for Gradle scripts, since it's classpath may depend on other files (`.properties` for example)
|
||||
updater.forceConfigurationReload(file)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class GradleScriptConfigurationLoader(project: Project) : DefaultScriptConfigurationLoader(project) {
|
||||
private val useProjectImport: Boolean
|
||||
get() = Registry.`is`("kotlin.gradle.scripts.useIdeaProjectImport", false)
|
||||
|
||||
override fun shouldRunInBackground(scriptDefinition: ScriptDefinition): Boolean {
|
||||
return if (useProjectImport) false else super.shouldRunInBackground(scriptDefinition)
|
||||
}
|
||||
|
||||
override fun loadDependencies(
|
||||
isFirstLoad: Boolean,
|
||||
virtualFile: VirtualFile,
|
||||
scriptDefinition: ScriptDefinition,
|
||||
context: ScriptConfigurationLoadingContext
|
||||
): Boolean {
|
||||
if (!isGradleKotlinScript(virtualFile)) return false
|
||||
|
||||
if (useProjectImport) {
|
||||
// do nothing, project import notification will be already showed
|
||||
// and configuration for gradle build scripts will be saved at the end of import
|
||||
// todo: use default configuration loader for out-of-project scripts?
|
||||
|
||||
return true
|
||||
} else {
|
||||
// Gradle read files from FS
|
||||
GlobalScope.launch(EDT(project)) {
|
||||
runWriteAction {
|
||||
FileDocumentManager.getInstance().saveAllDocuments()
|
||||
}
|
||||
}
|
||||
|
||||
return super.loadDependencies(isFirstLoad, virtualFile, scriptDefinition, context)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getInputsStamp(file: KtFile): CachedConfigurationInputs {
|
||||
return getGradleScriptInputsStamp(project, file.virtualFile, file) ?: super.getInputsStamp(file)
|
||||
}
|
||||
}
|
||||
|
||||
data class GradleKotlinScriptConfigurationInputs(
|
||||
val buildScriptAndPluginsSections: String
|
||||
) : CachedConfigurationInputs {
|
||||
override fun isUpToDate(project: Project, file: VirtualFile, ktFile: KtFile?): Boolean {
|
||||
val actualStamp = getGradleScriptInputsStamp(project, file, ktFile)
|
||||
return actualStamp == this
|
||||
}
|
||||
}
|
||||
|
||||
fun isGradleKotlinScript(virtualFile: VirtualFile) = virtualFile.name.endsWith(".gradle.kts")
|
||||
|
||||
fun getGradleScriptInputsStamp(
|
||||
project: Project,
|
||||
file: VirtualFile,
|
||||
givenKtFile: KtFile? = null
|
||||
): GradleKotlinScriptConfigurationInputs? {
|
||||
if (!isGradleKotlinScript(file)) return null
|
||||
|
||||
return runReadAction {
|
||||
val ktFile = givenKtFile ?: PsiManager.getInstance(project).findFile(file) as? KtFile
|
||||
|
||||
if (ktFile != null) {
|
||||
val result = StringBuilder()
|
||||
ktFile.script?.blockExpression
|
||||
?.getChildrenOfType<KtScriptInitializer>()
|
||||
?.forEach {
|
||||
val call = it.children.singleOrNull() as? KtCallExpression
|
||||
val callRef = call?.firstChild?.text
|
||||
if (callRef == "buildscript" || callRef == "plugins") {
|
||||
result.append(callRef)
|
||||
val lambda = call.lambdaArguments.singleOrNull()
|
||||
lambda?.accept(object : PsiRecursiveElementVisitor(false) {
|
||||
override fun visitElement(element: PsiElement) {
|
||||
super.visitElement(element)
|
||||
when (element) {
|
||||
is PsiWhiteSpace -> if (element.text.contains("\n")) result.append("\n")
|
||||
is LeafPsiElement -> result.append(element.text)
|
||||
}
|
||||
}
|
||||
})
|
||||
result.append("\n")
|
||||
}
|
||||
}
|
||||
|
||||
GradleKotlinScriptConfigurationInputs(result.toString())
|
||||
} else null
|
||||
}
|
||||
}
|
||||
@@ -78,11 +78,11 @@
|
||||
implementation="org.jetbrains.kotlin.idea.script.ScriptTemplatesFromDependenciesProvider"/>
|
||||
|
||||
<scriptDefinitionContributor id="StandardScriptDefinitionContributor"
|
||||
order="last"
|
||||
implementation="org.jetbrains.kotlin.idea.core.script.StandardScriptDefinitionContributor"/>
|
||||
order="last"
|
||||
implementation="org.jetbrains.kotlin.idea.core.script.StandardScriptDefinitionContributor"/>
|
||||
|
||||
<scriptDefinitionContributor id="ConsoleScriptDefinitionContributor"
|
||||
implementation="org.jetbrains.kotlin.console.ConsoleScriptDefinitionContributor"/>
|
||||
implementation="org.jetbrains.kotlin.console.ConsoleScriptDefinitionContributor"/>
|
||||
|
||||
<idePlatformKindResolution implementation="org.jetbrains.kotlin.caches.resolve.JvmPlatformKindResolution"/>
|
||||
<idePlatformKindResolution implementation="org.jetbrains.kotlin.caches.resolve.JsPlatformKindResolution"/>
|
||||
@@ -93,7 +93,7 @@
|
||||
|
||||
<j2kConverterExtension implementation="org.jetbrains.kotlin.idea.j2k.OldJ2kConverterExtension"/>
|
||||
<j2kConverterExtension implementation="org.jetbrains.kotlin.nj2k.NewJ2kConverterExtension"/>
|
||||
</extensions>
|
||||
</extensions>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<registryKey key="kotlin.mpp.editTargetPlatformEnabled"
|
||||
@@ -104,6 +104,10 @@
|
||||
description="Add all transitive dependencies on MPP modules when Android project is imported"
|
||||
defaultValue="true"
|
||||
restartRequired="false"/>
|
||||
<registryKey key="kotlin.gradle.scripts.useIdeaProjectImport"
|
||||
description="Use IDEA project import mechanics to load Kotlin Gradle scripts configuration"
|
||||
defaultValue="false"
|
||||
restartRequired="false"/>
|
||||
<fileEditorProvider implementation="org.jetbrains.kotlin.idea.scratch.ui.KtScratchFileEditorProvider"/>
|
||||
|
||||
</extensions>
|
||||
|
||||
Reference in New Issue
Block a user