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.impl.scopes.LibraryScopeBase
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.ProjectJdkTable
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.roots.*
|
||||
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.psi.search.DelegatingGlobalSearchScope
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.search.NonClasspathDirectoriesScope
|
||||
import com.intellij.psi.util.CachedValueProvider
|
||||
import com.intellij.psi.util.CachedValuesManager
|
||||
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.name.Name
|
||||
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.emptyOrSingletonList
|
||||
import org.jetbrains.kotlin.utils.singletonOrEmptyList
|
||||
import java.io.File
|
||||
import java.lang.reflect.Method
|
||||
import java.util.*
|
||||
|
||||
@@ -296,21 +302,78 @@ data class ScriptModuleInfo(val project: Project, val scriptFile: VirtualFile,
|
||||
override val moduleOrigin: ModuleOrigin
|
||||
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 fun contentScope() = GlobalSearchScope.fileScope(project, scriptFile)
|
||||
|
||||
// TODO: only dependencies of this particular script
|
||||
override fun dependencies() = listOf(this, ScriptDependenciesModuleInfo(project))
|
||||
override fun dependencies(): List<IdeaModuleInfo> {
|
||||
return listOf(
|
||||
this,
|
||||
ScriptDependenciesModuleInfo(project, externalDependencies)
|
||||
) + sdkDependencies(externalDependencies, project)
|
||||
}
|
||||
}
|
||||
|
||||
data class ScriptDependenciesModuleInfo(val project: Project): IdeaModuleInfo {
|
||||
override fun dependencies() = listOf(this)
|
||||
private fun sdkDependencies(scriptDependencies: KotlinScriptExternalDependencies?, project: Project): List<SdkInfo>
|
||||
= 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>")
|
||||
|
||||
// TODO: this is not very efficient because KotlinSourceFilterScope already checks if the files are in scripts classpath
|
||||
override fun contentScope() = KotlinSourceFilterScope.libraryClassFiles(KotlinScriptConfigurationManager.getInstance(project).getAllScriptsClasspathScope(), project)
|
||||
override fun contentScope(): GlobalSearchScope {
|
||||
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
|
||||
get() = ModuleOrigin.LIBRARY
|
||||
|
||||
+40
-14
@@ -27,7 +27,6 @@ import com.intellij.psi.util.CachedValuesManager
|
||||
import com.intellij.psi.util.PsiModificationTracker
|
||||
import com.intellij.util.containers.SLRUCache
|
||||
import org.jetbrains.kotlin.analyzer.EmptyResolverForProject
|
||||
import org.jetbrains.kotlin.idea.project.outOfBlockModificationCount
|
||||
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
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.idea.project.AnalyzerFacadeProvider
|
||||
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.util.ProjectRootsUtil
|
||||
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.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptyList
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.sumByLong
|
||||
|
||||
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 facadeForScriptDependencies by lazy {
|
||||
val globalContext = GlobalContext()
|
||||
ProjectResolutionFacade(
|
||||
|
||||
private val facadesForScriptDependencies: SLRUCache<ComparableScriptDependencies, ProjectResolutionFacade> =
|
||||
object : SLRUCache<ComparableScriptDependencies, ProjectResolutionFacade>(2, 3) {
|
||||
override fun createValue(key: ComparableScriptDependencies?): 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",
|
||||
project, globalContext,
|
||||
globalResolveSessionProvider(
|
||||
"dependencies of scripts",
|
||||
JvmPlatform, // TODO: Js scripts?
|
||||
null, // TODO: provide sdk via dependencies
|
||||
allModules = listOf(ScriptDependenciesModuleInfo(project)),
|
||||
platform,
|
||||
sdk,
|
||||
reuseDataFrom = sdkFacade,
|
||||
allModules = dependenciesModuleInfo.dependencies(),
|
||||
//TODO: provide correct trackers
|
||||
dependencies = listOf(
|
||||
LibraryModificationTracker.getInstance(project), //TODO: provide correct trackers
|
||||
LibraryModificationTracker.getInstance(project),
|
||||
ProjectRootModificationTracker.getInstance(project)
|
||||
),
|
||||
moduleFilter = { true }
|
||||
moduleFilter = { it == dependenciesModuleInfo },
|
||||
syntheticFiles = syntheticFiles
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private inner class GlobalFacade(platform: TargetPlatform, sdk: Sdk?) {
|
||||
private val sdkContext = GlobalContext()
|
||||
val facadeForSdk = ProjectResolutionFacade(
|
||||
@@ -96,6 +117,7 @@ class KotlinCacheServiceImpl(val project: Project) : KotlinCacheService {
|
||||
"sdk $sdk",
|
||||
platform,
|
||||
sdk,
|
||||
allModules = sdk?.let { SdkInfo(project, it) }.singletonOrEmptyList(),
|
||||
moduleFilter = { it is SdkInfo },
|
||||
dependencies = listOf(
|
||||
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()
|
||||
ProjectResolutionFacade(
|
||||
"facadeForSynthetic in ScriptModuleInfo",
|
||||
project, globalContext,
|
||||
makeGlobalResolveSessionProvider(
|
||||
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)) {
|
||||
return ScriptDependenciesModuleInfo(project)
|
||||
return ScriptDependenciesModuleInfo(project, null)
|
||||
}
|
||||
|
||||
return NotUnderContentRootModuleInfo
|
||||
|
||||
Reference in New Issue
Block a user