Introduce ModuleInfo -> SdkInfo cache

This fixes KT-35050 properly by caching this mapping and preventing
inspecting dependencies multiple times.
This commit is contained in:
Dmitry Savvinov
2020-03-19 18:23:27 +03:00
parent be37e7135a
commit 7d5396ac95
7 changed files with 79 additions and 16 deletions
@@ -16,7 +16,6 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.jvm.JvmBuiltIns
import org.jetbrains.kotlin.context.ProjectContext
import org.jetbrains.kotlin.idea.caches.project.SdkInfo
import org.jetbrains.kotlin.idea.caches.project.findSdkAcrossDependencies
import org.jetbrains.kotlin.idea.caches.resolve.BuiltInsCacheKey
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.impl.JvmIdePlatformKind
@@ -65,6 +65,8 @@ interface IdeaModuleInfo : org.jetbrains.kotlin.idea.caches.resolve.IdeaModuleIn
val moduleOrigin: ModuleOrigin
val project: Project?
override val capabilities: Map<ModuleDescriptor.Capability<*>, Any?>
get() = super.capabilities + mapOf(OriginCapability to moduleOrigin)
@@ -156,6 +158,9 @@ interface ModuleSourceInfo : IdeaModuleInfo, TrackableModuleInfo {
override val moduleOrigin: ModuleOrigin
get() = ModuleOrigin.MODULE
override val project: Project
get() = module.project
override val platform: TargetPlatform
get() = TargetPlatformDetector.getPlatform(module)
@@ -284,7 +289,7 @@ private class ModuleTestSourceScope(module: Module) : ModuleSourceScope(module)
override fun toString() = "ModuleTestSourceScope($module)"
}
open class LibraryInfo(val project: Project, val library: Library) : IdeaModuleInfo, LibraryModuleInfo, BinaryModuleInfo {
open class LibraryInfo(override val project: Project, val library: Library) : IdeaModuleInfo, LibraryModuleInfo, BinaryModuleInfo {
override val moduleOrigin: ModuleOrigin
get() = ModuleOrigin.LIBRARY
@@ -329,7 +334,7 @@ open class LibraryInfo(val project: Project, val library: Library) : IdeaModuleI
}
data class LibrarySourceInfo(val project: Project, val library: Library, override val binariesModuleInfo: BinaryModuleInfo) :
data class LibrarySourceInfo(override val project: Project, val library: Library, override val binariesModuleInfo: BinaryModuleInfo) :
IdeaModuleInfo, SourceForBinaryModuleInfo {
override val name: Name = Name.special(KotlinIdeaAnalysisBundle.message("sources.for.library.0", library.name.toString()))
@@ -355,7 +360,7 @@ data class LibrarySourceInfo(val project: Project, val library: Library, overrid
}
//TODO: (module refactoring) there should be separate SdkSourceInfo but there are no kotlin source in existing sdks for now :)
data class SdkInfo(val project: Project, val sdk: Sdk) : IdeaModuleInfo {
data class SdkInfo(override val project: Project, val sdk: Sdk) : IdeaModuleInfo {
override val moduleOrigin: ModuleOrigin
get() = ModuleOrigin.LIBRARY
@@ -378,6 +383,9 @@ object NotUnderContentRootModuleInfo : IdeaModuleInfo {
override val name: Name = Name.special(KotlinIdeaAnalysisBundle.message("special.module.for.files.not.under.source.root"))
override val project: Project?
get() = null
override fun contentScope() = GlobalSearchScope.EMPTY_SCOPE
//TODO: (module refactoring) dependency on runtime can be of use here
@@ -481,6 +489,9 @@ data class PlatformModuleInfo(
override val containedModules: List<ModuleSourceInfo> = listOf(platformModule) + commonModules
override val project: Project
get() = platformModule.module.project
override val platform: TargetPlatform
get() = platformModule.platform
@@ -22,7 +22,7 @@ import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatformAnalyzerServices
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
data class ScriptModuleInfo(
val project: Project,
override val project: Project,
val scriptFile: VirtualFile,
val scriptDefinition: ScriptDefinition
) : IdeaModuleInfo {
@@ -59,7 +59,7 @@ data class ScriptModuleInfo(
get() = JvmPlatformAnalyzerServices
}
sealed class ScriptDependenciesInfo(val project: Project) : IdeaModuleInfo, BinaryModuleInfo {
sealed class ScriptDependenciesInfo(override val project: Project) : IdeaModuleInfo, BinaryModuleInfo {
abstract val sdk: Sdk?
override val name = Name.special(KotlinIdeaAnalysisBundle.message("script.dependencies"))
@@ -118,7 +118,7 @@ sealed class ScriptDependenciesInfo(val project: Project) : IdeaModuleInfo, Bina
}
}
sealed class ScriptDependenciesSourceInfo(val project: Project) : IdeaModuleInfo, SourceForBinaryModuleInfo {
sealed class ScriptDependenciesSourceInfo(override val project: Project) : IdeaModuleInfo, SourceForBinaryModuleInfo {
override val name = Name.special(KotlinIdeaAnalysisBundle.message("source.for.script.dependencies"))
override val binariesModuleInfo: ScriptDependenciesInfo
@@ -0,0 +1,52 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.caches.project
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.util.containers.ConcurrentWeakKeySoftValueHashMap
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.caches.project.cacheInvalidatingOnRootModifications
/**
* Maintains and caches mapping ModuleInfo -> SdkInfo *form its dependencies*
* (note that this SDK might be different from Project SDK)
*
* Cache is needed because one and the same library might (and usually does)
* participate as dependency in several modules, so if someone queries a lot
* of modules for their SDKs (ex.: determine built-ins for each module in a
* project), we end up inspecting one and the same dependency multiple times.
*
* With projects with abnormally high amount of dependencies this might lead
* to performance issues.
*/
interface SdkInfoCache {
fun findOrGetCachedSdk(moduleInfo: ModuleInfo): SdkInfo?
companion object {
fun getInstance(project: Project): SdkInfoCache =
ServiceManager.getService(project, SdkInfoCache::class.java)
}
}
class SdkInfoCacheImpl(private val project: Project) : SdkInfoCache {
private val cache: MutableMap<ModuleInfo, SdkInfo?>
// Note: it's ok to use thread-unsafe map, because 'doFindSdk' is pure
get() = project.cacheInvalidatingOnRootModifications { mutableMapOf() }
override fun findOrGetCachedSdk(moduleInfo: ModuleInfo): SdkInfo? {
if (!cache.containsKey(moduleInfo)) {
cache[moduleInfo] = doFindSdk(moduleInfo)
}
return cache[moduleInfo]
}
private fun doFindSdk(moduleInfo: ModuleInfo): SdkInfo? =
moduleInfo.dependencies().lazyClosure { it.dependencies() }.firstOrNull { it is SdkInfo } as SdkInfo?
}
@@ -17,6 +17,7 @@ import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
import org.jetbrains.kotlin.asJava.elements.KtLightElement
import org.jetbrains.kotlin.caches.project.cacheByClassInvalidatingOnRootModifications
import org.jetbrains.kotlin.caches.project.cacheInvalidatingOnRootModifications
import org.jetbrains.kotlin.idea.caches.lightClasses.KtLightClassForDecompiledDeclaration
import org.jetbrains.kotlin.idea.core.isInTestSourceContentKotlinAware
@@ -43,16 +44,10 @@ fun PsiElement.getNullableModuleInfo(): IdeaModuleInfo? = this.collectInfos(Modu
fun PsiElement.getModuleInfos(): Sequence<IdeaModuleInfo> = this.collectInfos(ModuleInfoCollector.ToSequence)
private fun ModuleInfo.doFindSdk(): SdkInfo? = dependencies().lazyClosure { it.dependencies() }.firstIsInstanceOrNull<SdkInfo>()
fun ModuleInfo.findSdkAcrossDependencies(): SdkInfo? {
return when (this) {
// It is important to check for cases of test/production explicitly, because we're using lambda's class
// of 'cacheInvalidatingOnRootModifications' as key for cache, and it should be different for Production/Source
is ModuleProductionSourceInfo -> module.cacheInvalidatingOnRootModifications { doFindSdk() }
is ModuleTestSourceInfo -> module.cacheInvalidatingOnRootModifications { doFindSdk() }
else -> doFindSdk()
}
val project = (this as? IdeaModuleInfo)?.project ?: return null
return SdkInfoCache.getInstance(project).findOrGetCachedSdk(this)
}
fun getModuleInfoByVirtualFile(project: Project, virtualFile: VirtualFile): IdeaModuleInfo? =
@@ -362,6 +362,9 @@
<projectService serviceInterface="org.jetbrains.kotlin.idea.caches.project.LibraryDependenciesCache"
serviceImplementation="org.jetbrains.kotlin.idea.caches.project.LibraryDependenciesCacheImpl"/>
<projectService serviceInterface="org.jetbrains.kotlin.idea.caches.project.SdkInfoCache"
serviceImplementation="org.jetbrains.kotlin.idea.caches.project.SdkInfoCacheImpl"/>
<projectService serviceInterface="org.jetbrains.kotlin.idea.caches.resolve.IdePackageOracleFactory"
serviceImplementation="org.jetbrains.kotlin.idea.caches.resolve.IdePackageOracleFactory"/>
@@ -371,6 +371,9 @@
<projectService serviceInterface="org.jetbrains.kotlin.idea.caches.project.LibraryDependenciesCache"
serviceImplementation="org.jetbrains.kotlin.idea.caches.project.LibraryDependenciesCacheImpl"/>
<projectService serviceInterface="org.jetbrains.kotlin.idea.caches.project.SdkInfoCache"
serviceImplementation="org.jetbrains.kotlin.idea.caches.project.SdkInfoCacheImpl"/>
<projectService serviceInterface="org.jetbrains.kotlin.idea.caches.resolve.IdePackageOracleFactory"
serviceImplementation="org.jetbrains.kotlin.idea.caches.resolve.IdePackageOracleFactory"/>