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
This commit is contained in:
Natalia Selezneva
2019-12-11 13:25:20 +03:00
parent 13b4e8716c
commit 2f35d6d868
8 changed files with 114 additions and 93 deletions
@@ -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)
@@ -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)
}
}
@@ -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 {
@@ -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<ScriptDiagnostic>,
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<ScriptConfigurationSnapshotForFS>() },
write = { writeObject<ScriptConfigurationSnapshotForFS>(it) }
)
@@ -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 <T : Any> DataOutput.writeNullable(nullable: T?, writeT: DataOutput.(T) -> U
fun <T : Any> DataInput.readNullable(readT: DataInput.() -> T): T? {
val hasValue = readBoolean()
return if (hasValue) readT() else null
}
inline fun <reified T : Any> 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 <reified T : Any> 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
}
}
@@ -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()
@@ -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
*/
@@ -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
}
/**