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:
Pavel V. Talanov
2016-07-14 17:06:36 +03:00
committed by Ilya Chernikov
parent 350abbe8cd
commit a8416035b1
3 changed files with 110 additions and 21 deletions
@@ -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
@@ -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