From 19d35d7f277dfefc89d5854710a63d530fc2d926 Mon Sep 17 00:00:00 2001 From: Sergey Rostov Date: Thu, 31 Oct 2019 11:17:14 +0300 Subject: [PATCH] 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 --- .../DefaultScriptConfigurationManager.kt | 12 +- .../idea/core/script/configuration/gradle.kt | 137 ++++++++++++++++++ idea/resources/META-INF/extensions/ide.xml | 12 +- 3 files changed, 152 insertions(+), 9 deletions(-) create mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/gradle.kt diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/DefaultScriptConfigurationManager.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/DefaultScriptConfigurationManager.kt index cf724d971aa..f72b77e6030 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/DefaultScriptConfigurationManager.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/DefaultScriptConfigurationManager.kt @@ -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 = listOf( ScriptOutsiderFileConfigurationLoader(project), ScriptConfigurationFileAttributeCache(project), + GradleScriptConfigurationLoader(project), DefaultScriptConfigurationLoader(project) ) private val listeners: List = listOf( + GradleScriptListener(), DefaultScriptChangeListener() ) diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/gradle.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/gradle.kt new file mode 100644 index 00000000000..6502ab2d785 --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/gradle.kt @@ -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() + ?.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 + } +} \ No newline at end of file diff --git a/idea/resources/META-INF/extensions/ide.xml b/idea/resources/META-INF/extensions/ide.xml index e1e07ff592f..f0ed001b820 100644 --- a/idea/resources/META-INF/extensions/ide.xml +++ b/idea/resources/META-INF/extensions/ide.xml @@ -78,11 +78,11 @@ implementation="org.jetbrains.kotlin.idea.script.ScriptTemplatesFromDependenciesProvider"/> + order="last" + implementation="org.jetbrains.kotlin.idea.core.script.StandardScriptDefinitionContributor"/> + implementation="org.jetbrains.kotlin.console.ConsoleScriptDefinitionContributor"/> @@ -93,7 +93,7 @@ - + +