Refactor templates from dependencies loading:
move into background thread avoid parallel execution other refactorings #KT-27669 fixed
This commit is contained in:
@@ -6,6 +6,8 @@
|
||||
package org.jetbrains.kotlin.idea.script
|
||||
|
||||
import com.intellij.ProjectTopics
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.Task
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.ModuleRootEvent
|
||||
import com.intellij.openapi.roots.ModuleRootListener
|
||||
@@ -24,100 +26,136 @@ import kotlin.concurrent.write
|
||||
|
||||
class ScriptTemplatesFromDependenciesProvider(private val project: Project) : ScriptDefinitionContributor {
|
||||
|
||||
data class TemplatesWithCp(
|
||||
val templates: List<String>,
|
||||
val classpath: List<File>
|
||||
)
|
||||
|
||||
private var templates: TemplatesWithCp? = null
|
||||
private val lock = ReentrantReadWriteLock()
|
||||
private val templatesLock = ReentrantReadWriteLock()
|
||||
|
||||
init {
|
||||
val connection = project.messageBus.connect()
|
||||
connection.subscribe(ProjectTopics.PROJECT_ROOTS, object : ModuleRootListener {
|
||||
override fun rootsChanged(event: ModuleRootEvent) {
|
||||
if (project.isInitialized) {
|
||||
var templatesChanged = false
|
||||
lock.read {
|
||||
val newTemplates = getScriptTemplates()
|
||||
if (newTemplates != templates) lock.write {
|
||||
templates = newTemplates
|
||||
templatesChanged = true
|
||||
}
|
||||
}
|
||||
if (templatesChanged) {
|
||||
ScriptDefinitionsManager.getInstance(project).reloadDefinitionsBy(this@ScriptTemplatesFromDependenciesProvider)
|
||||
}
|
||||
asyncRunUpdateScriptTemplates()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun getScriptTemplates(): TemplatesWithCp {
|
||||
val templates = LinkedHashSet<String>()
|
||||
val classpath = LinkedHashSet<File>()
|
||||
val templatesPath = "META-INF/kotlin/script/templates/"
|
||||
val processedRoots = hashSetOf<VirtualFile>()
|
||||
|
||||
fun addTemplatesFromRoot(vfile: VirtualFile): Boolean {
|
||||
var templatesFound = false
|
||||
val root = JarFileSystem.getInstance().getJarRootForLocalFile(vfile) ?: vfile
|
||||
if (root.isValid) {
|
||||
root.findFileByRelativePath(templatesPath)?.takeIf { it.isDirectory }?.children?.forEach {
|
||||
if (it.isValid && !it.isDirectory) {
|
||||
templates.add(it.name)
|
||||
templatesFound = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return templatesFound
|
||||
}
|
||||
|
||||
// processing source roots from the same project first since the resources are copied to the classes roots only on compilation
|
||||
project.allModules().forEach { module ->
|
||||
OrderEnumerator.orderEntries(module).withoutDepModules().withoutLibraries().withoutSdk().sourceRoots.forEach {
|
||||
if (addTemplatesFromRoot(it)) {
|
||||
classpath.addAll(OrderEnumerator.orderEntries(module).withoutSdk().classesRoots.mapNotNull {
|
||||
it.canonicalPath?.let { File(it.removeSuffix("!/")) }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
project.allModules().forEach { module ->
|
||||
// assuming that all libraries are placed into classes roots
|
||||
// TODO: extract exact library dependencies instead of putting all module dependencies into classpath
|
||||
OrderEnumerator.orderEntries(module).withoutDepModules().withoutSdk().classesRoots.forEach {
|
||||
if (addTemplatesFromRoot(it)) {
|
||||
// minimizing the classpath needed to use the template by taking cp only from modules with new templates found
|
||||
// on the other hand the approach may fail if some module contains a template without proper classpath, while
|
||||
// the other has properly configured classpath, so assuming that the dependencies are set correctly everywhere
|
||||
classpath.addAll(OrderEnumerator.orderEntries(module).withoutSdk().classesRoots.mapNotNull {
|
||||
it.canonicalPath?.let { File(it.removeSuffix("!/")) }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return TemplatesWithCp(templates.toList(), classpath.toList())
|
||||
}
|
||||
|
||||
override val id = "ScriptTemplatesFromDependenciesProvider"
|
||||
|
||||
override fun getDefinitions(): List<KotlinScriptDefinition> {
|
||||
lock.read {
|
||||
val templatesCopy = templatesLock.read {
|
||||
if (templates == null) {
|
||||
val newTemplates = getScriptTemplates()
|
||||
lock.write {
|
||||
templates = newTemplates
|
||||
syncUpdateScriptTemplates(false)
|
||||
}
|
||||
templates ?: return emptyList()
|
||||
}
|
||||
return loadDefinitionsFromTemplates(
|
||||
templateClassNames = templatesCopy.templates,
|
||||
templateClasspath = templatesCopy.classpath,
|
||||
environment = mapOf(
|
||||
"projectRoot" to (project.basePath ?: project.baseDir.canonicalPath)?.let(::File)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private var areRootsChanged = false
|
||||
private var backgroundTask: TemplatesCollectorBackgroundTask? = null
|
||||
private val backgroundTaskLock = ReentrantReadWriteLock()
|
||||
|
||||
private inner class TemplatesCollectorBackgroundTask
|
||||
: Task.Backgroundable(project, "Kotlin: scanning dependencies for script definitions...", true) {
|
||||
|
||||
override fun run(indicator: ProgressIndicator) {
|
||||
while (true) {
|
||||
backgroundTaskLock.read {
|
||||
if (indicator.isCanceled || !areRootsChanged) {
|
||||
backgroundTaskLock.write {
|
||||
backgroundTask = null
|
||||
}
|
||||
return
|
||||
}
|
||||
areRootsChanged = false
|
||||
}
|
||||
if (syncUpdateScriptTemplates(true)) {
|
||||
ScriptDefinitionsManager.getInstance(project).reloadDefinitionsBy(this@ScriptTemplatesFromDependenciesProvider)
|
||||
}
|
||||
}
|
||||
}
|
||||
return loadDefinitionsFromTemplates(
|
||||
templateClassNames = templates!!.templates,
|
||||
templateClasspath = templates!!.classpath,
|
||||
environment = mapOf(
|
||||
"projectRoot" to (project.basePath ?: project.baseDir.canonicalPath)?.let(::File))
|
||||
)
|
||||
}
|
||||
|
||||
private fun asyncRunUpdateScriptTemplates() {
|
||||
backgroundTaskLock.write {
|
||||
areRootsChanged = true
|
||||
if (backgroundTask == null) {
|
||||
backgroundTask = TemplatesCollectorBackgroundTask()
|
||||
backgroundTask!!.queue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun syncUpdateScriptTemplates(force: Boolean): Boolean {
|
||||
val wasRunning = templatesLock.isWriteLocked
|
||||
templatesLock.write {
|
||||
if (wasRunning && !force && templates != null) return true
|
||||
val newTemplates = scriptDefinitionsFromDependencies(project)
|
||||
if (newTemplates != templates) {
|
||||
templates = newTemplates
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class TemplatesWithCp(
|
||||
val templates: List<String>,
|
||||
val classpath: List<File>
|
||||
)
|
||||
|
||||
private fun scriptDefinitionsFromDependencies(project: Project): TemplatesWithCp {
|
||||
val templates = LinkedHashSet<String>()
|
||||
val classpath = LinkedHashSet<File>()
|
||||
val templatesPath = "META-INF/kotlin/script/templates/"
|
||||
|
||||
fun addTemplatesFromRoot(vfile: VirtualFile): Boolean {
|
||||
var templatesFound = false
|
||||
val root = JarFileSystem.getInstance().getJarRootForLocalFile(vfile) ?: vfile
|
||||
if (root.isValid) {
|
||||
root.findFileByRelativePath(templatesPath)?.takeIf { it.isDirectory }?.children?.forEach {
|
||||
if (it.isValid && !it.isDirectory) {
|
||||
templates.add(it.name)
|
||||
templatesFound = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return templatesFound
|
||||
}
|
||||
|
||||
// processing source roots from the same project first since the resources are copied to the classes roots only on compilation
|
||||
project.allModules().forEach { module ->
|
||||
OrderEnumerator.orderEntries(module).withoutDepModules().withoutLibraries().withoutSdk().sourceRoots.forEach { root ->
|
||||
if (addTemplatesFromRoot(root)) {
|
||||
classpath.addAll(OrderEnumerator.orderEntries(module).withoutSdk().classesRoots.mapNotNull {
|
||||
it.canonicalPath?.removeSuffix("!/").let(::File)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
project.allModules().forEach { module ->
|
||||
// assuming that all libraries are placed into classes roots
|
||||
// TODO: extract exact library dependencies instead of putting all module dependencies into classpath
|
||||
OrderEnumerator.orderEntries(module).withoutDepModules().withoutSdk().classesRoots.forEach { root ->
|
||||
if (addTemplatesFromRoot(root)) {
|
||||
// minimizing the classpath needed to use the template by taking cp only from modules with new templates found
|
||||
// on the other hand the approach may fail if some module contains a template without proper classpath, while
|
||||
// the other has properly configured classpath, so assuming that the dependencies are set correctly everywhere
|
||||
classpath.addAll(OrderEnumerator.orderEntries(module).withoutSdk().classesRoots.mapNotNull {
|
||||
it.canonicalPath?.removeSuffix("!/").let(::File)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return TemplatesWithCp(templates.toList(), classpath.toList())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user