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() // todo: cache.clear()
clearClassRootsCaches() clearClassRootsCaches()
cache.clear()
if (project.isOpen) { if (project.isOpen) {
val openedScripts = FileEditorManager.getInstance(project).openFiles.filterNot { it.isNonScript() } val openedScripts = FileEditorManager.getInstance(project).openFiles.filterNot { it.isNonScript() }
updateHighlighting(openedScripts) updateHighlighting(openedScripts)
@@ -124,7 +124,7 @@ internal class DefaultScriptConfigurationManager(project: Project) :
override fun createCache() = object : ScriptConfigurationMemoryCache(project) { override fun createCache() = object : ScriptConfigurationMemoryCache(project) {
override fun setLoaded(file: VirtualFile, configurationSnapshot: ScriptConfigurationSnapshot) { override fun setLoaded(file: VirtualFile, configurationSnapshot: ScriptConfigurationSnapshot) {
super.setLoaded(file, configurationSnapshot) 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.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import java.io.Serializable
import kotlin.script.experimental.api.ScriptDiagnostic import kotlin.script.experimental.api.ScriptDiagnostic
/** /**
@@ -55,7 +56,7 @@ data class ScriptConfigurationSnapshot(
val configuration: ScriptCompilationConfigurationWrapper? val configuration: ScriptCompilationConfigurationWrapper?
) )
interface CachedConfigurationInputs { interface CachedConfigurationInputs: Serializable {
fun isUpToDate(project: Project, file: VirtualFile, ktFile: KtFile? = null): Boolean fun isUpToDate(project: Project, file: VirtualFile, ktFile: KtFile? = null): Boolean
object OutOfDate : CachedConfigurationInputs { object OutOfDate : CachedConfigurationInputs {
@@ -9,19 +9,19 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile 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.ScriptConfigurationLoader
import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoadingContext 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.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.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition 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.KtFileScriptSource
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper.FromCompilationConfiguration import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper.FromCompilationConfiguration
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper.FromLegacy import java.io.Serializable
import java.io.*
import kotlin.script.experimental.api.ScriptCompilationConfiguration 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( internal class ScriptConfigurationFileAttributeCache(
val project: Project val project: Project
@@ -39,42 +39,37 @@ internal class ScriptConfigurationFileAttributeCache(
val virtualFile = ktFile.originalFile.virtualFile val virtualFile = ktFile.originalFile.virtualFile
val fromFs = load(virtualFile) ?: return false val fromFs = load(virtualFile) ?: return false
val result =
context.saveNewConfiguration(
virtualFile,
ScriptConfigurationSnapshot( ScriptConfigurationSnapshot(
CachedConfigurationInputs.OutOfDate, // todo(KT-34444): save inputs to fs fromFs.inputs,
listOf(), // todo(KT-34444): save reports to fs fromFs.reports,
fromFs FromCompilationConfiguration(KtFileScriptSource(ktFile), fromFs.configuration)
) )
context.saveNewConfiguration(virtualFile, result) )
return true return fromFs.inputs.isUpToDate(ktFile.project, virtualFile, ktFile)
} }
private fun load( private fun load(
virtualFile: VirtualFile virtualFile: VirtualFile
): ScriptCompilationConfigurationWrapper? { ): ScriptConfigurationSnapshotForFS? {
val ktFile = project.getKtFile(virtualFile) ?: return null val configurationSnapshot = virtualFile.scriptConfigurationSnapshot ?: return null
val scriptSource = KtFileScriptSource(ktFile) debug(virtualFile) { "configuration from fileAttributes = $configurationSnapshot" }
val configurationFromAttributes = val configuration = configurationSnapshot.configuration ?: return null
virtualFile.scriptCompilationConfiguration?.let {
FromCompilationConfiguration(scriptSource, it)
} ?: virtualFile.scriptDependencies?.let {
FromLegacy(scriptSource, it, ktFile.findScriptDefinition())
} ?: return null
if (!areDependenciesValid(virtualFile, configuration)) {
debug(virtualFile) { "configuration from fileAttributes = $configurationFromAttributes" }
if (!areDependenciesValid(virtualFile, configurationFromAttributes)) {
save(virtualFile, null) save(virtualFile, null)
return null return null
} }
return configurationFromAttributes return configurationSnapshot
} }
private fun areDependenciesValid(file: VirtualFile, configuration: ScriptCompilationConfigurationWrapper): Boolean { private fun areDependenciesValid(file: VirtualFile, configuration: ScriptCompilationConfiguration): Boolean {
return configuration.dependenciesClassPath.all { val classpath = configuration.get(ScriptCompilationConfiguration.dependencies).toClassPathOrEmpty()
return classpath.all {
if (it.exists()) { if (it.exists()) {
true true
} else { } else {
@@ -83,68 +78,31 @@ internal class ScriptConfigurationFileAttributeCache(
} }
false false
} }
} }
} }
fun save(file: VirtualFile, value: ScriptCompilationConfigurationWrapper?) { fun save(file: VirtualFile, value: ScriptConfigurationSnapshot?) {
if (value == null) { if (value == null) {
file.scriptDependencies = null file.scriptConfigurationSnapshot = null
file.scriptCompilationConfiguration = null
} else { } else {
if (value is ScriptCompilationConfigurationWrapper.FromLegacy) { file.scriptConfigurationSnapshot = ScriptConfigurationSnapshotForFS(
file.scriptDependencies = value.legacyDependencies value.inputs,
} else { value.reports,
if (file.scriptDependencies != null) file.scriptDependencies = null value.configuration?.configuration
file.scriptCompilationConfiguration = value.configuration )
}
} }
} }
} }
private var VirtualFile.scriptDependencies: ScriptDependencies? by cachedFileAttribute( private class ScriptConfigurationSnapshotForFS(
name = "kotlin-script-dependencies", val inputs: CachedConfigurationInputs,
version = 3, val reports: List<ScriptDiagnostic>,
read = { val configuration: ScriptCompilationConfiguration?
ScriptDependencies( ) : Serializable
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 var VirtualFile.scriptCompilationConfiguration: ScriptCompilationConfiguration? by cachedFileAttribute( private var VirtualFile.scriptConfigurationSnapshot: ScriptConfigurationSnapshotForFS? by cachedFileAttribute(
name = "kotlin-script-compilation-configuration", name = "kotlin-script-dependencies",
version = 1, version = 4,
read = { read = { readObject<ScriptConfigurationSnapshotForFS>() },
val size = readInt() write = { writeObject<ScriptConfigurationSnapshotForFS>(it) }
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)
}
) )
@@ -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. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/ */
@@ -94,3 +94,23 @@ fun <T : Any> DataInput.readNullable(readT: DataInput.() -> T): T? {
val hasValue = readBoolean() val hasValue = readBoolean()
return if (hasValue) readT() else null 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) 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) { private fun assertConfigurationUpToDate(file: KtFile) {
scriptConfigurationManager.updater.ensureUpToDatedConfigurationSuggested(file) scriptConfigurationManager.updater.ensureUpToDatedConfigurationSuggested(file)
assertNoBackgroundTasks() 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. * 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.ByteArrayOutputStream
import java.io.File import java.io.File
import java.io.PrintStream import java.io.PrintStream
import java.lang.RuntimeException import java.io.Serializable
/** /**
* The single script diagnostic report * The single script diagnostic report
@@ -25,7 +25,7 @@ data class ScriptDiagnostic(
val sourcePath: String? = null, val sourcePath: String? = null,
val location: SourceCode.Location? = null, val location: SourceCode.Location? = null,
val exception: Throwable? = null val exception: Throwable? = null
) { ) : Serializable {
/** /**
* The diagnostic severity * 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. * 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 col source code position column
* @param absolutePos absolute source code text position, if available * @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 * The source code positions range
* @param start range start position * @param start range start position
* @param end range end position (after the last char) * @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 * The source code location, pointing either at a position or at a range
* @param start location start position * @param start location start position
* @param end optional range location end position (after the last char) * @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
} }
/** /**