GradleBuildRoot: ability to detect if file was existed before import

This commit is contained in:
Sergey Rostov
2020-05-29 15:33:28 +03:00
parent 7384c89ddd
commit 6f0bd6c122
8 changed files with 73 additions and 40 deletions
@@ -101,6 +101,7 @@ private fun KotlinDslScriptsModel.toListOfScriptModels(project: Project): List<K
}
class KotlinDslGradleBuildSync(val workingDir: String, val taskId: ExternalSystemTaskId) {
val ts = System.currentTimeMillis()
var project: Project? = null
val projectRoots = mutableSetOf<String>()
val models = mutableListOf<KotlinDslScriptModel>()
@@ -25,7 +25,9 @@ import java.io.File
*
* See [GradleBuildRootsManager] for more details.
*/
sealed class GradleBuildRoot(private val lastModifiedFiles: LastModifiedFiles) {
sealed class GradleBuildRoot(
private val lastModifiedFiles: LastModifiedFiles
) {
@Volatile
var importing = false
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.idea.scripting.gradle.roots
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel
data class GradleBuildRootData(
val importTs: Long,
val projectRoots: Collection<String>,
val templateClasspath: Collection<String>,
val models: Collection<KotlinDslScriptModel>
@@ -19,7 +19,7 @@ import java.io.DataInputStream
import java.io.DataOutput
internal object GradleBuildRootDataSerializer {
private val attribute = FileAttribute("kotlin-dsl-script-models", 5, false)
private val attribute = FileAttribute("kotlin-dsl-script-models", 6, false)
fun read(buildRoot: VirtualFile): GradleBuildRootData? {
return attribute.readAttribute(buildRoot)?.use {
@@ -54,6 +54,7 @@ internal fun writeKotlinDslScriptModels(output: DataOutput, data: GradleBuildRoo
strings.addStrings(it.imports)
}
strings.writeHeader()
output.writeLong(data.importTs)
strings.writeStringIds(data.projectRoots)
strings.writeStringIds(data.templateClasspath)
output.writeList(data.models) {
@@ -69,9 +70,9 @@ internal fun writeKotlinDslScriptModels(output: DataOutput, data: GradleBuildRoo
internal fun readKotlinDslScriptModels(input: DataInputStream, buildRoot: String): GradleBuildRootData {
val strings = StringsPool.reader(input)
val importTs = input.readLong()
val projectRoots = strings.readStrings()
val templateClasspath = strings.readStrings()
val models = input.readList {
KotlinDslScriptModel(
strings.readString(),
@@ -83,7 +84,7 @@ internal fun readKotlinDslScriptModels(input: DataInputStream, buildRoot: String
)
}
return GradleBuildRootData(projectRoots, templateClasspath, models)
return GradleBuildRootData(importTs, projectRoots, templateClasspath, models)
}
private object StringsPool {
@@ -61,11 +61,18 @@ abstract class GradleBuildRootsLocator {
standalone
}
class ScriptUnderRoot(
/**
* Timestamp of an moment when script file was discovered (indexed).
* Used to detect if that script was existed at the moment of import
*/
abstract fun getScriptFirstSeenTs(path: String): Long
inner class ScriptUnderRoot(
val filePath: String,
val root: GradleBuildRoot?,
val script: GradleScriptInfo? = null,
val standalone: Boolean = false,
val nearest: GradleBuildRoot? = null
val nearest: GradleBuildRoot? = root
) {
val notificationKind: NotificationKind
get() = when {
@@ -76,13 +83,20 @@ abstract class GradleBuildRootsLocator {
else -> when (nearest) {
is Legacy -> NotificationKind.dontCare
is New -> NotificationKind.wasNotImportedAfterCreation
is Imported -> NotificationKind.notEvaluatedInLastImport // todo: wasNotImportedAfterCreation
is Imported -> when {
wasImportedAndNotEvaluated -> NotificationKind.notEvaluatedInLastImport
else -> NotificationKind.wasNotImportedAfterCreation
}
}
}
private val isImported: Boolean
get() = script != null
private val wasImportedAndNotEvaluated: Boolean
get() = nearest is Imported &&
getScriptFirstSeenTs(filePath) < nearest.data.importTs
override fun toString(): String {
return "ScriptUnderRoot(root=$root, script=$script, standalone=$standalone, nearest=$nearest)"
}
@@ -96,7 +110,7 @@ abstract class GradleBuildRootsLocator {
val scriptInfo = getScriptInfo(filePath)
val imported = scriptInfo?.buildRoot
if (imported != null) return ScriptUnderRoot(imported, scriptInfo)
if (imported != null) return ScriptUnderRoot(filePath, imported, scriptInfo)
if (filePath.endsWith("/build.gradle.kts") ||
filePath.endsWith("/settings.gradle.kts") ||
@@ -104,7 +118,7 @@ abstract class GradleBuildRootsLocator {
) {
// build|settings|init.gradle.kts scripts should be located near gradle project root only
val gradleBuild = roots.getBuildByProjectDir(filePath.substringBeforeLast("/"))
if (gradleBuild != null) return ScriptUnderRoot(gradleBuild)
if (gradleBuild != null) return ScriptUnderRoot(filePath, gradleBuild)
}
// other scripts: "included", "precompiled" scripts, scripts in unlinked projects,
@@ -112,17 +126,17 @@ abstract class GradleBuildRootsLocator {
val standaloneScriptRoot = roots.getStandaloneScriptRoot(filePath)
if (standaloneScriptRoot != null) {
return ScriptUnderRoot(standaloneScriptRoot, standalone = true)
return ScriptUnderRoot(filePath, standaloneScriptRoot, standalone = true)
}
if (searchNearestLegacy) {
val nearest = roots.findNearestRoot(filePath)
return when (nearest) {
is Legacy -> ScriptUnderRoot(nearest)
else -> ScriptUnderRoot(null, nearest = nearest)
is Legacy -> ScriptUnderRoot(filePath, nearest)
else -> ScriptUnderRoot(filePath, null, nearest = nearest)
}
} else {
return ScriptUnderRoot(null)
return ScriptUnderRoot(filePath, null)
}
}
}
@@ -107,6 +107,8 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(),
override fun getScriptInfo(localPath: String): GradleScriptInfo? =
manager.getLightScriptInfo(localPath) as? GradleScriptInfo
override fun getScriptFirstSeenTs(path: String): Long = 0
fun fileChanged(filePath: String, ts: Long = System.currentTimeMillis()) {
findAffectedFileRoot(filePath)?.fileChanged(filePath, ts)
scheduleLastModifiedFilesSave()
@@ -117,31 +119,34 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(),
updateNotifications { it.startsWith(workingDir) }
}
fun update(build: KotlinDslGradleBuildSync) {
fun update(sync: KotlinDslGradleBuildSync) {
// fast path for linked gradle builds without .gradle.kts support
if (build.models.isEmpty()) {
val root = getBuildRootByWorkingDir(build.workingDir) ?: return
if (sync.models.isEmpty()) {
val root = getBuildRootByWorkingDir(sync.workingDir) ?: return
if (root is Imported && root.data.models.isEmpty()) return
}
try {
val root = actualizeBuildRoot(build.workingDir) ?: return
root.importing = false
val oldRoot = actualizeBuildRoot(sync.workingDir) ?: return
oldRoot.importing = false
if (root is Legacy) return
if (oldRoot is Legacy) return
val templateClasspath = GradleScriptDefinitionsContributor.getDefinitionsTemplateClasspath(project)
val newData = GradleBuildRootData(build.projectRoots, templateClasspath, build.models)
val mergedData = if (build.failed && root is Imported) merge(root.data, newData) else newData
val newData = GradleBuildRootData(sync.ts, sync.projectRoots, templateClasspath, sync.models)
val mergedData = if (sync.failed && oldRoot is Imported) merge(oldRoot.data, newData) else newData
val lastModifiedFilesReset = LastModifiedFiles()
val newSupport = tryCreateImportedRoot(build.workingDir, lastModifiedFilesReset) { mergedData } ?: return
GradleBuildRootDataSerializer.write(newSupport.dir ?: return, mergedData)
newSupport.saveLastModifiedFiles()
val newRoot = tryCreateImportedRoot(
sync.workingDir,
lastModifiedFilesReset
) { mergedData } ?: return
GradleBuildRootDataSerializer.write(newRoot.dir ?: return, mergedData)
newRoot.saveLastModifiedFiles()
add(newSupport)
add(newRoot)
} finally {
updateNotifications { it.startsWith(build.workingDir) }
updateNotifications { it.startsWith(sync.workingDir) }
}
}
@@ -152,7 +157,7 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(),
val models = old.models.associateByTo(mutableMapOf()) { it.file }
new.models.associateByTo(models) { it.file }
return GradleBuildRootData(roots, new.templateClasspath, models.values)
return GradleBuildRootData(new.importTs, roots, new.templateClasspath, models.values)
}
private val lastModifiedFilesSaveScheduled = AtomicBoolean()
@@ -20,6 +20,7 @@ class GradleBuildRootDataSerializerTest {
@Test
fun write() {
val data = GradleBuildRootData(
123,
listOf("a", "b", "c"),
listOf("a"),
listOf(
@@ -11,11 +11,14 @@ import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel
import kotlin.test.assertEquals
open class AbstractGradleBuildRootsLocatorTest {
private val scripts = mutableMapOf<String, GradleScriptInfo>()
private val scripts = mutableMapOf<String, ScriptFixture>()
private val locator = MyRootsLocator()
class ScriptFixture(val introductionTs: Long, val info: GradleScriptInfo)
private inner class MyRootsLocator : GradleBuildRootsLocator() {
override fun getScriptInfo(localPath: String): GradleScriptInfo? = scripts[localPath]
override fun getScriptFirstSeenTs(path: String): Long = scripts[path]?.introductionTs ?: 0
override fun getScriptInfo(localPath: String): GradleScriptInfo? = scripts[localPath]?.info
fun accessRoots() = roots
}
@@ -26,13 +29,15 @@ open class AbstractGradleBuildRootsLocatorTest {
fun newImportedGradleProject(
dir: String,
relativeProjectRoots: List<String> = listOf(""),
relativeScripts: List<String> = listOf("build.gradle.kts")
relativeScripts: List<String> = listOf("build.gradle.kts"),
ts: Long = 0
) {
val pathPrefix = "$dir/"
val root = Imported(
dir,
null,
GradleBuildRootData(
ts,
relativeProjectRoots.map { (pathPrefix + it).removeSuffix("/") },
listOf(),
listOf()
@@ -44,16 +49,19 @@ open class AbstractGradleBuildRootsLocatorTest {
relativeScripts.forEach {
val path = pathPrefix + it
scripts[path] = GradleScriptInfo(
root,
null,
KotlinDslScriptModel(
file = path,
inputs = GradleKotlinScriptConfigurationInputs("", 0, dir),
classPath = listOf(),
sourcePath = listOf(),
imports = listOf(),
messages = listOf()
scripts[path] = ScriptFixture(
0,
GradleScriptInfo(
root,
null,
KotlinDslScriptModel(
file = path,
inputs = GradleKotlinScriptConfigurationInputs("", 0, dir),
classPath = listOf(),
sourcePath = listOf(),
imports = listOf(),
messages = listOf()
)
)
)
}