Refactor script dependencies management

This commit is contained in:
Ilya Chernikov
2016-06-09 19:56:37 +02:00
committed by Pavel V. Talanov
parent c244414f3c
commit 082290f8e3
23 changed files with 470 additions and 510 deletions
@@ -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
}
@@ -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
@@ -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()
}
@@ -0,0 +1,118 @@
/*
* 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.core.script
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.search.FileTypeIndex
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.idea.util.application.runReadAction
import org.jetbrains.kotlin.script.*
import org.jetbrains.kotlin.utils.PathUtil
import java.io.File
import java.io.FileNotFoundException
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
class KotlinScriptConfigurationManager(
private val project: Project,
private val scriptDefinitionProvider: KotlinScriptDefinitionProvider,
private val scriptExternalImportsProvider: KotlinScriptExternalImportsProvider?
) {
private val kotlinEnvVars: Map<String, List<String>> by lazy {
generateKotlinScriptClasspathEnvVarsFromPaths(project, PathUtil.getKotlinPathsForIdeaPlugin())
}
init {
reloadScriptDefinitions()
// TODO: sort out read/write action business and if possible make it lazy (e.g. move to getAllScriptsClasspath)
runReadAction { cacheAllScriptsExtraImports() }
}
private var allScriptsClasspathCache: List<VirtualFile>? = null
private val cacheLock = ReentrantReadWriteLock()
fun getScriptClasspath(file: VirtualFile): List<VirtualFile> =
scriptExternalImportsProvider
?.getExternalImports(file)
?.flatMap { it.classpath }
?.map { it.classpathEntryToVfs() }
?: emptyList()
fun getAllScriptsClasspath(): List<VirtualFile> = cacheLock.read {
if (allScriptsClasspathCache == null) {
allScriptsClasspathCache =
(scriptExternalImportsProvider?.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(project, it) }.toTypedArray())
}
}
private fun reloadScriptDefinitions() {
loadScriptConfigsFromProjectRoot(File(project.basePath ?: "")).let {
if (it.isNotEmpty()) {
scriptDefinitionProvider.setScriptDefinitions(
it.map { KotlinConfigurableScriptDefinition(it, kotlinEnvVars) } + StandardScriptDefinition)
}
}
}
private fun cacheAllScriptsExtraImports() {
scriptExternalImportsProvider?.apply {
invalidateCaches()
cacheExternalImports(
scriptDefinitionProvider.getAllKnownFileTypes()
.flatMap { FileTypeIndex.getFiles(it, GlobalSearchScope.allScope(project)) })
}
}
companion object {
@JvmStatic
fun getInstance(project: Project): KotlinScriptConfigurationManager =
ServiceManager.getService(project, KotlinScriptConfigurationManager::class.java)
}
}
class KotlinScriptDependenciesIndexableSetContributor : IndexableSetContributor() {
override fun getAdditionalProjectRootsToIndex(project: Project): Set<VirtualFile> =
KotlinScriptConfigurationManager.getInstance(project).getAllScriptsClasspath().toSet()
override fun getAdditionalRootsToIndex(): Set<VirtualFile> = emptySet()
}
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.script
package org.jetbrains.kotlin.idea.core.script
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectFileIndex
@@ -26,18 +26,19 @@ 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.idea.core.script.KotlinScriptConfigurationManager
import org.jetbrains.kotlin.load.java.JavaClassFinderImpl
import org.jetbrains.kotlin.resolve.jvm.KotlinSafeClassFinder
class KotlinScriptDependenciesClassFinder(project: Project,
private val kotlinScriptConfigurationManager: KotlinScriptConfigurationManager
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)
val v = createCache(scriptClasspath)
return v
}
}
+7 -7
View File
@@ -40,9 +40,6 @@
<component>
<implementation-class>org.jetbrains.kotlin.asJava.KotlinCodeBlockModificationListener</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.script.KotlinScriptConfigurationManager</implementation-class>
</component>
</project-components>
<application-components>
@@ -250,8 +247,11 @@
<projectService serviceInterface="org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider"
serviceImplementation="org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider"/>
<projectService serviceInterface="org.jetbrains.kotlin.script.KotlinScriptExtraImportsProvider"
serviceImplementation="org.jetbrains.kotlin.script.KotlinScriptExtraImportsProvider"/>
<projectService serviceInterface="org.jetbrains.kotlin.script.KotlinScriptExternalImportsProvider"
serviceImplementation="org.jetbrains.kotlin.script.KotlinScriptExternalImportsProvider"/>
<projectService serviceInterface="org.jetbrains.kotlin.idea.core.script.KotlinScriptConfigurationManager"
serviceImplementation="org.jetbrains.kotlin.idea.core.script.KotlinScriptConfigurationManager"/>
<projectService serviceInterface="org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder"
serviceImplementation="org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder"/>
@@ -293,7 +293,7 @@
<projectService serviceInterface="org.jetbrains.kotlin.console.KotlinConsoleKeeper"
serviceImplementation="org.jetbrains.kotlin.console.KotlinConsoleKeeper"/>
<indexedRootsProvider implementation="org.jetbrains.kotlin.idea.script.KotlinScriptDependenciesIndexableSetContributor"/>
<indexedRootsProvider implementation="org.jetbrains.kotlin.idea.core.script.KotlinScriptDependenciesIndexableSetContributor"/>
<errorHandler implementation="org.jetbrains.kotlin.idea.reporter.KotlinReportSubmitter"/>
@@ -575,7 +575,7 @@
implementationClass="org.jetbrains.kotlin.idea.hierarchy.overrides.KotlinOverrideHierarchyProvider" />
<java.elementFinder implementation="org.jetbrains.kotlin.asJava.JavaElementFinder"/>
<java.elementFinder implementation="org.jetbrains.kotlin.idea.script.KotlinScriptDependenciesClassFinder"/>
<java.elementFinder implementation="org.jetbrains.kotlin.idea.core.script.KotlinScriptDependenciesClassFinder"/>
<java.shortNamesCache implementation="org.jetbrains.kotlin.idea.caches.KotlinShortNamesCache"/>
<stubElementTypeHolder class="org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes"/>