Revert due to test failing in 201
This commit is contained in:
@@ -95,7 +95,7 @@ private val constantMap = ConcurrentHashMap<Function0<*>, Any>()
|
|||||||
fun String.indexOfOrNull(char: Char, startIndex: Int = 0, ignoreCase: Boolean = false): Int? =
|
fun String.indexOfOrNull(char: Char, startIndex: Int = 0, ignoreCase: Boolean = false): Int? =
|
||||||
indexOf(char, startIndex, ignoreCase).takeIf { it >= 0 }
|
indexOf(char, startIndex, ignoreCase).takeIf { it >= 0 }
|
||||||
|
|
||||||
fun String.lastIndexOfOrNull(char: Char, startIndex: Int = lastIndex, ignoreCase: Boolean = false): Int? =
|
fun String.lastIndexOfOrNull(char: Char, startIndex: Int = 0, ignoreCase: Boolean = false): Int? =
|
||||||
lastIndexOf(char, startIndex, ignoreCase).takeIf { it >= 0 }
|
lastIndexOf(char, startIndex, ignoreCase).takeIf { it >= 0 }
|
||||||
|
|
||||||
inline fun <T, R : Any> Iterable<T>.firstNotNullResult(transform: (T) -> R?): R? {
|
inline fun <T, R : Any> Iterable<T>.firstNotNullResult(transform: (T) -> R?): R? {
|
||||||
|
|||||||
+21
-8
@@ -27,6 +27,8 @@ import com.intellij.psi.search.GlobalSearchScope
|
|||||||
import com.intellij.util.io.URLUtil
|
import com.intellij.util.io.URLUtil
|
||||||
import org.jetbrains.annotations.TestOnly
|
import org.jetbrains.annotations.TestOnly
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.CompositeScriptConfigurationManager
|
import org.jetbrains.kotlin.idea.core.script.configuration.CompositeScriptConfigurationManager
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoader
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.psi.UserDataProperty
|
import org.jetbrains.kotlin.psi.UserDataProperty
|
||||||
import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider
|
import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider
|
||||||
@@ -67,6 +69,12 @@ interface ScriptConfigurationManager {
|
|||||||
*/
|
*/
|
||||||
fun getConfiguration(file: KtFile): ScriptCompilationConfigurationWrapper?
|
fun getConfiguration(file: KtFile): ScriptCompilationConfigurationWrapper?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload the configuration for [file] even it is already loaded.
|
||||||
|
* [loader] is used to load configuration. Other loaders aren't taken into account.
|
||||||
|
*/
|
||||||
|
fun forceReloadConfiguration(file: VirtualFile, loader: ScriptConfigurationLoader): ScriptCompilationConfigurationWrapper?
|
||||||
|
|
||||||
@Deprecated("Use getScriptClasspath(KtFile) instead")
|
@Deprecated("Use getScriptClasspath(KtFile) instead")
|
||||||
fun getScriptClasspath(file: VirtualFile): List<VirtualFile>
|
fun getScriptClasspath(file: VirtualFile): List<VirtualFile>
|
||||||
|
|
||||||
@@ -90,9 +98,14 @@ interface ScriptConfigurationManager {
|
|||||||
fun isConfigurationLoadingInProgress(file: KtFile): Boolean
|
fun isConfigurationLoadingInProgress(file: KtFile): Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update caches that depends on script definitions and do update if necessary
|
* See [ScriptConfigurationUpdater].
|
||||||
*/
|
*/
|
||||||
fun updateScriptDefinitionReferences()
|
val updater: ScriptConfigurationUpdater
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all caches and re-highlighting opened scripts
|
||||||
|
*/
|
||||||
|
fun clearConfigurationCachesAndRehighlight()
|
||||||
|
|
||||||
///////////////
|
///////////////
|
||||||
// classpath roots info:
|
// classpath roots info:
|
||||||
@@ -113,14 +126,14 @@ interface ScriptConfigurationManager {
|
|||||||
ServiceManager.getService(project, ScriptConfigurationManager::class.java)
|
ServiceManager.getService(project, ScriptConfigurationManager::class.java)
|
||||||
|
|
||||||
fun toVfsRoots(roots: Iterable<File>): List<VirtualFile> {
|
fun toVfsRoots(roots: Iterable<File>): List<VirtualFile> {
|
||||||
return roots.mapNotNull { classpathEntryToVfs(it) }
|
return roots.mapNotNull { it.classpathEntryToVfs() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun classpathEntryToVfs(file: File): VirtualFile? {
|
private fun File.classpathEntryToVfs(): VirtualFile? {
|
||||||
val res = when {
|
val res = when {
|
||||||
!file.exists() -> null
|
!exists() -> null
|
||||||
file.isDirectory -> StandardFileSystems.local()?.findFileByPath(file.canonicalPath)
|
isDirectory -> StandardFileSystems.local()?.findFileByPath(this.canonicalPath)
|
||||||
file.isFile -> StandardFileSystems.jar()?.findFileByPath(file.canonicalPath + URLUtil.JAR_SEPARATOR)
|
isFile -> StandardFileSystems.jar()?.findFileByPath(this.canonicalPath + URLUtil.JAR_SEPARATOR)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
// TODO: report this somewhere, but do not throw: assert(res != null, { "Invalid classpath entry '$this': exists: ${exists()}, is directory: $isDirectory, is file: $isFile" })
|
// TODO: report this somewhere, but do not throw: assert(res != null, { "Invalid classpath entry '$this': exists: ${exists()}, is directory: $isDirectory, is file: $isFile" })
|
||||||
@@ -137,7 +150,7 @@ interface ScriptConfigurationManager {
|
|||||||
@TestOnly
|
@TestOnly
|
||||||
fun clearCaches(project: Project) {
|
fun clearCaches(project: Project) {
|
||||||
(getInstance(project) as CompositeScriptConfigurationManager).default
|
(getInstance(project) as CompositeScriptConfigurationManager).default
|
||||||
.updateScriptDefinitions()
|
.clearCaches()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearManualConfigurationLoadingIfNeeded(file: VirtualFile) {
|
fun clearManualConfigurationLoadingIfNeeded(file: VirtualFile) {
|
||||||
|
|||||||
+1
-8
@@ -27,7 +27,6 @@ import com.intellij.util.containers.SLRUMap
|
|||||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||||
import org.jetbrains.kotlin.idea.caches.project.SdkInfo
|
import org.jetbrains.kotlin.idea.caches.project.SdkInfo
|
||||||
import org.jetbrains.kotlin.idea.caches.project.getScriptRelatedModuleInfo
|
import org.jetbrains.kotlin.idea.caches.project.getScriptRelatedModuleInfo
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.CompositeScriptConfigurationManager
|
|
||||||
import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings
|
import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings
|
||||||
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
|
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
|
||||||
import org.jetbrains.kotlin.idea.util.getProjectJdkTableSafe
|
import org.jetbrains.kotlin.idea.util.getProjectJdkTableSafe
|
||||||
@@ -65,15 +64,9 @@ class ScriptDefinitionsManager(private val project: Project) : LazyScriptDefinit
|
|||||||
private val scriptDefinitionsCacheLock = ReentrantLock()
|
private val scriptDefinitionsCacheLock = ReentrantLock()
|
||||||
private val scriptDefinitionsCache = SLRUMap<String, ScriptDefinition>(10, 10)
|
private val scriptDefinitionsCache = SLRUMap<String, ScriptDefinition>(10, 10)
|
||||||
|
|
||||||
val configurations = (ScriptConfigurationManager.getInstance(project) as CompositeScriptConfigurationManager)
|
|
||||||
|
|
||||||
override fun findDefinition(script: SourceCode): ScriptDefinition? {
|
override fun findDefinition(script: SourceCode): ScriptDefinition? {
|
||||||
val locationId = script.locationId ?: return null
|
val locationId = script.locationId ?: return null
|
||||||
if (nonScriptId(locationId)) return null
|
if (nonScriptId(locationId)) return null
|
||||||
|
|
||||||
val fastPath = configurations.tryGetScriptDefinitionFast(locationId)
|
|
||||||
if (fastPath != null) return fastPath
|
|
||||||
|
|
||||||
if (!isReady()) return null
|
if (!isReady()) return null
|
||||||
|
|
||||||
val cached = scriptDefinitionsCacheLock.withLock { scriptDefinitionsCache.get(locationId) }
|
val cached = scriptDefinitionsCacheLock.withLock { scriptDefinitionsCache.get(locationId) }
|
||||||
@@ -197,7 +190,7 @@ class ScriptDefinitionsManager(private val project: Project) : LazyScriptDefinit
|
|||||||
scriptDefinitionsCacheLock.withLock { scriptDefinitionsCache.clear() }
|
scriptDefinitionsCacheLock.withLock { scriptDefinitionsCache.clear() }
|
||||||
|
|
||||||
// TODO: clear by script type/definition
|
// TODO: clear by script type/definition
|
||||||
ScriptConfigurationManager.getInstance(project).updateScriptDefinitionReferences()
|
ScriptConfigurationManager.getInstance(project).clearConfigurationCachesAndRehighlight()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ScriptDefinitionsSource.safeGetDefinitions(): List<ScriptDefinition> {
|
private fun ScriptDefinitionsSource.safeGetDefinitions(): List<ScriptDefinition> {
|
||||||
|
|||||||
+76
-90
@@ -6,9 +6,6 @@
|
|||||||
package org.jetbrains.kotlin.idea.core.script.configuration
|
package org.jetbrains.kotlin.idea.core.script.configuration
|
||||||
|
|
||||||
import com.intellij.ProjectTopics
|
import com.intellij.ProjectTopics
|
||||||
import com.intellij.codeInsight.daemon.OutsidersPsiFileSupport
|
|
||||||
import com.intellij.openapi.diagnostic.logger
|
|
||||||
import com.intellij.openapi.progress.ProcessCanceledException
|
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.projectRoots.Sdk
|
import com.intellij.openapi.projectRoots.Sdk
|
||||||
import com.intellij.openapi.roots.ModuleRootEvent
|
import com.intellij.openapi.roots.ModuleRootEvent
|
||||||
@@ -18,106 +15,69 @@ import com.intellij.psi.search.GlobalSearchScope
|
|||||||
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
||||||
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker
|
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangesNotifier
|
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangesNotifier
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoader
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache
|
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsUpdater
|
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.utils.getKtFile
|
import org.jetbrains.kotlin.idea.core.script.configuration.utils.getKtFile
|
||||||
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.isNonScript
|
||||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||||
|
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
|
||||||
|
|
||||||
/**
|
|
||||||
* The [CompositeScriptConfigurationManager] will provide redirection of [ScriptConfigurationManager] calls to the
|
|
||||||
* custom [ScriptingSupport] or [DefaultScriptingSupport] if that script lack of custom [ScriptingSupport].
|
|
||||||
*
|
|
||||||
* The [ScriptConfigurationManager] is implemented by caching all scripts using the [ScriptClassRootsCache].
|
|
||||||
* The [ScriptClassRootsCache] is always available and never blacked by the writer, as all writes occurs
|
|
||||||
* using the copy-on-write strategy.
|
|
||||||
*
|
|
||||||
* This cache are loaded on start and will be updating asynchronously using the [updater].
|
|
||||||
* Sync updates still my be occurred from the [DefaultScriptingSupport].
|
|
||||||
*
|
|
||||||
* We are also watching all script documents:
|
|
||||||
* [notifier] will call first applicable [ScriptChangesNotifier.listeners] when editor is activated or document changed.
|
|
||||||
* Listener should do something to invalidate configuration and schedule reloading.
|
|
||||||
*/
|
|
||||||
class CompositeScriptConfigurationManager(val project: Project) : ScriptConfigurationManager {
|
class CompositeScriptConfigurationManager(val project: Project) : ScriptConfigurationManager {
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
private val notifier = ScriptChangesNotifier(project)
|
private val notifier = ScriptChangesNotifier(project, updater)
|
||||||
|
|
||||||
val updater = ScriptClassRootsUpdater(project, this)
|
private val providers = ScriptingSupport.Provider.EPN.getPoint(project).extensionList
|
||||||
|
|
||||||
private val plugins = ScriptingSupport.EPN.getPoint(project).extensionList
|
val default = DefaultScriptingSupport(project)
|
||||||
|
|
||||||
val default = DefaultScriptingSupport(this)
|
private val managers
|
||||||
|
get() = mutableListOf<ScriptingSupport>().also { managers ->
|
||||||
|
managers.add(default)
|
||||||
|
providers.forEach { managers.addAll(it.all) }
|
||||||
|
}
|
||||||
|
|
||||||
fun tryGetScriptDefinitionFast(locationId: String): ScriptDefinition? {
|
private fun getRelatedManager(file: VirtualFile): ScriptingSupport =
|
||||||
return classpathRoots.getLightScriptInfo(locationId)?.definition
|
providers.firstNotNullResult { it.getSupport(file) } ?: default
|
||||||
}
|
|
||||||
|
private fun getRelatedManager(file: KtFile): ScriptingSupport =
|
||||||
|
getRelatedManager(file.originalFile.virtualFile)
|
||||||
|
|
||||||
private fun getOrLoadConfiguration(
|
private fun getOrLoadConfiguration(
|
||||||
virtualFile: VirtualFile,
|
virtualFile: VirtualFile,
|
||||||
preloadedKtFile: KtFile? = null
|
preloadedKtFile: KtFile? = null
|
||||||
): ScriptCompilationConfigurationWrapper? {
|
): ScriptCompilationConfigurationWrapper? =
|
||||||
val scriptConfiguration = classpathRoots.getScriptConfiguration(virtualFile)
|
getRelatedManager(virtualFile).getOrLoadConfiguration(virtualFile, preloadedKtFile)
|
||||||
if (scriptConfiguration != null) return scriptConfiguration
|
|
||||||
|
|
||||||
// check that this script should be loaded later in special way (e.g. gradle project import)
|
override fun getConfiguration(file: KtFile) = getOrLoadConfiguration(file.originalFile.virtualFile, file)
|
||||||
// (and not for syntactic diff files)
|
|
||||||
if (!OutsidersPsiFileSupport.isOutsiderFile(virtualFile)) {
|
|
||||||
if (plugins.any { it.isApplicable(virtualFile) }) return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return default.getOrLoadConfiguration(virtualFile, preloadedKtFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getConfiguration(file: KtFile) =
|
|
||||||
getOrLoadConfiguration(file.originalFile.virtualFile, file)
|
|
||||||
|
|
||||||
override fun hasConfiguration(file: KtFile): Boolean =
|
override fun hasConfiguration(file: KtFile): Boolean =
|
||||||
classpathRoots.contains(file.originalFile.virtualFile)
|
getRelatedManager(file).hasCachedConfiguration(file)
|
||||||
|
|
||||||
override fun isConfigurationLoadingInProgress(file: KtFile): Boolean =
|
override fun isConfigurationLoadingInProgress(file: KtFile): Boolean =
|
||||||
plugins.firstOrNull { it.isApplicable(file.originalFile.virtualFile) }?.isConfigurationLoadingInProgress(file)
|
getRelatedManager(file).isConfigurationLoadingInProgress(file)
|
||||||
?: default.isConfigurationLoadingInProgress(file)
|
|
||||||
|
|
||||||
@Volatile
|
override val updater: ScriptConfigurationUpdater
|
||||||
private var classpathRoots: ScriptClassRootsCache = recreateRootsCache()
|
get() = object : ScriptConfigurationUpdater {
|
||||||
|
override fun ensureUpToDatedConfigurationSuggested(file: KtFile) =
|
||||||
|
getRelatedManager(file).updater.ensureUpToDatedConfigurationSuggested(file)
|
||||||
|
|
||||||
fun getLightScriptInfo(file: String): ScriptClassRootsCache.LightScriptInfo? =
|
override fun ensureConfigurationUpToDate(files: List<KtFile>): Boolean =
|
||||||
classpathRoots.getLightScriptInfo(file)
|
files.groupBy { getRelatedManager(it) }.all { (manager, files) ->
|
||||||
|
manager.updater.ensureConfigurationUpToDate(files)
|
||||||
|
}
|
||||||
|
|
||||||
private fun recreateRootsCache(): ScriptClassRootsCache {
|
override fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile) =
|
||||||
val builder = ScriptClassRootsCache.Builder(project)
|
getRelatedManager(file).updater.suggestToUpdateConfigurationIfOutOfDate(file)
|
||||||
default.collectConfigurations(builder)
|
|
||||||
plugins.forEach { it.collectConfigurations(builder) }
|
|
||||||
return builder.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun collectRootsAndCheckNew(): ScriptClassRootsCache.Updates {
|
|
||||||
val old = classpathRoots
|
|
||||||
val new = recreateRootsCache()
|
|
||||||
classpathRoots = new
|
|
||||||
return new.diff(old)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateScriptDefinitionReferences() {
|
|
||||||
ScriptDependenciesModificationTracker.getInstance(project).incModificationCount()
|
|
||||||
|
|
||||||
default.updateScriptDefinitions()
|
|
||||||
|
|
||||||
if (classpathRoots.customDefinitionsUsed) {
|
|
||||||
updater.ensureUpdateScheduled()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val connection = project.messageBus.connect(project)
|
val connection = project.messageBus.connect(project)
|
||||||
connection.subscribe(ProjectTopics.PROJECT_ROOTS, object : ModuleRootListener {
|
connection.subscribe(ProjectTopics.PROJECT_ROOTS, object : ModuleRootListener {
|
||||||
override fun rootsChanged(event: ModuleRootEvent) {
|
override fun rootsChanged(event: ModuleRootEvent) {
|
||||||
if (event.isCausedByFileTypesChange) return
|
managers.forEach {
|
||||||
|
it.clearClassRootsCaches(project)
|
||||||
if (classpathRoots.hasInvalidSdk(project)) {
|
|
||||||
updater.ensureUpdateScheduled()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -128,39 +88,49 @@ class CompositeScriptConfigurationManager(val project: Project) : ScriptConfigur
|
|||||||
* Loads script configuration if classpath roots don't contain [file] yet
|
* Loads script configuration if classpath roots don't contain [file] yet
|
||||||
*/
|
*/
|
||||||
private fun getActualClasspathRoots(file: VirtualFile): ScriptClassRootsCache {
|
private fun getActualClasspathRoots(file: VirtualFile): ScriptClassRootsCache {
|
||||||
try {
|
val manager = getRelatedManager(file)
|
||||||
// we should run default loader if this [file] is not cached in [classpathRoots]
|
|
||||||
// and it is not supported by any of [plugins]
|
val classpathRoots = manager.classpathRoots
|
||||||
// getOrLoadConfiguration will do this
|
if (classpathRoots.contains(file)) {
|
||||||
// (despite that it's result becomes unused, it still may populate [classpathRoots])
|
return classpathRoots
|
||||||
getOrLoadConfiguration(file, null)
|
|
||||||
} catch (cancelled: ProcessCanceledException) {
|
|
||||||
// read actions may be cancelled if we are called by impatient reader
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.classpathRoots
|
getOrLoadConfiguration(file)
|
||||||
|
|
||||||
|
return manager.classpathRoots
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getScriptSdk(file: VirtualFile): Sdk? =
|
override fun getScriptSdk(file: VirtualFile): Sdk? =
|
||||||
getActualClasspathRoots(file).getScriptSdk(file)
|
getActualClasspathRoots(file).getScriptSdk(file)
|
||||||
|
|
||||||
override fun getFirstScriptsSdk(): Sdk? =
|
override fun getFirstScriptsSdk(): Sdk? {
|
||||||
classpathRoots.firstScriptSdk
|
managers.forEach {
|
||||||
|
it.classpathRoots.firstScriptSdk?.let { sdk -> return sdk }
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
override fun getScriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope =
|
override fun getScriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope =
|
||||||
classpathRoots.getScriptDependenciesClassFilesScope(file)
|
getActualClasspathRoots(file).getScriptDependenciesClassFilesScope(file)
|
||||||
|
|
||||||
override fun getAllScriptsDependenciesClassFilesScope(): GlobalSearchScope =
|
override fun getAllScriptsDependenciesClassFilesScope(): GlobalSearchScope =
|
||||||
classpathRoots.allDependenciesClassFilesScope
|
GlobalSearchScope.union(managers.map { it.classpathRoots.allDependenciesClassFilesScope })
|
||||||
|
|
||||||
override fun getAllScriptDependenciesSourcesScope(): GlobalSearchScope =
|
override fun getAllScriptDependenciesSourcesScope(): GlobalSearchScope =
|
||||||
classpathRoots.allDependenciesSourcesScope
|
GlobalSearchScope.union(managers.map { it.classpathRoots.allDependenciesSourcesScope })
|
||||||
|
|
||||||
override fun getAllScriptsDependenciesClassFiles(): List<VirtualFile> =
|
override fun getAllScriptsDependenciesClassFiles(): List<VirtualFile> =
|
||||||
classpathRoots.allDependenciesClassFiles
|
managers.flatMap { it.classpathRoots.allDependenciesClassFiles }
|
||||||
|
|
||||||
override fun getAllScriptDependenciesSources(): List<VirtualFile> =
|
override fun getAllScriptDependenciesSources(): List<VirtualFile> =
|
||||||
classpathRoots.allDependenciesSources
|
managers.flatMap { it.classpathRoots.allDependenciesSources }
|
||||||
|
|
||||||
|
override fun forceReloadConfiguration(file: VirtualFile, loader: ScriptConfigurationLoader): ScriptCompilationConfigurationWrapper? {
|
||||||
|
val ktFile = project.getKtFile(file, null) ?: return null
|
||||||
|
|
||||||
|
return default.forceReloadConfiguration(ktFile, loader)
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////
|
///////////////////
|
||||||
// Adapters for deprecated API
|
// Adapters for deprecated API
|
||||||
@@ -174,4 +144,20 @@ class CompositeScriptConfigurationManager(val project: Project) : ScriptConfigur
|
|||||||
|
|
||||||
override fun getScriptClasspath(file: KtFile): List<VirtualFile> =
|
override fun getScriptClasspath(file: KtFile): List<VirtualFile> =
|
||||||
ScriptConfigurationManager.toVfsRoots(getConfiguration(file)?.dependenciesClassPath.orEmpty())
|
ScriptConfigurationManager.toVfsRoots(getConfiguration(file)?.dependenciesClassPath.orEmpty())
|
||||||
|
|
||||||
|
private fun clearCaches() {
|
||||||
|
managers.forEach {
|
||||||
|
it.clearCaches()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearConfigurationCachesAndRehighlight() {
|
||||||
|
ScriptDependenciesModificationTracker.getInstance(project).incModificationCount()
|
||||||
|
|
||||||
|
clearCaches()
|
||||||
|
|
||||||
|
ScriptingSupportHelper.updateHighlighting(project) {
|
||||||
|
!it.isNonScript()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+94
-191
@@ -20,6 +20,7 @@ import org.jetbrains.annotations.TestOnly
|
|||||||
import org.jetbrains.kotlin.idea.core.script.*
|
import org.jetbrains.kotlin.idea.core.script.*
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptConfigurationManagerExtensions.LOADER
|
import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptConfigurationManagerExtensions.LOADER
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.*
|
import org.jetbrains.kotlin.idea.core.script.configuration.cache.*
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.DefaultScriptConfigurationLoader
|
import org.jetbrains.kotlin.idea.core.script.configuration.loader.DefaultScriptConfigurationLoader
|
||||||
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
|
||||||
@@ -36,73 +37,21 @@ import java.util.concurrent.locks.ReentrantLock
|
|||||||
import kotlin.concurrent.withLock
|
import kotlin.concurrent.withLock
|
||||||
import kotlin.script.experimental.api.ScriptDiagnostic
|
import kotlin.script.experimental.api.ScriptDiagnostic
|
||||||
|
|
||||||
/**
|
class DefaultScriptingSupport(project: Project) : DefaultScriptingSupportBase(project) {
|
||||||
* Standard implementation of scripts configuration loading and caching.
|
|
||||||
*
|
|
||||||
* ## Loading initiation
|
|
||||||
*
|
|
||||||
* [getOrLoadConfiguration] will be called when we need to show or analyze some script file.
|
|
||||||
*
|
|
||||||
* As described in [DefaultScriptingSupportBase], configuration may be loaded from [cache]
|
|
||||||
* or [reloadOutOfDateConfiguration] will be called on [cache] miss.
|
|
||||||
*
|
|
||||||
* There are 2 tiers [cache]: memory and FS. For now FS cache implemented by [ScriptConfigurationLoader]
|
|
||||||
* because we are not storing classpath roots yet. As a workaround cache.all() will return only memory
|
|
||||||
* cached configurations. So, for now we are indexing roots that loaded from FS with
|
|
||||||
* default [reloadOutOfDateConfiguration] mechanics.
|
|
||||||
*
|
|
||||||
* Also, [ensureLoadedFromCache] may be called from [UnusedSymbolInspection]
|
|
||||||
* to ensure that configuration of all scripts containing some symbol are up-to-date or try load it in sync.
|
|
||||||
*
|
|
||||||
* ## Loading
|
|
||||||
*
|
|
||||||
* When requested, configuration will be loaded using first applicable [loaders].
|
|
||||||
* It can work synchronously or asynchronously.
|
|
||||||
*
|
|
||||||
* Synchronous loader will be called just immediately. Despite this, its result may not be applied immediately,
|
|
||||||
* see next section for details.
|
|
||||||
*
|
|
||||||
* Asynchronous loader will be called in background thread (by [BackgroundExecutor]).
|
|
||||||
*
|
|
||||||
* ## Applying
|
|
||||||
*
|
|
||||||
* By default loaded configuration will *not* be applied immediately. Instead, we show in editor notification
|
|
||||||
* that suggests user to apply changed configuration. This was done to avoid sporadically starting indexing of new roots,
|
|
||||||
* which may happens regularly for large Gradle projects.
|
|
||||||
*
|
|
||||||
* Notification will be displayed when configuration is going to be updated. First configuration will be loaded
|
|
||||||
* without notification.
|
|
||||||
*
|
|
||||||
* This behavior may be disabled by enabling "auto reload" in project settings.
|
|
||||||
* When enabled, all loaded configurations will be applied immediately, without any notification.
|
|
||||||
*
|
|
||||||
* ## Concurrency
|
|
||||||
*
|
|
||||||
* Each files may be in on of this state:
|
|
||||||
* - scriptDefinition is not ready
|
|
||||||
* - not loaded
|
|
||||||
* - up-to-date
|
|
||||||
* - invalid, in queue (in [BackgroundExecutor] queue)
|
|
||||||
* - invalid, loading
|
|
||||||
* - invalid, waiting for apply
|
|
||||||
*
|
|
||||||
* [reloadOutOfDateConfiguration] guard this states. See it's docs for more details.
|
|
||||||
*/
|
|
||||||
class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : DefaultScriptingSupportBase(manager) {
|
|
||||||
// TODO public for tests
|
// TODO public for tests
|
||||||
val backgroundExecutor: BackgroundExecutor =
|
val backgroundExecutor: BackgroundExecutor =
|
||||||
if (ApplicationManager.getApplication().isUnitTestMode) TestingBackgroundExecutor(manager.updater)
|
if (ApplicationManager.getApplication().isUnitTestMode) TestingBackgroundExecutor(rootsIndexer)
|
||||||
else DefaultBackgroundExecutor(project, manager.updater)
|
else DefaultBackgroundExecutor(project, rootsIndexer)
|
||||||
|
|
||||||
private val outsiderLoader = ScriptOutsiderFileConfigurationLoader(project)
|
private val outsiderLoader = ScriptOutsiderFileConfigurationLoader(project)
|
||||||
private val fileAttributeCache = ScriptConfigurationFileAttributeCache(project)
|
private val fileAttributeCache = ScriptConfigurationFileAttributeCache(project)
|
||||||
private val defaultLoader = DefaultScriptConfigurationLoader(project)
|
private val defaultLoader = DefaultScriptConfigurationLoader(project)
|
||||||
private val loaders: List<ScriptConfigurationLoader>
|
private val loaders: Sequence<ScriptConfigurationLoader>
|
||||||
get() = mutableListOf<ScriptConfigurationLoader>().apply {
|
get() = sequence {
|
||||||
add(outsiderLoader)
|
yield(outsiderLoader)
|
||||||
add(fileAttributeCache)
|
yield(fileAttributeCache)
|
||||||
addAll(LOADER.getPoint(project).extensionList)
|
yieldAll(LOADER.getPoint(project).extensionList)
|
||||||
add(defaultLoader)
|
yield(defaultLoader)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val saveLock = ReentrantLock()
|
private val saveLock = ReentrantLock()
|
||||||
@@ -121,7 +70,7 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De
|
|||||||
*
|
*
|
||||||
* Each files may be in on of the states described below:
|
* Each files may be in on of the states described below:
|
||||||
* - scriptDefinition is not ready. `ScriptDefinitionsManager.getInstance(project).isReady() == false`.
|
* - scriptDefinition is not ready. `ScriptDefinitionsManager.getInstance(project).isReady() == false`.
|
||||||
* [updateScriptDefinitions] will be called when [ScriptDefinitionsManager] will be ready
|
* [clearConfigurationCachesAndRehighlight] will be called when [ScriptDefinitionsManager] will be ready
|
||||||
* which will call [reloadOutOfDateConfiguration] for opened editors.
|
* which will call [reloadOutOfDateConfiguration] for opened editors.
|
||||||
* - unknown. When [isFirstLoad] true (`cache[file] == null`).
|
* - unknown. When [isFirstLoad] true (`cache[file] == null`).
|
||||||
* - up-to-date. `cache[file]?.upToDate == true`.
|
* - up-to-date. `cache[file]?.upToDate == true`.
|
||||||
@@ -155,39 +104,36 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De
|
|||||||
override fun reloadOutOfDateConfiguration(
|
override fun reloadOutOfDateConfiguration(
|
||||||
file: KtFile,
|
file: KtFile,
|
||||||
isFirstLoad: Boolean,
|
isFirstLoad: Boolean,
|
||||||
|
loadEvenWillNotBeApplied: Boolean,
|
||||||
forceSync: Boolean,
|
forceSync: Boolean,
|
||||||
isPostponedLoad: Boolean,
|
isPostponedLoad: Boolean
|
||||||
fromCacheOnly: Boolean
|
) {
|
||||||
): Boolean {
|
val virtualFile = file.originalFile.virtualFile ?: return
|
||||||
val virtualFile = file.originalFile.virtualFile ?: return false
|
|
||||||
|
|
||||||
if (!ScriptDefinitionsManager.getInstance(project).isReady()) return false
|
val autoReloadEnabled = KotlinScriptingSettings.getInstance(project).isAutoReloadEnabled
|
||||||
val scriptDefinition = file.findScriptDefinition() ?: return false
|
val shouldLoad = isFirstLoad || loadEvenWillNotBeApplied || autoReloadEnabled
|
||||||
|
if (!shouldLoad) return
|
||||||
|
|
||||||
|
val postponeLoading = isPostponedLoad && !autoReloadEnabled && !isFirstLoad
|
||||||
|
|
||||||
|
if (!ScriptDefinitionsManager.getInstance(project).isReady()) return
|
||||||
|
val scriptDefinition = file.findScriptDefinition() ?: return
|
||||||
|
|
||||||
val (async, sync) = loaders.partition { it.shouldRunInBackground(scriptDefinition) }
|
val (async, sync) = loaders.partition { it.shouldRunInBackground(scriptDefinition) }
|
||||||
|
|
||||||
val syncLoader = sync.firstOrNull { it.loadDependencies(isFirstLoad, file, scriptDefinition, loadingContext) }
|
val syncLoader = sync.firstOrNull { it.loadDependencies(isFirstLoad, file, scriptDefinition, loadingContext) }
|
||||||
if (syncLoader == null) {
|
if (syncLoader == null) {
|
||||||
if (!fromCacheOnly) {
|
if (forceSync) {
|
||||||
if (forceSync) {
|
loaders.firstOrNull { it.loadDependencies(isFirstLoad, file, scriptDefinition, loadingContext) }
|
||||||
loaders.firstOrNull { it.loadDependencies(isFirstLoad, file, scriptDefinition, loadingContext) }
|
} else {
|
||||||
} else {
|
if (postponeLoading) {
|
||||||
val autoReloadEnabled = KotlinScriptingSettings.getInstance(project).isAutoReloadEnabled
|
LoadScriptConfigurationNotificationFactory.showNotification(virtualFile, project) {
|
||||||
val postponeLoading = isPostponedLoad && !autoReloadEnabled
|
runAsyncLoaders(file, virtualFile, scriptDefinition, async, isLoadingPostponed = true)
|
||||||
|
|
||||||
if (postponeLoading) {
|
|
||||||
LoadScriptConfigurationNotificationFactory.showNotification(virtualFile, project) {
|
|
||||||
runAsyncLoaders(file, virtualFile, scriptDefinition, async, isLoadingPostponed = true)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
runAsyncLoaders(file, virtualFile, scriptDefinition, async, isLoadingPostponed = false)
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
runAsyncLoaders(file, virtualFile, scriptDefinition, async, isLoadingPostponed = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +150,7 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De
|
|||||||
val applied = cached?.applied
|
val applied = cached?.applied
|
||||||
if (applied != null && applied.inputs.isUpToDate(project, virtualFile)) {
|
if (applied != null && applied.inputs.isUpToDate(project, virtualFile)) {
|
||||||
// in case user reverted to applied configuration
|
// in case user reverted to applied configuration
|
||||||
suggestOrSaveConfiguration(virtualFile, applied, skipNotification = isLoadingPostponed)
|
suggestOrSaveConfiguration(virtualFile, applied, isLoadingPostponed)
|
||||||
} else if (cached == null || !cached.isUpToDate(project, virtualFile)) {
|
} else if (cached == null || !cached.isUpToDate(project, virtualFile)) {
|
||||||
// don't start loading if nothing was changed
|
// don't start loading if nothing was changed
|
||||||
// (in case we checking for up-to-date and loading concurrently)
|
// (in case we checking for up-to-date and loading concurrently)
|
||||||
@@ -220,7 +166,7 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De
|
|||||||
if (!ScriptDefinitionsManager.getInstance(project).isReady()) return null
|
if (!ScriptDefinitionsManager.getInstance(project).isReady()) return null
|
||||||
val scriptDefinition = file.findScriptDefinition() ?: return null
|
val scriptDefinition = file.findScriptDefinition() ?: return null
|
||||||
|
|
||||||
manager.updater.update {
|
rootsIndexer.transaction {
|
||||||
if (!loader.shouldRunInBackground(scriptDefinition)) {
|
if (!loader.shouldRunInBackground(scriptDefinition)) {
|
||||||
loader.loadDependencies(false, file, scriptDefinition, loadingContext)
|
loader.loadDependencies(false, file, scriptDefinition, loadingContext)
|
||||||
} else {
|
} else {
|
||||||
@@ -234,16 +180,8 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val loadingContext = object : ScriptConfigurationLoadingContext {
|
private val loadingContext = object : ScriptConfigurationLoadingContext {
|
||||||
/**
|
|
||||||
* Used from [ScriptOutsiderFileConfigurationLoader] only.
|
|
||||||
*/
|
|
||||||
override fun getCachedConfiguration(file: VirtualFile): ScriptConfigurationSnapshot? =
|
override fun getCachedConfiguration(file: VirtualFile): ScriptConfigurationSnapshot? =
|
||||||
getAppliedConfiguration(file) ?: getFromGlobalCache(file)
|
getAppliedConfiguration(file)
|
||||||
|
|
||||||
private fun getFromGlobalCache(file: VirtualFile): ScriptConfigurationSnapshot? {
|
|
||||||
val info = manager.getLightScriptInfo(file.path) ?: return null
|
|
||||||
return ScriptConfigurationSnapshot(CachedConfigurationInputs.UpToDate, listOf(), info.buildConfiguration())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun suggestNewConfiguration(file: VirtualFile, newResult: ScriptConfigurationSnapshot) {
|
override fun suggestNewConfiguration(file: VirtualFile, newResult: ScriptConfigurationSnapshot) {
|
||||||
suggestOrSaveConfiguration(file, newResult, false)
|
suggestOrSaveConfiguration(file, newResult, false)
|
||||||
@@ -286,7 +224,7 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De
|
|||||||
file.removeScriptDependenciesNotificationPanel(project)
|
file.removeScriptDependenciesNotificationPanel(project)
|
||||||
}
|
}
|
||||||
saveReports(file, newResult.reports)
|
saveReports(file, newResult.reports)
|
||||||
setAppliedConfiguration(file, newResult, syncUpdate = true)
|
setAppliedConfiguration(file, newResult)
|
||||||
} else {
|
} else {
|
||||||
debug(file) {
|
debug(file) {
|
||||||
"configuration changed, notification is shown: old = $oldConfiguration, new = $newConfiguration"
|
"configuration changed, notification is shown: old = $oldConfiguration, new = $newConfiguration"
|
||||||
@@ -302,7 +240,7 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De
|
|||||||
onClick = {
|
onClick = {
|
||||||
saveReports(file, newResult.reports)
|
saveReports(file, newResult.reports)
|
||||||
file.removeScriptDependenciesNotificationPanel(project)
|
file.removeScriptDependenciesNotificationPanel(project)
|
||||||
manager.updater.update {
|
rootsIndexer.transaction {
|
||||||
setAppliedConfiguration(file, newResult)
|
setAppliedConfiguration(file, newResult)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -334,28 +272,10 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun getInstance(project: Project) =
|
|
||||||
(ScriptConfigurationManager.getInstance(project) as CompositeScriptConfigurationManager).default
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
abstract class DefaultScriptingSupportBase(val project: Project) : ScriptingSupport() {
|
||||||
* Abstraction for [DefaultScriptingSupportBase] based [cache] and [reloadOutOfDateConfiguration].
|
protected val rootsIndexer = ScriptClassRootsIndexer(project)
|
||||||
* Among this two methods concrete implementation should provide script changes listening.
|
|
||||||
*
|
|
||||||
* Basically all requests routed to [cache]. If there is no entry in [cache] or it is considered out-of-date,
|
|
||||||
* then [reloadOutOfDateConfiguration] will be called, which, in turn, should call [setAppliedConfiguration]
|
|
||||||
* immediately or in some future (e.g. after user will click "apply context" or/and configuration will
|
|
||||||
* be calculated by some background thread).
|
|
||||||
*
|
|
||||||
* [ScriptClassRootsCache] will be calculated based on [cache]d configurations.
|
|
||||||
* Every change in [cache] will invalidate [ScriptClassRootsCache] cache.
|
|
||||||
*/
|
|
||||||
abstract class DefaultScriptingSupportBase(val manager: CompositeScriptConfigurationManager) {
|
|
||||||
val project: Project
|
|
||||||
get() = manager.project
|
|
||||||
|
|
||||||
@Suppress("LeakingThis")
|
@Suppress("LeakingThis")
|
||||||
protected val cache: ScriptConfigurationCache = createCache()
|
protected val cache: ScriptConfigurationCache = createCache()
|
||||||
@@ -369,18 +289,19 @@ abstract class DefaultScriptingSupportBase(val manager: CompositeScriptConfigura
|
|||||||
* (e.g. after user will click "apply context" or/and configuration will be calculated by some background thread).
|
* (e.g. after user will click "apply context" or/and configuration will be calculated by some background thread).
|
||||||
*
|
*
|
||||||
* @param isFirstLoad may be set explicitly for optimization reasons (to avoid expensive fs cache access)
|
* @param isFirstLoad may be set explicitly for optimization reasons (to avoid expensive fs cache access)
|
||||||
|
* @param loadEvenWillNotBeApplied may should be set to false only on requests from particular editor, when
|
||||||
|
* user can see potential notification and accept new configuration. In other cases this should be `false` since
|
||||||
|
* loaded configuration will be just leaved in hidden user notification and cannot be used in any way.
|
||||||
* @param forceSync should be used in tests only
|
* @param forceSync should be used in tests only
|
||||||
* @param isPostponedLoad is used to postspone loading: show a notification for out of date script and start loading when user request
|
* @param isPostponedLoad is used to postspone loading: show a notification for out of date script and start loading when user request
|
||||||
* @param fromCacheOnly load only when builtin fast synchronous loaders are available
|
|
||||||
* @return true, if configuration loaded in sync
|
|
||||||
*/
|
*/
|
||||||
protected abstract fun reloadOutOfDateConfiguration(
|
protected abstract fun reloadOutOfDateConfiguration(
|
||||||
file: KtFile,
|
file: KtFile,
|
||||||
isFirstLoad: Boolean = getAppliedConfiguration(file.originalFile.virtualFile) == null,
|
isFirstLoad: Boolean = getAppliedConfiguration(file.originalFile.virtualFile) == null,
|
||||||
|
loadEvenWillNotBeApplied: Boolean = false,
|
||||||
forceSync: Boolean = false,
|
forceSync: Boolean = false,
|
||||||
isPostponedLoad: Boolean = false,
|
isPostponedLoad: Boolean = false
|
||||||
fromCacheOnly: Boolean = false
|
)
|
||||||
): Boolean
|
|
||||||
|
|
||||||
fun getCachedConfigurationState(file: VirtualFile?): ScriptConfigurationState? {
|
fun getCachedConfigurationState(file: VirtualFile?): ScriptConfigurationState? {
|
||||||
if (file == null) return null
|
if (file == null) return null
|
||||||
@@ -390,14 +311,14 @@ abstract class DefaultScriptingSupportBase(val manager: CompositeScriptConfigura
|
|||||||
fun getAppliedConfiguration(file: VirtualFile?): ScriptConfigurationSnapshot? =
|
fun getAppliedConfiguration(file: VirtualFile?): ScriptConfigurationSnapshot? =
|
||||||
getCachedConfigurationState(file)?.applied
|
getCachedConfigurationState(file)?.applied
|
||||||
|
|
||||||
private fun hasCachedConfiguration(file: KtFile): Boolean =
|
override fun hasCachedConfiguration(file: KtFile): Boolean =
|
||||||
getAppliedConfiguration(file.originalFile.virtualFile) != null
|
getAppliedConfiguration(file.originalFile.virtualFile) != null
|
||||||
|
|
||||||
fun isConfigurationLoadingInProgress(file: KtFile): Boolean {
|
override fun isConfigurationLoadingInProgress(file: KtFile): Boolean {
|
||||||
return !hasCachedConfiguration(file) && !ScriptConfigurationManager.isManualConfigurationLoading(file.originalFile.virtualFile)
|
return !hasCachedConfiguration(file) && !ScriptConfigurationManager.isManualConfigurationLoading(file.originalFile.virtualFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOrLoadConfiguration(
|
override fun getOrLoadConfiguration(
|
||||||
virtualFile: VirtualFile,
|
virtualFile: VirtualFile,
|
||||||
preloadedKtFile: KtFile?
|
preloadedKtFile: KtFile?
|
||||||
): ScriptCompilationConfigurationWrapper? {
|
): ScriptCompilationConfigurationWrapper? {
|
||||||
@@ -405,86 +326,73 @@ abstract class DefaultScriptingSupportBase(val manager: CompositeScriptConfigura
|
|||||||
if (cached != null) return cached.configuration
|
if (cached != null) return cached.configuration
|
||||||
|
|
||||||
val ktFile = project.getKtFile(virtualFile, preloadedKtFile) ?: return null
|
val ktFile = project.getKtFile(virtualFile, preloadedKtFile) ?: return null
|
||||||
manager.updater.update {
|
rootsIndexer.transaction {
|
||||||
reloadOutOfDateConfiguration(ktFile, isFirstLoad = true)
|
reloadOutOfDateConfiguration(ktFile, isFirstLoad = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return getAppliedConfiguration(virtualFile)?.configuration
|
return getAppliedConfiguration(virtualFile)?.configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override val updater: ScriptConfigurationUpdater = object : ScriptConfigurationUpdater {
|
||||||
* Load new configuration and suggest to apply it (only if it is changed)
|
override fun ensureUpToDatedConfigurationSuggested(file: KtFile) {
|
||||||
*/
|
reloadIfOutOfDate(listOf(file), loadEvenWillNotBeApplied = true, isPostponedLoad = false)
|
||||||
fun ensureUpToDatedConfigurationSuggested(file: KtFile) {
|
}
|
||||||
reloadIfOutOfDate(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
override fun ensureConfigurationUpToDate(files: List<KtFile>): Boolean {
|
||||||
* Show notification about changed script configuration with action to start loading it
|
return reloadIfOutOfDate(files, loadEvenWillNotBeApplied = false, isPostponedLoad = false)
|
||||||
*/
|
}
|
||||||
fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile) {
|
|
||||||
reloadIfOutOfDate(file, isPostponedLoad = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun reloadIfOutOfDate(file: KtFile, isPostponedLoad: Boolean = false) {
|
override fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile) {
|
||||||
if (!ScriptDefinitionsManager.getInstance(project).isReady()) return
|
reloadIfOutOfDate(listOf(file), loadEvenWillNotBeApplied = true, isPostponedLoad = true)
|
||||||
|
|
||||||
manager.updater.update {
|
|
||||||
val virtualFile = file.originalFile.virtualFile
|
|
||||||
if (virtualFile != null) {
|
|
||||||
val state = cache[virtualFile]
|
|
||||||
if (state == null || !state.isUpToDate(project, virtualFile, file)) {
|
|
||||||
reloadOutOfDateConfiguration(
|
|
||||||
file,
|
|
||||||
isFirstLoad = state == null,
|
|
||||||
isPostponedLoad = isPostponedLoad
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun reloadIfOutOfDate(files: List<KtFile>, loadEvenWillNotBeApplied: Boolean, isPostponedLoad: Boolean): Boolean {
|
||||||
* Ensure that any configuration for [files] is loaded from cache
|
|
||||||
*/
|
|
||||||
fun ensureLoadedFromCache(files: List<KtFile>): Boolean {
|
|
||||||
if (!ScriptDefinitionsManager.getInstance(project).isReady()) return false
|
if (!ScriptDefinitionsManager.getInstance(project).isReady()) return false
|
||||||
|
|
||||||
var allLoaded = true
|
var upToDate = true
|
||||||
manager.updater.update {
|
rootsIndexer.transaction {
|
||||||
files.forEach { file ->
|
files.forEach { file ->
|
||||||
val virtualFile = file.originalFile.virtualFile
|
val virtualFile = file.originalFile.virtualFile
|
||||||
if (virtualFile != null) {
|
if (virtualFile != null) {
|
||||||
val state = cache[virtualFile]
|
val state = cache[virtualFile]
|
||||||
if (state == null) {
|
if (state == null || !state.isUpToDate(project, virtualFile, file)) {
|
||||||
if (!reloadOutOfDateConfiguration(
|
upToDate = false
|
||||||
file,
|
reloadOutOfDateConfiguration(
|
||||||
isFirstLoad = true,
|
file,
|
||||||
fromCacheOnly = true
|
isFirstLoad = state == null,
|
||||||
)
|
loadEvenWillNotBeApplied = loadEvenWillNotBeApplied,
|
||||||
) {
|
isPostponedLoad = isPostponedLoad
|
||||||
allLoaded = false
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return allLoaded
|
return upToDate
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun setAppliedConfiguration(
|
protected open fun setAppliedConfiguration(
|
||||||
file: VirtualFile,
|
file: VirtualFile,
|
||||||
newConfigurationSnapshot: ScriptConfigurationSnapshot?,
|
newConfigurationSnapshot: ScriptConfigurationSnapshot?
|
||||||
syncUpdate: Boolean = false
|
|
||||||
) {
|
) {
|
||||||
manager.updater.checkInTransaction()
|
rootsIndexer.checkInTransaction()
|
||||||
val newConfiguration = newConfigurationSnapshot?.configuration
|
val newConfiguration = newConfigurationSnapshot?.configuration
|
||||||
debug(file) { "configuration changed = $newConfiguration" }
|
debug(file) { "configuration changed = $newConfiguration" }
|
||||||
|
|
||||||
if (newConfiguration != null) {
|
if (newConfiguration != null) {
|
||||||
|
if (hasNotCachedRoots(newConfiguration)) {
|
||||||
|
debug(file) { "new class roots found: $newConfiguration" }
|
||||||
|
rootsIndexer.markNewRoot()
|
||||||
|
}
|
||||||
|
|
||||||
cache.setApplied(file, newConfigurationSnapshot)
|
cache.setApplied(file, newConfigurationSnapshot)
|
||||||
manager.updater.invalidate(file, synchronous = syncUpdate)
|
|
||||||
|
clearClassRootsCaches(project)
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptingSupportHelper.updateHighlighting(project) {
|
||||||
|
it == file
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,6 +403,10 @@ abstract class DefaultScriptingSupportBase(val manager: CompositeScriptConfigura
|
|||||||
cache.setLoaded(file, configurationSnapshot)
|
cache.setLoaded(file, configurationSnapshot)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun hasNotCachedRoots(configuration: ScriptCompilationConfigurationWrapper): Boolean {
|
||||||
|
return classpathRoots.hasNotCachedRoots(DefaultClassRootsCache.extractRoots(project, configuration))
|
||||||
|
}
|
||||||
|
|
||||||
@TestOnly
|
@TestOnly
|
||||||
internal fun updateScriptDependenciesSynchronously(file: PsiFile) {
|
internal fun updateScriptDependenciesSynchronously(file: PsiFile) {
|
||||||
file.findScriptDefinition() ?: return
|
file.findScriptDefinition() ?: return
|
||||||
@@ -504,29 +416,20 @@ abstract class DefaultScriptingSupportBase(val manager: CompositeScriptConfigura
|
|||||||
val virtualFile = file.virtualFile
|
val virtualFile = file.virtualFile
|
||||||
if (cache[virtualFile]?.isUpToDate(project, virtualFile, file) == true) return
|
if (cache[virtualFile]?.isUpToDate(project, virtualFile, file) == true) return
|
||||||
|
|
||||||
manager.updater.update {
|
rootsIndexer.transaction {
|
||||||
reloadOutOfDateConfiguration(file, forceSync = true)
|
reloadOutOfDateConfiguration(file, forceSync = true, loadEvenWillNotBeApplied = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateScriptDefinitions() {
|
override fun clearCaches() {
|
||||||
cache.clear()
|
cache.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun collectConfigurations(builder: ScriptClassRootsCache.Builder) {
|
override fun recreateRootsCache(): ScriptClassRootsCache {
|
||||||
// todo: drop the hell below
|
return DefaultClassRootsCache(
|
||||||
// keep this one only:
|
project,
|
||||||
// cache.allApplied().forEach { (vFile, configuration) -> builder.add(vFile, configuration) }
|
cache.allApplied()
|
||||||
|
)
|
||||||
// own builder for saving to storage
|
|
||||||
val ownBuilder = ScriptClassRootsCache.Builder(project)
|
|
||||||
val rootsStorage = ScriptClassRootsStorage.getInstance(project)
|
|
||||||
|
|
||||||
rootsStorage.load(ownBuilder)
|
|
||||||
cache.allApplied().forEach { (vFile, configuration) -> ownBuilder.add(vFile, configuration) }
|
|
||||||
rootsStorage.save(ownBuilder)
|
|
||||||
|
|
||||||
builder.add(ownBuilder)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+65
-26
@@ -6,37 +6,76 @@
|
|||||||
package org.jetbrains.kotlin.idea.core.script.configuration
|
package org.jetbrains.kotlin.idea.core.script.configuration
|
||||||
|
|
||||||
import com.intellij.openapi.extensions.ExtensionPointName
|
import com.intellij.openapi.extensions.ExtensionPointName
|
||||||
|
import com.intellij.openapi.extensions.Extensions
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import com.intellij.psi.PsiElementFinder
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.KotlinScriptDependenciesClassFinder
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache
|
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener
|
import org.jetbrains.kotlin.idea.core.script.debug
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||||
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
|
import kotlin.concurrent.withLock
|
||||||
|
|
||||||
/**
|
|
||||||
* Extension point for overriding default Kotlin scripting support.
|
|
||||||
*
|
|
||||||
* Implementation should store script configuration internally (in memory and/or fs),
|
|
||||||
* and provide it inside [collectConfigurations] using the [ScriptClassRootsCache.LightScriptInfo].
|
|
||||||
* Custom data can be attached to [ScriptClassRootsCache.LightScriptInfo] and retrieved
|
|
||||||
* by calling [ScriptClassRootsCache.getLightScriptInfo].
|
|
||||||
*
|
|
||||||
* [ScriptChangeListener] can be used to listen script changes.
|
|
||||||
* [CompositeScriptConfigurationManager.updater] should be used to schedule configuration reloading.
|
|
||||||
*
|
|
||||||
* [isApplicable] should return true for files that is covered by that support.
|
|
||||||
*
|
|
||||||
* [isConfigurationLoadingInProgress] is used to pause analyzing.
|
|
||||||
*
|
|
||||||
* Long read: [idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/README.md].
|
|
||||||
*
|
|
||||||
* @sample GradleBuildRootsManager
|
|
||||||
*/
|
|
||||||
abstract class ScriptingSupport {
|
abstract class ScriptingSupport {
|
||||||
abstract fun isApplicable(file: VirtualFile): Boolean
|
abstract class Provider {
|
||||||
abstract fun isConfigurationLoadingInProgress(file: KtFile): Boolean
|
abstract val all: Collection<ScriptingSupport>
|
||||||
abstract fun collectConfigurations(builder: ScriptClassRootsCache.Builder)
|
|
||||||
|
|
||||||
companion object {
|
abstract fun getSupport(file: VirtualFile): ScriptingSupport?
|
||||||
val EPN: ExtensionPointName<ScriptingSupport> =
|
|
||||||
ExtensionPointName.create("org.jetbrains.kotlin.scripting.idea.scriptingSupport")
|
companion object {
|
||||||
|
val EPN: ExtensionPointName<Provider> =
|
||||||
|
ExtensionPointName.create("org.jetbrains.kotlin.scripting.idea.scriptingSupportProvider")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun clearCaches()
|
||||||
|
abstract fun hasCachedConfiguration(file: KtFile): Boolean
|
||||||
|
abstract fun isConfigurationLoadingInProgress(file: KtFile): Boolean
|
||||||
|
abstract fun getOrLoadConfiguration(virtualFile: VirtualFile, preloadedKtFile: KtFile? = null): ScriptCompilationConfigurationWrapper?
|
||||||
|
|
||||||
|
abstract val updater: ScriptConfigurationUpdater
|
||||||
|
|
||||||
|
private val classpathRootsLock = ReentrantLock()
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var _classpathRoots: ScriptClassRootsCache? = null
|
||||||
|
val classpathRoots: ScriptClassRootsCache
|
||||||
|
get() {
|
||||||
|
val value1 = _classpathRoots
|
||||||
|
if (value1 != null) return value1
|
||||||
|
|
||||||
|
classpathRootsLock.withLock {
|
||||||
|
val value2 = _classpathRoots
|
||||||
|
if (value2 != null) return value2
|
||||||
|
|
||||||
|
val value3 = recreateRootsCache()
|
||||||
|
value3.saveClassRootsToStorage()
|
||||||
|
_classpathRoots = value3
|
||||||
|
return value3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract fun recreateRootsCache(): ScriptClassRootsCache
|
||||||
|
|
||||||
|
fun clearClassRootsCaches(project: Project) {
|
||||||
|
debug { "class roots caches cleared" }
|
||||||
|
|
||||||
|
classpathRootsLock.withLock {
|
||||||
|
_classpathRoots = null
|
||||||
|
}
|
||||||
|
|
||||||
|
val kotlinScriptDependenciesClassFinder =
|
||||||
|
Extensions.getArea(project)
|
||||||
|
.getExtensionPoint(PsiElementFinder.EP_NAME).extensions
|
||||||
|
.filterIsInstance<KotlinScriptDependenciesClassFinder>()
|
||||||
|
.single()
|
||||||
|
|
||||||
|
kotlinScriptDependenciesClassFinder.clearCache()
|
||||||
|
|
||||||
|
ScriptDependenciesModificationTracker.getInstance(project).incModificationCount()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.core.script.configuration
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
|
||||||
|
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import com.intellij.psi.PsiManager
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.jetbrains.kotlin.idea.core.util.EDT
|
||||||
|
|
||||||
|
object ScriptingSupportHelper {
|
||||||
|
fun updateHighlighting(project: Project, filter: (VirtualFile) -> Boolean) {
|
||||||
|
if (!project.isOpen) return
|
||||||
|
|
||||||
|
val openFiles = FileEditorManager.getInstance(project).openFiles
|
||||||
|
val openedScripts = openFiles.filter { filter(it) }
|
||||||
|
|
||||||
|
if (openedScripts.isEmpty()) return
|
||||||
|
|
||||||
|
GlobalScope.launch(EDT(project)) {
|
||||||
|
if (project.isDisposed) return@launch
|
||||||
|
|
||||||
|
openedScripts.forEach {
|
||||||
|
PsiManager.getInstance(project).findFile(it)?.let { psiFile ->
|
||||||
|
DaemonCodeAnalyzer.getInstance(project).restart(psiFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-5
@@ -31,7 +31,7 @@ interface ScriptConfigurationCache {
|
|||||||
fun setApplied(file: VirtualFile, configurationSnapshot: ScriptConfigurationSnapshot)
|
fun setApplied(file: VirtualFile, configurationSnapshot: ScriptConfigurationSnapshot)
|
||||||
fun setLoaded(file: VirtualFile, configurationSnapshot: ScriptConfigurationSnapshot)
|
fun setLoaded(file: VirtualFile, configurationSnapshot: ScriptConfigurationSnapshot)
|
||||||
|
|
||||||
fun allApplied(): List<Pair<VirtualFile, ScriptCompilationConfigurationWrapper>>
|
fun allApplied(): Map<VirtualFile, ScriptCompilationConfigurationWrapper>
|
||||||
fun clear()
|
fun clear()
|
||||||
|
|
||||||
fun getAnyLoadedScript(): ScriptCompilationConfigurationWrapper?
|
fun getAnyLoadedScript(): ScriptCompilationConfigurationWrapper?
|
||||||
@@ -63,10 +63,6 @@ interface CachedConfigurationInputs: Serializable {
|
|||||||
override fun isUpToDate(project: Project, file: VirtualFile, ktFile: KtFile?): Boolean = false
|
override fun isUpToDate(project: Project, file: VirtualFile, ktFile: KtFile?): Boolean = false
|
||||||
}
|
}
|
||||||
|
|
||||||
object UpToDate: CachedConfigurationInputs {
|
|
||||||
override fun isUpToDate(project: Project, file: VirtualFile, ktFile: KtFile?): Boolean = true
|
|
||||||
}
|
|
||||||
|
|
||||||
data class PsiModificationStamp(
|
data class PsiModificationStamp(
|
||||||
val fileModificationStamp: Long,
|
val fileModificationStamp: Long,
|
||||||
val psiModificationStamp: Long
|
val psiModificationStamp: Long
|
||||||
|
|||||||
+3
-3
@@ -41,11 +41,11 @@ open class ScriptConfigurationMemoryCache(
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
override fun allApplied(): List<Pair<VirtualFile, ScriptCompilationConfigurationWrapper>> {
|
override fun allApplied(): Map<VirtualFile, ScriptCompilationConfigurationWrapper> {
|
||||||
val result = mutableListOf<Pair<VirtualFile, ScriptCompilationConfigurationWrapper>>()
|
val result = hashMapOf<VirtualFile, ScriptCompilationConfigurationWrapper>()
|
||||||
for ((file, configuration) in memoryCache.entrySet()) {
|
for ((file, configuration) in memoryCache.entrySet()) {
|
||||||
if (configuration.applied?.configuration != null) {
|
if (configuration.applied?.configuration != null) {
|
||||||
result.add(Pair(file, configuration.applied.configuration))
|
result[file] = configuration.applied.configuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|||||||
+4
-4
@@ -9,14 +9,14 @@ import com.intellij.openapi.vfs.VirtualFile
|
|||||||
import org.jetbrains.kotlin.scripting.definitions.isNonScript
|
import org.jetbrains.kotlin.scripting.definitions.isNonScript
|
||||||
|
|
||||||
open class DefaultScriptChangeListener(project: Project) : ScriptChangeListener(project) {
|
open class DefaultScriptChangeListener(project: Project) : ScriptChangeListener(project) {
|
||||||
override fun editorActivated(vFile: VirtualFile) {
|
override fun editorActivated(vFile: VirtualFile, updater: ScriptConfigurationUpdater) {
|
||||||
val file = getAnalyzableKtFileForScript(vFile) ?: return
|
val file = getAnalyzableKtFileForScript(vFile) ?: return
|
||||||
default.ensureUpToDatedConfigurationSuggested(file)
|
updater.ensureUpToDatedConfigurationSuggested(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun documentChanged(vFile: VirtualFile) {
|
override fun documentChanged(vFile: VirtualFile, updater: ScriptConfigurationUpdater) {
|
||||||
val file = getAnalyzableKtFileForScript(vFile) ?: return
|
val file = getAnalyzableKtFileForScript(vFile) ?: return
|
||||||
default.ensureUpToDatedConfigurationSuggested(file)
|
updater.ensureUpToDatedConfigurationSuggested(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isApplicable(vFile: VirtualFile): Boolean {
|
override fun isApplicable(vFile: VirtualFile): Boolean {
|
||||||
|
|||||||
+7
-8
@@ -9,21 +9,20 @@ import com.intellij.openapi.extensions.ExtensionPointName
|
|||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import com.intellij.psi.PsiManager
|
import com.intellij.psi.PsiManager
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptingSupport
|
|
||||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||||
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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [ScriptChangesNotifier] will call first applicable [ScriptChangeListener] when editor is activated or document changed.
|
* [ScriptChangesNotifier] will call first applicable [ScriptChangeListener] when editor is activated or document changed.
|
||||||
* Listener should do something to invalidate configuration and schedule reloading.
|
*
|
||||||
|
* Listener may call [ScriptConfigurationUpdater] to invalidate configuration and schedule reloading.
|
||||||
|
*
|
||||||
|
* @see DefaultScriptConfigurationManager for more details.
|
||||||
*/
|
*/
|
||||||
abstract class ScriptChangeListener(val project: Project) {
|
abstract class ScriptChangeListener(protected val project: Project) {
|
||||||
val default: DefaultScriptingSupport
|
abstract fun editorActivated(vFile: VirtualFile, updater: ScriptConfigurationUpdater)
|
||||||
get() = DefaultScriptingSupport.getInstance(project)
|
abstract fun documentChanged(vFile: VirtualFile, updater: ScriptConfigurationUpdater)
|
||||||
|
|
||||||
abstract fun editorActivated(vFile: VirtualFile)
|
|
||||||
abstract fun documentChanged(vFile: VirtualFile)
|
|
||||||
|
|
||||||
abstract fun isApplicable(vFile: VirtualFile): Boolean
|
abstract fun isApplicable(vFile: VirtualFile): Boolean
|
||||||
|
|
||||||
|
|||||||
+11
-9
@@ -22,7 +22,8 @@ import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChange
|
|||||||
import org.jetbrains.kotlin.idea.core.script.isScriptChangesNotifierDisabled
|
import org.jetbrains.kotlin.idea.core.script.isScriptChangesNotifierDisabled
|
||||||
|
|
||||||
internal class ScriptChangesNotifier(
|
internal class ScriptChangesNotifier(
|
||||||
private val project: Project
|
private val project: Project,
|
||||||
|
private val updater: ScriptConfigurationUpdater,
|
||||||
) {
|
) {
|
||||||
private val scriptsQueue = Alarm(Alarm.ThreadToUse.POOLED_THREAD, project)
|
private val scriptsQueue = Alarm(Alarm.ThreadToUse.POOLED_THREAD, project)
|
||||||
private val scriptChangesListenerDelay = 1400
|
private val scriptChangesListenerDelay = 1400
|
||||||
@@ -45,10 +46,10 @@ internal class ScriptChangesNotifier(
|
|||||||
|
|
||||||
private fun runScriptDependenciesUpdateIfNeeded(file: VirtualFile) {
|
private fun runScriptDependenciesUpdateIfNeeded(file: VirtualFile) {
|
||||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
getListener(project, file)?.editorActivated(file)
|
getListener(project, file)?.editorActivated(file, updater)
|
||||||
} else {
|
} else {
|
||||||
AppExecutorUtil.getAppExecutorService().submit {
|
AppExecutorUtil.getAppExecutorService().submit {
|
||||||
getListener(project, file)?.editorActivated(file)
|
getListener(project, file)?.editorActivated(file, updater)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,11 +68,11 @@ internal class ScriptChangesNotifier(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
getListener(project, file)?.documentChanged(file)
|
getListener(project, file)?.documentChanged(file, updater)
|
||||||
} else {
|
} else {
|
||||||
scriptsQueue.cancelAllRequests()
|
scriptsQueue.cancelAllRequests()
|
||||||
scriptsQueue.addRequest(
|
scriptsQueue.addRequest(
|
||||||
{ getListener(project, file)?.documentChanged(file) },
|
{ getListener(project, file)?.documentChanged(file, updater) },
|
||||||
scriptChangesListenerDelay,
|
scriptChangesListenerDelay,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
@@ -83,10 +84,11 @@ internal class ScriptChangesNotifier(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val defaultListener = DefaultScriptChangeListener(project)
|
private val defaultListener = DefaultScriptChangeListener(project)
|
||||||
private val listeners: Collection<ScriptChangeListener> = mutableListOf<ScriptChangeListener>().apply {
|
private val listeners: Sequence<ScriptChangeListener>
|
||||||
addAll(LISTENER.getPoint(project).extensionList)
|
get() = sequence {
|
||||||
add(defaultListener)
|
yieldAll(LISTENER.getPoint(project).extensionList)
|
||||||
}
|
yield(defaultListener)
|
||||||
|
}
|
||||||
|
|
||||||
private fun getListener(project: Project, file: VirtualFile): ScriptChangeListener? {
|
private fun getListener(project: Project, file: VirtualFile): ScriptChangeListener? {
|
||||||
if (project.isDisposed || areListenersDisabled()) return null
|
if (project.isDisposed || areListenersDisabled()) return null
|
||||||
|
|||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.core.script.configuration.listener
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationCacheScope
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
|
||||||
|
interface ScriptConfigurationUpdater {
|
||||||
|
/**
|
||||||
|
* Ensure that configuration for [file] is up-to-date,
|
||||||
|
* or new configuration loading is started
|
||||||
|
*/
|
||||||
|
fun ensureUpToDatedConfigurationSuggested(file: KtFile)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that configurations for given [files] is up-to-date or try synchronously update
|
||||||
|
* them. Synchronous update will be occurred only if auto apply is enabled and
|
||||||
|
* configuration for given file will not be loaded in background.
|
||||||
|
*
|
||||||
|
* @return true, if all files are already up-to-date or updated during this call
|
||||||
|
*/
|
||||||
|
fun ensureConfigurationUpToDate(files: List<KtFile>): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suggest to update script configuration if configuration for [file] is out of date
|
||||||
|
*/
|
||||||
|
fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile)
|
||||||
|
}
|
||||||
+5
-9
@@ -15,7 +15,6 @@ import com.intellij.util.containers.HashSetQueue
|
|||||||
import org.jetbrains.kotlin.idea.core.script.debug
|
import org.jetbrains.kotlin.idea.core.script.debug
|
||||||
import org.jetbrains.kotlin.idea.core.util.KotlinIdeaCoreBundle
|
import org.jetbrains.kotlin.idea.core.util.KotlinIdeaCoreBundle
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.swing.SwingUtilities
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sequentially loads script configuration in background.
|
* Sequentially loads script configuration in background.
|
||||||
@@ -33,7 +32,7 @@ import javax.swing.SwingUtilities
|
|||||||
*/
|
*/
|
||||||
internal class DefaultBackgroundExecutor(
|
internal class DefaultBackgroundExecutor(
|
||||||
val project: Project,
|
val project: Project,
|
||||||
val rootsManager: ScriptClassRootsUpdater
|
val rootsManager: ScriptClassRootsIndexer
|
||||||
) : BackgroundExecutor {
|
) : BackgroundExecutor {
|
||||||
companion object {
|
companion object {
|
||||||
const val PROGRESS_INDICATOR_DELAY = 1000
|
const val PROGRESS_INDICATOR_DELAY = 1000
|
||||||
@@ -138,7 +137,7 @@ internal class DefaultBackgroundExecutor(
|
|||||||
private fun ensureInTransaction() {
|
private fun ensureInTransaction() {
|
||||||
if (inTransaction) return
|
if (inTransaction) return
|
||||||
inTransaction = true
|
inTransaction = true
|
||||||
rootsManager.beginUpdating()
|
rootsManager.startTransaction()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@@ -228,12 +227,9 @@ internal class DefaultBackgroundExecutor(
|
|||||||
override fun start() {
|
override fun start() {
|
||||||
super.start()
|
super.start()
|
||||||
|
|
||||||
// executeOnPooledThread requires read lock, and we may fail to acquire it
|
BackgroundTaskUtil.executeOnPooledThread(project, Runnable {
|
||||||
SwingUtilities.invokeLater {
|
run()
|
||||||
BackgroundTaskUtil.executeOnPooledThread(project, {
|
})
|
||||||
run()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
|
|||||||
+86
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.core.script.configuration.utils
|
||||||
|
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.projectRoots.Sdk
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import com.intellij.util.containers.ConcurrentFactoryMap
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
||||||
|
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
internal class DefaultClassRootsCache(
|
||||||
|
project: Project,
|
||||||
|
private val all: Map<VirtualFile, ScriptCompilationConfigurationWrapper>
|
||||||
|
) : ScriptClassRootsCache(
|
||||||
|
project,
|
||||||
|
ScriptClassRootsStorage.Companion.Key("default"),
|
||||||
|
extractRoots(all, project)
|
||||||
|
) {
|
||||||
|
|
||||||
|
override fun getConfiguration(file: VirtualFile) = all[file]
|
||||||
|
|
||||||
|
override fun contains(file: VirtualFile): Boolean = file in all
|
||||||
|
|
||||||
|
private val scriptsSdksCache: Map<VirtualFile, Sdk?> =
|
||||||
|
ConcurrentFactoryMap.createWeakMap { file ->
|
||||||
|
return@createWeakMap getScriptSdkOrDefault(all[file]?.javaHome, project)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getScriptSdk(file: VirtualFile): Sdk? = scriptsSdksCache[file]
|
||||||
|
|
||||||
|
override val firstScriptSdk: Sdk? by lazy {
|
||||||
|
getScriptSdkOrDefault(all.values.firstOrNull()?.javaHome, project)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun extractRoots(
|
||||||
|
project: Project,
|
||||||
|
configuration: ScriptCompilationConfigurationWrapper
|
||||||
|
): ScriptClassRootsStorage.Companion.ScriptClassRoots {
|
||||||
|
val scriptSdk = getScriptSdkOrDefault(configuration.javaHome, project)
|
||||||
|
if (scriptSdk != null && !scriptSdk.isAlreadyIndexed(project)) {
|
||||||
|
return ScriptClassRootsStorage.Companion.ScriptClassRoots(
|
||||||
|
toStringValues(configuration.dependenciesClassPath),
|
||||||
|
toStringValues(configuration.dependenciesSources),
|
||||||
|
setOf(scriptSdk)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ScriptClassRootsStorage.Companion.ScriptClassRoots(
|
||||||
|
toStringValues(configuration.dependenciesClassPath),
|
||||||
|
toStringValues(configuration.dependenciesSources),
|
||||||
|
emptySet()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun extractRoots(
|
||||||
|
all: Map<VirtualFile, ScriptCompilationConfigurationWrapper>,
|
||||||
|
project: Project
|
||||||
|
): ScriptClassRootsStorage.Companion.ScriptClassRoots {
|
||||||
|
val classpath = mutableSetOf<File>()
|
||||||
|
val sources = mutableSetOf<File>()
|
||||||
|
val sdks = mutableSetOf<Sdk>()
|
||||||
|
|
||||||
|
for ((_, configuration) in all) {
|
||||||
|
val scriptSdk = getScriptSdkOrDefault(configuration.javaHome, project)
|
||||||
|
if (scriptSdk != null && !scriptSdk.isAlreadyIndexed(project)) {
|
||||||
|
sdks.add(scriptSdk)
|
||||||
|
}
|
||||||
|
|
||||||
|
classpath.addAll(configuration.dependenciesClassPath)
|
||||||
|
sources.addAll(configuration.dependenciesSources)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ScriptClassRootsStorage.Companion.ScriptClassRoots(
|
||||||
|
toStringValues(classpath),
|
||||||
|
toStringValues(sources),
|
||||||
|
sdks
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+90
-183
@@ -12,101 +12,100 @@ import com.intellij.openapi.projectRoots.Sdk
|
|||||||
import com.intellij.openapi.roots.ModuleRootManager
|
import com.intellij.openapi.roots.ModuleRootManager
|
||||||
import com.intellij.openapi.roots.OrderRootType
|
import com.intellij.openapi.roots.OrderRootType
|
||||||
import com.intellij.openapi.roots.ProjectRootManager
|
import com.intellij.openapi.roots.ProjectRootManager
|
||||||
import com.intellij.openapi.util.io.FileUtil.toSystemIndependentName
|
import com.intellij.openapi.vfs.*
|
||||||
import com.intellij.openapi.vfs.VfsUtil
|
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
|
||||||
import com.intellij.psi.search.GlobalSearchScope
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
import com.intellij.psi.search.NonClasspathDirectoriesScope.compose
|
import com.intellij.psi.search.NonClasspathDirectoriesScope
|
||||||
|
import com.intellij.util.containers.ConcurrentFactoryMap
|
||||||
import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks
|
import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks
|
||||||
import org.jetbrains.kotlin.idea.core.script.LOG
|
import org.jetbrains.kotlin.idea.core.script.LOG
|
||||||
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager.Companion.classpathEntryToVfs
|
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
||||||
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager.Companion.toVfsRoots
|
|
||||||
import org.jetbrains.kotlin.idea.util.getProjectJdkTableSafe
|
import org.jetbrains.kotlin.idea.util.getProjectJdkTableSafe
|
||||||
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
|
|
||||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.lang.ref.Reference
|
|
||||||
import java.lang.ref.SoftReference
|
|
||||||
|
|
||||||
class ScriptClassRootsCache(
|
abstract class ScriptClassRootsCache(
|
||||||
val scripts: Map<String, LightScriptInfo>,
|
private val project: Project,
|
||||||
val classes: Set<String>,
|
private val rootsCacheKey: ScriptClassRootsStorage.Companion.Key,
|
||||||
val sources: Set<String>,
|
private val roots: ScriptClassRootsStorage.Companion.ScriptClassRoots
|
||||||
val sdks: Map<String?, Sdk?>,
|
|
||||||
private val nonModulesSdks: Collection<Sdk>,
|
|
||||||
val customDefinitionsUsed: Boolean
|
|
||||||
) {
|
) {
|
||||||
abstract class LightScriptInfo(val definition: ScriptDefinition?) {
|
protected abstract fun getConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper?
|
||||||
@Volatile
|
|
||||||
var heavyCache: Reference<HeavyScriptInfo>? = null
|
|
||||||
|
|
||||||
abstract fun buildConfiguration(): ScriptCompilationConfigurationWrapper?
|
abstract val firstScriptSdk: Sdk?
|
||||||
}
|
|
||||||
|
|
||||||
class DirectScriptInfo(val result: ScriptCompilationConfigurationWrapper) : LightScriptInfo(null) {
|
abstract fun getScriptSdk(file: VirtualFile): Sdk?
|
||||||
override fun buildConfiguration(): ScriptCompilationConfigurationWrapper = result
|
|
||||||
}
|
|
||||||
|
|
||||||
class HeavyScriptInfo(
|
abstract fun contains(file: VirtualFile): Boolean
|
||||||
|
|
||||||
|
private class Fat(
|
||||||
val scriptConfiguration: ScriptCompilationConfigurationWrapper,
|
val scriptConfiguration: ScriptCompilationConfigurationWrapper,
|
||||||
val classFilesScope: GlobalSearchScope,
|
val classFilesScope: GlobalSearchScope
|
||||||
val sdk: Sdk?
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Builder(val project: Project) {
|
val allDependenciesClassFiles by lazy {
|
||||||
val scripts = mutableMapOf<String, LightScriptInfo>()
|
ScriptClassRootsStorage.getInstance(project).loadClasspathRoots(rootsCacheKey)
|
||||||
|
}
|
||||||
|
|
||||||
private val defaultSdk = getScriptDefaultSdk()
|
val allDependenciesSources by lazy {
|
||||||
val sdks = mutableMapOf<String?, Sdk?>(null to defaultSdk)
|
ScriptClassRootsStorage.getInstance(project).loadSourcesRoots(rootsCacheKey)
|
||||||
|
}
|
||||||
|
|
||||||
val classes = mutableSetOf<String>()
|
val allDependenciesClassFilesScope by lazy {
|
||||||
val sources = mutableSetOf<String>()
|
NonClasspathDirectoriesScope.compose(allDependenciesClassFiles)
|
||||||
private var customDefinitionsUsed: Boolean = false
|
}
|
||||||
|
|
||||||
fun build(): ScriptClassRootsCache {
|
val allDependenciesSourcesScope by lazy {
|
||||||
val nonIndexedSdks = collectNonIndexedSdks()
|
NonClasspathDirectoriesScope.compose(allDependenciesSources)
|
||||||
return ScriptClassRootsCache(scripts, classes, sources, sdks, nonIndexedSdks, customDefinitionsUsed)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun collectNonIndexedSdks(): MutableSet<Sdk> {
|
private val scriptsDependenciesCache: MutableMap<VirtualFile, Fat> =
|
||||||
val nonIndexedSdks = sdks.values.filterNotNullTo(mutableSetOf())
|
ConcurrentFactoryMap.createWeakMap { file ->
|
||||||
ModuleManager.getInstance(project).modules.map {
|
val configuration = getConfiguration(file) ?: return@createWeakMap null
|
||||||
nonIndexedSdks.remove(ModuleRootManager.getInstance(it).sdk)
|
|
||||||
|
val roots = configuration.dependenciesClassPath
|
||||||
|
val sdk = getScriptSdk(file)
|
||||||
|
|
||||||
|
@Suppress("FoldInitializerAndIfToElvis")
|
||||||
|
if (sdk == null) {
|
||||||
|
return@createWeakMap Fat(
|
||||||
|
configuration,
|
||||||
|
NonClasspathDirectoriesScope.compose(ScriptConfigurationManager.toVfsRoots(roots))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return nonIndexedSdks
|
|
||||||
|
return@createWeakMap Fat(
|
||||||
|
configuration,
|
||||||
|
NonClasspathDirectoriesScope.compose(
|
||||||
|
sdk.rootProvider.getFiles(OrderRootType.CLASSES).toList() + ScriptConfigurationManager.toVfsRoots(roots)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun useCustomScriptDefinition() {
|
fun getScriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope {
|
||||||
customDefinitionsUsed = true
|
return scriptsDependenciesCache[file]?.classFilesScope ?: GlobalSearchScope.EMPTY_SCOPE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getScriptConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? {
|
||||||
|
return scriptsDependenciesCache[file]?.scriptConfiguration
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasNotCachedRoots(roots: ScriptClassRootsStorage.Companion.ScriptClassRoots): Boolean {
|
||||||
|
return !ScriptClassRootsStorage.getInstance(project).containsAll(rootsCacheKey, roots)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveClassRootsToStorage() {
|
||||||
|
val rootsStorage = ScriptClassRootsStorage.getInstance(project)
|
||||||
|
if (roots.classpathFiles.isNotEmpty() || roots.sourcesFiles.isNotEmpty() || roots.sdks.isNotEmpty()) {
|
||||||
|
rootsStorage.save(rootsCacheKey, roots)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun toStringValues(prop: Collection<File>): Set<String> {
|
||||||
|
return prop.mapNotNull { it.absolutePath }.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun add(
|
fun getScriptSdkOrDefault(javaHome: File?, project: Project): Sdk? {
|
||||||
vFile: VirtualFile,
|
return javaHome?.let { getScriptSdkByJavaHome(it) } ?: getScriptDefaultSdk(project)
|
||||||
configuration: ScriptCompilationConfigurationWrapper
|
|
||||||
) {
|
|
||||||
addSdk(configuration.javaHome)
|
|
||||||
|
|
||||||
configuration.dependenciesClassPath.forEach { classes.add(it.absolutePath) }
|
|
||||||
configuration.dependenciesSources.forEach { sources.add(it.absolutePath) }
|
|
||||||
|
|
||||||
scripts[vFile.path] = DirectScriptInfo(configuration)
|
|
||||||
|
|
||||||
useCustomScriptDefinition()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun add(other: Builder) {
|
|
||||||
classes.addAll(other.classes)
|
|
||||||
sources.addAll(other.sources)
|
|
||||||
sdks.putAll(other.sdks)
|
|
||||||
scripts.putAll(other.scripts)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addSdk(javaHome: File?): Sdk? {
|
|
||||||
if (javaHome == null) return defaultSdk
|
|
||||||
val canonicalPath = toSystemIndependentName(javaHome.canonicalPath)
|
|
||||||
return sdks.getOrPut(canonicalPath) {
|
|
||||||
getScriptSdkByJavaHome(javaHome) ?: defaultSdk
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getScriptSdkByJavaHome(javaHome: File): Sdk? {
|
private fun getScriptSdkByJavaHome(javaHome: File): Sdk? {
|
||||||
@@ -120,13 +119,7 @@ class ScriptClassRootsCache(
|
|||||||
return getProjectJdkTableSafe().allJdks.find { it.homeDirectory == javaHomeVF }
|
return getProjectJdkTableSafe().allJdks.find { it.homeDirectory == javaHomeVF }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addSdkByName(sdkName: String) {
|
private fun getScriptDefaultSdk(project: Project): Sdk? {
|
||||||
val sdk = getProjectJdkTableSafe().allJdks.find { it.name == sdkName } ?: defaultSdk ?: return
|
|
||||||
val homePath = sdk.homePath ?: return
|
|
||||||
sdks[homePath] = sdk
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getScriptDefaultSdk(): Sdk? {
|
|
||||||
val projectSdk = ProjectRootManager.getInstance(project).projectSdk?.takeIf { it.canBeUsedForScript() }
|
val projectSdk = ProjectRootManager.getInstance(project).projectSdk?.takeIf { it.canBeUsedForScript() }
|
||||||
if (projectSdk != null) return projectSdk
|
if (projectSdk != null) return projectSdk
|
||||||
|
|
||||||
@@ -140,118 +133,32 @@ class ScriptClassRootsCache(
|
|||||||
"projectSdk = ${ProjectRootManager.getInstance(project).projectSdk}, " +
|
"projectSdk = ${ProjectRootManager.getInstance(project).projectSdk}, " +
|
||||||
"all sdks = ${getAllProjectSdks().joinToString("\n")}"
|
"all sdks = ${getAllProjectSdks().joinToString("\n")}"
|
||||||
)
|
)
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Sdk.canBeUsedForScript() = sdkType is JavaSdkType
|
private fun Sdk.canBeUsedForScript() = sdkType is JavaSdkType
|
||||||
}
|
|
||||||
|
|
||||||
fun getLightScriptInfo(file: String) = scripts[file]
|
fun Sdk.isAlreadyIndexed(project: Project): Boolean {
|
||||||
|
return ModuleManager.getInstance(project).modules.any { ModuleRootManager.getInstance(it).sdk == this }
|
||||||
fun contains(file: VirtualFile): Boolean = file.path in scripts
|
|
||||||
|
|
||||||
private fun getHeavyScriptInfo(file: String): HeavyScriptInfo? {
|
|
||||||
val lightScriptInfo = getLightScriptInfo(file) ?: return null
|
|
||||||
val heavy0 = lightScriptInfo.heavyCache?.get()
|
|
||||||
if (heavy0 != null) return heavy0
|
|
||||||
synchronized(lightScriptInfo) {
|
|
||||||
val heavy1 = lightScriptInfo.heavyCache?.get()
|
|
||||||
if (heavy1 != null) return heavy1
|
|
||||||
val heavy2 = computeHeavy(lightScriptInfo)
|
|
||||||
lightScriptInfo.heavyCache = SoftReference(heavy2)
|
|
||||||
return heavy2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun computeHeavy(lightScriptInfo: LightScriptInfo): HeavyScriptInfo? {
|
|
||||||
val configuration = lightScriptInfo.buildConfiguration() ?: return null
|
|
||||||
|
|
||||||
val roots = configuration.dependenciesClassPath
|
|
||||||
val sdk = sdks[configuration.javaHome?.canonicalPath]
|
|
||||||
|
|
||||||
return if (sdk == null) {
|
|
||||||
HeavyScriptInfo(configuration, compose(toVfsRoots(roots)), null)
|
|
||||||
} else {
|
|
||||||
val sdkClasses = sdk.rootProvider.getFiles(OrderRootType.CLASSES).toList()
|
|
||||||
HeavyScriptInfo(configuration, compose(sdkClasses + toVfsRoots(roots)), sdk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val firstScriptSdk: Sdk? = sdks.values.firstOrNull()
|
|
||||||
|
|
||||||
val allDependenciesClassFiles: List<VirtualFile>
|
|
||||||
|
|
||||||
val allDependenciesSources: List<VirtualFile>
|
|
||||||
|
|
||||||
init {
|
|
||||||
allDependenciesClassFiles = mutableSetOf<VirtualFile>().also { result ->
|
|
||||||
nonModulesSdks.forEach { result.addAll(it.rootProvider.getFiles(OrderRootType.CLASSES)) }
|
|
||||||
classes.mapNotNullTo(result) { classpathEntryToVfs(File(it)) }
|
|
||||||
}.toList()
|
|
||||||
|
|
||||||
allDependenciesSources = mutableSetOf<VirtualFile>().also { result ->
|
|
||||||
nonModulesSdks.forEach { result.addAll(it.rootProvider.getFiles(OrderRootType.SOURCES)) }
|
|
||||||
sources.mapNotNullTo(result) { classpathEntryToVfs(File(it)) }
|
|
||||||
}.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
val allDependenciesClassFilesScope = compose(allDependenciesClassFiles)
|
|
||||||
|
|
||||||
val allDependenciesSourcesScope = compose(allDependenciesSources)
|
|
||||||
|
|
||||||
fun getScriptConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? =
|
|
||||||
getHeavyScriptInfo(file.path)?.scriptConfiguration
|
|
||||||
|
|
||||||
fun getScriptSdk(file: VirtualFile): Sdk? =
|
|
||||||
getHeavyScriptInfo(file.path)?.sdk
|
|
||||||
|
|
||||||
fun getScriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope =
|
|
||||||
getHeavyScriptInfo(file.path)?.classFilesScope ?: GlobalSearchScope.EMPTY_SCOPE
|
|
||||||
|
|
||||||
fun hasInvalidSdk(project: Project): Boolean {
|
|
||||||
val builder = Builder(project)
|
|
||||||
if (sdks.any { (home, sdk) -> builder.addSdk(home?.let(::File)) != sdk }) return true
|
|
||||||
if (builder.collectNonIndexedSdks() != nonModulesSdks) return true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun diff(old: ScriptClassRootsCache): Updates =
|
|
||||||
Updates(
|
|
||||||
hasNewRoots(old),
|
|
||||||
getChangedScripts(old)
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun hasNewRoots(old: ScriptClassRootsCache): Boolean {
|
|
||||||
return classes.any { it !in old.classes }
|
|
||||||
|| sources.any { it !in old.sources }
|
|
||||||
|| sdks.any { it.key !in old.sdks }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getChangedScripts(old: ScriptClassRootsCache): Set<String> {
|
|
||||||
val changed = mutableSetOf<String>()
|
|
||||||
|
|
||||||
scripts.forEach {
|
|
||||||
if (old.scripts[it.key] != it.value) {
|
|
||||||
changed.add(it.key)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
old.scripts.forEach {
|
fun empty(project: Project) = object : ScriptClassRootsCache(
|
||||||
if (it.key !in scripts) {
|
project,
|
||||||
changed.add(it.key)
|
ScriptClassRootsStorage.Companion.Key("empty"),
|
||||||
}
|
ScriptClassRootsStorage.Companion.ScriptClassRoots(
|
||||||
|
setOf(),
|
||||||
|
setOf(),
|
||||||
|
setOf()
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
override fun getConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? = null
|
||||||
|
|
||||||
|
override val firstScriptSdk: Sdk? = null
|
||||||
|
|
||||||
|
override fun getScriptSdk(file: VirtualFile): Sdk? = null
|
||||||
|
|
||||||
|
override fun contains(file: VirtualFile): Boolean = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return changed
|
|
||||||
}
|
|
||||||
|
|
||||||
class Updates(
|
|
||||||
val hasNewRoots: Boolean,
|
|
||||||
val updatedScripts: Set<String>
|
|
||||||
) {
|
|
||||||
val changed: Boolean
|
|
||||||
get() = hasNewRoots || updatedScripts.isNotEmpty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+80
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.core.script.configuration.utils
|
||||||
|
|
||||||
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
|
import com.intellij.openapi.application.TransactionGuard
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.roots.ex.ProjectRootManagerEx
|
||||||
|
import com.intellij.openapi.util.EmptyRunnable
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.debug
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||||
|
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility for postponing indexing of new roots to the end of some bulk operation.
|
||||||
|
*/
|
||||||
|
class ScriptClassRootsIndexer(val project: Project) {
|
||||||
|
private var newRootsPresent: Boolean = false
|
||||||
|
private val concurrentTransactions = AtomicInteger()
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun markNewRoot() {
|
||||||
|
checkInTransaction()
|
||||||
|
newRootsPresent = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkInTransaction() {
|
||||||
|
check(concurrentTransactions.get() > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T> transaction(body: () -> T): T {
|
||||||
|
startTransaction()
|
||||||
|
return try {
|
||||||
|
body()
|
||||||
|
} finally {
|
||||||
|
commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startTransaction() {
|
||||||
|
concurrentTransactions.incrementAndGet()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun commit() {
|
||||||
|
concurrentTransactions.decrementAndGet()
|
||||||
|
|
||||||
|
// run indexing even in inner transaction
|
||||||
|
// (outer transaction may be async, so it would be better to not wait it)
|
||||||
|
startIndexingIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun startIndexingIfNeeded() {
|
||||||
|
if (!newRootsPresent) return
|
||||||
|
newRootsPresent = false
|
||||||
|
|
||||||
|
val doNotifyRootsChanged = Runnable {
|
||||||
|
runWriteAction {
|
||||||
|
if (project.isDisposed) return@runWriteAction
|
||||||
|
|
||||||
|
debug { "roots change event" }
|
||||||
|
|
||||||
|
ProjectRootManagerEx.getInstanceEx(project)?.makeRootsChange(EmptyRunnable.getInstance(), false, true)
|
||||||
|
ScriptDependenciesModificationTracker.getInstance(project).incModificationCount()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
|
TransactionGuard.submitTransaction(project, doNotifyRootsChanged)
|
||||||
|
} else {
|
||||||
|
TransactionGuard.getInstance().submitTransactionLater(project, doNotifyRootsChanged)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+122
-22
@@ -7,44 +7,144 @@ package org.jetbrains.kotlin.idea.core.script.configuration.utils
|
|||||||
|
|
||||||
import com.intellij.openapi.components.*
|
import com.intellij.openapi.components.*
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.util.xmlb.XmlSerializerUtil
|
import com.intellij.openapi.projectRoots.ProjectJdkTable
|
||||||
|
import com.intellij.openapi.projectRoots.Sdk
|
||||||
|
import com.intellij.openapi.roots.OrderRootType
|
||||||
|
import com.intellij.openapi.vfs.JarFileSystem
|
||||||
|
import com.intellij.openapi.vfs.StandardFileSystems
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import org.jdom.Element
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.debug
|
||||||
|
|
||||||
/**
|
|
||||||
* This cache is used by [org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptingSupport] only.
|
|
||||||
* @see org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptingSupport.collectConfigurations
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@State(
|
@State(
|
||||||
name = "ScriptClassRootsStorage",
|
name = "ScriptClassRootsStorage",
|
||||||
storages = [Storage(StoragePathMacros.CACHE_FILE)]
|
storages = [Storage(StoragePathMacros.CACHE_FILE)]
|
||||||
)
|
)
|
||||||
class ScriptClassRootsStorage(val project: Project) : PersistentStateComponent<ScriptClassRootsStorage> {
|
class ScriptClassRootsStorage : PersistentStateComponent<Element> {
|
||||||
private var classpath: Set<String> = hashSetOf()
|
|
||||||
private var sources: Set<String> = hashSetOf()
|
|
||||||
private var sdks: Set<String> = hashSetOf()
|
|
||||||
|
|
||||||
override fun getState(): ScriptClassRootsStorage? {
|
private var classpath: MutableMap<String, Set<String>> = hashMapOf()
|
||||||
return this
|
private var sources: MutableMap<String, Set<String>> = hashMapOf()
|
||||||
|
private var sdks: MutableMap<String, Set<String>> = hashMapOf()
|
||||||
|
|
||||||
|
override fun getState(): Element {
|
||||||
|
val root = Element("ScriptClassRootsStorage")
|
||||||
|
|
||||||
|
storeCollection(root, classpath, "classpath")
|
||||||
|
storeCollection(root, sources, "sources")
|
||||||
|
storeCollection(root, sdks, "sdk")
|
||||||
|
|
||||||
|
return root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadState(state: ScriptClassRootsStorage) {
|
private fun storeCollection(root: Element, col: Map<String, Set<String>>, name: String) {
|
||||||
XmlSerializerUtil.copyBean(state, this)
|
for ((key, paths) in col) {
|
||||||
|
for (path in paths) {
|
||||||
|
val element = Element(name)
|
||||||
|
element.setAttribute("path", path)
|
||||||
|
element.setAttribute("key", key)
|
||||||
|
root.addContent(element)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun save(roots: ScriptClassRootsCache.Builder) {
|
override fun loadState(state: Element) {
|
||||||
classpath = roots.classes
|
classpath = readCollection(state, "classpath")
|
||||||
sources = roots.sources
|
sources = readCollection(state, "sources")
|
||||||
sdks = roots.sdks.values.mapNotNullTo(mutableSetOf()) { it?.name }
|
sdks = readCollection(state, "sdks")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load(builder: ScriptClassRootsCache.Builder) {
|
private fun readCollection(root: Element, name: String): MutableMap<String, Set<String>> {
|
||||||
builder.sources.addAll(sources)
|
val result: MutableMap<String, HashSet<String>> = hashMapOf()
|
||||||
builder.classes.addAll(classpath)
|
for (it in root.getChildren(name)) {
|
||||||
sdks.forEach(builder::addSdkByName)
|
result.getOrPut(it.getAttributeValue("key")) {
|
||||||
|
hashSetOf()
|
||||||
|
}.add(it.getAttributeValue("path"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return result as MutableMap<String, Set<String>>
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toStringNames(sdks: Collection<Sdk>): Set<String> {
|
||||||
|
return sdks.map { it.name }.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toVirtualFiles(prop: Set<String>?, sources: Boolean): List<VirtualFile> {
|
||||||
|
if (prop == null) return emptyList()
|
||||||
|
|
||||||
|
val rootType = if (sources) OrderRootType.SOURCES else OrderRootType.CLASSES
|
||||||
|
return prop.mapNotNull { ProjectJdkTable.getInstance().findJdk(it) }
|
||||||
|
.flatMap { it.rootProvider.getFiles(rootType).toList() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toVirtualFiles(prop: Collection<String>?): List<VirtualFile> {
|
||||||
|
if (prop == null) return emptyList()
|
||||||
|
|
||||||
|
return prop.mapNotNull {
|
||||||
|
if (it.endsWith(JarFileSystem.PROTOCOL)) {
|
||||||
|
StandardFileSystems.jar()?.findFileByPath(it + JarFileSystem.JAR_SEPARATOR)?.let {
|
||||||
|
return@mapNotNull it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StandardFileSystems.local()?.findFileByPath(it)?.let {
|
||||||
|
return@mapNotNull it
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: report this somewhere, but do not throw: assert(res != null, { "Invalid classpath entry '$this': exists: ${exists()}, is directory: $isDirectory, is file: $isFile" })
|
||||||
|
|
||||||
|
null
|
||||||
|
}.distinct()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun containsAll(key: Key, configuration: ScriptClassRoots): Boolean {
|
||||||
|
if (configuration.classpathFiles.isNotEmpty() && classpath[key.value]?.containsAll(configuration.classpathFiles) != true) {
|
||||||
|
debug { "class roots were changed: old = $classpath, new = ${configuration.classpathFiles}" }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (configuration.sourcesFiles.isNotEmpty() && sources[key.value]?.containsAll(configuration.sourcesFiles) != true) {
|
||||||
|
debug { "source roots were changed: old = $sources, new = ${configuration.sourcesFiles}" }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (configuration.sdks.isNotEmpty() && sdks[key.value]?.containsAll(toStringNames(configuration.sdks)) != true) {
|
||||||
|
debug { "sdk classes were changed: old = $sdks, new = ${configuration.sdks.map { it.homePath }}" }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun save(key: Key, configuration: ScriptClassRoots) {
|
||||||
|
// TODO: do not drop all storage on save: KT-34444
|
||||||
|
classpath.getOrPut(key.value, { emptySet() })
|
||||||
|
classpath.replace(key.value, configuration.classpathFiles)
|
||||||
|
|
||||||
|
sources.getOrPut(key.value, { emptySet() })
|
||||||
|
sources.replace(key.value, configuration.sourcesFiles)
|
||||||
|
|
||||||
|
sdks.getOrPut(key.value, { emptySet() })
|
||||||
|
sdks.replace(key.value, toStringNames(configuration.sdks))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadClasspathRoots(key: Key): List<VirtualFile> {
|
||||||
|
return toVirtualFiles(sdks[key.value], false) + toVirtualFiles(classpath[key.value])
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadSourcesRoots(key: Key): List<VirtualFile> {
|
||||||
|
return toVirtualFiles(sdks[key.value], true) + toVirtualFiles(sources[key.value])
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun getInstance(project: Project): ScriptClassRootsStorage =
|
fun getInstance(project: Project): ScriptClassRootsStorage =
|
||||||
ServiceManager.getService(project, ScriptClassRootsStorage::class.java)
|
ServiceManager.getService(project, ScriptClassRootsStorage::class.java)
|
||||||
|
|
||||||
|
data class ScriptClassRoots(
|
||||||
|
val classpathFiles: Set<String>,
|
||||||
|
val sourcesFiles: Set<String>,
|
||||||
|
val sdks: Set<Sdk>
|
||||||
|
)
|
||||||
|
|
||||||
|
val EMPTY = ScriptClassRoots(emptySet(), emptySet(), emptySet())
|
||||||
|
|
||||||
|
data class Key(val value: String)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
-194
@@ -1,194 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.jetbrains.kotlin.idea.core.script.configuration.utils
|
|
||||||
|
|
||||||
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
|
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
|
||||||
import com.intellij.openapi.application.TransactionGuard
|
|
||||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
|
||||||
import com.intellij.openapi.progress.ProcessCanceledException
|
|
||||||
import com.intellij.openapi.progress.ProgressIndicator
|
|
||||||
import com.intellij.openapi.progress.ProgressManager
|
|
||||||
import com.intellij.openapi.progress.util.BackgroundTaskUtil
|
|
||||||
import com.intellij.openapi.project.Project
|
|
||||||
import com.intellij.openapi.roots.ex.ProjectRootManagerEx
|
|
||||||
import com.intellij.openapi.util.EmptyRunnable
|
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
|
||||||
import com.intellij.psi.PsiElementFinder
|
|
||||||
import com.intellij.psi.PsiManager
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.jetbrains.kotlin.idea.core.script.KotlinScriptDependenciesClassFinder
|
|
||||||
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker
|
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.CompositeScriptConfigurationManager
|
|
||||||
import org.jetbrains.kotlin.idea.core.script.debug
|
|
||||||
import org.jetbrains.kotlin.idea.core.util.EDT
|
|
||||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
|
||||||
import kotlin.concurrent.withLock
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility for postponing indexing of new roots to the end of some bulk operation.
|
|
||||||
*/
|
|
||||||
class ScriptClassRootsUpdater(
|
|
||||||
val project: Project,
|
|
||||||
val manager: CompositeScriptConfigurationManager
|
|
||||||
) {
|
|
||||||
private var invalidated: Boolean = false
|
|
||||||
private var syncUpdateRequired: Boolean = false
|
|
||||||
private val concurrentUpdates = AtomicInteger()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param synchronous Used from legacy FS cache only, don't use
|
|
||||||
*/
|
|
||||||
@Synchronized
|
|
||||||
@Suppress("UNUSED_PARAMETER")
|
|
||||||
fun invalidate(file: VirtualFile, synchronous: Boolean = false) {
|
|
||||||
// todo: record invalided files for some optimisations in update
|
|
||||||
invalidate(synchronous)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param synchronous Used from legacy FS cache only, don't use
|
|
||||||
*/
|
|
||||||
@Synchronized
|
|
||||||
fun invalidate(synchronous: Boolean = false) {
|
|
||||||
checkInTransaction()
|
|
||||||
invalidated = true
|
|
||||||
if (synchronous) {
|
|
||||||
syncUpdateRequired = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun checkInTransaction() {
|
|
||||||
check(concurrentUpdates.get() > 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <T> update(body: () -> T): T {
|
|
||||||
beginUpdating()
|
|
||||||
return try {
|
|
||||||
body()
|
|
||||||
} finally {
|
|
||||||
commit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun beginUpdating() {
|
|
||||||
concurrentUpdates.incrementAndGet()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun commit() {
|
|
||||||
concurrentUpdates.decrementAndGet()
|
|
||||||
|
|
||||||
// run update even in inner transaction
|
|
||||||
// (outer transaction may be async, so it would be better to not wait it)
|
|
||||||
scheduleUpdateIfInvalid()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun scheduleUpdateIfInvalid() {
|
|
||||||
if (!invalidated) return
|
|
||||||
invalidated = false
|
|
||||||
|
|
||||||
if (syncUpdateRequired || ApplicationManager.getApplication().isUnitTestMode) {
|
|
||||||
syncUpdateRequired = false
|
|
||||||
updateSynchronously()
|
|
||||||
} else {
|
|
||||||
ensureUpdateScheduled()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val syncLock = ReentrantLock()
|
|
||||||
private var scheduledUpdate: ProgressIndicator? = null
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun ensureUpdateScheduled() {
|
|
||||||
scheduledUpdate?.cancel()
|
|
||||||
scheduledUpdate = BackgroundTaskUtil.executeOnPooledThread(project) {
|
|
||||||
doUpdate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun updateSynchronously() {
|
|
||||||
scheduledUpdate?.cancel()
|
|
||||||
doUpdate(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun doUpdate(underProgressManager: Boolean = true) {
|
|
||||||
syncLock.withLock {
|
|
||||||
try {
|
|
||||||
val updates = manager.collectRootsAndCheckNew()
|
|
||||||
|
|
||||||
if (!updates.changed) return
|
|
||||||
|
|
||||||
if (underProgressManager) {
|
|
||||||
ProgressManager.checkCanceled()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updates.hasNewRoots) {
|
|
||||||
notifyRootsChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
PsiElementFinder.EP.findExtensionOrFail(KotlinScriptDependenciesClassFinder::class.java, project)
|
|
||||||
.clearCache()
|
|
||||||
|
|
||||||
ScriptDependenciesModificationTracker.getInstance(project).incModificationCount()
|
|
||||||
|
|
||||||
if (updates.updatedScripts.isNotEmpty()) {
|
|
||||||
updateHighlighting(project) {
|
|
||||||
it.path in updates.updatedScripts
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (cancel: ProcessCanceledException) {
|
|
||||||
if (underProgressManager) throw cancel
|
|
||||||
} finally {
|
|
||||||
synchronized(this) {
|
|
||||||
scheduledUpdate = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun notifyRootsChanged() {
|
|
||||||
val doNotifyRootsChanged = Runnable {
|
|
||||||
runWriteAction {
|
|
||||||
if (project.isDisposed) return@runWriteAction
|
|
||||||
|
|
||||||
debug { "roots change event" }
|
|
||||||
|
|
||||||
ProjectRootManagerEx.getInstanceEx(project)?.makeRootsChange(EmptyRunnable.getInstance(), false, true)
|
|
||||||
ScriptDependenciesModificationTracker.getInstance(project).incModificationCount()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
|
||||||
TransactionGuard.submitTransaction(project, doNotifyRootsChanged)
|
|
||||||
} else {
|
|
||||||
TransactionGuard.getInstance().submitTransactionLater(project, doNotifyRootsChanged)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateHighlighting(project: Project, filter: (VirtualFile) -> Boolean) {
|
|
||||||
if (!project.isOpen) return
|
|
||||||
|
|
||||||
val openFiles = FileEditorManager.getInstance(project).openFiles
|
|
||||||
val openedScripts = openFiles.filter { filter(it) }
|
|
||||||
|
|
||||||
if (openedScripts.isEmpty()) return
|
|
||||||
|
|
||||||
GlobalScope.launch(EDT(project)) {
|
|
||||||
if (project.isDisposed) return@launch
|
|
||||||
|
|
||||||
openedScripts.forEach {
|
|
||||||
PsiManager.getInstance(project).findFile(it)?.let { psiFile ->
|
|
||||||
DaemonCodeAnalyzer.getInstance(project).restart(psiFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-2
@@ -10,7 +10,7 @@ import com.intellij.openapi.vfs.VirtualFile
|
|||||||
import com.intellij.util.containers.HashSetQueue
|
import com.intellij.util.containers.HashSetQueue
|
||||||
|
|
||||||
class TestingBackgroundExecutor internal constructor(
|
class TestingBackgroundExecutor internal constructor(
|
||||||
private val rootsManager: ScriptClassRootsUpdater
|
private val rootsManager: ScriptClassRootsIndexer
|
||||||
) : BackgroundExecutor {
|
) : BackgroundExecutor {
|
||||||
val backgroundQueue = HashSetQueue<BackgroundTask>()
|
val backgroundQueue = HashSetQueue<BackgroundTask>()
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ class TestingBackgroundExecutor internal constructor(
|
|||||||
|
|
||||||
actions()
|
actions()
|
||||||
|
|
||||||
rootsManager.update {
|
rootsManager.transaction {
|
||||||
copy.forEach {
|
copy.forEach {
|
||||||
it.actions()
|
it.actions()
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-6
@@ -9,16 +9,12 @@ import com.intellij.openapi.externalSystem.service.project.autoimport.AsyncFileC
|
|||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import com.intellij.openapi.vfs.VirtualFileManager
|
import com.intellij.openapi.vfs.VirtualFileManager
|
||||||
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
|
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
|
|
||||||
|
|
||||||
fun addVfsListener(
|
fun addVfsListener(watcher: GradleScriptInputsWatcher) {
|
||||||
watcher: GradleScriptListener,
|
|
||||||
buildRootsManager: GradleBuildRootsManager
|
|
||||||
) {
|
|
||||||
VirtualFileManager.getInstance().addAsyncFileListener(
|
VirtualFileManager.getInstance().addAsyncFileListener(
|
||||||
object : AsyncFileChangeListenerBase() {
|
object : AsyncFileChangeListenerBase() {
|
||||||
override fun isRelevant(path: String): Boolean {
|
override fun isRelevant(path: String): Boolean {
|
||||||
return buildRootsManager.maybeAffectedGradleProjectFile(path)
|
return isInAffectedGradleProjectFiles(watcher.project, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateFile(file: VirtualFile, event: VFileEvent) {
|
override fun updateFile(file: VirtualFile, event: VFileEvent) {
|
||||||
|
|||||||
+2
-6
@@ -9,12 +9,8 @@ import com.intellij.openapi.vfs.VirtualFileManager
|
|||||||
import com.intellij.openapi.vfs.newvfs.BulkFileListener
|
import com.intellij.openapi.vfs.newvfs.BulkFileListener
|
||||||
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
|
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
|
||||||
import com.intellij.util.concurrency.SequentialTaskExecutor
|
import com.intellij.util.concurrency.SequentialTaskExecutor
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
|
|
||||||
|
|
||||||
fun addVfsListener(
|
fun addVfsListener(watcher: GradleScriptInputsWatcher) {
|
||||||
watcher: GradleScriptListener,
|
|
||||||
buildRootsManager: GradleBuildRootsManager
|
|
||||||
) {
|
|
||||||
watcher.project.messageBus.connect().subscribe(
|
watcher.project.messageBus.connect().subscribe(
|
||||||
VirtualFileManager.VFS_CHANGES,
|
VirtualFileManager.VFS_CHANGES,
|
||||||
object : BulkFileListener {
|
object : BulkFileListener {
|
||||||
@@ -26,7 +22,7 @@ fun addVfsListener(
|
|||||||
if (watcher.project.isDisposed) return@Runnable
|
if (watcher.project.isDisposed) return@Runnable
|
||||||
for (event in events) {
|
for (event in events) {
|
||||||
val file = event.file ?: continue
|
val file = event.file ?: continue
|
||||||
if (buildRootsManager.maybeAffectedGradleProjectFile(event.path)) {
|
if (isInAffectedGradleProjectFiles(watcher.project, event.path)) {
|
||||||
watcher.fileChanged(event.path, file.timeStamp)
|
watcher.fileChanged(event.path, file.timeStamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-6
@@ -9,16 +9,12 @@ import com.intellij.openapi.externalSystem.autoimport.AsyncFileChangeListenerBas
|
|||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import com.intellij.openapi.vfs.VirtualFileManager
|
import com.intellij.openapi.vfs.VirtualFileManager
|
||||||
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
|
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
|
|
||||||
|
|
||||||
fun addVfsListener(
|
fun addVfsListener(watcher: GradleScriptInputsWatcher) {
|
||||||
watcher: GradleScriptListener,
|
|
||||||
buildRootsManager: GradleBuildRootsManager
|
|
||||||
) {
|
|
||||||
VirtualFileManager.getInstance().addAsyncFileListener(
|
VirtualFileManager.getInstance().addAsyncFileListener(
|
||||||
object : AsyncFileChangeListenerBase() {
|
object : AsyncFileChangeListenerBase() {
|
||||||
override fun isRelevant(path: String): Boolean {
|
override fun isRelevant(path: String): Boolean {
|
||||||
return buildRootsManager.maybeAffectedGradleProjectFile(path)
|
return isInAffectedGradleProjectFiles(watcher.project, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateFile(file: VirtualFile, event: VFileEvent) {
|
override fun updateFile(file: VirtualFile, event: VFileEvent) {
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.scripting.gradle
|
||||||
|
|
||||||
|
import com.intellij.openapi.util.io.FileUtil
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel
|
||||||
|
|
||||||
|
data class ConfigurationData(
|
||||||
|
val templateClasspath: List<String>,
|
||||||
|
val models: List<KotlinDslScriptModel>
|
||||||
|
)
|
||||||
|
|
||||||
|
class Configuration(val data: ConfigurationData) {
|
||||||
|
|
||||||
|
private val scripts: Map<String, KotlinDslScriptModel>
|
||||||
|
val sourcePath: MutableSet<String>
|
||||||
|
|
||||||
|
val classFilePath: MutableSet<String> = mutableSetOf()
|
||||||
|
|
||||||
|
init {
|
||||||
|
val allModels = data.models
|
||||||
|
|
||||||
|
scripts = allModels.associateBy { it.file }
|
||||||
|
sourcePath = allModels.flatMapTo(mutableSetOf()) { it.sourcePath }
|
||||||
|
|
||||||
|
classFilePath.addAll(data.templateClasspath)
|
||||||
|
allModels.flatMapTo(classFilePath) { it.classPath }
|
||||||
|
}
|
||||||
|
fun scriptModel(file: VirtualFile): KotlinDslScriptModel? {
|
||||||
|
return scripts[FileUtil.toSystemDependentName(file.path)]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.scripting.gradle
|
||||||
|
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.projectRoots.Sdk
|
||||||
|
import com.intellij.openapi.vfs.VfsUtil
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache.Companion.getScriptSdkOrDefault
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsStorage
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsStorage.Companion.ScriptClassRoots
|
||||||
|
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel
|
||||||
|
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
|
||||||
|
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||||
|
import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource
|
||||||
|
import org.jetbrains.kotlin.scripting.resolve.adjustByDefinition
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.script.experimental.api.*
|
||||||
|
import kotlin.script.experimental.jvm.JvmDependency
|
||||||
|
import kotlin.script.experimental.jvm.jdkHome
|
||||||
|
import kotlin.script.experimental.jvm.jvm
|
||||||
|
|
||||||
|
class GradleClassRootsCache(
|
||||||
|
val project: Project,
|
||||||
|
val context: GradleKtsContext,
|
||||||
|
val configuration: Configuration
|
||||||
|
) : ScriptClassRootsCache(
|
||||||
|
project,
|
||||||
|
ScriptClassRootsStorage.Companion.Key("gradle"),
|
||||||
|
extractRoots(context, configuration, project)
|
||||||
|
) {
|
||||||
|
override fun getConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? =
|
||||||
|
configuration.scriptModel(file)?.toScriptConfiguration()
|
||||||
|
|
||||||
|
private fun KotlinDslScriptModel.toScriptConfiguration(): ScriptCompilationConfigurationWrapper? {
|
||||||
|
val scriptFile = File(file)
|
||||||
|
val virtualFile = VfsUtil.findFile(scriptFile.toPath(), true)!!
|
||||||
|
|
||||||
|
val definition = virtualFile.findScriptDefinition(project) ?: return null
|
||||||
|
|
||||||
|
return ScriptCompilationConfigurationWrapper.FromCompilationConfiguration(
|
||||||
|
VirtualFileScriptSource(virtualFile),
|
||||||
|
definition.compilationConfiguration.with {
|
||||||
|
if (context.javaHome != null) {
|
||||||
|
jvm.jdkHome(context.javaHome)
|
||||||
|
}
|
||||||
|
defaultImports(imports)
|
||||||
|
dependencies(JvmDependency(classPath.map { File(it) }))
|
||||||
|
ide.dependenciesSources(JvmDependency(sourcePath.map { File(it) }))
|
||||||
|
}.adjustByDefinition(definition)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getScriptSdk(file: VirtualFile): Sdk? {
|
||||||
|
return firstScriptSdk
|
||||||
|
}
|
||||||
|
|
||||||
|
override val firstScriptSdk: Sdk? = getScriptSdkOrDefault(context.javaHome, project)
|
||||||
|
|
||||||
|
// called to ensure that configuration for file is loaded
|
||||||
|
// as we cannot force loading, we always return true
|
||||||
|
override fun contains(file: VirtualFile): Boolean = true
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun extractRoots(context: GradleKtsContext, configuration: Configuration?, project: Project): ScriptClassRoots {
|
||||||
|
if (configuration == null) {
|
||||||
|
return ScriptClassRootsStorage.EMPTY
|
||||||
|
}
|
||||||
|
val scriptSdk = getScriptSdkOrDefault(context.javaHome, project)
|
||||||
|
if (scriptSdk != null && !scriptSdk.isAlreadyIndexed(project)) {
|
||||||
|
return ScriptClassRootsStorage.Companion.ScriptClassRoots(
|
||||||
|
configuration.classFilePath,
|
||||||
|
configuration.sourcePath,
|
||||||
|
setOf(scriptSdk)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return ScriptClassRoots(
|
||||||
|
configuration.classFilePath,
|
||||||
|
configuration.sourcePath,
|
||||||
|
getScriptSdkOrDefault(context.javaHome, project)?.let { setOf(it) } ?: setOf()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@ private const val kotlinDslNotificationGroupId = "Gradle Kotlin DSL Scripts"
|
|||||||
private var Project.notificationPanel: ScriptConfigurationChangedNotification?
|
private var Project.notificationPanel: ScriptConfigurationChangedNotification?
|
||||||
by UserDataProperty<Project, ScriptConfigurationChangedNotification>(Key.create("load.script.configuration.panel"))
|
by UserDataProperty<Project, ScriptConfigurationChangedNotification>(Key.create("load.script.configuration.panel"))
|
||||||
|
|
||||||
|
|
||||||
fun showNotificationForProjectImport(project: Project) {
|
fun showNotificationForProjectImport(project: Project) {
|
||||||
val existingPanel = project.notificationPanel
|
val existingPanel = project.notificationPanel
|
||||||
if (existingPanel != null) {
|
if (existingPanel != null) {
|
||||||
|
|||||||
+7
-5
@@ -21,17 +21,19 @@ import com.intellij.testFramework.LightVirtualFileBase
|
|||||||
import org.jetbrains.kotlin.idea.KotlinIcons
|
import org.jetbrains.kotlin.idea.KotlinIcons
|
||||||
import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle
|
import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModelResolver
|
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModelResolver
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
|
import org.jetbrains.kotlin.psi.UserDataProperty
|
||||||
import org.jetbrains.plugins.gradle.service.project.GradlePartialResolverPolicy
|
import org.jetbrains.plugins.gradle.service.project.GradlePartialResolverPolicy
|
||||||
|
import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverExtension
|
||||||
import org.jetbrains.plugins.gradle.util.GradleConstants
|
import org.jetbrains.plugins.gradle.util.GradleConstants
|
||||||
|
import java.util.function.Predicate
|
||||||
|
|
||||||
fun runPartialGradleImport(project: Project) {
|
fun runPartialGradleImport(project: Project) {
|
||||||
getGradleProjectSettings(project).forEach { gradleProjectSettings ->
|
getGradleProjectSettings(project).forEach {
|
||||||
ExternalSystemUtil.refreshProject(
|
ExternalSystemUtil.refreshProject(
|
||||||
gradleProjectSettings.externalProjectPath,
|
it.externalProjectPath,
|
||||||
ImportSpecBuilder(project, GradleConstants.SYSTEM_ID)
|
ImportSpecBuilder(project, GradleConstants.SYSTEM_ID)
|
||||||
.projectResolverPolicy(
|
.projectResolverPolicy(
|
||||||
GradlePartialResolverPolicy { it is KotlinDslScriptModelResolver }
|
GradlePartialResolverPolicy(Predicate<GradleProjectResolverExtension?> { it is KotlinDslScriptModelResolver })
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -79,6 +81,6 @@ class LoadConfigurationAction : AnAction(
|
|||||||
if (virtualFile is LightVirtualFileBase) return false
|
if (virtualFile is LightVirtualFileBase) return false
|
||||||
if (virtualFile == null || !virtualFile.isValid) return false
|
if (virtualFile == null || !virtualFile.isValid) return false
|
||||||
|
|
||||||
return GradleBuildRootsManager.getInstance(project).isConfigurationOutOfDate(virtualFile)
|
return GradleScriptingSupportProvider.getInstance(project).isConfigurationOutOfDate(virtualFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+7
-17
@@ -5,12 +5,10 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.scripting.gradle
|
package org.jetbrains.kotlin.idea.scripting.gradle
|
||||||
|
|
||||||
import com.intellij.openapi.progress.ProcessCanceledException
|
import com.intellij.openapi.components.service
|
||||||
import com.intellij.openapi.project.Project
|
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.cache.CachedConfigurationInputs
|
import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigurationInputs
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
|
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRoot
|
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,27 +16,19 @@ import org.jetbrains.kotlin.psi.KtFile
|
|||||||
* 1. It is out of date when essential [sections] are changed
|
* 1. It is out of date when essential [sections] are changed
|
||||||
* @see getGradleScriptInputsStamp
|
* @see getGradleScriptInputsStamp
|
||||||
* 2. When some related file is changed (other gradle script, gradle.properties file)
|
* 2. When some related file is changed (other gradle script, gradle.properties file)
|
||||||
* @see GradleBuildRoot.Linked.areRelatedFilesChangedBefore
|
* @see GradleScriptInputsWatcher.areRelatedFilesUpToDate
|
||||||
*
|
*
|
||||||
* [lastModifiedTs] is needed to check if some related file was changed since last update
|
* [inputsTS] is needed to check if some related file was changed since last update
|
||||||
*/
|
*/
|
||||||
data class GradleKotlinScriptConfigurationInputs(
|
data class GradleKotlinScriptConfigurationInputs(
|
||||||
val sections: String,
|
val sections: String,
|
||||||
val lastModifiedTs: Long,
|
val inputsTS: Long
|
||||||
val buildRoot: String? = null
|
|
||||||
) : CachedConfigurationInputs {
|
) : CachedConfigurationInputs {
|
||||||
override fun isUpToDate(project: Project, file: VirtualFile, ktFile: KtFile?): Boolean {
|
override fun isUpToDate(project: Project, file: VirtualFile, ktFile: KtFile?): Boolean {
|
||||||
try {
|
val actualStamp = getGradleScriptInputsStamp(project, file, ktFile) ?: return false
|
||||||
val actualStamp = getGradleScriptInputsStamp(project, file, ktFile) ?: return false
|
|
||||||
|
|
||||||
if (actualStamp.sections != this.sections) return false
|
if (actualStamp.sections != this.sections) return false
|
||||||
|
|
||||||
return buildRoot == null ||
|
return project.service<GradleScriptInputsWatcher>().areRelatedFilesUpToDate(file, inputsTS)
|
||||||
GradleBuildRootsManager.getInstance(project)
|
|
||||||
.getBuildRoot(buildRoot)
|
|
||||||
?.areRelatedFilesChangedBefore(file, lastModifiedTs) ?: false
|
|
||||||
} catch (cancel: ProcessCanceledException) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.scripting.gradle
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class GradleKtsContext(val javaHome: File?)
|
||||||
+5
-21
@@ -1,9 +1,9 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2020 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.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.jetbrains.kotlin.idea.scripting.gradle.legacy
|
package org.jetbrains.kotlin.idea.scripting.gradle
|
||||||
|
|
||||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
@@ -15,20 +15,11 @@ import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigura
|
|||||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.DefaultScriptConfigurationLoader
|
import org.jetbrains.kotlin.idea.core.script.configuration.loader.DefaultScriptConfigurationLoader
|
||||||
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.util.EDT
|
import org.jetbrains.kotlin.idea.core.util.EDT
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.getGradleScriptInputsStamp
|
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.isGradleKotlinScript
|
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
|
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.useScriptConfigurationFromImportOnly
|
|
||||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||||
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
|
||||||
|
|
||||||
/**
|
class GradleScriptConfigurationLoaderForOutOfProjectScripts(project: Project) : DefaultScriptConfigurationLoader(project) {
|
||||||
* Loader that performs loading for .gralde.kts scripts configuration through the [DefaultScriptingSupport]
|
|
||||||
*
|
|
||||||
* TODO(gradle6): remove
|
|
||||||
*/
|
|
||||||
class GradleLegacyScriptConfigurationLoaderForOutOfProjectScripts(project: Project) : DefaultScriptConfigurationLoader(project) {
|
|
||||||
override fun loadDependencies(
|
override fun loadDependencies(
|
||||||
isFirstLoad: Boolean,
|
isFirstLoad: Boolean,
|
||||||
ktFile: KtFile,
|
ktFile: KtFile,
|
||||||
@@ -52,14 +43,7 @@ class GradleLegacyScriptConfigurationLoaderForOutOfProjectScripts(project: Proje
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
class GradleScriptConfigurationLoader(project: Project) : DefaultScriptConfigurationLoader(project) {
|
||||||
* Loader that performs loading for .gralde.kts scripts configuration through the [DefaultScriptingSupport]
|
|
||||||
*
|
|
||||||
* TODO(gradle6): remove
|
|
||||||
*/
|
|
||||||
class GradleLegacyScriptConfigurationLoader(project: Project) : DefaultScriptConfigurationLoader(project) {
|
|
||||||
private val buildRootsManager = GradleBuildRootsManager.getInstance(project)
|
|
||||||
|
|
||||||
override fun shouldRunInBackground(scriptDefinition: ScriptDefinition): Boolean {
|
override fun shouldRunInBackground(scriptDefinition: ScriptDefinition): Boolean {
|
||||||
return if (useScriptConfigurationFromImportOnly()) false else super.shouldRunInBackground(scriptDefinition)
|
return if (useScriptConfigurationFromImportOnly()) false else super.shouldRunInBackground(scriptDefinition)
|
||||||
}
|
}
|
||||||
@@ -80,7 +64,7 @@ class GradleLegacyScriptConfigurationLoader(project: Project) : DefaultScriptCon
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!buildRootsManager.isAffectedGradleProjectFile(vFile.path)) {
|
if (!isInAffectedGradleProjectFiles(ktFile.project, vFile.path)) {
|
||||||
ScriptConfigurationManager.markFileWithManualConfigurationLoading(vFile)
|
ScriptConfigurationManager.markFileWithManualConfigurationLoading(vFile)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
+2
-18
@@ -11,7 +11,6 @@ import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
|
|||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.util.EnvironmentUtil
|
import com.intellij.util.EnvironmentUtil
|
||||||
import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle
|
import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle
|
||||||
import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionContributor
|
|
||||||
import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionSourceAsContributor
|
import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionSourceAsContributor
|
||||||
import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionsManager
|
import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionsManager
|
||||||
import org.jetbrains.kotlin.idea.core.script.loadDefinitionsFromTemplates
|
import org.jetbrains.kotlin.idea.core.script.loadDefinitionsFromTemplates
|
||||||
@@ -41,21 +40,6 @@ import kotlin.script.experimental.location.ScriptExpectedLocation
|
|||||||
import kotlin.script.templates.standard.ScriptTemplateWithArgs
|
import kotlin.script.templates.standard.ScriptTemplateWithArgs
|
||||||
|
|
||||||
class GradleScriptDefinitionsContributor(private val project: Project) : ScriptDefinitionSourceAsContributor {
|
class GradleScriptDefinitionsContributor(private val project: Project) : ScriptDefinitionSourceAsContributor {
|
||||||
companion object {
|
|
||||||
fun getDefinitions(project: Project) =
|
|
||||||
ScriptDefinitionContributor.EP_NAME.getExtensions(project)
|
|
||||||
.filterIsInstance<GradleScriptDefinitionsContributor>()
|
|
||||||
.single().definitions.toList()
|
|
||||||
|
|
||||||
fun getDefinitionsTemplateClasspath(project: Project): MutableSet<String> =
|
|
||||||
mutableSetOf<String>().also { result ->
|
|
||||||
getDefinitions(project).forEach { definition ->
|
|
||||||
definition.asLegacyOrNull<KotlinScriptDefinitionFromAnnotatedTemplate>()
|
|
||||||
?.templateClasspath
|
|
||||||
?.mapNotNullTo(result) { it.path }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override val id: String = "Gradle Kotlin DSL"
|
override val id: String = "Gradle Kotlin DSL"
|
||||||
private val failedToLoad = AtomicBoolean(false)
|
private val failedToLoad = AtomicBoolean(false)
|
||||||
@@ -144,8 +128,8 @@ class GradleScriptDefinitionsContributor(private val project: Project) : ScriptD
|
|||||||
private fun kotlinStdlibAndCompiler(gradleLibDir: File): List<File> {
|
private fun kotlinStdlibAndCompiler(gradleLibDir: File): List<File> {
|
||||||
// additionally need compiler jar to load gradle resolver
|
// additionally need compiler jar to load gradle resolver
|
||||||
return gradleLibDir.listFiles { file ->
|
return gradleLibDir.listFiles { file ->
|
||||||
file.name.startsWith("kotlin-compiler-embeddable") || file.name.startsWith("kotlin-stdlib")
|
file.name.startsWith("kotlin-compiler-embeddable") || file.name.startsWith("kotlin-stdlib")
|
||||||
}
|
}
|
||||||
.firstOrNull()?.let(::listOf).orEmpty()
|
.firstOrNull()?.let(::listOf).orEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.scripting.gradle
|
||||||
|
|
||||||
|
import com.intellij.openapi.components.*
|
||||||
|
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import org.jetbrains.annotations.TestOnly
|
||||||
|
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
|
||||||
|
import org.jetbrains.plugins.gradle.util.GradleConstants
|
||||||
|
|
||||||
|
@State(
|
||||||
|
name = "KotlinBuildScriptsModificationInfo",
|
||||||
|
storages = [Storage(StoragePathMacros.CACHE_FILE)]
|
||||||
|
)
|
||||||
|
class GradleScriptInputsWatcher(val project: Project) : PersistentStateComponent<GradleScriptInputsWatcher.Storage> {
|
||||||
|
private var storage = Storage()
|
||||||
|
|
||||||
|
private var cachedGradleProjectsRoots: Set<String>? = null
|
||||||
|
|
||||||
|
fun getGradleProjectsRoots(): Set<String> {
|
||||||
|
if (cachedGradleProjectsRoots == null) {
|
||||||
|
cachedGradleProjectsRoots = computeGradleProjectRoots(project)
|
||||||
|
}
|
||||||
|
return cachedGradleProjectsRoots ?: emptySet()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveGradleProjectRootsAfterImport(roots: Set<String>) {
|
||||||
|
val oldRoots = cachedGradleProjectsRoots
|
||||||
|
if (oldRoots != null && oldRoots.isNotEmpty()) {
|
||||||
|
cachedGradleProjectsRoots = oldRoots + roots
|
||||||
|
} else {
|
||||||
|
cachedGradleProjectsRoots = roots
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun computeGradleProjectRoots(project: Project): Set<String> {
|
||||||
|
val gradleSettings = ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID)
|
||||||
|
if (gradleSettings.getLinkedProjectsSettings().isEmpty()) return setOf()
|
||||||
|
|
||||||
|
val projectSettings = gradleSettings.getLinkedProjectsSettings().filterIsInstance<GradleProjectSettings>().firstOrNull()
|
||||||
|
?: return setOf()
|
||||||
|
|
||||||
|
return projectSettings.modules.takeIf { it.isNotEmpty() } ?: setOf(projectSettings.externalProjectPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startWatching() {
|
||||||
|
addVfsListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun areRelatedFilesUpToDate(file: VirtualFile, timeStamp: Long): Boolean {
|
||||||
|
return storage.lastModifiedTimeStampExcept(file.path) < timeStamp
|
||||||
|
}
|
||||||
|
|
||||||
|
class Storage {
|
||||||
|
private val lastModifiedFiles = LastModifiedFiles()
|
||||||
|
|
||||||
|
fun lastModifiedTimeStampExcept(filePath: String): Long {
|
||||||
|
return lastModifiedFiles.lastModifiedTimeStampExcept(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fileChanged(filePath: String, ts: Long) {
|
||||||
|
lastModifiedFiles.fileChanged(ts, filePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getState(): Storage {
|
||||||
|
return storage
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadState(state: Storage) {
|
||||||
|
this.storage = state
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fileChanged(filePath: String, ts: Long) {
|
||||||
|
storage.fileChanged(filePath, ts)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearState() {
|
||||||
|
storage = Storage()
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOnly
|
||||||
|
fun clearAndRefillState() {
|
||||||
|
loadState(project.service<GradleScriptInputsWatcher>().state)
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
-38
@@ -1,62 +1,44 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2020 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.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.jetbrains.kotlin.idea.scripting.gradle
|
package org.jetbrains.kotlin.idea.scripting.gradle
|
||||||
|
|
||||||
|
import com.intellij.openapi.components.service
|
||||||
import com.intellij.openapi.project.Project
|
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.listener.ScriptChangeListener
|
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.legacy.GradleLegacyScriptListener
|
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
|
|
||||||
|
|
||||||
class GradleScriptListener(project: Project) : ScriptChangeListener(project) {
|
|
||||||
// todo(gradle6): remove
|
|
||||||
private val legacy = GradleLegacyScriptListener(project)
|
|
||||||
private val buildRootsManager = GradleBuildRootsManager.getInstance(project)
|
|
||||||
|
|
||||||
|
open class GradleScriptListener(project: Project) : ScriptChangeListener(project) {
|
||||||
init {
|
init {
|
||||||
// listen changes using VFS events, including gradle-configuration related files
|
// start GradleScriptInputsWatcher to track changes in gradle-configuration related files
|
||||||
addVfsListener(this, buildRootsManager)
|
project.service<GradleScriptInputsWatcher>().startWatching()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fileChanged(filePath: String, ts: Long) =
|
override fun editorActivated(vFile: VirtualFile, updater: ScriptConfigurationUpdater) {
|
||||||
buildRootsManager.fileChanged(filePath, ts)
|
if (!isInAffectedGradleProjectFiles(project, vFile.path)) return
|
||||||
|
|
||||||
override fun isApplicable(vFile: VirtualFile) =
|
if (useScriptConfigurationFromImportOnly()) {
|
||||||
// todo(gradle6): replace with `isCustomScriptingSupport(vFile)`
|
// do nothing
|
||||||
legacy.isApplicable(vFile)
|
|
||||||
|
|
||||||
private fun isCustomScriptingSupport(vFile: VirtualFile) =
|
|
||||||
buildRootsManager.isApplicable(vFile)
|
|
||||||
|
|
||||||
override fun editorActivated(vFile: VirtualFile) {
|
|
||||||
if (isCustomScriptingSupport(vFile)) {
|
|
||||||
checkUpToDate(vFile)
|
|
||||||
} else {
|
} else {
|
||||||
legacy.editorActivated(vFile)
|
val file = getAnalyzableKtFileForScript(vFile) ?: return
|
||||||
|
updater.suggestToUpdateConfigurationIfOutOfDate(file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun documentChanged(vFile: VirtualFile) {
|
override fun documentChanged(vFile: VirtualFile, updater: ScriptConfigurationUpdater) {
|
||||||
fileChanged(vFile.path, System.currentTimeMillis())
|
if (!isInAffectedGradleProjectFiles(project, vFile.path)) return
|
||||||
|
|
||||||
if (isCustomScriptingSupport(vFile)) {
|
val file = getAnalyzableKtFileForScript(vFile)
|
||||||
checkUpToDate(vFile)
|
if (file != null) {
|
||||||
} else {
|
// *.gradle.kts file was changed
|
||||||
legacy.documentChanged(vFile)
|
updater.suggestToUpdateConfigurationIfOutOfDate(file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkUpToDate(vFile: VirtualFile) {
|
override fun isApplicable(vFile: VirtualFile): Boolean {
|
||||||
val upToDate = GradleBuildRootsManager.getInstance(project)
|
return isGradleKotlinScript(vFile)
|
||||||
.getScriptInfo(vFile)?.model?.inputs?.isUpToDate(project, vFile) ?: return
|
|
||||||
|
|
||||||
if (upToDate) {
|
|
||||||
hideNotificationForProjectImport(project)
|
|
||||||
} else {
|
|
||||||
showNotificationForProjectImport(project)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.scripting.gradle
|
||||||
|
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||||
|
|
||||||
|
class GradleScriptingSupport(
|
||||||
|
val project: Project,
|
||||||
|
val buildRoot: VirtualFile,
|
||||||
|
val context: GradleKtsContext,
|
||||||
|
val configuration: Configuration
|
||||||
|
) : ScriptingSupport() {
|
||||||
|
|
||||||
|
override fun recreateRootsCache() = GradleClassRootsCache(project, context, configuration)
|
||||||
|
|
||||||
|
fun isConfigurationOutOfDate(file: VirtualFile): Boolean {
|
||||||
|
val scriptModel = configuration.scriptModel(file) ?: return false
|
||||||
|
return !scriptModel.inputs.isUpToDate(project, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearCaches() {
|
||||||
|
// todo: should clear up to date
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasCachedConfiguration(file: KtFile): Boolean =
|
||||||
|
configuration.scriptModel(file.originalFile.virtualFile) != null
|
||||||
|
|
||||||
|
// TODO: can be true during import
|
||||||
|
override fun isConfigurationLoadingInProgress(file: KtFile): Boolean = false
|
||||||
|
|
||||||
|
override fun getOrLoadConfiguration(virtualFile: VirtualFile, preloadedKtFile: KtFile?): ScriptCompilationConfigurationWrapper? {
|
||||||
|
return classpathRoots.getScriptConfiguration(virtualFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val updater: ScriptConfigurationUpdater
|
||||||
|
get() = object : ScriptConfigurationUpdater {
|
||||||
|
override fun ensureUpToDatedConfigurationSuggested(file: KtFile) {
|
||||||
|
// do nothing for gradle scripts
|
||||||
|
}
|
||||||
|
|
||||||
|
// unused symbol inspection should not initiate loading
|
||||||
|
override fun ensureConfigurationUpToDate(files: List<KtFile>): Boolean = true
|
||||||
|
|
||||||
|
override fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile) {
|
||||||
|
val vFile = file.originalFile.virtualFile
|
||||||
|
|
||||||
|
if (isConfigurationOutOfDate(vFile)) {
|
||||||
|
showNotificationForProjectImport(project)
|
||||||
|
} else {
|
||||||
|
hideNotificationForProjectImport(project)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+208
@@ -0,0 +1,208 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.scripting.gradle
|
||||||
|
|
||||||
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
|
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.vfs.VfsUtil
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupportHelper
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsIndexer
|
||||||
|
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslGradleBuildSync
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
|
||||||
|
import org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotatedTemplate
|
||||||
|
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||||
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
|
||||||
|
import org.jetbrains.plugins.gradle.config.GradleSettingsListenerAdapter
|
||||||
|
import org.jetbrains.plugins.gradle.settings.GradleExecutionSettings
|
||||||
|
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
|
||||||
|
import org.jetbrains.plugins.gradle.settings.GradleSettingsListener
|
||||||
|
import org.jetbrains.plugins.gradle.util.GradleConstants
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates [GradleScriptingSupport] per each linked Gradle build.
|
||||||
|
* Gradle project sync should call [update].
|
||||||
|
*/
|
||||||
|
class GradleScriptingSupportProvider(val project: Project) : ScriptingSupport.Provider() {
|
||||||
|
private val rootsIndexer = ScriptClassRootsIndexer(project)
|
||||||
|
|
||||||
|
val roots = RootsIndex<GradleScriptingSupport>()
|
||||||
|
override val all get() = roots.values
|
||||||
|
|
||||||
|
private val VirtualFile.localPath
|
||||||
|
get() = path
|
||||||
|
|
||||||
|
private fun findRoot(file: VirtualFile) = roots.findRoot(file.localPath)
|
||||||
|
|
||||||
|
override fun getSupport(file: VirtualFile): ScriptingSupport? {
|
||||||
|
if (isGradleKotlinScript(file)) {
|
||||||
|
findRoot(file)?.let { return it }
|
||||||
|
|
||||||
|
val externalProjectSettings = findExternalProjectSettings(file) ?: return null
|
||||||
|
if (kotlinDslScriptsModelImportSupported(getGradleVersion(project, externalProjectSettings))) {
|
||||||
|
return unlinkedFilesSupport
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findExternalProjectSettings(file: VirtualFile): GradleProjectSettings? {
|
||||||
|
return getGradleProjectSettings(project)
|
||||||
|
.filter { file.localPath.startsWith(it.externalProjectPath) }
|
||||||
|
.maxBy { it.externalProjectPath.length }
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
ApplicationManager.getApplication().executeOnPooledThread {
|
||||||
|
roots.update { set ->
|
||||||
|
getGradleProjectSettings(project).forEach { gradleProjectSettings ->
|
||||||
|
if (kotlinDslScriptsModelImportSupported(getGradleVersion(project, gradleProjectSettings))) {
|
||||||
|
val support = createSupport(gradleProjectSettings.externalProjectPath) {
|
||||||
|
KotlinDslScriptModels.read(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (support != null) {
|
||||||
|
set(support.buildRoot.localPath, support)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// subscribe to gradle build unlink
|
||||||
|
val listener = object : GradleSettingsListenerAdapter() {
|
||||||
|
override fun onProjectsUnlinked(linkedProjectPaths: MutableSet<String>) {
|
||||||
|
linkedProjectPaths.forEach {
|
||||||
|
val buildRoot = VfsUtil.findFile(Paths.get(it), false)
|
||||||
|
if (buildRoot != null) {
|
||||||
|
if (roots.remove(buildRoot.localPath) != null) {
|
||||||
|
KotlinDslScriptModels.remove(buildRoot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
project.messageBus.connect(project).subscribe(GradleSettingsListener.TOPIC, listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(build: KotlinDslGradleBuildSync) {
|
||||||
|
// fast path for linked gradle builds without .gradle.kts support
|
||||||
|
if (build.models.isEmpty()) {
|
||||||
|
val root = roots.findRoot(build.workingDir) ?: return
|
||||||
|
if (root.configuration.data.models.isEmpty()) return
|
||||||
|
}
|
||||||
|
|
||||||
|
val templateClasspath = findTemplateClasspath(build) ?: return
|
||||||
|
val data = ConfigurationData(templateClasspath, build.models)
|
||||||
|
val newSupport = createSupport(build.workingDir) { data } ?: return
|
||||||
|
KotlinDslScriptModels.write(newSupport.buildRoot, data)
|
||||||
|
|
||||||
|
roots[newSupport.buildRoot.localPath] = newSupport
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSupport(
|
||||||
|
externalProjectPath: String,
|
||||||
|
dataProvider: (buildRoot: VirtualFile) -> ConfigurationData?
|
||||||
|
): GradleScriptingSupport? {
|
||||||
|
val gradleExeSettings =
|
||||||
|
ExternalSystemApiUtil.getExecutionSettings<GradleExecutionSettings>(
|
||||||
|
project,
|
||||||
|
externalProjectPath,
|
||||||
|
GradleConstants.SYSTEM_ID
|
||||||
|
)
|
||||||
|
|
||||||
|
val buildRoot = VfsUtil.findFile(Paths.get(externalProjectPath), true) ?: return null
|
||||||
|
val data = dataProvider(buildRoot) ?: return null
|
||||||
|
|
||||||
|
val newSupport = GradleScriptingSupport(
|
||||||
|
project,
|
||||||
|
buildRoot,
|
||||||
|
GradleKtsContext(gradleExeSettings.javaHome?.let { File(it) }),
|
||||||
|
Configuration(data)
|
||||||
|
)
|
||||||
|
|
||||||
|
val oldSupport = roots.findRoot(externalProjectPath)
|
||||||
|
if (oldSupport != null) {
|
||||||
|
rootsIndexer.transaction {
|
||||||
|
val newRoots = GradleClassRootsCache.extractRoots(newSupport.context, newSupport.configuration, project)
|
||||||
|
if (oldSupport.classpathRoots.hasNotCachedRoots(newRoots)) {
|
||||||
|
rootsIndexer.markNewRoot()
|
||||||
|
}
|
||||||
|
|
||||||
|
newSupport.clearClassRootsCaches(project)
|
||||||
|
|
||||||
|
ScriptingSupportHelper.updateHighlighting(project) {
|
||||||
|
newSupport.configuration.scriptModel(it) != null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideNotificationForProjectImport(project)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newSupport
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findTemplateClasspath(build: KotlinDslGradleBuildSync): List<String>? {
|
||||||
|
val anyScript = VfsUtil.findFile(Paths.get(build.models.first().file), true)!!
|
||||||
|
// todo: find definition according to build.workingDir
|
||||||
|
val definition = anyScript.findScriptDefinition(project) ?: return null
|
||||||
|
return definition.asLegacyOrNull<KotlinScriptDefinitionFromAnnotatedTemplate>()
|
||||||
|
?.templateClasspath?.map { it.path }
|
||||||
|
}
|
||||||
|
|
||||||
|
private val unlinkedFilesSupport = object : ScriptingSupport() {
|
||||||
|
override fun clearCaches() {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasCachedConfiguration(file: KtFile): Boolean = false
|
||||||
|
override fun isConfigurationLoadingInProgress(file: KtFile): Boolean = false
|
||||||
|
|
||||||
|
override fun getOrLoadConfiguration(
|
||||||
|
virtualFile: VirtualFile,
|
||||||
|
preloadedKtFile: KtFile?
|
||||||
|
): ScriptCompilationConfigurationWrapper? = null
|
||||||
|
|
||||||
|
override val updater: ScriptConfigurationUpdater
|
||||||
|
get() = object : ScriptConfigurationUpdater {
|
||||||
|
override fun ensureUpToDatedConfigurationSuggested(file: KtFile) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun ensureConfigurationUpToDate(files: List<KtFile>): Boolean = false
|
||||||
|
|
||||||
|
override fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun recreateRootsCache(): ScriptClassRootsCache = ScriptClassRootsCache.empty(project)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isMissingConfigurationCanBeLoadedDuringImport(file: VirtualFile): Boolean {
|
||||||
|
findRoot(file)?.let { return false }
|
||||||
|
|
||||||
|
val externalProjectSettings = findExternalProjectSettings(file) ?: return false
|
||||||
|
return kotlinDslScriptsModelImportSupported(getGradleVersion(project, externalProjectSettings))
|
||||||
|
}
|
||||||
|
|
||||||
|
// used in 201
|
||||||
|
@Suppress("UNUSED")
|
||||||
|
fun isConfigurationOutOfDate(file: VirtualFile): Boolean {
|
||||||
|
val support = findRoot(file) ?: return false
|
||||||
|
return support.isConfigurationOutOfDate(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getInstance(project: Project): GradleScriptingSupportProvider =
|
||||||
|
EPN.getPoint(project).extensionList.firstIsInstance()
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-51
@@ -5,15 +5,6 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.scripting.gradle
|
package org.jetbrains.kotlin.idea.scripting.gradle
|
||||||
|
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
|
||||||
import com.intellij.openapi.vfs.newvfs.FileAttribute
|
|
||||||
import org.jetbrains.kotlin.idea.core.util.readNullable
|
|
||||||
import org.jetbrains.kotlin.idea.core.util.readStringList
|
|
||||||
import org.jetbrains.kotlin.idea.core.util.writeNullable
|
|
||||||
import org.jetbrains.kotlin.idea.core.util.writeStringList
|
|
||||||
import java.io.DataInputStream
|
|
||||||
import java.io.DataOutput
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optimized collection for storing last modified files with ability to
|
* Optimized collection for storing last modified files with ability to
|
||||||
* get time of last modified file expect given one ([lastModifiedTimeStampExcept]).
|
* get time of last modified file expect given one ([lastModifiedTimeStampExcept]).
|
||||||
@@ -23,14 +14,9 @@ import java.io.DataOutput
|
|||||||
*
|
*
|
||||||
* Actually works by storing two last timestamps with the set of files modified at this times.
|
* Actually works by storing two last timestamps with the set of files modified at this times.
|
||||||
*/
|
*/
|
||||||
class LastModifiedFiles(
|
class LastModifiedFiles {
|
||||||
private var last: SimultaneouslyChangedFiles = SimultaneouslyChangedFiles(),
|
private var last: SimultaneouslyChangedFiles = SimultaneouslyChangedFiles()
|
||||||
private var previous: SimultaneouslyChangedFiles = SimultaneouslyChangedFiles()
|
private var previous: SimultaneouslyChangedFiles = SimultaneouslyChangedFiles()
|
||||||
) {
|
|
||||||
init {
|
|
||||||
previous.fileIds.removeAll(last.fileIds)
|
|
||||||
if (previous.fileIds.isEmpty()) previous = SimultaneouslyChangedFiles()
|
|
||||||
}
|
|
||||||
|
|
||||||
class SimultaneouslyChangedFiles(
|
class SimultaneouslyChangedFiles(
|
||||||
val ts: Long = Long.MIN_VALUE,
|
val ts: Long = Long.MIN_VALUE,
|
||||||
@@ -41,10 +27,7 @@ class LastModifiedFiles(
|
|||||||
fun fileChanged(ts: Long, fileId: String) {
|
fun fileChanged(ts: Long, fileId: String) {
|
||||||
when {
|
when {
|
||||||
ts > last.ts -> {
|
ts > last.ts -> {
|
||||||
val prevPrev = previous
|
|
||||||
previous = last
|
previous = last
|
||||||
previous.fileIds.remove(fileId)
|
|
||||||
if (previous.fileIds.isEmpty()) previous = prevPrev
|
|
||||||
last = SimultaneouslyChangedFiles(ts, hashSetOf(fileId))
|
last = SimultaneouslyChangedFiles(ts, hashSetOf(fileId))
|
||||||
}
|
}
|
||||||
ts == last.ts -> last.fileIds.add(fileId)
|
ts == last.ts -> last.fileIds.add(fileId)
|
||||||
@@ -57,36 +40,4 @@ class LastModifiedFiles(
|
|||||||
last.fileIds.size == 1 && last.fileIds.contains(fileId) -> previous.ts
|
last.fileIds.size == 1 && last.fileIds.contains(fileId) -> previous.ts
|
||||||
else -> last.ts
|
else -> last.ts
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val fileAttribute = FileAttribute("last-modified-files", 1, false)
|
|
||||||
|
|
||||||
fun read(buildRoot: VirtualFile): LastModifiedFiles? {
|
|
||||||
return fileAttribute.readAttribute(buildRoot)?.use {
|
|
||||||
it.readNullable {
|
|
||||||
LastModifiedFiles(readSCF(it), readSCF(it))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun write(buildRoot: VirtualFile, data: LastModifiedFiles?) {
|
|
||||||
fileAttribute.writeAttribute(buildRoot).use {
|
|
||||||
it.writeNullable(data) { data ->
|
|
||||||
writeSCF(data.last)
|
|
||||||
writeSCF(data.previous)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun remove(buildRoot: VirtualFile) {
|
|
||||||
write(buildRoot, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun readSCF(it: DataInputStream) = SimultaneouslyChangedFiles(it.readLong(), it.readStringList().toMutableSet())
|
|
||||||
|
|
||||||
private fun DataOutput.writeSCF(last: SimultaneouslyChangedFiles) {
|
|
||||||
writeLong(last.ts)
|
|
||||||
writeStringList(last.fileIds)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+10
-14
@@ -16,24 +16,18 @@ import com.intellij.ui.EditorNotificationPanel
|
|||||||
import com.intellij.ui.EditorNotifications
|
import com.intellij.ui.EditorNotifications
|
||||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||||
import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle
|
import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.legacy.GradleLegacyScriptConfigurationLoaderForOutOfProjectScripts
|
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRoot
|
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
|
|
||||||
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
|
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
|
||||||
import org.jetbrains.plugins.gradle.util.GradleConstants
|
import org.jetbrains.plugins.gradle.util.GradleConstants
|
||||||
|
|
||||||
class MissingGradleScriptConfigurationNotificationProvider(private val project: Project) :
|
class MissingGradleScriptConfigurationNotificationProvider(private val project: Project) : EditorNotifications.Provider<EditorNotificationPanel>() {
|
||||||
EditorNotifications.Provider<EditorNotificationPanel>() {
|
|
||||||
override fun getKey(): Key<EditorNotificationPanel> = KEY
|
override fun getKey(): Key<EditorNotificationPanel> = KEY
|
||||||
|
|
||||||
override fun createNotificationPanel(file: VirtualFile, fileEditor: FileEditor): EditorNotificationPanel? {
|
override fun createNotificationPanel(file: VirtualFile, fileEditor: FileEditor): EditorNotificationPanel? {
|
||||||
if (!isGradleKotlinScript(file)) return null
|
if (!isGradleKotlinScript(file)) return null
|
||||||
if (file.fileType != KotlinFileType.INSTANCE) return null
|
if (file.fileType != KotlinFileType.INSTANCE) return null
|
||||||
|
|
||||||
val scriptUnderRoot = GradleBuildRootsManager.getInstance(project).findScriptBuildRoot(file)
|
if (!isInAffectedGradleProjectFiles(project, file.path)) {
|
||||||
val root = scriptUnderRoot?.root
|
return EditorNotificationPanel().apply {
|
||||||
return when {
|
|
||||||
root is GradleBuildRoot.Unlinked -> EditorNotificationPanel().apply {
|
|
||||||
text(KotlinIdeaGradleBundle.message("text.the.associated.gradle.project.isn.t.imported"))
|
text(KotlinIdeaGradleBundle.message("text.the.associated.gradle.project.isn.t.imported"))
|
||||||
|
|
||||||
val linkProjectText = KotlinIdeaGradleBundle.message("action.label.text.load.script.configuration")
|
val linkProjectText = KotlinIdeaGradleBundle.message("action.label.text.load.script.configuration")
|
||||||
@@ -59,20 +53,22 @@ class MissingGradleScriptConfigurationNotificationProvider(private val project:
|
|||||||
linkProjectText
|
linkProjectText
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
root is GradleBuildRoot.New && !root.importing -> EditorNotificationPanel().apply {
|
}
|
||||||
|
|
||||||
|
if (GradleScriptingSupportProvider.getInstance(project).isMissingConfigurationCanBeLoadedDuringImport(file)) {
|
||||||
|
return EditorNotificationPanel().apply {
|
||||||
text(getMissingConfigurationNotificationText())
|
text(getMissingConfigurationNotificationText())
|
||||||
createActionLabel(getMissingConfigurationActionText()) {
|
createActionLabel(getMissingConfigurationActionText()) {
|
||||||
runPartialGradleImport(project)
|
runPartialGradleImport(project)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private val loaderForOutOfProjectScripts by lazy {
|
private val loaderForOutOfProjectScripts by lazy {
|
||||||
GradleLegacyScriptConfigurationLoaderForOutOfProjectScripts(
|
GradleScriptConfigurationLoaderForOutOfProjectScripts(project)
|
||||||
project
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
# Kotlin Gradle build scripts support.
|
|
||||||
|
|
||||||
## Why custom Scripting support
|
|
||||||
|
|
||||||
The scripting support for Gradle becomes tricky since the default one was designed for a little bit another case: each script becomes configured individually on first opening/usage and loading supposes to be instant.
|
|
||||||
|
|
||||||
Unfortunately, this is not working well for the Gradle case: the script configuration gathering is long operation since the Gradle configuration phase required to be executed to get it. For large projects, it may take from 5 seconds for hot daemon and to minutes for initial project loading. It may download dependencies, compile and build buildSrc project and all these things will be done on first script usage, even when we need just to know is such word in reference to our symbol when we are calling "Find usages". Among that, Gradle is not able to gather individual script configuration and always provides configuration for all scripts.
|
|
||||||
|
|
||||||
Taking this in mind, it becomes better to have this strategy for Gradle scripts:
|
|
||||||
- The user performing Gradle project importing/reimporting by executing Gradle configuration phase
|
|
||||||
- We have all Gradle scripts configured
|
|
||||||
- We can save this configuration to FS cache which can be persisted between IntelliJ restarts.
|
|
||||||
|
|
||||||
## More details about what we already have
|
|
||||||
|
|
||||||
[ScriptConfigurationManager] service already existed for providing and updating scripts configuration. It was working by loading scripts configuration on the first opening, storing in-memory cache, and FS cache.
|
|
||||||
|
|
||||||
It also reloads it after each typing (with some throttling) and notifies the user if the configuration is changed with a suggestion to apply a new configuration. We may apply this configuration automatically, but it may introduce indexing which breaks UX (some action becomes unavailable during indexing). To leverage this situation, we are showing notification instead and indexing will occur only after an explicit user action. We can do this automatically if no new source and classes are introduced in dependencies, but this is not implemented yet.
|
|
||||||
|
|
||||||
Note: as we don't know in general when some script change causes script configuration change, we always run configuration reloading and comparing new configuration with the old one. If we can know that some particular change will affect script configuration, we can show notification before starting expensive loading, but this requires some knowledge of the script, so should be implemented by the script definition author. [ScriptConfigurationManager] provides ability to implement up-to-date stamp by extension point. It is implemented for the older Gradle versions, see "DefaultScriptingSupport extensions for older Gradle versions" below.
|
|
||||||
|
|
||||||
The manager also creates a list of all classes and sources of script dependencies to properly support analyzing of scripts. All these calculations are cached and invalidated on each change in script configurations.
|
|
||||||
|
|
||||||
Sometimes this loading may occur in the background, sometimes in sync. We also may apply configuration automatically in some cases (first loading, loading in sync, etc). All that introduces complex states intersection which is tricky to support. Adding another layer of complexity to that implementation is not a way to go.
|
|
||||||
|
|
||||||
## Custom scripting support
|
|
||||||
|
|
||||||
After some experiments and several iterations, the final idea is to provide the ability to create custom mechanics for managing the scripts configurations without breaking default scripting support.
|
|
||||||
|
|
||||||
As we already need a cache of all known scripts, the union of its classpath, and source path which is rebuilt from the scratch on any change, we may just provide the ability to populate this cache with custom content on each invalidation. That the main idea behind custom scripting support.
|
|
||||||
|
|
||||||
The other idea is to don't make any blocking work synchronously while getting some configuration. The cache should be always ready for unblocking reading. All updating operation is asynchronous and non-blocking thanks to copy-on-write strategy.
|
|
||||||
|
|
||||||
So, it should work in that way:
|
|
||||||
- Custom scripting support should manage the scripts configurations on its own (with own in-memory/fs cache).
|
|
||||||
- It should implement a method that will gather all script configuration providers and populate the classpath/sourcepath union that is used to get files for indexing.
|
|
||||||
- It may implement ScriptChangeListener extension point to do some invalidation on document change events.
|
|
||||||
- It should call `updater.update { ... updater.invalidate() ... }` when the new script configurations are required. The manager will schedule an asynchronous update in the background.
|
|
||||||
- It is not able to load new configurations lazily on the first request (it is still possible to calculate script configuration lazily, but file name should be already registered)
|
|
||||||
|
|
||||||
The [CompositeScriptConfigurationManager] will provide redirection of [ScriptConfigurationManager] calls to the custom [ScriptingSupport] or [DefaultScriptingSupport].
|
|
||||||
|
|
||||||
## Gradle CustomScriptingSupport
|
|
||||||
|
|
||||||
`GradleBuildRootsManager` implementing `ScriptingSupport` described above.
|
|
||||||
|
|
||||||
There are many Gradle builds can be linked with a single IntelliJ project (don't confuse with the Gradle project and included build). This complicates implementation a bit, as we should manage this builds separately: each of them may have its own script definitions, Gradle version and java home. Typically, the IntelliJ project has no more than one [GradleBuildRoot].
|
|
||||||
|
|
||||||
`GradleBuildRootsManager` actually managing linked Gradle builds. See it's KDoc for more details.
|
|
||||||
|
|
||||||
The script configuration is stored in FS by using the IntelliJ VFS file attributes.
|
|
||||||
|
|
||||||
Note that we can provide custom scripting support only for projects that using Gradle 6.0 and later, as gathering script models unavailable in older Gradle versions. `GradleBuildRootsManager` falling back to default scripting support for such linked Gradle builds. It is extended through: see "DefaultScriptingSupport extensions for older Gradle versions" for more details.
|
|
||||||
|
|
||||||
## Watching files states across IntelliJ restarts
|
|
||||||
|
|
||||||
To have consistent sate of scripts, we should also be aware of external script changes. This is achieved by watching files using the IntelliJ VFS events. [GradleScriptInputsWatcher] is responsible for that.
|
|
||||||
|
|
||||||
The first tricky part is that scripts are depending on each other: so, when one script is changed, we actually should invalidate all other scripts as we don't know dependencies between them (Gradle will provide this information later, but it is not yet implemented). Actually, we should know the last modified timestamp of all scripts excepting a particular one. This can be achieved by storing timestamps of two last modified files. [LastModifiedFiles] utility is responsible for that.
|
|
||||||
|
|
||||||
Another tricky part is that we should track only scripts belong the Gradle project and should ignore all other `*.gradle.kts` files (in `testData` for example). This is achieved by storing Gradle project roots, as scripts can be exactly near Gradle project roots (excepting included and precompiled scripts which are not fully supported yet). This can be gathered from the Gradle project import information or from GradleProjectSettings when the import has not occurred yet. `GradleBuildRootsManager` does it.
|
|
||||||
|
|
||||||
## Out of project Gradle scripts
|
|
||||||
|
|
||||||
There are scripts that are not linked to any import Gradle project. In `testData` for example. Currently, we are suggesting to import Gradle project for such scripts, but we also may implement standalone Gradle scripts supporting using the `DefaultScriptingSupport` for explicitly listed scripts. We can implement this in the future if it will be valuable for users.
|
|
||||||
|
|
||||||
## DefaultScriptingSupport extensions for older Gradle versions
|
|
||||||
|
|
||||||
For Gradle versions before 6.0, we are still falling back to the [DefaultScriptingSupport] with some extensions.
|
|
||||||
|
|
||||||
We are showing notification before loading as we knew what changes will cause it. This is done by:
|
|
||||||
- implementing `org.jetbrains.kotlin.scripting.idea.listener` extension point and calling `suggestToUpdateConfigurationIfOutOfDate` instead of `ensureUpToDatedConfigurationSuggested` on document changes.
|
|
||||||
- implementing `org.jetbrains.kotlin.scripting.idea.loader` extension point and overriding `getInputsStamp`
|
|
||||||
|
|
||||||
We are also managing out of project scripts the same way as it is done in `GradleBuildRootsManager`.
|
|
||||||
+15
-11
@@ -5,17 +5,19 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.scripting.gradle.importing
|
package org.jetbrains.kotlin.idea.scripting.gradle.importing
|
||||||
|
|
||||||
|
import com.intellij.openapi.components.service
|
||||||
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId
|
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.util.Key
|
import com.intellij.openapi.util.Key
|
||||||
import com.intellij.openapi.util.io.FileUtil.toSystemIndependentName
|
import com.intellij.openapi.util.io.FileUtil
|
||||||
import com.intellij.openapi.vfs.VfsUtil
|
import com.intellij.openapi.vfs.VfsUtil
|
||||||
import org.gradle.tooling.model.kotlin.dsl.EditorReportSeverity
|
import org.gradle.tooling.model.kotlin.dsl.EditorReportSeverity
|
||||||
import org.gradle.tooling.model.kotlin.dsl.KotlinDslScriptsModel
|
import org.gradle.tooling.model.kotlin.dsl.KotlinDslScriptsModel
|
||||||
import org.jetbrains.kotlin.gradle.BrokenKotlinDslScriptsModel
|
import org.jetbrains.kotlin.gradle.BrokenKotlinDslScriptsModel
|
||||||
import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle
|
import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle
|
||||||
|
import org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptInputsWatcher
|
||||||
|
import org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptingSupportProvider
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.getGradleScriptInputsStamp
|
import org.jetbrains.kotlin.idea.scripting.gradle.getGradleScriptInputsStamp
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
|
|
||||||
import org.jetbrains.kotlin.psi.NotNullableUserDataProperty
|
import org.jetbrains.kotlin.psi.NotNullableUserDataProperty
|
||||||
import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext
|
import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -90,19 +92,18 @@ private fun KotlinDslScriptsModel.toListOfScriptModels(project: Project): List<K
|
|||||||
|
|
||||||
// todo(KT-34440): take inputs snapshot before starting import
|
// todo(KT-34440): take inputs snapshot before starting import
|
||||||
KotlinDslScriptModel(
|
KotlinDslScriptModel(
|
||||||
toSystemIndependentName(file.absolutePath),
|
file.absolutePath,
|
||||||
getGradleScriptInputsStamp(project, virtualFile)!!, // TODO: NPE
|
// TODO: NPE
|
||||||
model.classPath.map { toSystemIndependentName(it.absolutePath) },
|
getGradleScriptInputsStamp(project, virtualFile)!!,
|
||||||
model.sourcePath.map { toSystemIndependentName(it.absolutePath) },
|
model.classPath.map { it.absolutePath },
|
||||||
|
model.sourcePath.map { it.absolutePath },
|
||||||
model.implicitImports,
|
model.implicitImports,
|
||||||
messages
|
messages
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class KotlinDslGradleBuildSync(val workingDir: String, val taskId: ExternalSystemTaskId) {
|
class KotlinDslGradleBuildSync(val workingDir: String, val taskId: ExternalSystemTaskId) {
|
||||||
val projectRoots = mutableSetOf<String>()
|
|
||||||
val models = mutableListOf<KotlinDslScriptModel>()
|
val models = mutableListOf<KotlinDslScriptModel>()
|
||||||
var failed = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveScriptModels(project: Project, build: KotlinDslGradleBuildSync) {
|
fun saveScriptModels(project: Project, build: KotlinDslGradleBuildSync) {
|
||||||
@@ -112,8 +113,11 @@ fun saveScriptModels(project: Project, build: KotlinDslGradleBuildSync) {
|
|||||||
errorReporter.reportError(File(model.file), model)
|
errorReporter.reportError(File(model.file), model)
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: use real info about projects
|
project.service<GradleScriptInputsWatcher>().saveGradleProjectRootsAfterImport(
|
||||||
build.projectRoots.addAll(build.models.map { toSystemIndependentName(File(it.file).parent) })
|
build.models.map { FileUtil.toSystemIndependentName(File(it.file).parent) }.toSet()
|
||||||
|
)
|
||||||
|
|
||||||
GradleBuildRootsManager.getInstance(project).update(build)
|
GradleScriptingSupportProvider.getInstance(project).update(build)
|
||||||
|
|
||||||
|
project.service<GradleScriptInputsWatcher>().clearState()
|
||||||
}
|
}
|
||||||
-25
@@ -9,8 +9,6 @@ import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId
|
|||||||
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListenerAdapter
|
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListenerAdapter
|
||||||
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType
|
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType
|
||||||
import org.jetbrains.kotlin.idea.framework.GRADLE_SYSTEM_ID
|
import org.jetbrains.kotlin.idea.framework.GRADLE_SYSTEM_ID
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
|
|
||||||
import java.lang.Exception
|
|
||||||
|
|
||||||
class KotlinDslSyncListener : ExternalSystemTaskNotificationListenerAdapter() {
|
class KotlinDslSyncListener : ExternalSystemTaskNotificationListenerAdapter() {
|
||||||
override fun onStart(id: ExternalSystemTaskId, workingDir: String?) {
|
override fun onStart(id: ExternalSystemTaskId, workingDir: String?) {
|
||||||
@@ -20,7 +18,6 @@ class KotlinDslSyncListener : ExternalSystemTaskNotificationListenerAdapter() {
|
|||||||
if (workingDir == null) return
|
if (workingDir == null) return
|
||||||
val project = id.findProject() ?: return
|
val project = id.findProject() ?: return
|
||||||
|
|
||||||
GradleBuildRootsManager.getInstance(project).markImportingInProgress(workingDir)
|
|
||||||
project.kotlinGradleDslSync[id] = KotlinDslGradleBuildSync(workingDir, id)
|
project.kotlinGradleDslSync[id] = KotlinDslGradleBuildSync(workingDir, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,26 +30,4 @@ class KotlinDslSyncListener : ExternalSystemTaskNotificationListenerAdapter() {
|
|||||||
|
|
||||||
saveScriptModels(project, sync)
|
saveScriptModels(project, sync)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(id: ExternalSystemTaskId, e: Exception) {
|
|
||||||
if (id.type != ExternalSystemTaskType.RESOLVE_PROJECT) return
|
|
||||||
if (id.projectSystemId != GRADLE_SYSTEM_ID) return
|
|
||||||
|
|
||||||
val project = id.findProject() ?: return
|
|
||||||
val sync = project.kotlinGradleDslSync[id] ?: return
|
|
||||||
|
|
||||||
sync.failed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCancel(id: ExternalSystemTaskId) {
|
|
||||||
if (id.type != ExternalSystemTaskType.RESOLVE_PROJECT) return
|
|
||||||
if (id.projectSystemId != GRADLE_SYSTEM_ID) return
|
|
||||||
|
|
||||||
val project = id.findProject() ?: return
|
|
||||||
|
|
||||||
val cancelled = project.kotlinGradleDslSync.remove(id)
|
|
||||||
if (cancelled != null) {
|
|
||||||
GradleBuildRootsManager.getInstance(project).markImportingInProgress(cancelled.workingDir, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
-37
@@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2020 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.jetbrains.kotlin.idea.scripting.gradle.legacy
|
|
||||||
|
|
||||||
import com.intellij.openapi.project.Project
|
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener
|
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.isGradleKotlinScript
|
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
|
|
||||||
|
|
||||||
// called from GradleScriptListener
|
|
||||||
// todo(gradle6): remove
|
|
||||||
class GradleLegacyScriptListener(project: Project) : ScriptChangeListener(project) {
|
|
||||||
private val buildRootsManager = GradleBuildRootsManager.getInstance(project)
|
|
||||||
|
|
||||||
override fun isApplicable(vFile: VirtualFile) =
|
|
||||||
isGradleKotlinScript(vFile)
|
|
||||||
|
|
||||||
override fun editorActivated(vFile: VirtualFile) =
|
|
||||||
checkUpToDate(vFile)
|
|
||||||
|
|
||||||
override fun documentChanged(vFile: VirtualFile) =
|
|
||||||
checkUpToDate(vFile)
|
|
||||||
|
|
||||||
private fun checkUpToDate(vFile: VirtualFile) {
|
|
||||||
if (!buildRootsManager.isAffectedGradleProjectFile(vFile.path)) return
|
|
||||||
|
|
||||||
val file = getAnalyzableKtFileForScript(vFile)
|
|
||||||
if (file != null) {
|
|
||||||
// *.gradle.kts file was changed
|
|
||||||
default.suggestToUpdateConfigurationIfOutOfDate(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-153
@@ -1,153 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2020 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.jetbrains.kotlin.idea.scripting.gradle.roots
|
|
||||||
|
|
||||||
import com.intellij.openapi.project.Project
|
|
||||||
import com.intellij.openapi.vfs.LocalFileSystem
|
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache
|
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptDefinitionsContributor
|
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.LastModifiedFiles
|
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel
|
|
||||||
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
|
|
||||||
import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource
|
|
||||||
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [GradleBuildRoot] is a linked gradle build (don't confuse with gradle project and included build).
|
|
||||||
* Each [GradleBuildRoot] may have it's own Gradle version, Java home and other settings.
|
|
||||||
*
|
|
||||||
* Typically, IntelliJ project have no more than one [GradleBuildRoot].
|
|
||||||
*
|
|
||||||
* See [GradleBuildRootsManager] for more details.
|
|
||||||
*/
|
|
||||||
sealed class GradleBuildRoot {
|
|
||||||
/**
|
|
||||||
* The script not related to any Gradle build that is linked to IntelliJ Project,
|
|
||||||
* or we cannot known what is it
|
|
||||||
*/
|
|
||||||
@Suppress("CanSealedSubClassBeObject")
|
|
||||||
class Unlinked : GradleBuildRoot()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Linked project, that may be itself: [Legacy], [New] or [Imported].
|
|
||||||
*/
|
|
||||||
abstract class Linked : GradleBuildRoot() {
|
|
||||||
@Volatile
|
|
||||||
var importing = false
|
|
||||||
|
|
||||||
abstract val manager: GradleBuildRootsManager
|
|
||||||
|
|
||||||
abstract val pathPrefix: String
|
|
||||||
|
|
||||||
abstract val projectRoots: Collection<String>
|
|
||||||
|
|
||||||
private val dir: VirtualFile?
|
|
||||||
get() = LocalFileSystem.getInstance().findFileByPath(pathPrefix)
|
|
||||||
|
|
||||||
private lateinit var lastModifiedFiles: LastModifiedFiles
|
|
||||||
|
|
||||||
fun loadLastModifiedFiles() {
|
|
||||||
lastModifiedFiles = dir?.let { LastModifiedFiles.read(it) } ?: LastModifiedFiles()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveLastModifiedFiles() {
|
|
||||||
LastModifiedFiles.write(dir ?: return, lastModifiedFiles)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun areRelatedFilesChangedBefore(file: VirtualFile, lastModified: Long): Boolean =
|
|
||||||
lastModifiedFiles.lastModifiedTimeStampExcept(file.path) < lastModified
|
|
||||||
|
|
||||||
fun fileChanged(filePath: String, ts: Long) {
|
|
||||||
lastModifiedFiles.fileChanged(ts, filePath)
|
|
||||||
manager.scheduleLastModifiedFilesSave()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class WithoutScriptModels(settings: GradleProjectSettings) : Linked() {
|
|
||||||
final override val pathPrefix = settings.externalProjectPath!!
|
|
||||||
final override val projectRoots = settings.modules.takeIf { it.isNotEmpty() } ?: listOf(pathPrefix)
|
|
||||||
|
|
||||||
init {
|
|
||||||
loadLastModifiedFiles()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gradle build with old Gradle version (<6.0)
|
|
||||||
*/
|
|
||||||
class Legacy(
|
|
||||||
override val manager: GradleBuildRootsManager,
|
|
||||||
settings: GradleProjectSettings
|
|
||||||
) : WithoutScriptModels(settings) {
|
|
||||||
init {
|
|
||||||
loadLastModifiedFiles()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Linked but not yet imported Gradle build.
|
|
||||||
*/
|
|
||||||
class New(
|
|
||||||
override val manager: GradleBuildRootsManager,
|
|
||||||
settings: GradleProjectSettings
|
|
||||||
) : WithoutScriptModels(settings) {
|
|
||||||
init {
|
|
||||||
loadLastModifiedFiles()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Imported Gradle build.
|
|
||||||
* Each imported build have info about all of it's Kotlin Build Scripts.
|
|
||||||
*/
|
|
||||||
class Imported(
|
|
||||||
override val manager: GradleBuildRootsManager,
|
|
||||||
val dir: VirtualFile,
|
|
||||||
val javaHome: File?,
|
|
||||||
val data: GradleBuildRootData
|
|
||||||
) : Linked() {
|
|
||||||
val project: Project
|
|
||||||
get() = manager.project
|
|
||||||
|
|
||||||
override val pathPrefix: String = dir.path
|
|
||||||
|
|
||||||
override val projectRoots: Collection<String>
|
|
||||||
get() = data.projectRoots
|
|
||||||
|
|
||||||
init {
|
|
||||||
loadLastModifiedFiles()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun collectConfigurations(builder: ScriptClassRootsCache.Builder) {
|
|
||||||
if (javaHome != null) {
|
|
||||||
builder.addSdk(javaHome)
|
|
||||||
}
|
|
||||||
|
|
||||||
val definitions = GradleScriptDefinitionsContributor.getDefinitions(project)
|
|
||||||
|
|
||||||
builder.classes.addAll(data.templateClasspath)
|
|
||||||
data.models.forEach { script ->
|
|
||||||
val definition = selectScriptDefinition(script, definitions)
|
|
||||||
|
|
||||||
builder.scripts[script.file] = GradleScriptInfo(this, definition, script)
|
|
||||||
|
|
||||||
builder.classes.addAll(script.classPath)
|
|
||||||
builder.sources.addAll(script.sourcePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun selectScriptDefinition(
|
|
||||||
script: KotlinDslScriptModel,
|
|
||||||
definitions: List<ScriptDefinition>
|
|
||||||
): ScriptDefinition? {
|
|
||||||
val file = LocalFileSystem.getInstance().findFileByPath(script.file) ?: return null
|
|
||||||
val scriptSource = VirtualFileScriptSource(file)
|
|
||||||
return definitions.firstOrNull { it.isScript(scriptSource) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-14
@@ -1,14 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2020 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.jetbrains.kotlin.idea.scripting.gradle.roots
|
|
||||||
|
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel
|
|
||||||
|
|
||||||
data class GradleBuildRootData(
|
|
||||||
val projectRoots: Collection<String>,
|
|
||||||
val templateClasspath: Collection<String>,
|
|
||||||
val models: Collection<KotlinDslScriptModel>
|
|
||||||
)
|
|
||||||
-60
@@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2020 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.jetbrains.kotlin.idea.scripting.gradle.roots
|
|
||||||
|
|
||||||
import com.intellij.openapi.diagnostic.logger
|
|
||||||
|
|
||||||
class GradleBuildRootIndex {
|
|
||||||
private val log = logger<GradleBuildRootIndex>()
|
|
||||||
|
|
||||||
private val byWorkingDir = HashMap<String, GradleBuildRoot.Linked>()
|
|
||||||
private val byProjectDir = HashMap<String, GradleBuildRoot.Linked>()
|
|
||||||
|
|
||||||
val list: Collection<GradleBuildRoot.Linked>
|
|
||||||
get() = byWorkingDir.values
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun rebuildProjectRoots() {
|
|
||||||
byProjectDir.clear()
|
|
||||||
byWorkingDir.values.forEach { buildRoot ->
|
|
||||||
buildRoot.projectRoots.forEach {
|
|
||||||
byProjectDir[it] = buildRoot
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun getBuildByRootDir(dir: String) = byWorkingDir[dir]
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun findNearestRoot(path: String): GradleBuildRoot.Linked? {
|
|
||||||
var max: Pair<String, GradleBuildRoot.Linked>? = null
|
|
||||||
byWorkingDir.entries.forEach {
|
|
||||||
if (path.startsWith(it.key) && (max == null || it.key.length > max!!.first.length)) {
|
|
||||||
max = it.key to it.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return max?.second
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun getBuildByProjectDir(projectDir: String) = byProjectDir[projectDir]
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun add(value: GradleBuildRoot.Linked): GradleBuildRoot.Linked? {
|
|
||||||
val prefix = value.pathPrefix
|
|
||||||
val old = byWorkingDir.put(prefix, value)
|
|
||||||
rebuildProjectRoots()
|
|
||||||
log.info("$prefix: $old -> $value")
|
|
||||||
return old
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun remove(prefix: String) = byWorkingDir.remove(prefix)?.also {
|
|
||||||
rebuildProjectRoots()
|
|
||||||
log.info("$prefix: removed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-393
@@ -1,393 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2020 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.jetbrains.kotlin.idea.scripting.gradle.roots
|
|
||||||
|
|
||||||
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
|
|
||||||
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
|
|
||||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
|
||||||
import com.intellij.openapi.progress.util.BackgroundTaskUtil
|
|
||||||
import com.intellij.openapi.project.Project
|
|
||||||
import com.intellij.openapi.vfs.LocalFileSystem
|
|
||||||
import com.intellij.openapi.vfs.VfsUtil
|
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
|
||||||
import com.intellij.psi.PsiManager
|
|
||||||
import com.intellij.ui.EditorNotifications
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.CompositeScriptConfigurationManager
|
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport
|
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache
|
|
||||||
import org.jetbrains.kotlin.idea.core.util.EDT
|
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.*
|
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslGradleBuildSync
|
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
|
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.lastIndexOfOrNull
|
|
||||||
import org.jetbrains.plugins.gradle.config.GradleSettingsListenerAdapter
|
|
||||||
import org.jetbrains.plugins.gradle.settings.*
|
|
||||||
import org.jetbrains.plugins.gradle.util.GradleConstants
|
|
||||||
import java.io.File
|
|
||||||
import java.nio.file.Paths
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [GradleBuildRoot] is a linked gradle build (don't confuse with gradle project and included build).
|
|
||||||
* Each [GradleBuildRoot] may have it's own Gradle version, Java home and other settings.
|
|
||||||
*
|
|
||||||
* Typically, IntelliJ project have no more than one [GradleBuildRoot].
|
|
||||||
*
|
|
||||||
* This manager allows to find related Gradle build by the Gradle Kotlin script file path.
|
|
||||||
* Each imported build have info about all of it's Kotlin Build Scripts.
|
|
||||||
* It is populated by calling [update], stored in FS and will be loaded from FS on next project opening
|
|
||||||
*
|
|
||||||
* [CompositeScriptConfigurationManager] may ask about known scripts by calling [collectConfigurations].
|
|
||||||
*
|
|
||||||
* It also used to show related notification and floating actions depending on root kind, state and script state itself.
|
|
||||||
*
|
|
||||||
* Roots may be:
|
|
||||||
* - [GradleBuildRoot.Unlinked] - The script not related to any Gradle build that is linked to IntelliJ Project,
|
|
||||||
* or we cannot known what is it
|
|
||||||
* - [GradleBuildRoot.Linked] - Linked project, that may be itself:
|
|
||||||
* - [GradleBuildRoot.Legacy] - Gradle build with old Gradle version (<6.0)
|
|
||||||
* - [GradleBuildRoot.New] - not yet imported
|
|
||||||
* - [GradleBuildRoot.Imported] - imported
|
|
||||||
*/
|
|
||||||
class GradleBuildRootsManager(val project: Project) : ScriptingSupport() {
|
|
||||||
private val manager: CompositeScriptConfigurationManager
|
|
||||||
get() = ScriptConfigurationManager.getInstance(project) as CompositeScriptConfigurationManager
|
|
||||||
|
|
||||||
private val updater
|
|
||||||
get() = manager.updater
|
|
||||||
|
|
||||||
private val roots = GradleBuildRootIndex()
|
|
||||||
|
|
||||||
////////////
|
|
||||||
/// ScriptingSupport.Provider implementation:
|
|
||||||
|
|
||||||
override fun isApplicable(file: VirtualFile): Boolean {
|
|
||||||
val scriptUnderRoot = findScriptBuildRoot(file) ?: return false
|
|
||||||
if (scriptUnderRoot.root is GradleBuildRoot.Legacy) return false
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isConfigurationLoadingInProgress(file: KtFile): Boolean {
|
|
||||||
return when (val root = findScriptBuildRoot(file.originalFile.virtualFile)?.root) {
|
|
||||||
is GradleBuildRoot.Linked -> root.importing
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// used in 201
|
|
||||||
@Suppress("UNUSED")
|
|
||||||
fun isConfigurationOutOfDate(file: VirtualFile): Boolean {
|
|
||||||
val script = getScriptInfo(file) ?: return false
|
|
||||||
return !script.model.inputs.isUpToDate(project, file)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun collectConfigurations(builder: ScriptClassRootsCache.Builder) {
|
|
||||||
roots.list.forEach { root ->
|
|
||||||
if (root is GradleBuildRoot.Imported) {
|
|
||||||
root.collectConfigurations(builder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////
|
|
||||||
|
|
||||||
private val VirtualFile.localPath
|
|
||||||
get() = path
|
|
||||||
|
|
||||||
fun getScriptInfo(file: VirtualFile): GradleScriptInfo? =
|
|
||||||
getScriptInfo(file.localPath)
|
|
||||||
|
|
||||||
fun getScriptInfo(localPath: String): GradleScriptInfo? =
|
|
||||||
manager.getLightScriptInfo(localPath) as? GradleScriptInfo
|
|
||||||
|
|
||||||
class ScriptUnderRoot(
|
|
||||||
val root: GradleBuildRoot?,
|
|
||||||
val script: GradleScriptInfo? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
fun findScriptBuildRoot(gradleKtsFile: VirtualFile): ScriptUnderRoot? =
|
|
||||||
findScriptBuildRoot(gradleKtsFile.path)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fast method that can return false positive
|
|
||||||
*/
|
|
||||||
fun maybeAffectedGradleProjectFile(filePath: String): Boolean =
|
|
||||||
filePath.endsWith("/gradle.properties") ||
|
|
||||||
filePath.endsWith("/gradle.local") ||
|
|
||||||
filePath.endsWith("/gradle-wrapper.properties") ||
|
|
||||||
filePath.endsWith("/build.gradle.kts") ||
|
|
||||||
filePath.endsWith("/settings.gradle.kts") ||
|
|
||||||
filePath.endsWith("/init.gradle.kts")
|
|
||||||
|
|
||||||
fun isAffectedGradleProjectFile(filePath: String): Boolean =
|
|
||||||
findAffectedFileRoot(filePath) != null
|
|
||||||
|
|
||||||
fun fileChanged(filePath: String, ts: Long = System.currentTimeMillis()) {
|
|
||||||
findAffectedFileRoot(filePath)?.fileChanged(filePath, ts)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findAffectedFileRoot(filePath: String): GradleBuildRoot.Linked? {
|
|
||||||
if (filePath.endsWith("/gradle.properties") ||
|
|
||||||
filePath.endsWith("/gradle.local")
|
|
||||||
) {
|
|
||||||
return roots.getBuildByProjectDir(filePath.substringBeforeLast("/"))
|
|
||||||
} else if (filePath.endsWith("/gradle-wrapper.properties")) {
|
|
||||||
val gradleWrapperDirIndex = filePath.lastIndexOfOrNull('/') ?: return null
|
|
||||||
val gradleDirIndex = filePath.lastIndexOfOrNull('/', gradleWrapperDirIndex - 1) ?: return null
|
|
||||||
val buildDirIndex = filePath.lastIndexOfOrNull('/', gradleDirIndex - 1) ?: return null
|
|
||||||
return roots.getBuildByRootDir(filePath.substring(0, buildDirIndex))
|
|
||||||
}
|
|
||||||
|
|
||||||
return findScriptBuildRoot(filePath, searchNearestLegacy = false)?.root as? GradleBuildRoot.Linked
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findScriptBuildRoot(filePath: String, searchNearestLegacy: Boolean = true): ScriptUnderRoot? {
|
|
||||||
if (!filePath.endsWith(".gradle.kts")) return null
|
|
||||||
|
|
||||||
val scriptInfo = getScriptInfo(filePath)
|
|
||||||
val imported = scriptInfo?.buildRoot
|
|
||||||
if (imported != null) return ScriptUnderRoot(imported, scriptInfo)
|
|
||||||
|
|
||||||
if (filePath.endsWith("/build.gradle.kts") ||
|
|
||||||
filePath.endsWith("/settings.gradle.kts") ||
|
|
||||||
filePath.endsWith("/init.gradle.kts")
|
|
||||||
) {
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// other scripts: "included", "precompiled" scripts, scripts in unlinked projects,
|
|
||||||
// or just random files with ".gradle.kts" ending
|
|
||||||
|
|
||||||
// todo(gradle6): remove, it is required only for projects with old gradle
|
|
||||||
if (searchNearestLegacy) {
|
|
||||||
val found = roots.findNearestRoot(filePath)
|
|
||||||
if (found is GradleBuildRoot.Legacy) return ScriptUnderRoot(found)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ScriptUnderRoot(GradleBuildRoot.Unlinked())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getBuildRoot(gradleWorkingDir: String) =
|
|
||||||
roots.getBuildByRootDir(gradleWorkingDir)
|
|
||||||
|
|
||||||
fun markImportingInProgress(workingDir: String, inProgress: Boolean = true) {
|
|
||||||
actualizeBuildRoot(workingDir)?.importing = inProgress
|
|
||||||
updateNotifications(workingDir)
|
|
||||||
hideNotificationForProjectImport(project)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun update(build: KotlinDslGradleBuildSync) {
|
|
||||||
// fast path for linked gradle builds without .gradle.kts support
|
|
||||||
if (build.models.isEmpty()) {
|
|
||||||
val root = roots.getBuildByRootDir(build.workingDir) ?: return
|
|
||||||
if (root is GradleBuildRoot.Imported && root.data.models.isEmpty()) return
|
|
||||||
}
|
|
||||||
|
|
||||||
val root = actualizeBuildRoot(build.workingDir) ?: return
|
|
||||||
root.importing = false
|
|
||||||
|
|
||||||
if (root is GradleBuildRoot.Legacy) return
|
|
||||||
|
|
||||||
val templateClasspath = GradleScriptDefinitionsContributor.getDefinitionsTemplateClasspath(project)
|
|
||||||
val newData = GradleBuildRootData(build.projectRoots, templateClasspath, build.models)
|
|
||||||
val mergedData = if (build.failed && root is GradleBuildRoot.Imported) merge(root.data, newData) else newData
|
|
||||||
|
|
||||||
val newSupport = tryCreateImportedRoot(build.workingDir) { mergedData } ?: return
|
|
||||||
GradleBuildRootDataSerializer.write(newSupport.dir, mergedData)
|
|
||||||
|
|
||||||
add(newSupport)
|
|
||||||
|
|
||||||
hideNotificationForProjectImport(project)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun merge(old: GradleBuildRootData, new: GradleBuildRootData): GradleBuildRootData {
|
|
||||||
val roots = old.projectRoots.toMutableSet()
|
|
||||||
roots.addAll(new.projectRoots)
|
|
||||||
|
|
||||||
val models = old.models.associateByTo(mutableMapOf()) { it.file }
|
|
||||||
new.models.associateByTo(models) { it.file }
|
|
||||||
|
|
||||||
return GradleBuildRootData(roots, new.templateClasspath, models.values)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val lastModifiedFilesSaveScheduled = AtomicBoolean()
|
|
||||||
|
|
||||||
fun scheduleLastModifiedFilesSave() {
|
|
||||||
if (lastModifiedFilesSaveScheduled.compareAndSet(false, true)) {
|
|
||||||
BackgroundTaskUtil.executeOnPooledThread(project) {
|
|
||||||
if (lastModifiedFilesSaveScheduled.compareAndSet(true, false)) {
|
|
||||||
roots.list.forEach {
|
|
||||||
it.saveLastModifiedFiles()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
getGradleProjectSettings(project).forEach {
|
|
||||||
// don't call this.add, as we are inside scripting manager initialization
|
|
||||||
roots.add(loadLinkedRoot(it))
|
|
||||||
}
|
|
||||||
|
|
||||||
// subscribe to linked gradle project modification
|
|
||||||
val listener = object : GradleSettingsListenerAdapter() {
|
|
||||||
override fun onProjectsLinked(settings: MutableCollection<GradleProjectSettings>) {
|
|
||||||
settings.forEach {
|
|
||||||
add(loadLinkedRoot(it))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onProjectsUnlinked(linkedProjectPaths: MutableSet<String>) {
|
|
||||||
linkedProjectPaths.forEach {
|
|
||||||
remove(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onGradleHomeChange(oldPath: String?, newPath: String?, linkedProjectPath: String) {
|
|
||||||
reloadBuildRoot(linkedProjectPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onGradleDistributionTypeChange(currentValue: DistributionType?, linkedProjectPath: String) {
|
|
||||||
reloadBuildRoot(linkedProjectPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
project.messageBus.connect(project).subscribe(GradleSettingsListener.TOPIC, listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getGradleProjectSettings(workingDir: String): GradleProjectSettings? {
|
|
||||||
return (ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID) as GradleSettings)
|
|
||||||
.getLinkedProjectSettings(workingDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check that root under [workingDir] in sync with it's [GradleProjectSettings].
|
|
||||||
* Actually this should be true, but we may miss some change events.
|
|
||||||
* For that cases we are rechecking this on each Gradle Project sync (importing/reimporting)
|
|
||||||
*/
|
|
||||||
private fun actualizeBuildRoot(workingDir: String): GradleBuildRoot.Linked? {
|
|
||||||
val actualSettings = getGradleProjectSettings(workingDir)
|
|
||||||
val buildRoot = roots.getBuildByRootDir(workingDir)
|
|
||||||
|
|
||||||
return when {
|
|
||||||
buildRoot != null -> when {
|
|
||||||
!buildRoot.checkActual(actualSettings) -> reloadBuildRoot(workingDir)
|
|
||||||
else -> buildRoot
|
|
||||||
}
|
|
||||||
actualSettings != null -> loadLinkedRoot(actualSettings)
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun GradleBuildRoot.Linked.checkActual(actualSettings: GradleProjectSettings?): Boolean {
|
|
||||||
if (actualSettings == null) return false
|
|
||||||
|
|
||||||
val knownAsSupported = this !is GradleBuildRoot.Legacy
|
|
||||||
val shouldBeSupported = kotlinDslScriptsModelImportSupported(actualSettings.resolveGradleVersion().version)
|
|
||||||
return knownAsSupported == shouldBeSupported
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun reloadBuildRoot(rootPath: String): GradleBuildRoot.Linked? {
|
|
||||||
val settings = getGradleProjectSettings(rootPath)
|
|
||||||
if (settings == null) {
|
|
||||||
remove(rootPath)
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
val newRoot = loadLinkedRoot(settings)
|
|
||||||
add(newRoot)
|
|
||||||
return newRoot
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadLinkedRoot(settings: GradleProjectSettings) =
|
|
||||||
tryLoadFromFsCache(settings) ?: createOtherLinkedRoot(settings)
|
|
||||||
|
|
||||||
private fun tryLoadFromFsCache(settings: GradleProjectSettings) =
|
|
||||||
tryCreateImportedRoot(settings.externalProjectPath) {
|
|
||||||
GradleBuildRootDataSerializer.read(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createOtherLinkedRoot(settings: GradleProjectSettings): GradleBuildRoot.Linked {
|
|
||||||
val supported = kotlinDslScriptsModelImportSupported(settings.resolveGradleVersion().version)
|
|
||||||
return when {
|
|
||||||
supported -> GradleBuildRoot.New(this, settings)
|
|
||||||
else -> GradleBuildRoot.Legacy(this, settings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun tryCreateImportedRoot(
|
|
||||||
externalProjectPath: String,
|
|
||||||
dataProvider: (buildRoot: VirtualFile) -> GradleBuildRootData?
|
|
||||||
): GradleBuildRoot.Imported? {
|
|
||||||
val buildRoot = VfsUtil.findFile(Paths.get(externalProjectPath), true) ?: return null
|
|
||||||
val data = dataProvider(buildRoot) ?: return null
|
|
||||||
val javaHome = ExternalSystemApiUtil
|
|
||||||
.getExecutionSettings<GradleExecutionSettings>(project, externalProjectPath, GradleConstants.SYSTEM_ID)
|
|
||||||
.javaHome?.let { File(it) }
|
|
||||||
|
|
||||||
return GradleBuildRoot.Imported(this, buildRoot, javaHome, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun add(newRoot: GradleBuildRoot.Linked) {
|
|
||||||
val old = roots.add(newRoot)
|
|
||||||
if (old is GradleBuildRoot.Imported) removeData(old.pathPrefix)
|
|
||||||
if (old is GradleBuildRoot.Imported || newRoot is GradleBuildRoot.Imported) {
|
|
||||||
updater.ensureUpdateScheduled()
|
|
||||||
}
|
|
||||||
|
|
||||||
updateNotifications(newRoot.pathPrefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun remove(rootPath: String) {
|
|
||||||
val removed = roots.remove(rootPath)
|
|
||||||
if (removed is GradleBuildRoot.Imported) {
|
|
||||||
removeData(rootPath)
|
|
||||||
updater.ensureUpdateScheduled()
|
|
||||||
}
|
|
||||||
|
|
||||||
updateNotifications(rootPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeData(rootPath: String) {
|
|
||||||
val buildRoot = LocalFileSystem.getInstance().findFileByPath(rootPath)
|
|
||||||
if (buildRoot != null) {
|
|
||||||
GradleBuildRootDataSerializer.remove(buildRoot)
|
|
||||||
LastModifiedFiles.remove(buildRoot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateNotifications(dir1: String) {
|
|
||||||
if (!project.isOpen) return
|
|
||||||
|
|
||||||
val openedScripts = FileEditorManager.getInstance(project).openFiles.filter {
|
|
||||||
it.path.startsWith(dir1) && isGradleKotlinScript(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (openedScripts.isEmpty()) return
|
|
||||||
|
|
||||||
GlobalScope.launch(EDT(project)) {
|
|
||||||
if (project.isDisposed) return@launch
|
|
||||||
|
|
||||||
openedScripts.forEach {
|
|
||||||
val ktFile = PsiManager.getInstance(project).findFile(it)
|
|
||||||
if (ktFile != null) DaemonCodeAnalyzer.getInstance(project).restart(ktFile)
|
|
||||||
EditorNotifications.getInstance(project).updateAllNotifications()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun getInstance(project: Project): GradleBuildRootsManager =
|
|
||||||
EPN.getPoint(project).extensionList.firstIsInstance()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-66
@@ -1,66 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2020 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.jetbrains.kotlin.idea.scripting.gradle.roots
|
|
||||||
|
|
||||||
import com.intellij.openapi.vfs.VfsUtil
|
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache
|
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel
|
|
||||||
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
|
|
||||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
|
||||||
import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource
|
|
||||||
import org.jetbrains.kotlin.scripting.resolve.adjustByDefinition
|
|
||||||
import java.io.File
|
|
||||||
import kotlin.script.experimental.api.*
|
|
||||||
import kotlin.script.experimental.jvm.JvmDependency
|
|
||||||
import kotlin.script.experimental.jvm.jdkHome
|
|
||||||
import kotlin.script.experimental.jvm.jvm
|
|
||||||
|
|
||||||
class GradleScriptInfo(
|
|
||||||
val buildRoot: GradleBuildRoot.Imported,
|
|
||||||
scriptDefinition: ScriptDefinition?,
|
|
||||||
val model: KotlinDslScriptModel
|
|
||||||
) : ScriptClassRootsCache.LightScriptInfo(scriptDefinition) {
|
|
||||||
override fun buildConfiguration(): ScriptCompilationConfigurationWrapper? {
|
|
||||||
val javaHome = buildRoot.javaHome
|
|
||||||
|
|
||||||
val scriptFile = File(model.file)
|
|
||||||
val virtualFile = VfsUtil.findFile(scriptFile.toPath(), true)!!
|
|
||||||
|
|
||||||
if (definition == null) return null
|
|
||||||
|
|
||||||
return ScriptCompilationConfigurationWrapper.FromCompilationConfiguration(
|
|
||||||
VirtualFileScriptSource(virtualFile),
|
|
||||||
definition.compilationConfiguration.with {
|
|
||||||
if (javaHome != null) {
|
|
||||||
jvm.jdkHome(javaHome)
|
|
||||||
}
|
|
||||||
defaultImports(model.imports)
|
|
||||||
dependencies(JvmDependency(model.classPath.map { File(it) }))
|
|
||||||
ide.dependenciesSources(JvmDependency(model.sourcePath.map { File(it) }))
|
|
||||||
}.adjustByDefinition(definition)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as GradleScriptInfo
|
|
||||||
|
|
||||||
if (buildRoot.pathPrefix != other.buildRoot.pathPrefix) return false
|
|
||||||
if (model != other.model) return false
|
|
||||||
if (definition != other.definition) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = buildRoot.pathPrefix.hashCode()
|
|
||||||
result = 31 * result + model.hashCode()
|
|
||||||
result = 31 * result + definition.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+12
-18
@@ -3,7 +3,7 @@
|
|||||||
* 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.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.jetbrains.kotlin.idea.scripting.gradle.roots
|
package org.jetbrains.kotlin.idea.scripting.gradle
|
||||||
|
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import com.intellij.openapi.vfs.newvfs.FileAttribute
|
import com.intellij.openapi.vfs.newvfs.FileAttribute
|
||||||
@@ -11,27 +11,25 @@ import org.jetbrains.kotlin.idea.core.util.readNullable
|
|||||||
import org.jetbrains.kotlin.idea.core.util.readString
|
import org.jetbrains.kotlin.idea.core.util.readString
|
||||||
import org.jetbrains.kotlin.idea.core.util.writeNullable
|
import org.jetbrains.kotlin.idea.core.util.writeNullable
|
||||||
import org.jetbrains.kotlin.idea.core.util.writeString
|
import org.jetbrains.kotlin.idea.core.util.writeString
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.GradleKotlinScriptConfigurationInputs
|
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.LastModifiedFiles
|
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel
|
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel
|
||||||
import java.io.DataInput
|
import java.io.DataInput
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
import java.io.DataOutput
|
import java.io.DataOutput
|
||||||
|
|
||||||
internal object GradleBuildRootDataSerializer {
|
internal object KotlinDslScriptModels {
|
||||||
private val attribute = FileAttribute("kotlin-dsl-script-models", 5, false)
|
private val attribute = FileAttribute("kotlin-dsl-script-models", 3, false)
|
||||||
|
|
||||||
fun read(buildRoot: VirtualFile): GradleBuildRootData? {
|
fun read(buildRoot: VirtualFile): ConfigurationData? {
|
||||||
return attribute.readAttribute(buildRoot)?.use {
|
return attribute.readAttribute(buildRoot)?.use {
|
||||||
it.readNullable {
|
it.readNullable {
|
||||||
readKotlinDslScriptModels(it, buildRoot.path)
|
readKotlinDslScriptModels(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun write(buildRoot: VirtualFile, data: GradleBuildRootData?) {
|
fun write(buildRoot: VirtualFile, configuration: ConfigurationData?) {
|
||||||
attribute.writeAttribute(buildRoot).use {
|
attribute.writeAttribute(buildRoot).use {
|
||||||
it.writeNullable(data) {
|
it.writeNullable(configuration) {
|
||||||
writeKotlinDslScriptModels(this, it)
|
writeKotlinDslScriptModels(this, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,13 +37,11 @@ internal object GradleBuildRootDataSerializer {
|
|||||||
|
|
||||||
fun remove(buildRoot: VirtualFile) {
|
fun remove(buildRoot: VirtualFile) {
|
||||||
write(buildRoot, null)
|
write(buildRoot, null)
|
||||||
LastModifiedFiles.remove(buildRoot)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun writeKotlinDslScriptModels(output: DataOutput, data: GradleBuildRootData) {
|
internal fun writeKotlinDslScriptModels(output: DataOutput, data: ConfigurationData) {
|
||||||
val strings = StringsPool.writer(output)
|
val strings = StringsPool.writer(output)
|
||||||
strings.addStrings(data.projectRoots)
|
|
||||||
strings.addStrings(data.templateClasspath)
|
strings.addStrings(data.templateClasspath)
|
||||||
data.models.forEach {
|
data.models.forEach {
|
||||||
strings.addString(it.file)
|
strings.addString(it.file)
|
||||||
@@ -54,28 +50,26 @@ internal fun writeKotlinDslScriptModels(output: DataOutput, data: GradleBuildRoo
|
|||||||
strings.addStrings(it.imports)
|
strings.addStrings(it.imports)
|
||||||
}
|
}
|
||||||
strings.writeHeader()
|
strings.writeHeader()
|
||||||
strings.writeStringIds(data.projectRoots)
|
|
||||||
strings.writeStringIds(data.templateClasspath)
|
strings.writeStringIds(data.templateClasspath)
|
||||||
output.writeList(data.models) {
|
output.writeList(data.models) {
|
||||||
strings.writeStringId(it.file)
|
strings.writeStringId(it.file)
|
||||||
output.writeString(it.inputs.sections)
|
output.writeString(it.inputs.sections)
|
||||||
output.writeLong(it.inputs.lastModifiedTs)
|
output.writeLong(it.inputs.inputsTS)
|
||||||
strings.writeStringIds(it.classPath)
|
strings.writeStringIds(it.classPath)
|
||||||
strings.writeStringIds(it.sourcePath)
|
strings.writeStringIds(it.sourcePath)
|
||||||
strings.writeStringIds(it.imports)
|
strings.writeStringIds(it.imports)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun readKotlinDslScriptModels(input: DataInputStream, buildRoot: String): GradleBuildRootData {
|
internal fun readKotlinDslScriptModels(input: DataInputStream): ConfigurationData {
|
||||||
val strings = StringsPool.reader(input)
|
val strings = StringsPool.reader(input)
|
||||||
|
|
||||||
val projectRoots = strings.readStrings()
|
|
||||||
val templateClasspath = strings.readStrings()
|
val templateClasspath = strings.readStrings()
|
||||||
|
|
||||||
val models = input.readList {
|
val models = input.readList {
|
||||||
KotlinDslScriptModel(
|
KotlinDslScriptModel(
|
||||||
strings.readString(),
|
strings.readString(),
|
||||||
GradleKotlinScriptConfigurationInputs(input.readString(), input.readLong(), buildRoot),
|
GradleKotlinScriptConfigurationInputs(input.readString(), input.readLong()),
|
||||||
strings.readStrings(),
|
strings.readStrings(),
|
||||||
strings.readStrings(),
|
strings.readStrings(),
|
||||||
strings.readStrings(),
|
strings.readStrings(),
|
||||||
@@ -83,7 +77,7 @@ internal fun readKotlinDslScriptModels(input: DataInputStream, buildRoot: String
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return GradleBuildRootData(projectRoots, templateClasspath, models)
|
return ConfigurationData(templateClasspath, models)
|
||||||
}
|
}
|
||||||
|
|
||||||
private object StringsPool {
|
private object StringsPool {
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.scripting.gradle
|
package org.jetbrains.kotlin.idea.scripting.gradle
|
||||||
|
|
||||||
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
|
import com.intellij.openapi.components.service
|
||||||
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
|
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.util.registry.Registry
|
import com.intellij.openapi.util.registry.Registry
|
||||||
@@ -12,21 +14,36 @@ import com.intellij.openapi.vfs.VirtualFile
|
|||||||
import com.intellij.psi.*
|
import com.intellij.psi.*
|
||||||
import com.intellij.psi.impl.source.tree.LeafPsiElement
|
import com.intellij.psi.impl.source.tree.LeafPsiElement
|
||||||
import org.gradle.util.GradleVersion
|
import org.gradle.util.GradleVersion
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRoot
|
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
|
|
||||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.psi.KtScriptInitializer
|
import org.jetbrains.kotlin.psi.KtScriptInitializer
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
|
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
|
||||||
|
import org.jetbrains.plugins.gradle.settings.GradleLocalSettings
|
||||||
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
|
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
|
||||||
import org.jetbrains.plugins.gradle.settings.GradleSettings
|
import org.jetbrains.plugins.gradle.settings.GradleSettings
|
||||||
import org.jetbrains.plugins.gradle.util.GradleConstants
|
import org.jetbrains.plugins.gradle.util.GradleConstants
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
private val sections = arrayListOf("buildscript", "plugins", "initscript", "pluginManagement")
|
private val sections = arrayListOf("buildscript", "plugins", "initscript", "pluginManagement")
|
||||||
|
|
||||||
fun isGradleKotlinScript(virtualFile: VirtualFile) = virtualFile.name.endsWith(".gradle.kts")
|
fun isGradleKotlinScript(virtualFile: VirtualFile) = virtualFile.name.endsWith(".gradle.kts")
|
||||||
|
|
||||||
|
fun isInAffectedGradleProjectFiles(project: Project, filePath: String): Boolean {
|
||||||
|
if (filePath.endsWith("/gradle.properties")) return true
|
||||||
|
if (filePath.endsWith("/gradle-wrapper.properties")) return true
|
||||||
|
|
||||||
|
if (filePath.endsWith(".gradle") || filePath.endsWith(".gradle.kts")) {
|
||||||
|
if (ApplicationManager.getApplication().isUnitTestModeWithoutAffectedGradleProjectFilesCheck) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return filePath.substringBeforeLast("/") in project.service<GradleScriptInputsWatcher>().getGradleProjectsRoots()
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
fun getGradleScriptInputsStamp(
|
fun getGradleScriptInputsStamp(
|
||||||
project: Project,
|
project: Project,
|
||||||
file: VirtualFile,
|
file: VirtualFile,
|
||||||
@@ -63,8 +80,7 @@ fun getGradleScriptInputsStamp(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val buildRoot = GradleBuildRootsManager.getInstance(project).findScriptBuildRoot(file)?.root as? GradleBuildRoot.Linked
|
GradleKotlinScriptConfigurationInputs(result.toString(), givenTimeStamp)
|
||||||
GradleKotlinScriptConfigurationInputs(result.toString(), givenTimeStamp, buildRoot?.pathPrefix)
|
|
||||||
} else null
|
} else null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,5 +95,50 @@ fun useScriptConfigurationFromImportOnly(): Boolean {
|
|||||||
return Registry.`is`("kotlin.gradle.scripts.useIdeaProjectImport", false)
|
return Registry.`is`("kotlin.gradle.scripts.useIdeaProjectImport", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getGradleProjectSettings(project: Project): Collection<GradleProjectSettings> =
|
fun getGradleVersion(project: Project, settings: GradleProjectSettings): String {
|
||||||
(ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID) as GradleSettings).linkedProjectsSettings
|
val localVersion = GradleLocalSettings.getInstance(project).getGradleVersion(settings.externalProjectPath)
|
||||||
|
if (localVersion != null) return localVersion
|
||||||
|
|
||||||
|
return settings.resolveGradleVersion().version
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getGradleProjectSettings(project: Project): Collection<GradleProjectSettings> {
|
||||||
|
val gradleSettings = ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID) as GradleSettings
|
||||||
|
return gradleSettings.getLinkedProjectsSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
class RootsIndex<T : Any> {
|
||||||
|
internal val tree = TreeMap<String, T>()
|
||||||
|
var values: Collection<T> = listOf()
|
||||||
|
internal set
|
||||||
|
|
||||||
|
fun findRoot(path: String): T? {
|
||||||
|
// race condition can be ignored
|
||||||
|
val values = values
|
||||||
|
val size = values.size
|
||||||
|
if (size == 0) return null
|
||||||
|
if (size == 1) return values.single() // we can omit prefix check
|
||||||
|
return tree.floorEntry(path).takeIf { path.startsWith(it.key) }?.value
|
||||||
|
}
|
||||||
|
|
||||||
|
internal inline fun update(updater: (insert: (prefix: String, value: T) -> Unit) -> Unit) {
|
||||||
|
synchronized(this) {
|
||||||
|
updater { prefix, value -> tree[prefix] = value }
|
||||||
|
values = tree.values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
operator fun set(prefix: String, value: T) {
|
||||||
|
val moreCommon = tree.lowerKey(prefix)
|
||||||
|
check(moreCommon == null || !prefix.startsWith(moreCommon)) {
|
||||||
|
"Cannot add root `${prefix}`. More common root already added: `$moreCommon`"
|
||||||
|
}
|
||||||
|
|
||||||
|
tree[prefix] = value
|
||||||
|
values = tree.values
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun remove(prefix: String) = tree.remove(prefix)
|
||||||
|
}
|
||||||
+3
-4
@@ -10,7 +10,6 @@ import junit.framework.AssertionFailedError
|
|||||||
import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle
|
import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle
|
||||||
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
||||||
import org.jetbrains.kotlin.idea.core.script.applySuggestedScriptConfiguration
|
import org.jetbrains.kotlin.idea.core.script.applySuggestedScriptConfiguration
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.CompositeScriptConfigurationManager
|
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.DefaultScriptConfigurationLoader
|
import org.jetbrains.kotlin.idea.core.script.configuration.loader.DefaultScriptConfigurationLoader
|
||||||
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.areSimilar
|
import org.jetbrains.kotlin.idea.core.script.configuration.utils.areSimilar
|
||||||
@@ -38,7 +37,7 @@ class GradleKtsImportTest : GradleImportingTestCase() {
|
|||||||
fun data(): Collection<Array<Any?>> = listOf(arrayOf<Any?>("6.0.1"))
|
fun data(): Collection<Array<Any?>> = listOf(arrayOf<Any?>("6.0.1"))
|
||||||
}
|
}
|
||||||
|
|
||||||
val scriptConfigurationManager get() = ScriptConfigurationManager.getInstance(myProject) as CompositeScriptConfigurationManager
|
val scriptConfigurationManager get() = ScriptConfigurationManager.getInstance(myProject)
|
||||||
val projectDir get() = File(GradleSettings.getInstance(myProject).linkedProjectsSettings.first().externalProjectPath)
|
val projectDir get() = File(GradleSettings.getInstance(myProject).linkedProjectsSettings.first().externalProjectPath)
|
||||||
|
|
||||||
override fun testDataDirName(): String {
|
override fun testDataDirName(): String {
|
||||||
@@ -99,8 +98,8 @@ class GradleKtsImportTest : GradleImportingTestCase() {
|
|||||||
|
|
||||||
// reload configuration and check this it is not changed
|
// reload configuration and check this it is not changed
|
||||||
scripts.forEach {
|
scripts.forEach {
|
||||||
val reloadedConfiguration = scriptConfigurationManager.default.forceReloadConfiguration(
|
val reloadedConfiguration = scriptConfigurationManager.forceReloadConfiguration(
|
||||||
it.psiFile,
|
it.virtualFile,
|
||||||
object : DefaultScriptConfigurationLoader(it.psiFile.project) {
|
object : DefaultScriptConfigurationLoader(it.psiFile.project) {
|
||||||
override fun shouldRunInBackground(scriptDefinition: ScriptDefinition) = false
|
override fun shouldRunInBackground(scriptDefinition: ScriptDefinition) = false
|
||||||
override fun loadDependencies(
|
override fun loadDependencies(
|
||||||
|
|||||||
+18
-51
@@ -5,24 +5,19 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.scripting.gradle
|
package org.jetbrains.kotlin.idea.scripting.gradle
|
||||||
|
|
||||||
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
|
import com.intellij.openapi.components.service
|
||||||
import com.intellij.openapi.vfs.LocalFileSystem
|
|
||||||
import com.intellij.openapi.vfs.VfsUtil
|
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import com.intellij.psi.PsiFile
|
import com.intellij.psi.PsiFile
|
||||||
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
||||||
import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationLoadingTest
|
import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationLoadingTest
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
|
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners
|
import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner
|
||||||
import org.jetbrains.plugins.gradle.settings.DistributionType
|
|
||||||
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
|
|
||||||
import org.jetbrains.plugins.gradle.util.GradleConstants
|
import org.jetbrains.plugins.gradle.util.GradleConstants
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@RunWith(JUnit3RunnerWithInners::class)
|
@RunWith(JUnit3WithIdeaConfigurationRunner::class)
|
||||||
open class GradleScriptListenerTest : AbstractScriptConfigurationLoadingTest() {
|
open class GradleScriptInputsWatcherTest : AbstractScriptConfigurationLoadingTest() {
|
||||||
companion object {
|
companion object {
|
||||||
internal const val outsidePlaceholder = "// OUTSIDE_SECTIONS"
|
internal const val outsidePlaceholder = "// OUTSIDE_SECTIONS"
|
||||||
internal const val insidePlaceholder = "// INSIDE_SECTIONS"
|
internal const val insidePlaceholder = "// INSIDE_SECTIONS"
|
||||||
@@ -30,12 +25,14 @@ open class GradleScriptListenerTest : AbstractScriptConfigurationLoadingTest() {
|
|||||||
|
|
||||||
private lateinit var testFiles: TestFiles
|
private lateinit var testFiles: TestFiles
|
||||||
|
|
||||||
data class TestFiles(
|
data class TestFiles(val buildKts: KtFile, val settings: KtFile, val prop: PsiFile)
|
||||||
val buildKts: KtFile,
|
|
||||||
val settings: KtFile,
|
override fun setUp() {
|
||||||
val prop: PsiFile,
|
super.setUp()
|
||||||
val gradleWrapperProperties: VirtualFile
|
|
||||||
)
|
// should be initialized explicitly because we do not have a real Gradle Project in this test
|
||||||
|
project.service<GradleScriptInputsWatcher>().startWatching()
|
||||||
|
}
|
||||||
|
|
||||||
override fun setUpTestProject() {
|
override fun setUpTestProject() {
|
||||||
val rootDir = "idea/testData/script/definition/loading/gradle/"
|
val rootDir = "idea/testData/script/definition/loading/gradle/"
|
||||||
@@ -43,35 +40,12 @@ open class GradleScriptListenerTest : AbstractScriptConfigurationLoadingTest() {
|
|||||||
val settings: KtFile = addFileToProject(rootDir + GradleConstants.KOTLIN_DSL_SETTINGS_FILE_NAME)
|
val settings: KtFile = addFileToProject(rootDir + GradleConstants.KOTLIN_DSL_SETTINGS_FILE_NAME)
|
||||||
val prop: PsiFile = addFileToProject(rootDir + "gradle.properties")
|
val prop: PsiFile = addFileToProject(rootDir + "gradle.properties")
|
||||||
|
|
||||||
val gradleWrapperProperties = VfsUtil.virtualToIoFile(settings.virtualFile.parent)
|
|
||||||
.resolve("gradle/wrapper/gradle-wrapper.properties")
|
|
||||||
gradleWrapperProperties.parentFile.mkdirs()
|
|
||||||
gradleWrapperProperties.writeText(
|
|
||||||
"""
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
|
||||||
distributionPath=wrapper/dists
|
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-1.0.0-bin.zip
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
|
||||||
zipStorePath=wrapper/dists
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
|
|
||||||
val buildGradleKts = File(rootDir).walkTopDown().find { it.name == GradleConstants.KOTLIN_DSL_SCRIPT_NAME }
|
val buildGradleKts = File(rootDir).walkTopDown().find { it.name == GradleConstants.KOTLIN_DSL_SCRIPT_NAME }
|
||||||
?: error("Couldn't find main script")
|
?: error("Couldn't find main script")
|
||||||
configureScriptFile(rootDir, buildGradleKts)
|
configureScriptFile(rootDir, buildGradleKts)
|
||||||
val build = (myFile as? KtFile) ?: error("")
|
val build = (myFile as? KtFile) ?: error("")
|
||||||
|
|
||||||
val newProjectSettings = GradleProjectSettings()
|
testFiles = TestFiles(build, settings, prop)
|
||||||
newProjectSettings.distributionType = DistributionType.DEFAULT_WRAPPED
|
|
||||||
newProjectSettings.externalProjectPath = settings.virtualFile.parent.path
|
|
||||||
ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID).linkProject(newProjectSettings)
|
|
||||||
|
|
||||||
testFiles = TestFiles(
|
|
||||||
build,
|
|
||||||
settings,
|
|
||||||
prop,
|
|
||||||
LocalFileSystem.getInstance().findFileByIoFile(gradleWrapperProperties)!!
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun <reified T : Any> addFileToProject(file: String): T {
|
private inline fun <reified T : Any> addFileToProject(file: String): T {
|
||||||
@@ -211,6 +185,8 @@ open class GradleScriptListenerTest : AbstractScriptConfigurationLoadingTest() {
|
|||||||
|
|
||||||
changeSettingsKtsOutsideSections()
|
changeSettingsKtsOutsideSections()
|
||||||
|
|
||||||
|
project.service<GradleScriptInputsWatcher>().clearAndRefillState()
|
||||||
|
|
||||||
assertConfigurationUpToDate(testFiles.settings)
|
assertConfigurationUpToDate(testFiles.settings)
|
||||||
assertConfigurationUpdateWasDone(testFiles.buildKts)
|
assertConfigurationUpdateWasDone(testFiles.buildKts)
|
||||||
}
|
}
|
||||||
@@ -244,7 +220,7 @@ open class GradleScriptListenerTest : AbstractScriptConfigurationLoadingTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun markFileChanged(virtualFile: VirtualFile, ts: Long) {
|
private fun markFileChanged(virtualFile: VirtualFile, ts: Long) {
|
||||||
GradleBuildRootsManager.getInstance(project).fileChanged(virtualFile.path, ts)
|
project.service<GradleScriptInputsWatcher>().fileChanged(virtualFile.path, ts)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testLoadedConfigurationWhenExternalFileChanged() {
|
fun testLoadedConfigurationWhenExternalFileChanged() {
|
||||||
@@ -256,23 +232,14 @@ open class GradleScriptListenerTest : AbstractScriptConfigurationLoadingTest() {
|
|||||||
assertConfigurationUpToDate(testFiles.buildKts)
|
assertConfigurationUpToDate(testFiles.buildKts)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testChangeGradleWrapperPropertiesFile() {
|
|
||||||
assertAndLoadInitialConfiguration(testFiles.buildKts)
|
|
||||||
|
|
||||||
markFileChanged(testFiles.gradleWrapperProperties, System.currentTimeMillis())
|
|
||||||
|
|
||||||
assertConfigurationUpdateWasDone(testFiles.buildKts)
|
|
||||||
assertConfigurationUpToDate(testFiles.buildKts)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun assertConfigurationUpToDate(file: KtFile) {
|
private fun assertConfigurationUpToDate(file: KtFile) {
|
||||||
scriptConfigurationManager.default.ensureUpToDatedConfigurationSuggested(file)
|
scriptConfigurationManager.updater.ensureUpToDatedConfigurationSuggested(file)
|
||||||
assertNoBackgroundTasks()
|
assertNoBackgroundTasks()
|
||||||
assertNoLoading()
|
assertNoLoading()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assertConfigurationUpdateWasDone(file: KtFile) {
|
private fun assertConfigurationUpdateWasDone(file: KtFile) {
|
||||||
scriptConfigurationManager.default.ensureUpToDatedConfigurationSuggested(file)
|
scriptConfigurationManager.updater.ensureUpToDatedConfigurationSuggested(file)
|
||||||
assertAndDoAllBackgroundTasks()
|
assertAndDoAllBackgroundTasks()
|
||||||
assertSingleLoading()
|
assertSingleLoading()
|
||||||
}
|
}
|
||||||
+8
-10
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.idea.scripting.gradle
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
||||||
import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationLoadingTest
|
import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationLoadingTest
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.legacy.GradleLegacyScriptConfigurationLoaderForOutOfProjectScripts
|
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner
|
import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner
|
||||||
import org.jetbrains.plugins.gradle.util.GradleConstants
|
import org.jetbrains.plugins.gradle.util.GradleConstants
|
||||||
@@ -35,15 +34,13 @@ class GradleScriptOutOfProjectTest : AbstractScriptConfigurationLoadingTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val loaderForOutOfProjectScripts by lazy {
|
private val loaderForOutOfProjectScripts by lazy {
|
||||||
GradleLegacyScriptConfigurationLoaderForOutOfProjectScripts(
|
GradleScriptConfigurationLoaderForOutOfProjectScripts(project)
|
||||||
project
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testManualLoadingForUpToDate() {
|
fun testManualLoadingForUpToDate() {
|
||||||
assertConfigurationShouldBeLoadedManually()
|
assertConfigurationShouldBeLoadedManually()
|
||||||
|
|
||||||
scriptConfigurationManager.default.forceReloadConfiguration(myFile as KtFile, loaderForOutOfProjectScripts)
|
scriptConfigurationManager.forceReloadConfiguration(myFile.virtualFile, loaderForOutOfProjectScripts)
|
||||||
|
|
||||||
assertConfigurationWasLoaded()
|
assertConfigurationWasLoaded()
|
||||||
}
|
}
|
||||||
@@ -56,7 +53,7 @@ class GradleScriptOutOfProjectTest : AbstractScriptConfigurationLoadingTest() {
|
|||||||
assertNoBackgroundTasks()
|
assertNoBackgroundTasks()
|
||||||
assertNoLoading()
|
assertNoLoading()
|
||||||
|
|
||||||
scriptConfigurationManager.default.forceReloadConfiguration(myFile as KtFile, loaderForOutOfProjectScripts)
|
scriptConfigurationManager.forceReloadConfiguration(myFile.virtualFile, loaderForOutOfProjectScripts)
|
||||||
|
|
||||||
assertConfigurationWasLoaded()
|
assertConfigurationWasLoaded()
|
||||||
}
|
}
|
||||||
@@ -64,7 +61,7 @@ class GradleScriptOutOfProjectTest : AbstractScriptConfigurationLoadingTest() {
|
|||||||
fun testFileAttributesForManualLoading() {
|
fun testFileAttributesForManualLoading() {
|
||||||
assertConfigurationShouldBeLoadedManually()
|
assertConfigurationShouldBeLoadedManually()
|
||||||
|
|
||||||
scriptConfigurationManager.default.forceReloadConfiguration(myFile as KtFile, loaderForOutOfProjectScripts)
|
scriptConfigurationManager.forceReloadConfiguration(myFile.virtualFile, loaderForOutOfProjectScripts)
|
||||||
|
|
||||||
assertConfigurationWasLoaded()
|
assertConfigurationWasLoaded()
|
||||||
|
|
||||||
@@ -76,22 +73,23 @@ class GradleScriptOutOfProjectTest : AbstractScriptConfigurationLoadingTest() {
|
|||||||
fun testNoNotificationAfterForReload() {
|
fun testNoNotificationAfterForReload() {
|
||||||
assertConfigurationShouldBeLoadedManually()
|
assertConfigurationShouldBeLoadedManually()
|
||||||
|
|
||||||
scriptConfigurationManager.default.forceReloadConfiguration(myFile as KtFile, loaderForOutOfProjectScripts)
|
scriptConfigurationManager.forceReloadConfiguration(myFile.virtualFile, loaderForOutOfProjectScripts)
|
||||||
assertConfigurationWasLoaded()
|
assertConfigurationWasLoaded()
|
||||||
|
|
||||||
makeChangesInsideSections()
|
makeChangesInsideSections()
|
||||||
|
|
||||||
scriptConfigurationManager.default.forceReloadConfiguration(myFile as KtFile, loaderForOutOfProjectScripts)
|
scriptConfigurationManager.forceReloadConfiguration(myFile.virtualFile, loaderForOutOfProjectScripts)
|
||||||
assertConfigurationWasLoaded()
|
assertConfigurationWasLoaded()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeChangesInsideSections() {
|
private fun makeChangesInsideSections() {
|
||||||
changeContents(myFile.text.replace(GradleScriptListenerTest.insidePlaceholder, "application"), myFile)
|
changeContents(myFile.text.replace(GradleScriptInputsWatcherTest.insidePlaceholder, "application"), myFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assertConfigurationShouldBeLoadedManually() {
|
private fun assertConfigurationShouldBeLoadedManually() {
|
||||||
assertNull(getConfiguration())
|
assertNull(getConfiguration())
|
||||||
|
assertAndDoAllBackgroundTasks()
|
||||||
assertNoLoading()
|
assertNoLoading()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-9
@@ -6,9 +6,6 @@
|
|||||||
package org.jetbrains.kotlin.idea.scripting.gradle
|
package org.jetbrains.kotlin.idea.scripting.gradle
|
||||||
|
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel
|
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootData
|
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.readKotlinDslScriptModels
|
|
||||||
import org.jetbrains.kotlin.idea.scripting.gradle.roots.writeKotlinDslScriptModels
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
@@ -16,16 +13,15 @@ import java.io.DataInputStream
|
|||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class GradleBuildRootDataSerializerTest {
|
class KotlinDslScriptModelsTest {
|
||||||
@Test
|
@Test
|
||||||
fun write() {
|
fun write() {
|
||||||
val data = GradleBuildRootData(
|
val data = ConfigurationData(
|
||||||
listOf("a", "b", "c"),
|
listOf("a", "b", "c"),
|
||||||
listOf("a"),
|
|
||||||
listOf(
|
listOf(
|
||||||
KotlinDslScriptModel(
|
KotlinDslScriptModel(
|
||||||
"a",
|
"a",
|
||||||
GradleKotlinScriptConfigurationInputs("b", 1, "a"),
|
GradleKotlinScriptConfigurationInputs("b", 1),
|
||||||
listOf("c", "a", "b"),
|
listOf("c", "a", "b"),
|
||||||
listOf("b", "c", "a"),
|
listOf("b", "c", "a"),
|
||||||
listOf("i", "c", "b"),
|
listOf("i", "c", "b"),
|
||||||
@@ -33,7 +29,7 @@ class GradleBuildRootDataSerializerTest {
|
|||||||
),
|
),
|
||||||
KotlinDslScriptModel(
|
KotlinDslScriptModel(
|
||||||
"a",
|
"a",
|
||||||
GradleKotlinScriptConfigurationInputs("b", 1, "a"),
|
GradleKotlinScriptConfigurationInputs("b", 1),
|
||||||
listOf("c", "a", "b"),
|
listOf("c", "a", "b"),
|
||||||
listOf("b", "c", "a"),
|
listOf("b", "c", "a"),
|
||||||
listOf("i", "c", "b"),
|
listOf("i", "c", "b"),
|
||||||
@@ -45,7 +41,7 @@ class GradleBuildRootDataSerializerTest {
|
|||||||
val buffer = ByteArrayOutputStream()
|
val buffer = ByteArrayOutputStream()
|
||||||
writeKotlinDslScriptModels(DataOutputStream(buffer), data)
|
writeKotlinDslScriptModels(DataOutputStream(buffer), data)
|
||||||
|
|
||||||
val restored = readKotlinDslScriptModels(DataInputStream(ByteArrayInputStream(buffer.toByteArray())), "a")
|
val restored = readKotlinDslScriptModels(DataInputStream(ByteArrayInputStream(buffer.toByteArray())))
|
||||||
|
|
||||||
assertEquals(data.toString(), restored.toString())
|
assertEquals(data.toString(), restored.toString())
|
||||||
}
|
}
|
||||||
-77
@@ -72,81 +72,4 @@ class LastModifiedFilesTest {
|
|||||||
assertEquals(3, files.lastModifiedTimeStampExcept("a"))
|
assertEquals(3, files.lastModifiedTimeStampExcept("a"))
|
||||||
assertEquals(3, files.lastModifiedTimeStampExcept("b"))
|
assertEquals(3, files.lastModifiedTimeStampExcept("b"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testSameFile() {
|
|
||||||
files.fileChanged(1, "x")
|
|
||||||
files.fileChanged(2, "x")
|
|
||||||
assertEquals(Long.MIN_VALUE, files.lastModifiedTimeStampExcept("x"))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testSameFile1() {
|
|
||||||
files.fileChanged(1, "z")
|
|
||||||
files.fileChanged(2, "x")
|
|
||||||
files.fileChanged(3, "x")
|
|
||||||
assertEquals(1, files.lastModifiedTimeStampExcept("x"))
|
|
||||||
assertEquals(3, files.lastModifiedTimeStampExcept("z"))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testSameFile2() {
|
|
||||||
files.fileChanged(1, "z")
|
|
||||||
files.fileChanged(1, "x")
|
|
||||||
files.fileChanged(2, "x")
|
|
||||||
assertEquals(1, files.lastModifiedTimeStampExcept("x"))
|
|
||||||
assertEquals(2, files.lastModifiedTimeStampExcept("z"))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testSameFile3() {
|
|
||||||
files.fileChanged(1, "x")
|
|
||||||
files.fileChanged(1, "y")
|
|
||||||
files.fileChanged(2, "x")
|
|
||||||
assertEquals(1, files.lastModifiedTimeStampExcept("x"))
|
|
||||||
assertEquals(2, files.lastModifiedTimeStampExcept("y"))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testSameFile4() {
|
|
||||||
files.fileChanged(1, "z")
|
|
||||||
files.fileChanged(2, "x")
|
|
||||||
files.fileChanged(2, "y")
|
|
||||||
files.fileChanged(3, "x")
|
|
||||||
assertEquals(2, files.lastModifiedTimeStampExcept("x"))
|
|
||||||
assertEquals(3, files.lastModifiedTimeStampExcept("y"))
|
|
||||||
assertEquals(3, files.lastModifiedTimeStampExcept("z"))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testLoadingAfterSameFileBug() {
|
|
||||||
val restored = LastModifiedFiles(
|
|
||||||
LastModifiedFiles.SimultaneouslyChangedFiles(2, mutableSetOf("x")),
|
|
||||||
LastModifiedFiles.SimultaneouslyChangedFiles(1, mutableSetOf("x"))
|
|
||||||
)
|
|
||||||
|
|
||||||
assertEquals(Long.MIN_VALUE, restored.lastModifiedTimeStampExcept("x"))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testLoadingAfterSameFileBug2() {
|
|
||||||
val restored = LastModifiedFiles(
|
|
||||||
LastModifiedFiles.SimultaneouslyChangedFiles(2, mutableSetOf("x", "y")),
|
|
||||||
LastModifiedFiles.SimultaneouslyChangedFiles(1, mutableSetOf("x"))
|
|
||||||
)
|
|
||||||
|
|
||||||
assertEquals(2, restored.lastModifiedTimeStampExcept("x"))
|
|
||||||
assertEquals(2, restored.lastModifiedTimeStampExcept("y"))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testLoadingAfterSameFileBug3() {
|
|
||||||
val restored = LastModifiedFiles(
|
|
||||||
LastModifiedFiles.SimultaneouslyChangedFiles(2, mutableSetOf("x", "y")),
|
|
||||||
LastModifiedFiles.SimultaneouslyChangedFiles(1, mutableSetOf("x", "z"))
|
|
||||||
)
|
|
||||||
|
|
||||||
assertEquals(2, restored.lastModifiedTimeStampExcept("x"))
|
|
||||||
assertEquals(2, restored.lastModifiedTimeStampExcept("y"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.scripting.gradle
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFails
|
||||||
|
|
||||||
|
class RootsIndexTest {
|
||||||
|
@Test
|
||||||
|
fun findRoot() {
|
||||||
|
val roots = RootsIndex<Int>()
|
||||||
|
roots["a/b/c"] = 1
|
||||||
|
assertFails { roots["a/b/c/d"] = 2 }
|
||||||
|
roots["a/c"] = 3
|
||||||
|
roots["d"] = 4
|
||||||
|
|
||||||
|
assertEquals(1, roots.findRoot("a/b/c/x"))
|
||||||
|
assertEquals(1, roots.findRoot("a/b/c"))
|
||||||
|
assertEquals(3, roots.findRoot("a/c/x"))
|
||||||
|
assertEquals(3, roots.findRoot("a/c"))
|
||||||
|
assertEquals(4, roots.findRoot("d"))
|
||||||
|
assertEquals(4, roots.findRoot("d/x"))
|
||||||
|
assertEquals(null, roots.findRoot("e"))
|
||||||
|
assertEquals(null, roots.findRoot("e/f"))
|
||||||
|
|
||||||
|
roots["d"] = 5
|
||||||
|
assertEquals(5, roots.findRoot("d"))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,10 +14,10 @@
|
|||||||
|
|
||||||
<extensions defaultExtensionNs="org.jetbrains.kotlin.scripting.idea">
|
<extensions defaultExtensionNs="org.jetbrains.kotlin.scripting.idea">
|
||||||
<listener order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptListener"/>
|
<listener order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptListener"/>
|
||||||
|
<loader order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptConfigurationLoader"/>
|
||||||
<loader order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.legacy.GradleLegacyScriptConfigurationLoader"/>
|
<scriptingSupportProvider
|
||||||
|
implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptingSupportProvider"
|
||||||
<scriptingSupport implementation="org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager"/>
|
/>
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
||||||
<extensions defaultExtensionNs="org.jetbrains.plugins.gradle">
|
<extensions defaultExtensionNs="org.jetbrains.plugins.gradle">
|
||||||
|
|||||||
@@ -14,11 +14,10 @@
|
|||||||
|
|
||||||
<extensions defaultExtensionNs="org.jetbrains.kotlin.scripting.idea">
|
<extensions defaultExtensionNs="org.jetbrains.kotlin.scripting.idea">
|
||||||
<listener order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptListener"/>
|
<listener order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptListener"/>
|
||||||
<listener order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.legacy.GradleLegacyScriptListener"/>
|
<loader order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptConfigurationLoader"/>
|
||||||
|
<scriptingSupportProvider
|
||||||
<loader order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.legacy.GradleLegacyScriptConfigurationLoader"/>
|
implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptingSupportProvider"
|
||||||
|
/>
|
||||||
<scriptingSupport implementation="org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager"/>
|
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
||||||
<extensions defaultExtensionNs="org.jetbrains.plugins.gradle">
|
<extensions defaultExtensionNs="org.jetbrains.plugins.gradle">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<idea-plugin>
|
<idea-plugin>
|
||||||
<extensionPoints>
|
<extensionPoints>
|
||||||
<extensionPoint qualifiedName="org.jetbrains.kotlin.scripting.idea.scriptingSupport"
|
<extensionPoint qualifiedName="org.jetbrains.kotlin.scripting.idea.scriptingSupportProvider"
|
||||||
interface="org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport"
|
interface="org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport$Provider"
|
||||||
area="IDEA_PROJECT"/>
|
area="IDEA_PROJECT"/>
|
||||||
</extensionPoints>
|
</extensionPoints>
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.idea.inspections
|
|||||||
|
|
||||||
import com.intellij.codeInsight.FileModificationService
|
import com.intellij.codeInsight.FileModificationService
|
||||||
import com.intellij.codeInsight.daemon.QuickFixBundle
|
import com.intellij.codeInsight.daemon.QuickFixBundle
|
||||||
|
import com.intellij.codeInsight.daemon.impl.HighlightInfoType
|
||||||
import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil
|
import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil
|
||||||
import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil
|
import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil
|
||||||
import com.intellij.codeInspection.*
|
import com.intellij.codeInspection.*
|
||||||
@@ -44,7 +45,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
|||||||
import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor
|
import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||||
import org.jetbrains.kotlin.idea.core.isInheritable
|
import org.jetbrains.kotlin.idea.core.isInheritable
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptingSupport
|
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
||||||
import org.jetbrains.kotlin.idea.core.toDescriptor
|
import org.jetbrains.kotlin.idea.core.toDescriptor
|
||||||
import org.jetbrains.kotlin.idea.findUsages.KotlinFindUsagesHandlerFactory
|
import org.jetbrains.kotlin.idea.findUsages.KotlinFindUsagesHandlerFactory
|
||||||
import org.jetbrains.kotlin.idea.findUsages.handlers.KotlinFindClassUsagesHandler
|
import org.jetbrains.kotlin.idea.findUsages.handlers.KotlinFindClassUsagesHandler
|
||||||
@@ -137,8 +138,7 @@ class UnusedSymbolInspection : AbstractKotlinInspection() {
|
|||||||
|
|
||||||
val usedScripts = findScriptsWithUsages(declaration)
|
val usedScripts = findScriptsWithUsages(declaration)
|
||||||
if (usedScripts.isNotEmpty()) {
|
if (usedScripts.isNotEmpty()) {
|
||||||
if (!DefaultScriptingSupport.getInstance(declaration.project).ensureLoadedFromCache(usedScripts)) {
|
if (!ScriptConfigurationManager.getInstance(declaration.project).updater.ensureConfigurationUpToDate(usedScripts)) {
|
||||||
// Not all script configuration are loaded; behave like it is used
|
|
||||||
return TOO_MANY_OCCURRENCES
|
return TOO_MANY_OCCURRENCES
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-4
@@ -13,7 +13,6 @@ import com.intellij.openapi.vfs.VirtualFile
|
|||||||
import com.intellij.psi.PsiDocumentManager
|
import com.intellij.psi.PsiDocumentManager
|
||||||
import com.intellij.psi.PsiFile
|
import com.intellij.psi.PsiFile
|
||||||
import org.jetbrains.kotlin.idea.core.script.*
|
import org.jetbrains.kotlin.idea.core.script.*
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.CompositeScriptConfigurationManager
|
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptConfigurationManagerExtensions
|
import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptConfigurationManagerExtensions
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.FileContentsDependentConfigurationLoader
|
import org.jetbrains.kotlin.idea.core.script.configuration.loader.FileContentsDependentConfigurationLoader
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.testingBackgroundExecutor
|
import org.jetbrains.kotlin.idea.core.script.configuration.testingBackgroundExecutor
|
||||||
@@ -22,7 +21,7 @@ import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
|||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
|
||||||
abstract class AbstractScriptConfigurationLoadingTest : AbstractScriptConfigurationTest() {
|
abstract class AbstractScriptConfigurationLoadingTest : AbstractScriptConfigurationTest() {
|
||||||
lateinit var scriptConfigurationManager: CompositeScriptConfigurationManager
|
lateinit var scriptConfigurationManager: ScriptConfigurationManager
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private var occurredLoadings = 0
|
private var occurredLoadings = 0
|
||||||
@@ -42,7 +41,7 @@ abstract class AbstractScriptConfigurationLoadingTest : AbstractScriptConfigurat
|
|||||||
testScriptConfigurationNotification = true
|
testScriptConfigurationNotification = true
|
||||||
ApplicationManager.getApplication().isScriptChangesNotifierDisabled = false
|
ApplicationManager.getApplication().isScriptChangesNotifierDisabled = false
|
||||||
|
|
||||||
scriptConfigurationManager = ServiceManager.getService(project, ScriptConfigurationManager::class.java) as CompositeScriptConfigurationManager
|
scriptConfigurationManager = ServiceManager.getService(project, ScriptConfigurationManager::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun tearDown() {
|
override fun tearDown() {
|
||||||
@@ -104,7 +103,7 @@ abstract class AbstractScriptConfigurationLoadingTest : AbstractScriptConfigurat
|
|||||||
protected fun makeChanges(contents: String, file: KtFile = myFile as KtFile) {
|
protected fun makeChanges(contents: String, file: KtFile = myFile as KtFile) {
|
||||||
changeContents(contents)
|
changeContents(contents)
|
||||||
|
|
||||||
scriptConfigurationManager.default.ensureUpToDatedConfigurationSuggested(file)
|
scriptConfigurationManager.updater.ensureUpToDatedConfigurationSuggested(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun changeContents(contents: String, file: PsiFile = myFile) {
|
protected fun changeContents(contents: String, file: PsiFile = myFile) {
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
package org.jetbrains.kotlin.idea.script
|
package org.jetbrains.kotlin.idea.script
|
||||||
|
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners
|
import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(JUnit3RunnerWithInners::class)
|
@RunWith(JUnit3WithIdeaConfigurationRunner::class)
|
||||||
class ScriptConfigurationLoadingTest : AbstractScriptConfigurationLoadingTest() {
|
class ScriptConfigurationLoadingTest : AbstractScriptConfigurationLoadingTest() {
|
||||||
fun testSimple() {
|
fun testSimple() {
|
||||||
assertAndLoadInitialConfiguration()
|
assertAndLoadInitialConfiguration()
|
||||||
@@ -163,10 +163,10 @@ class ScriptConfigurationLoadingTest : AbstractScriptConfigurationLoadingTest()
|
|||||||
|
|
||||||
fun testLoadingForUsagesSearch() {
|
fun testLoadingForUsagesSearch() {
|
||||||
assertAndLoadInitialConfiguration()
|
assertAndLoadInitialConfiguration()
|
||||||
assertTrue(scriptConfigurationManager.default.ensureLoadedFromCache(listOf(myFile as KtFile)))
|
assertTrue(scriptConfigurationManager.updater.ensureConfigurationUpToDate(listOf(myFile as KtFile)))
|
||||||
|
|
||||||
changeContents("A")
|
changeContents("A")
|
||||||
assertTrue(scriptConfigurationManager.default.ensureLoadedFromCache(listOf(myFile as KtFile)))
|
assertFalse(scriptConfigurationManager.updater.ensureConfigurationUpToDate(listOf(myFile as KtFile)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testReportsOnAutoApply() {
|
fun testReportsOnAutoApply() {
|
||||||
|
|||||||
-1
@@ -135,7 +135,6 @@ abstract class ScriptCompilationConfigurationWrapper(val script: SourceCode) {
|
|||||||
|
|
||||||
override val javaHome: File?
|
override val javaHome: File?
|
||||||
get() = configuration?.get(ScriptCompilationConfiguration.hostConfiguration)?.get(ScriptingHostConfiguration.jvm.jdkHome)
|
get() = configuration?.get(ScriptCompilationConfiguration.hostConfiguration)?.get(ScriptingHostConfiguration.jvm.jdkHome)
|
||||||
?: configuration?.get(ScriptingHostConfiguration.jvm.jdkHome)
|
|
||||||
|
|
||||||
override val defaultImports: List<String>
|
override val defaultImports: List<String>
|
||||||
get() = configuration?.get(ScriptCompilationConfiguration.defaultImports).orEmpty()
|
get() = configuration?.get(ScriptCompilationConfiguration.defaultImports).orEmpty()
|
||||||
|
|||||||
Reference in New Issue
Block a user