Refactor script dependencies management
This commit is contained in:
committed by
Pavel V. Talanov
parent
c244414f3c
commit
082290f8e3
+12
-17
@@ -23,27 +23,22 @@ import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.roots.*
|
||||
import com.intellij.openapi.roots.impl.libraries.LibraryEx
|
||||
import com.intellij.openapi.roots.libraries.Library
|
||||
import com.intellij.openapi.roots.libraries.LibraryTable
|
||||
import com.intellij.openapi.vfs.StandardFileSystems
|
||||
import com.intellij.openapi.vfs.VfsUtilCore
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.VirtualFileManager
|
||||
import com.intellij.psi.search.DelegatingGlobalSearchScope
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.util.CachedValueProvider
|
||||
import com.intellij.psi.util.CachedValuesManager
|
||||
import com.intellij.util.SmartList
|
||||
import com.intellij.util.io.URLUtil
|
||||
import org.jdom.Element
|
||||
import org.jetbrains.kotlin.analyzer.ModuleInfo
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||
import org.jetbrains.kotlin.script.KotlinScriptExtraImport
|
||||
import org.jetbrains.kotlin.utils.alwaysNull
|
||||
import org.jetbrains.kotlin.utils.emptyOrSingletonList
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.lang.reflect.Method
|
||||
import java.util.*
|
||||
|
||||
@@ -297,8 +292,7 @@ class ScriptModuleSearchScope(val scriptFile: VirtualFile, baseScope: GlobalSear
|
||||
}
|
||||
|
||||
internal data class ScriptModuleInfo(val project: Project, val module: Module?, val scriptFile: VirtualFile,
|
||||
val scriptDefinition: KotlinScriptDefinition,
|
||||
val scriptExtraImports: List<KotlinScriptExtraImport>) : IdeaModuleInfo {
|
||||
val scriptDefinition: KotlinScriptDefinition) : IdeaModuleInfo {
|
||||
override val moduleOrigin: ModuleOrigin
|
||||
get() = ModuleOrigin.OTHER
|
||||
|
||||
@@ -315,16 +309,17 @@ internal data class ScriptModuleInfo(val project: Project, val module: Module?,
|
||||
private fun dependenciesRoots(): List<VirtualFile> {
|
||||
// TODO: find out whether it should be cashed (some changes listener should be implemented for the cached roots)
|
||||
val jarfs = StandardFileSystems.jar()
|
||||
return (scriptDefinition.getScriptDependenciesClasspath() + scriptExtraImports.flatMap { it.classpath })
|
||||
.map { File(it).canonicalFile }
|
||||
.distinct()
|
||||
.mapNotNull {
|
||||
// TODO: ensure that the entries are checked elsewhere, so diagnostics is delivered to a user if files are not correctly specified
|
||||
if (it.isFile)
|
||||
jarfs.findFileByPath(it.absolutePath + URLUtil.JAR_SEPARATOR) ?: null // diag: Classpath entry points to a file that is not a JAR archive
|
||||
else
|
||||
StandardFileSystems.local().findFileByPath(it.absolutePath) ?: null // diag: Classpath entry points to a non-existent location
|
||||
}
|
||||
return scriptDefinition.getDependenciesFor(scriptFile, project)?.classpath
|
||||
?.map { File(it).canonicalFile }
|
||||
?.distinct()
|
||||
?.mapNotNull {
|
||||
// TODO: ensure that the entries are checked elsewhere, so diagnostics is delivered to a user if files are not correctly specified
|
||||
if (it.isFile)
|
||||
jarfs.findFileByPath(it.absolutePath + URLUtil.JAR_SEPARATOR) ?: null // diag: Classpath entry points to a file that is not a JAR archive
|
||||
else
|
||||
StandardFileSystems.local().findFileByPath(it.absolutePath) ?: null // diag: Classpath entry points to a non-existent location
|
||||
}
|
||||
?: emptyList()
|
||||
}
|
||||
|
||||
override fun dependencies() =
|
||||
|
||||
@@ -30,7 +30,6 @@ import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
||||
import org.jetbrains.kotlin.script.getScriptDefinition
|
||||
import org.jetbrains.kotlin.script.getScriptExtraImports
|
||||
import org.jetbrains.kotlin.utils.sure
|
||||
|
||||
fun PsiElement.getModuleInfo(): IdeaModuleInfo = this.getModuleInfo { reason ->
|
||||
@@ -125,7 +124,7 @@ private fun getModuleInfoByVirtualFile(project: Project, virtualFile: VirtualFil
|
||||
|
||||
val scriptDefinition = getScriptDefinition(virtualFile, project)
|
||||
if (scriptDefinition != null)
|
||||
return ScriptModuleInfo(project, module, virtualFile, scriptDefinition, getScriptExtraImports(virtualFile, project))
|
||||
return ScriptModuleInfo(project, module, virtualFile, scriptDefinition)
|
||||
|
||||
return NotUnderContentRootModuleInfo
|
||||
}
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ import org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.idea.decompiler.KtDecompiledFile
|
||||
import org.jetbrains.kotlin.idea.decompiler.textBuilder.DecompiledTextIndexer
|
||||
import org.jetbrains.kotlin.idea.script.KotlinScriptConfigurationManager
|
||||
import org.jetbrains.kotlin.idea.core.script.KotlinScriptConfigurationManager
|
||||
import org.jetbrains.kotlin.idea.stubindex.KotlinFullClassNameIndex
|
||||
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
||||
import org.jetbrains.kotlin.idea.stubindex.KotlinTopLevelFunctionFqnNameIndex
|
||||
|
||||
-164
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.script
|
||||
|
||||
import com.intellij.openapi.components.AbstractProjectComponent
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.ex.ProjectRootManagerEx
|
||||
import com.intellij.openapi.util.EmptyRunnable
|
||||
import com.intellij.openapi.vfs.StandardFileSystems
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.VirtualFileManager
|
||||
import com.intellij.openapi.vfs.newvfs.BulkFileListener
|
||||
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.util.indexing.IndexableSetContributor
|
||||
import com.intellij.util.io.URLUtil
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.FileLibraryScope
|
||||
import org.jetbrains.kotlin.script.*
|
||||
import org.jetbrains.kotlin.utils.PathUtil
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
import kotlin.concurrent.read
|
||||
import kotlin.concurrent.write
|
||||
|
||||
@Suppress("unused") // project component
|
||||
class KotlinScriptConfigurationManager(project: Project,
|
||||
private val scriptDefinitionProvider: KotlinScriptDefinitionProvider,
|
||||
private val scriptExtraImportsProvider: KotlinScriptExtraImportsProvider?,
|
||||
private val kotlinScriptDependenciesIndexableSetContributor: KotlinScriptDependenciesIndexableSetContributor?
|
||||
) : AbstractProjectComponent(project) {
|
||||
|
||||
private val kotlinEnvVars: Map<String, List<String>> by lazy { generateKotlinScriptClasspathEnvVarsFromPaths(myProject, PathUtil.getKotlinPathsForIdeaPlugin()) }
|
||||
|
||||
init {
|
||||
reloadScriptDefinitions()
|
||||
|
||||
// TODO: get rid of this expensive call as soon as makeRootsChange call will work reliably
|
||||
cacheAllScriptsExtraImports()
|
||||
|
||||
val weakThis = WeakReference(this)
|
||||
myProject.messageBus.connect().subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener.Adapter() {
|
||||
override fun after(events: List<VFileEvent>) {
|
||||
val changedExtraImportConfigs = ArrayList<VirtualFile>()
|
||||
var anyScriptDefinitionChanged = false
|
||||
events.forEach {
|
||||
it.file?.let {
|
||||
if (isScriptDefinitionConfigFile(it)) {
|
||||
anyScriptDefinitionChanged = true
|
||||
}
|
||||
else {
|
||||
weakThis.get()?.scriptExtraImportsProvider?.run {
|
||||
if (isExtraImportsConfig(it)) {
|
||||
changedExtraImportConfigs.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (anyScriptDefinitionChanged) {
|
||||
weakThis.get()?.reloadScriptDefinitions()
|
||||
}
|
||||
if (changedExtraImportConfigs.isNotEmpty()) {
|
||||
weakThis.get()?.scriptExtraImportsProvider?.invalidateExtraImportsByImportsFiles(changedExtraImportConfigs)
|
||||
}
|
||||
}
|
||||
})
|
||||
// omitting case then scriptExtraImportsProvider is not configured, considering it happens only in tests
|
||||
scriptExtraImportsProvider?.subscribeOnExtraImportsChanged { files ->
|
||||
weakThis.get()?.apply {
|
||||
cacheLock.write {
|
||||
allScriptsClasspathCache = null
|
||||
}
|
||||
ProjectRootManagerEx.getInstanceEx(myProject)?.makeRootsChange(EmptyRunnable.getInstance(), false, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var allScriptsClasspathCache: List<VirtualFile>? = null
|
||||
private val cacheLock = java.util.concurrent.locks.ReentrantReadWriteLock()
|
||||
|
||||
fun getScriptClasspath(file: VirtualFile): List<VirtualFile> =
|
||||
scriptExtraImportsProvider
|
||||
?.getExtraImports(file)
|
||||
?.flatMap { it.classpath }
|
||||
?.map { it.classpathEntryToVfs() }
|
||||
?: emptyList()
|
||||
|
||||
fun getAllScriptsClasspath(): List<VirtualFile> = cacheLock.read {
|
||||
if (allScriptsClasspathCache == null) {
|
||||
allScriptsClasspathCache =
|
||||
(scriptExtraImportsProvider?.getKnownCombinedClasspath() ?: emptyList())
|
||||
.distinct()
|
||||
.mapNotNull { it.classpathEntryToVfs() }
|
||||
}
|
||||
return allScriptsClasspathCache!!
|
||||
}
|
||||
|
||||
private fun String.classpathEntryToVfs(): VirtualFile =
|
||||
if (File(this).isDirectory)
|
||||
StandardFileSystems.local()?.findFileByPath(this) ?: throw FileNotFoundException("Classpath entry points to a non-existent location: ${this}")
|
||||
else
|
||||
StandardFileSystems.jar()?.findFileByPath(this + URLUtil.JAR_SEPARATOR) ?: throw FileNotFoundException("Classpath entry points to a file that is not a JAR archive: ${this}")
|
||||
|
||||
fun getAllScriptsClasspathScope(): GlobalSearchScope? {
|
||||
return getAllScriptsClasspath().let { cp ->
|
||||
if (cp.isEmpty()) null
|
||||
else GlobalSearchScope.union(cp.map { FileLibraryScope(myProject, it) }.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadScriptDefinitions() {
|
||||
loadScriptConfigsFromProjectRoot(File(myProject.basePath ?: ".")).let {
|
||||
if (it.isNotEmpty()) {
|
||||
scriptDefinitionProvider.setScriptDefinitions(
|
||||
it.map { KotlinConfigurableScriptDefinition(it, kotlinEnvVars) } + StandardScriptDefinition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun cacheAllScriptsExtraImports() {
|
||||
fun<R> VirtualFile.vfsWalkFiles(onFile: (VirtualFile) -> List<R>?): List<R> {
|
||||
assert(isDirectory)
|
||||
return children.flatMap { when {
|
||||
it.isDirectory -> it.vfsWalkFiles(onFile)
|
||||
else -> onFile(it) ?: emptyList()
|
||||
} }
|
||||
}
|
||||
myProject.baseDir.vfsWalkFiles {
|
||||
scriptExtraImportsProvider?.getExtraImports(it)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getInstance(project: Project): KotlinScriptConfigurationManager =
|
||||
project.getComponent(KotlinScriptConfigurationManager::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class KotlinScriptDependenciesIndexableSetContributor : IndexableSetContributor() {
|
||||
|
||||
override fun getAdditionalProjectRootsToIndex(project: Project): Set<VirtualFile> =
|
||||
KotlinScriptConfigurationManager.getInstance(project).getAllScriptsClasspath().toSet()
|
||||
|
||||
override fun getAdditionalRootsToIndex(): Set<VirtualFile> = emptySet()
|
||||
}
|
||||
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.script
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.ProjectFileIndex
|
||||
import com.intellij.openapi.roots.impl.PackageDirectoryCache
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.NonClasspathClassFinder
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.search.EverythingGlobalScope
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.util.containers.ConcurrentFactoryMap
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.ScriptModuleSearchScope
|
||||
import org.jetbrains.kotlin.load.java.JavaClassFinderImpl
|
||||
import org.jetbrains.kotlin.resolve.jvm.KotlinSafeClassFinder
|
||||
|
||||
class KotlinScriptDependenciesClassFinder(project: Project,
|
||||
private val kotlinScriptConfigurationManager: KotlinScriptConfigurationManager
|
||||
) : NonClasspathClassFinder(project), KotlinSafeClassFinder {
|
||||
|
||||
private val myCaches = object : ConcurrentFactoryMap<VirtualFile, PackageDirectoryCache>() {
|
||||
override fun create(file: VirtualFile): PackageDirectoryCache? {
|
||||
|
||||
val scriptClasspath = kotlinScriptConfigurationManager.getScriptClasspath(file)
|
||||
val v = NonClasspathClassFinder.createCache(scriptClasspath)
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
override fun calcClassRoots(): List<VirtualFile> = kotlinScriptConfigurationManager.getAllScriptsClasspath()
|
||||
|
||||
override fun getCache(scope: GlobalSearchScope?): PackageDirectoryCache =
|
||||
(scope as? ScriptModuleSearchScope ?:
|
||||
(scope as? JavaClassFinderImpl.FilterOutKotlinSourceFilesScope)?.base as? ScriptModuleSearchScope
|
||||
)?.let {
|
||||
myCaches.get(it.scriptFile)
|
||||
} ?: super.getCache(scope)
|
||||
|
||||
override fun clearCache() {
|
||||
super.clearCache()
|
||||
myCaches.clear()
|
||||
}
|
||||
|
||||
override fun findClass(qualifiedName: String, scope: GlobalSearchScope): PsiClass? =
|
||||
super.findClass(qualifiedName, scope)?.let { aClass ->
|
||||
when {
|
||||
scope is ScriptModuleSearchScope ||
|
||||
(scope as? JavaClassFinderImpl.FilterOutKotlinSourceFilesScope)?.base is ScriptModuleSearchScope ||
|
||||
scope is EverythingGlobalScope ||
|
||||
aClass.containingFile?.virtualFile.let { file ->
|
||||
file != null &&
|
||||
with (ProjectFileIndex.SERVICE.getInstance(myProject)) {
|
||||
!isInContent(file) &&
|
||||
!isInLibraryClasses(file) &&
|
||||
!isInLibrarySource(file)
|
||||
}
|
||||
} -> aClass
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user