IDE Script support: use particular scripts dependencies when building analyzers
Use javaHome property to detect jdk to analyze script files against, use default jdk if it's null
This commit is contained in:
committed by
Ilya Chernikov
parent
350abbe8cd
commit
a8416035b1
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.idea.caches.resolve
|
|||||||
import com.intellij.openapi.module.Module
|
import com.intellij.openapi.module.Module
|
||||||
import com.intellij.openapi.module.impl.scopes.LibraryScopeBase
|
import com.intellij.openapi.module.impl.scopes.LibraryScopeBase
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.projectRoots.ProjectJdkTable
|
||||||
import com.intellij.openapi.projectRoots.Sdk
|
import com.intellij.openapi.projectRoots.Sdk
|
||||||
import com.intellij.openapi.roots.*
|
import com.intellij.openapi.roots.*
|
||||||
import com.intellij.openapi.roots.impl.libraries.LibraryEx
|
import com.intellij.openapi.roots.impl.libraries.LibraryEx
|
||||||
@@ -26,6 +27,7 @@ import com.intellij.openapi.roots.libraries.Library
|
|||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import com.intellij.psi.search.DelegatingGlobalSearchScope
|
import com.intellij.psi.search.DelegatingGlobalSearchScope
|
||||||
import com.intellij.psi.search.GlobalSearchScope
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
|
import com.intellij.psi.search.NonClasspathDirectoriesScope
|
||||||
import com.intellij.psi.util.CachedValueProvider
|
import com.intellij.psi.util.CachedValueProvider
|
||||||
import com.intellij.psi.util.CachedValuesManager
|
import com.intellij.psi.util.CachedValuesManager
|
||||||
import com.intellij.util.SmartList
|
import com.intellij.util.SmartList
|
||||||
@@ -35,8 +37,12 @@ import org.jetbrains.kotlin.idea.core.script.KotlinScriptConfigurationManager
|
|||||||
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||||
|
import org.jetbrains.kotlin.script.KotlinScriptExternalDependencies
|
||||||
|
import org.jetbrains.kotlin.script.KotlinScriptExternalImportsProvider
|
||||||
import org.jetbrains.kotlin.utils.alwaysNull
|
import org.jetbrains.kotlin.utils.alwaysNull
|
||||||
import org.jetbrains.kotlin.utils.emptyOrSingletonList
|
import org.jetbrains.kotlin.utils.emptyOrSingletonList
|
||||||
|
import org.jetbrains.kotlin.utils.singletonOrEmptyList
|
||||||
|
import java.io.File
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@@ -296,21 +302,78 @@ data class ScriptModuleInfo(val project: Project, val scriptFile: VirtualFile,
|
|||||||
override val moduleOrigin: ModuleOrigin
|
override val moduleOrigin: ModuleOrigin
|
||||||
get() = ModuleOrigin.OTHER
|
get() = ModuleOrigin.OTHER
|
||||||
|
|
||||||
|
val externalDependencies by lazy {
|
||||||
|
KotlinScriptExternalImportsProvider.getInstance(project)?.getExternalImports(scriptFile)?.makeComparable()
|
||||||
|
}
|
||||||
|
|
||||||
override val name: Name = Name.special("<$SCRIPT_NAME_PREFIX${scriptDefinition.name}>")
|
override val name: Name = Name.special("<$SCRIPT_NAME_PREFIX${scriptDefinition.name}>")
|
||||||
|
|
||||||
override fun contentScope() = GlobalSearchScope.fileScope(project, scriptFile)
|
override fun contentScope() = GlobalSearchScope.fileScope(project, scriptFile)
|
||||||
|
|
||||||
// TODO: only dependencies of this particular script
|
override fun dependencies(): List<IdeaModuleInfo> {
|
||||||
override fun dependencies() = listOf(this, ScriptDependenciesModuleInfo(project))
|
return listOf(
|
||||||
|
this,
|
||||||
|
ScriptDependenciesModuleInfo(project, externalDependencies)
|
||||||
|
) + sdkDependencies(externalDependencies, project)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ScriptDependenciesModuleInfo(val project: Project): IdeaModuleInfo {
|
private fun sdkDependencies(scriptDependencies: KotlinScriptExternalDependencies?, project: Project): List<SdkInfo>
|
||||||
override fun dependencies() = listOf(this)
|
= findJdk(scriptDependencies, project)?.let { SdkInfo(project, it) }.singletonOrEmptyList()
|
||||||
|
|
||||||
|
fun findJdk(dependencies: KotlinScriptExternalDependencies?, project: Project): Sdk? {
|
||||||
|
val jdkTable = ProjectJdkTable.getInstance()
|
||||||
|
val allJdks = jdkTable.allJdks
|
||||||
|
// workaround for mismatched gradle wrapper and plugin version
|
||||||
|
val javaHome = try { dependencies?.javaHome } catch (e: Throwable) { null }
|
||||||
|
|
||||||
|
return allJdks.find { javaHome != null && it.homePath == javaHome } ?:
|
||||||
|
ProjectRootManager.getInstance(project).projectSdk ?:
|
||||||
|
allJdks.firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <T> tryGet(body: () -> T): T? {
|
||||||
|
return try {
|
||||||
|
body()
|
||||||
|
}
|
||||||
|
catch (e: Throwable) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ComparableScriptDependencies(
|
||||||
|
override val javaHome: String?,
|
||||||
|
override val classpath: Iterable<File>,
|
||||||
|
override val imports: Iterable<String>,
|
||||||
|
override val sources: Iterable<File>,
|
||||||
|
override val scripts: Iterable<File>
|
||||||
|
): KotlinScriptExternalDependencies
|
||||||
|
|
||||||
|
fun KotlinScriptExternalDependencies.makeComparable() = ComparableScriptDependencies(
|
||||||
|
tryGet { javaHome }, classpath, imports, sources, tryGet { scripts } ?: listOf()
|
||||||
|
)
|
||||||
|
|
||||||
|
class ScriptDependenciesModuleInfo(val project: Project, val dependencies: ComparableScriptDependencies?): IdeaModuleInfo {
|
||||||
|
override fun dependencies() = listOf(this) + sdkDependencies(dependencies, project)
|
||||||
|
|
||||||
override val name = Name.special("<Script dependencies>")
|
override val name = Name.special("<Script dependencies>")
|
||||||
|
|
||||||
// TODO: this is not very efficient because KotlinSourceFilterScope already checks if the files are in scripts classpath
|
override fun contentScope(): GlobalSearchScope {
|
||||||
override fun contentScope() = KotlinSourceFilterScope.libraryClassFiles(KotlinScriptConfigurationManager.getInstance(project).getAllScriptsClasspathScope(), project)
|
if (dependencies == null) {
|
||||||
|
// we do not know which scripts these dependencies are
|
||||||
|
return KotlinSourceFilterScope.libraryClassFiles(
|
||||||
|
KotlinScriptConfigurationManager.getInstance(project).getAllScriptsClasspathScope(), project
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val classpath = KotlinScriptConfigurationManager.toVfsRoots(dependencies.classpath)
|
||||||
|
// TODO: this is not very efficient because KotlinSourceFilterScope already checks if the files are in scripts classpath
|
||||||
|
return KotlinSourceFilterScope.libraryClassFiles(NonClasspathDirectoriesScope(classpath), project)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: intentionally not taking dependencies into account
|
||||||
|
// otherwise there is no way to implement getModuleInfo
|
||||||
|
override fun hashCode() = project.hashCode()
|
||||||
|
override fun equals(other: Any?): Boolean = other is ScriptDependenciesModuleInfo && this.project == other.project
|
||||||
|
|
||||||
override val moduleOrigin: ModuleOrigin
|
override val moduleOrigin: ModuleOrigin
|
||||||
get() = ModuleOrigin.LIBRARY
|
get() = ModuleOrigin.LIBRARY
|
||||||
|
|||||||
+40
-14
@@ -27,7 +27,6 @@ import com.intellij.psi.util.CachedValuesManager
|
|||||||
import com.intellij.psi.util.PsiModificationTracker
|
import com.intellij.psi.util.PsiModificationTracker
|
||||||
import com.intellij.util.containers.SLRUCache
|
import com.intellij.util.containers.SLRUCache
|
||||||
import org.jetbrains.kotlin.analyzer.EmptyResolverForProject
|
import org.jetbrains.kotlin.analyzer.EmptyResolverForProject
|
||||||
import org.jetbrains.kotlin.idea.project.outOfBlockModificationCount
|
|
||||||
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
|
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
|
||||||
@@ -37,6 +36,7 @@ import org.jetbrains.kotlin.context.GlobalContextImpl
|
|||||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
||||||
import org.jetbrains.kotlin.idea.project.AnalyzerFacadeProvider
|
import org.jetbrains.kotlin.idea.project.AnalyzerFacadeProvider
|
||||||
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
|
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
|
||||||
|
import org.jetbrains.kotlin.idea.project.outOfBlockModificationCount
|
||||||
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
||||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||||
import org.jetbrains.kotlin.js.resolve.JsPlatform
|
import org.jetbrains.kotlin.js.resolve.JsPlatform
|
||||||
@@ -48,6 +48,7 @@ import org.jetbrains.kotlin.resolve.diagnostics.KotlinSuppressCache
|
|||||||
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
|
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||||
|
import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptyList
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.sumByLong
|
import org.jetbrains.kotlin.utils.addToStdlib.sumByLong
|
||||||
|
|
||||||
internal val LOG = Logger.getInstance(KotlinCacheService::class.java)
|
internal val LOG = Logger.getInstance(KotlinCacheService::class.java)
|
||||||
@@ -66,27 +67,47 @@ class KotlinCacheServiceImpl(val project: Project) : KotlinCacheService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: soft reference?
|
|
||||||
// TODO: cache by script definition?
|
private val facadesForScriptDependencies: SLRUCache<ComparableScriptDependencies, ProjectResolutionFacade> =
|
||||||
private val facadeForScriptDependencies by lazy {
|
object : SLRUCache<ComparableScriptDependencies, ProjectResolutionFacade>(2, 3) {
|
||||||
val globalContext = GlobalContext()
|
override fun createValue(key: ComparableScriptDependencies?): ProjectResolutionFacade {
|
||||||
ProjectResolutionFacade(
|
return createFacadeForScriptDependencies(ScriptDependenciesModuleInfo(project, key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFacadeForScriptDependencies(dependencies: ComparableScriptDependencies?) = synchronized(facadesForScriptDependencies) {
|
||||||
|
facadesForScriptDependencies.get(dependencies)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createFacadeForScriptDependencies(
|
||||||
|
dependenciesModuleInfo: ScriptDependenciesModuleInfo,
|
||||||
|
syntheticFiles: Collection<KtFile> = listOf()
|
||||||
|
): ProjectResolutionFacade {
|
||||||
|
val sdk = findJdk(dependenciesModuleInfo.dependencies, project)
|
||||||
|
val platform = JvmPlatform // TODO: Js scripts?
|
||||||
|
val sdkFacade = GlobalFacade(platform, sdk).facadeForSdk
|
||||||
|
val globalContext = sdkFacade.globalContext.contextWithNewLockAndCompositeExceptionTracker()
|
||||||
|
return ProjectResolutionFacade(
|
||||||
"facadeForScriptDependencies",
|
"facadeForScriptDependencies",
|
||||||
project, globalContext,
|
project, globalContext,
|
||||||
globalResolveSessionProvider(
|
globalResolveSessionProvider(
|
||||||
"dependencies of scripts",
|
"dependencies of scripts",
|
||||||
JvmPlatform, // TODO: Js scripts?
|
platform,
|
||||||
null, // TODO: provide sdk via dependencies
|
sdk,
|
||||||
allModules = listOf(ScriptDependenciesModuleInfo(project)),
|
reuseDataFrom = sdkFacade,
|
||||||
|
allModules = dependenciesModuleInfo.dependencies(),
|
||||||
|
//TODO: provide correct trackers
|
||||||
dependencies = listOf(
|
dependencies = listOf(
|
||||||
LibraryModificationTracker.getInstance(project), //TODO: provide correct trackers
|
LibraryModificationTracker.getInstance(project),
|
||||||
ProjectRootModificationTracker.getInstance(project)
|
ProjectRootModificationTracker.getInstance(project)
|
||||||
),
|
),
|
||||||
moduleFilter = { true }
|
moduleFilter = { it == dependenciesModuleInfo },
|
||||||
|
syntheticFiles = syntheticFiles
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private inner class GlobalFacade(platform: TargetPlatform, sdk: Sdk?) {
|
private inner class GlobalFacade(platform: TargetPlatform, sdk: Sdk?) {
|
||||||
private val sdkContext = GlobalContext()
|
private val sdkContext = GlobalContext()
|
||||||
val facadeForSdk = ProjectResolutionFacade(
|
val facadeForSdk = ProjectResolutionFacade(
|
||||||
@@ -96,6 +117,7 @@ class KotlinCacheServiceImpl(val project: Project) : KotlinCacheService {
|
|||||||
"sdk $sdk",
|
"sdk $sdk",
|
||||||
platform,
|
platform,
|
||||||
sdk,
|
sdk,
|
||||||
|
allModules = sdk?.let { SdkInfo(project, it) }.singletonOrEmptyList(),
|
||||||
moduleFilter = { it is SdkInfo },
|
moduleFilter = { it is SdkInfo },
|
||||||
dependencies = listOf(
|
dependencies = listOf(
|
||||||
LibraryModificationTracker.getInstance(project),
|
LibraryModificationTracker.getInstance(project),
|
||||||
@@ -192,15 +214,19 @@ class KotlinCacheServiceImpl(val project: Project) : KotlinCacheService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
syntheticFileModule is ScriptModuleInfo || syntheticFileModule is ScriptDependenciesModuleInfo -> {
|
syntheticFileModule is ScriptDependenciesModuleInfo -> {
|
||||||
|
createFacadeForScriptDependencies(syntheticFileModule, files)
|
||||||
|
}
|
||||||
|
syntheticFileModule is ScriptModuleInfo -> {
|
||||||
|
val facadeForScriptDependencies = getFacadeForScriptDependencies(syntheticFileModule.externalDependencies)
|
||||||
val globalContext = facadeForScriptDependencies.globalContext.contextWithNewLockAndCompositeExceptionTracker()
|
val globalContext = facadeForScriptDependencies.globalContext.contextWithNewLockAndCompositeExceptionTracker()
|
||||||
ProjectResolutionFacade(
|
ProjectResolutionFacade(
|
||||||
"facadeForSynthetic in ScriptModuleInfo",
|
"facadeForSynthetic in ScriptModuleInfo",
|
||||||
project, globalContext,
|
project, globalContext,
|
||||||
makeGlobalResolveSessionProvider(
|
makeGlobalResolveSessionProvider(
|
||||||
reuseDataFrom = facadeForScriptDependencies,
|
reuseDataFrom = facadeForScriptDependencies,
|
||||||
moduleFilter = { it == syntheticFileModule },
|
allModules = syntheticFileModule.dependencies(),
|
||||||
allModules = syntheticFileModule.dependencies()
|
moduleFilter = { it == syntheticFileModule }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ private fun getModuleInfoByVirtualFile(project: Project, virtualFile: VirtualFil
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (KotlinScriptConfigurationManager.getInstance(project).getAllScriptsClasspathScope().contains(virtualFile)) {
|
if (KotlinScriptConfigurationManager.getInstance(project).getAllScriptsClasspathScope().contains(virtualFile)) {
|
||||||
return ScriptDependenciesModuleInfo(project)
|
return ScriptDependenciesModuleInfo(project, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
return NotUnderContentRootModuleInfo
|
return NotUnderContentRootModuleInfo
|
||||||
|
|||||||
Reference in New Issue
Block a user