From 2f35d6d8683133893eccae5a36666599b74b66c4 Mon Sep 17 00:00:00 2001 From: Natalia Selezneva Date: Wed, 11 Dec 2019 13:25:20 +0300 Subject: [PATCH] Scripting: save inputs stamp and diagnostics to file attributes Check if they are up to date and initiate configuration update only in case they are out of date ^KT-35205 --- .../AbstractScriptConfigurationManager.kt | 2 + .../DefaultScriptConfigurationManager.kt | 2 +- .../cache/ScriptConfigurationCache.kt | 3 +- .../ScriptConfigurationFileAttributeCache.kt | 124 ++++++------------ .../idea/core/util/FileAttributeUtils.kt | 22 +++- .../gradle/GradleScriptInputsWatcherTest.kt | 40 ++++++ .../script/experimental/api/errorHandling.kt | 6 +- .../script/experimental/api/scriptData.kt | 8 +- 8 files changed, 114 insertions(+), 93 deletions(-) diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/AbstractScriptConfigurationManager.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/AbstractScriptConfigurationManager.kt index 9f7eb018d50..c3506016c31 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/AbstractScriptConfigurationManager.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/AbstractScriptConfigurationManager.kt @@ -225,6 +225,8 @@ internal abstract class AbstractScriptConfigurationManager( // todo: cache.clear() clearClassRootsCaches() + cache.clear() + if (project.isOpen) { val openedScripts = FileEditorManager.getInstance(project).openFiles.filterNot { it.isNonScript() } updateHighlighting(openedScripts) 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 256eb4831e7..092eb5c86ef 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 @@ -124,7 +124,7 @@ internal class DefaultScriptConfigurationManager(project: Project) : override fun createCache() = object : ScriptConfigurationMemoryCache(project) { override fun setLoaded(file: VirtualFile, configurationSnapshot: ScriptConfigurationSnapshot) { super.setLoaded(file, configurationSnapshot) - fileAttributeCache.save(file, configurationSnapshot.configuration) + fileAttributeCache.save(file, configurationSnapshot) } } diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationCache.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationCache.kt index 20a0b5d93cf..ade97cb9227 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationCache.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationCache.kt @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.idea.core.script.configuration.utils.getKtFile import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper +import java.io.Serializable import kotlin.script.experimental.api.ScriptDiagnostic /** @@ -55,7 +56,7 @@ data class ScriptConfigurationSnapshot( val configuration: ScriptCompilationConfigurationWrapper? ) -interface CachedConfigurationInputs { +interface CachedConfigurationInputs: Serializable { fun isUpToDate(project: Project, file: VirtualFile, ktFile: KtFile? = null): Boolean object OutOfDate : CachedConfigurationInputs { diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationFileAttributeCache.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationFileAttributeCache.kt index aba848c530c..9e49a6de4d3 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationFileAttributeCache.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationFileAttributeCache.kt @@ -9,19 +9,19 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoader import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoadingContext -import org.jetbrains.kotlin.idea.core.script.configuration.utils.getKtFile import org.jetbrains.kotlin.idea.core.script.debug -import org.jetbrains.kotlin.idea.core.util.* +import org.jetbrains.kotlin.idea.core.util.cachedFileAttribute +import org.jetbrains.kotlin.idea.core.util.readObject +import org.jetbrains.kotlin.idea.core.util.writeObject import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition -import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition import org.jetbrains.kotlin.scripting.resolve.KtFileScriptSource -import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper.FromCompilationConfiguration -import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper.FromLegacy -import java.io.* +import java.io.Serializable import kotlin.script.experimental.api.ScriptCompilationConfiguration -import kotlin.script.experimental.dependencies.ScriptDependencies +import kotlin.script.experimental.api.ScriptDiagnostic +import kotlin.script.experimental.api.dependencies +import kotlin.script.experimental.jvm.impl.toClassPathOrEmpty internal class ScriptConfigurationFileAttributeCache( val project: Project @@ -39,42 +39,37 @@ internal class ScriptConfigurationFileAttributeCache( val virtualFile = ktFile.originalFile.virtualFile val fromFs = load(virtualFile) ?: return false - val result = + + context.saveNewConfiguration( + virtualFile, ScriptConfigurationSnapshot( - CachedConfigurationInputs.OutOfDate, // todo(KT-34444): save inputs to fs - listOf(), // todo(KT-34444): save reports to fs - fromFs + fromFs.inputs, + fromFs.reports, + FromCompilationConfiguration(KtFileScriptSource(ktFile), fromFs.configuration) ) - context.saveNewConfiguration(virtualFile, result) - return true + ) + return fromFs.inputs.isUpToDate(ktFile.project, virtualFile, ktFile) } private fun load( virtualFile: VirtualFile - ): ScriptCompilationConfigurationWrapper? { - val ktFile = project.getKtFile(virtualFile) ?: return null - val scriptSource = KtFileScriptSource(ktFile) + ): ScriptConfigurationSnapshotForFS? { + val configurationSnapshot = virtualFile.scriptConfigurationSnapshot ?: return null + debug(virtualFile) { "configuration from fileAttributes = $configurationSnapshot" } - val configurationFromAttributes = - virtualFile.scriptCompilationConfiguration?.let { - FromCompilationConfiguration(scriptSource, it) - } ?: virtualFile.scriptDependencies?.let { - FromLegacy(scriptSource, it, ktFile.findScriptDefinition()) - } ?: return null + val configuration = configurationSnapshot.configuration ?: return null - - debug(virtualFile) { "configuration from fileAttributes = $configurationFromAttributes" } - - if (!areDependenciesValid(virtualFile, configurationFromAttributes)) { + if (!areDependenciesValid(virtualFile, configuration)) { save(virtualFile, null) return null } - return configurationFromAttributes + return configurationSnapshot } - private fun areDependenciesValid(file: VirtualFile, configuration: ScriptCompilationConfigurationWrapper): Boolean { - return configuration.dependenciesClassPath.all { + private fun areDependenciesValid(file: VirtualFile, configuration: ScriptCompilationConfiguration): Boolean { + val classpath = configuration.get(ScriptCompilationConfiguration.dependencies).toClassPathOrEmpty() + return classpath.all { if (it.exists()) { true } else { @@ -83,68 +78,31 @@ internal class ScriptConfigurationFileAttributeCache( } false } - } } - fun save(file: VirtualFile, value: ScriptCompilationConfigurationWrapper?) { + fun save(file: VirtualFile, value: ScriptConfigurationSnapshot?) { if (value == null) { - file.scriptDependencies = null - file.scriptCompilationConfiguration = null + file.scriptConfigurationSnapshot = null } else { - if (value is ScriptCompilationConfigurationWrapper.FromLegacy) { - file.scriptDependencies = value.legacyDependencies - } else { - if (file.scriptDependencies != null) file.scriptDependencies = null - file.scriptCompilationConfiguration = value.configuration - } + file.scriptConfigurationSnapshot = ScriptConfigurationSnapshotForFS( + value.inputs, + value.reports, + value.configuration?.configuration + ) } } - } -private var VirtualFile.scriptDependencies: ScriptDependencies? by cachedFileAttribute( - name = "kotlin-script-dependencies", - version = 3, - read = { - ScriptDependencies( - classpath = readFileList(), - imports = readStringList(), - javaHome = readNullable(DataInput::readFile), - scripts = readFileList(), - sources = readFileList() - ) - }, - write = { - with(it) { - writeFileList(classpath) - writeStringList(imports) - writeNullable(javaHome, DataOutput::writeFile) - writeFileList(scripts) - writeFileList(sources) - } - } -) +private class ScriptConfigurationSnapshotForFS( + val inputs: CachedConfigurationInputs, + val reports: List, + val configuration: ScriptCompilationConfiguration? +) : Serializable -private var VirtualFile.scriptCompilationConfiguration: ScriptCompilationConfiguration? by cachedFileAttribute( - name = "kotlin-script-compilation-configuration", - version = 1, - read = { - val size = readInt() - val bytes = ByteArray(size) - read(bytes, 0, size) - val bis = ByteArrayInputStream(bytes) - ObjectInputStream(bis).use { ois -> - ois.readObject() as ScriptCompilationConfiguration - } - }, - write = { - val os = ByteArrayOutputStream() - ObjectOutputStream(os).use { oos -> - oos.writeObject(it) - } - val bytes = os.toByteArray() - writeInt(bytes.size) - write(bytes) - } +private var VirtualFile.scriptConfigurationSnapshot: ScriptConfigurationSnapshotForFS? by cachedFileAttribute( + name = "kotlin-script-dependencies", + version = 4, + read = { readObject() }, + write = { writeObject(it) } ) diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/util/FileAttributeUtils.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/util/FileAttributeUtils.kt index f051f5b9589..8281ddeddaa 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/util/FileAttributeUtils.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/util/FileAttributeUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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. */ @@ -93,4 +93,24 @@ fun DataOutput.writeNullable(nullable: T?, writeT: DataOutput.(T) -> U fun DataInput.readNullable(readT: DataInput.() -> T): T? { val hasValue = readBoolean() return if (hasValue) readT() else null +} + +inline fun DataOutputStream.writeObject(obj: T) { + val os = ByteArrayOutputStream() + ObjectOutputStream(os).use { oos -> + oos.writeObject(obj) + } + val bytes = os.toByteArray() + writeInt(bytes.size) + write(bytes) +} + +inline fun DataInputStream.readObject(): T { + val size = readInt() + val bytes = ByteArray(size) + read(bytes, 0, size) + val bis = ByteArrayInputStream(bytes) + return ObjectInputStream(bis).use { ois -> + ois.readObject() as T + } } \ No newline at end of file diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcherTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcherTest.kt index d6472da663a..06d400073f3 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcherTest.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcherTest.kt @@ -87,6 +87,46 @@ class GradleScriptInputsWatcherTest : AbstractScriptConfigurationLoadingTest() { assertConfigurationUpToDate(testFiles.settings) } + fun testFileAttributes() { + assertAndLoadInitialConfiguration(testFiles.buildKts) + + scriptConfigurationManager.clearConfigurationCachesAndRehighlight() + + assertConfigurationUpToDate(testFiles.buildKts) + } + + fun testFileAttributesUpToDate() { + assertAndLoadInitialConfiguration(testFiles.buildKts) + + scriptConfigurationManager.clearConfigurationCachesAndRehighlight() + + changeBuildKtsInsideSections() + + assertConfigurationUpdateWasDone(testFiles.buildKts) + } + + fun testFileAttributesUpToDateAfterChangeOutsideSections() { + assertAndLoadInitialConfiguration(testFiles.buildKts) + + scriptConfigurationManager.clearConfigurationCachesAndRehighlight() + + changeBuildKtsOutsideSections() + + assertConfigurationUpToDate(testFiles.buildKts) + } + + fun testFileAttributesUpdateAfterChangeOutsideSectionsOfOtherFile() { + assertAndLoadInitialConfiguration(testFiles.buildKts) + assertAndLoadInitialConfiguration(testFiles.settings) + + scriptConfigurationManager.clearConfigurationCachesAndRehighlight() + + changeSettingsKtsOutsideSections() + + assertConfigurationUpToDate(testFiles.settings) + assertConfigurationUpdateWasDone(testFiles.buildKts) + } + private fun assertConfigurationUpToDate(file: KtFile) { scriptConfigurationManager.updater.ensureUpToDatedConfigurationSuggested(file) assertNoBackgroundTasks() diff --git a/libraries/scripting/common/src/kotlin/script/experimental/api/errorHandling.kt b/libraries/scripting/common/src/kotlin/script/experimental/api/errorHandling.kt index e3562cd75c4..aded6656150 100644 --- a/libraries/scripting/common/src/kotlin/script/experimental/api/errorHandling.kt +++ b/libraries/scripting/common/src/kotlin/script/experimental/api/errorHandling.kt @@ -1,5 +1,5 @@ /* - * Copyright 2000-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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. */ @@ -10,7 +10,7 @@ package kotlin.script.experimental.api import java.io.ByteArrayOutputStream import java.io.File import java.io.PrintStream -import java.lang.RuntimeException +import java.io.Serializable /** * The single script diagnostic report @@ -25,7 +25,7 @@ data class ScriptDiagnostic( val sourcePath: String? = null, val location: SourceCode.Location? = null, val exception: Throwable? = null -) { +) : Serializable { /** * The diagnostic severity */ diff --git a/libraries/scripting/common/src/kotlin/script/experimental/api/scriptData.kt b/libraries/scripting/common/src/kotlin/script/experimental/api/scriptData.kt index a3b793a5517..a1b9a68cc4f 100644 --- a/libraries/scripting/common/src/kotlin/script/experimental/api/scriptData.kt +++ b/libraries/scripting/common/src/kotlin/script/experimental/api/scriptData.kt @@ -1,5 +1,5 @@ /* - * Copyright 2000-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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. */ @@ -36,21 +36,21 @@ interface SourceCode { * @param col source code position column * @param absolutePos absolute source code text position, if available */ - data class Position(val line: Int, val col: Int, val absolutePos: Int? = null) + data class Position(val line: Int, val col: Int, val absolutePos: Int? = null) : Serializable /** * The source code positions range * @param start range start position * @param end range end position (after the last char) */ - data class Range(val start: Position, val end: Position) + data class Range(val start: Position, val end: Position) : Serializable /** * The source code location, pointing either at a position or at a range * @param start location start position * @param end optional range location end position (after the last char) */ - data class Location(val start: Position, val end: Position? = null) + data class Location(val start: Position, val end: Position? = null) : Serializable } /**