Prepare infrastruct for fixing customized script resolving
Move ConfigManager and ClassFinder to the idea-analysis Add (back) IndexableSetContributor Add a method in ConfigManager to generate all dependencies scope Add and use delegating scope with possibility to get base scope from it (used in the class finder)
This commit is contained in:
@@ -330,7 +330,7 @@ internal data class CustomizedScriptModuleInfo(val project: Project, val module:
|
||||
}) ?: emptyList())
|
||||
}
|
||||
|
||||
private class FileLibraryScope(project: Project, private val libraryRoot: VirtualFile) : GlobalSearchScope(project) {
|
||||
class FileLibraryScope(project: Project, private val libraryRoot: VirtualFile) : GlobalSearchScope(project) {
|
||||
val cachedEntries : MutableSet<VirtualFile> = hashSetOf()
|
||||
|
||||
override fun contains(file: VirtualFile): Boolean =
|
||||
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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.components.ServiceManager
|
||||
import com.intellij.openapi.project.Project
|
||||
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 java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
@Suppress("unused") // project component
|
||||
class KotlinScriptConfigurationManager(private val project: Project,
|
||||
private val scriptDefinitionProvider: KotlinScriptDefinitionProvider,
|
||||
private val scriptExtraImportsProvider: KotlinScriptExtraImportsProvider?
|
||||
) : AbstractProjectComponent(project) {
|
||||
|
||||
private val kotlinEnvVars: Map<String, List<String>> by lazy { generateKotlinScriptClasspathEnvVarsForIdea(myProject) }
|
||||
|
||||
init {
|
||||
reloadScriptDefinitions()
|
||||
val conn = myProject.messageBus.connect()
|
||||
conn.subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener.Adapter() {
|
||||
override fun after(events: List<VFileEvent>) {
|
||||
var anyScriptDefinitionChanged = false
|
||||
events.filter { it is VFileEvent }.forEach {
|
||||
it.file?.let {
|
||||
if (!anyScriptDefinitionChanged && isScriptDefinitionConfigFile(it)) {
|
||||
anyScriptDefinitionChanged = true
|
||||
}
|
||||
scriptExtraImportsProvider?.run {
|
||||
if (isExtraImportsConfig(it)) {
|
||||
invalidateExtraImports(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (anyScriptDefinitionChanged) {
|
||||
reloadScriptDefinitions()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: cache
|
||||
fun getScriptClasspath(file: VirtualFile): List<VirtualFile> =
|
||||
getScriptClasspathRaw(file)
|
||||
.mapNotNull { StandardFileSystems.local().findFileByPath(it) }
|
||||
.distinct()
|
||||
|
||||
// TODO: cache
|
||||
fun getAllScriptsClasspath(): List<VirtualFile> {
|
||||
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()
|
||||
} }
|
||||
}
|
||||
return project.baseDir.vfsWalkFiles { getScriptClasspathRaw(it) }
|
||||
.distinct()
|
||||
.mapNotNull {
|
||||
if (File(it).isDirectory)
|
||||
StandardFileSystems.local()?.findFileByPath(it) ?: throw FileNotFoundException("Classpath entry points to a non-existent location: $it")
|
||||
else
|
||||
StandardFileSystems.jar()?.findFileByPath(it + URLUtil.JAR_SEPARATOR) ?: throw FileNotFoundException("Classpath entry points to a file that is not a JAR archive: $it")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllScriptsClasspathScope(): GlobalSearchScope =
|
||||
GlobalSearchScope.union(getAllScriptsClasspath().map { FileLibraryScope(project, it) }.toTypedArray())
|
||||
|
||||
private fun getScriptClasspathRaw(file: VirtualFile): List<String> =
|
||||
scriptDefinitionProvider.findScriptDefinition(file)?.getScriptDependenciesClasspath()?.let {
|
||||
it + (scriptExtraImportsProvider?.getExtraImports(file)?.flatMap { it.classpath } ?: emptyList())
|
||||
} ?: emptyList()
|
||||
|
||||
private fun reloadScriptDefinitions() {
|
||||
loadScriptConfigsFromProjectRoot(File(myProject.basePath ?: ".")).let {
|
||||
if (it.isNotEmpty()) {
|
||||
scriptDefinitionProvider.scriptDefinitions =
|
||||
it.map { KotlinConfigurableScriptDefinition(it, kotlinEnvVars) } + StandardScriptDefinition
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getInstance(project: Project): KotlinScriptConfigurationManager =
|
||||
project.getComponent(KotlinScriptConfigurationManager::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class KotlinScriptDependenciesIndexableSetContributor : IndexableSetContributor() {
|
||||
|
||||
override fun getAdditionalProjectRootsToIndex(project: Project): Set<VirtualFile> =
|
||||
super.getAdditionalProjectRootsToIndex(project) +
|
||||
KotlinScriptConfigurationManager.getInstance(project).getAllScriptsClasspath()
|
||||
|
||||
override fun getAdditionalRootsToIndex(): Set<VirtualFile> = emptySet()
|
||||
}
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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.CustomizedScriptModuleSearchScope
|
||||
import org.jetbrains.kotlin.load.java.JavaClassFinderImpl
|
||||
|
||||
@Suppress("unused") // project extension
|
||||
class KotlinScriptDependenciesClassFinder(project: Project,
|
||||
private val kotlinScriptConfigurationManager: KotlinScriptConfigurationManager
|
||||
) : NonClasspathClassFinder(project) {
|
||||
|
||||
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? CustomizedScriptModuleSearchScope ?:
|
||||
(scope as? JavaClassFinderImpl.DelegatingGlobalSearchScopeWithBaseAccess)?.base as? CustomizedScriptModuleSearchScope
|
||||
)?.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 CustomizedScriptModuleSearchScope ||
|
||||
(scope as? JavaClassFinderImpl.DelegatingGlobalSearchScopeWithBaseAccess)?.base is CustomizedScriptModuleSearchScope ||
|
||||
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