Support modification tracking for source-dependent LibraryInfo
#KT-39734
This commit is contained in:
@@ -40,10 +40,7 @@ import org.jetbrains.kotlin.idea.core.isInTestSourceContentKotlinAware
|
||||
import org.jetbrains.kotlin.idea.framework.effectiveKind
|
||||
import org.jetbrains.kotlin.idea.framework.platform
|
||||
import org.jetbrains.kotlin.idea.klib.AbstractKlibLibraryInfo
|
||||
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
|
||||
import org.jetbrains.kotlin.idea.project.findAnalyzerServices
|
||||
import org.jetbrains.kotlin.idea.project.getStableName
|
||||
import org.jetbrains.kotlin.idea.project.isHMPPEnabled
|
||||
import org.jetbrains.kotlin.idea.project.*
|
||||
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
||||
import org.jetbrains.kotlin.idea.util.isInSourceContentWithoutInjected
|
||||
import org.jetbrains.kotlin.idea.util.rootManager
|
||||
@@ -336,7 +333,9 @@ private class ModuleTestSourceScope(module: Module) : ModuleSourceScope(module)
|
||||
override fun toString() = "ModuleTestSourceScope($module)"
|
||||
}
|
||||
|
||||
abstract class LibraryInfo(override val project: Project, val library: Library) : IdeaModuleInfo, LibraryModuleInfo, BinaryModuleInfo {
|
||||
abstract class LibraryInfo(override val project: Project, val library: Library) :
|
||||
IdeaModuleInfo, LibraryModuleInfo, BinaryModuleInfo, TrackableModuleInfo {
|
||||
|
||||
override val moduleOrigin: ModuleOrigin
|
||||
get() = ModuleOrigin.LIBRARY
|
||||
|
||||
@@ -370,6 +369,13 @@ abstract class LibraryInfo(override val project: Project, val library: Library)
|
||||
override fun getLibraryRoots(): Collection<String> =
|
||||
library.getFiles(OrderRootType.CLASSES).mapNotNull(PathUtil::getLocalPath)
|
||||
|
||||
override fun createModificationTracker(): ModificationTracker {
|
||||
if (!project.libraryToSourceAnalysisEnabled)
|
||||
return ModificationTracker.NEVER_CHANGED
|
||||
|
||||
return ResolutionAnchorAwareLibraryModificationTracker(this)
|
||||
}
|
||||
|
||||
override fun toString() = "${this::class.simpleName}(libraryName=${library.name}, libraryRoots=${getLibraryRoots()})"
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.util.ModificationTracker
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionAnchorCacheService
|
||||
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
|
||||
|
||||
class ResolutionAnchorAwareLibraryModificationTracker(
|
||||
private val libraryInfo: LibraryInfo,
|
||||
) : ModificationTracker {
|
||||
override fun getModificationCount(): Long {
|
||||
val anchorDependencies = ResolutionAnchorCacheService.getInstance(libraryInfo.project).getDependencyResolutionAnchors(libraryInfo)
|
||||
val updaterForModules = KotlinCodeBlockModificationListener.getInstance(libraryInfo.project).perModuleOutOfCodeBlockTrackerUpdater
|
||||
return anchorDependencies.maxOfOrNull { updaterForModules.getModificationCount(it.module) } ?: 0
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.module.Module
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.ModuleRootManager
|
||||
import com.intellij.util.CommonProcessors
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionAnchorCacheService
|
||||
import org.jetbrains.kotlin.idea.caches.trackers.ModuleDependencyProviderExtension
|
||||
import org.jetbrains.kotlin.idea.project.libraryToSourceAnalysisEnabled
|
||||
|
||||
class ResolutionAnchorModuleDependencyProviderExtension(private val project: Project) : ModuleDependencyProviderExtension {
|
||||
/**
|
||||
* Consider modules M1, M2, M3, library L1 resolving via Resolution anchor M2, other libraries L2, L3 with the following dependencies:
|
||||
* M2 depends on M1
|
||||
* L1 depends on anchor M2
|
||||
* L2 depends on L1
|
||||
* L3 depends on L2
|
||||
* M3 depends on L3
|
||||
* Then modification of M1 should lead to complete invalidation of all modules and libraries in this example.
|
||||
*
|
||||
* Updates for libraries aren't managed here, corresponding ModificationTracker is responsible for that.
|
||||
* This extension provides missing dependencies from source-dependent library dependencies only to source modules.
|
||||
*/
|
||||
override fun getAdditionalDependencyModules(module: Module): Collection<Module> {
|
||||
if (!project.libraryToSourceAnalysisEnabled) return emptySet()
|
||||
|
||||
val resolutionAnchorDependencies = HashSet<ModuleSourceInfo>()
|
||||
ModuleRootManager.getInstance(module).orderEntries().recursively().forEachLibrary { library ->
|
||||
getIdeaModelInfosCache(project).getLibraryInfosForLibrary(library).flatMapTo(resolutionAnchorDependencies) { libraryInfo ->
|
||||
ResolutionAnchorCacheService.getInstance(project).getDependencyResolutionAnchors(libraryInfo)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
val additionalModules = HashSet<Module>()
|
||||
for (anchorModule in resolutionAnchorDependencies) {
|
||||
ModuleRootManager.getInstance(anchorModule.module).orderEntries().recursively()
|
||||
.forEachModule(CommonProcessors.CollectProcessor(additionalModules))
|
||||
}
|
||||
return additionalModules
|
||||
}
|
||||
}
|
||||
+2
-47
@@ -28,58 +28,13 @@ import org.jetbrains.kotlin.resolve.ResolutionAnchorProvider
|
||||
* manually for the libraries in project via resolution anchors. Anchor by itself is a source module which is mapped
|
||||
* to a library and used during resolution as a fallback.
|
||||
*/
|
||||
@State(name = "KotlinIdeAnchorService", storages = [Storage("anchors.xml")])
|
||||
class KotlinIdeResolutionAnchorService(
|
||||
val project: Project
|
||||
) : ResolutionAnchorProvider,
|
||||
PersistentStateComponent<KotlinIdeResolutionAnchorService.State> {
|
||||
|
||||
data class State(
|
||||
var moduleNameToAnchorName: Map<String, String> = emptyMap()
|
||||
)
|
||||
|
||||
private val logger = logger<KotlinIdeResolutionAnchorService>()
|
||||
|
||||
@JvmField
|
||||
@Volatile
|
||||
var myState: State = State()
|
||||
|
||||
private fun buildMapping(): Map<ModuleInfo, ModuleInfo> {
|
||||
val modulesByNames = getModuleInfosFromIdeaModel(project).associateBy { moduleInfo ->
|
||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
|
||||
when (moduleInfo) {
|
||||
is LibraryInfo -> moduleInfo.library.name
|
||||
is ModuleSourceInfo -> moduleInfo.module.name
|
||||
else -> moduleInfo.name.asString()
|
||||
}
|
||||
}
|
||||
|
||||
return myState.moduleNameToAnchorName.entries.mapNotNull { (moduleName, anchorName) ->
|
||||
val module = modulesByNames[moduleName] ?: return@mapNotNull notFoundModule(moduleName)
|
||||
val anchor = modulesByNames[anchorName] ?: return@mapNotNull notFoundModule(moduleName)
|
||||
module to anchor
|
||||
}.toMap()
|
||||
}
|
||||
|
||||
private fun notFoundModule(moduleName: String): Nothing? {
|
||||
logger.warn("Module <${moduleName}> not found in project model")
|
||||
return null
|
||||
}
|
||||
|
||||
private val moduleToAnchor: Map<ModuleInfo, ModuleInfo>
|
||||
get() = project.cacheInvalidatingOnRootModifications {
|
||||
buildMapping()
|
||||
}
|
||||
|
||||
override fun getState(): State = myState
|
||||
|
||||
override fun loadState(state: State) {
|
||||
XmlSerializerUtil.copyBean(state, myState)
|
||||
}
|
||||
|
||||
) : ResolutionAnchorProvider {
|
||||
override fun getResolutionAnchor(moduleDescriptor: ModuleDescriptor): ModuleDescriptor? {
|
||||
if (!project.libraryToSourceAnalysisEnabled) return null
|
||||
|
||||
val moduleToAnchor = ResolutionAnchorCacheService.getInstance(project).resolutionAnchorsForLibraries
|
||||
val moduleInfo = moduleDescriptor.moduleInfo ?: return null
|
||||
val keyModuleInfo = if (moduleInfo is SourceForBinaryModuleInfo) moduleInfo.binariesModuleInfo else moduleInfo
|
||||
val mapped = moduleToAnchor[keyModuleInfo] ?: return null
|
||||
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.resolve
|
||||
|
||||
import com.intellij.openapi.components.PersistentStateComponent
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.components.State
|
||||
import com.intellij.openapi.components.Storage
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import com.intellij.util.xmlb.XmlSerializerUtil
|
||||
import org.jetbrains.kotlin.analyzer.ModuleInfo
|
||||
import org.jetbrains.kotlin.caches.project.cacheByClassInvalidatingOnRootModifications
|
||||
import org.jetbrains.kotlin.idea.caches.project.LibraryDependenciesCache
|
||||
import org.jetbrains.kotlin.idea.caches.project.LibraryInfo
|
||||
import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo
|
||||
import org.jetbrains.kotlin.idea.caches.project.getModuleInfosFromIdeaModel
|
||||
import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus
|
||||
import org.jetbrains.kotlin.types.typeUtil.closure
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.cast
|
||||
|
||||
interface ResolutionAnchorCacheService {
|
||||
val resolutionAnchorsForLibraries: Map<LibraryInfo, ModuleSourceInfo>
|
||||
|
||||
fun getDependencyResolutionAnchors(libraryInfo: LibraryInfo): Set<ModuleSourceInfo>
|
||||
|
||||
companion object {
|
||||
val Default = object : ResolutionAnchorCacheService {
|
||||
override val resolutionAnchorsForLibraries: Map<LibraryInfo, ModuleSourceInfo>
|
||||
get() = emptyMap()
|
||||
|
||||
override fun getDependencyResolutionAnchors(libraryInfo: LibraryInfo): Set<ModuleSourceInfo> = emptySet()
|
||||
}
|
||||
|
||||
fun getInstance(project: Project): ResolutionAnchorCacheService =
|
||||
ServiceManager.getService(project, ResolutionAnchorCacheService::class.java) ?: Default
|
||||
}
|
||||
}
|
||||
|
||||
@State(name = "KotlinIdeAnchorService", storages = [Storage("anchors.xml")])
|
||||
class ResolutionAnchorCacheServiceImpl(val project: Project) :
|
||||
ResolutionAnchorCacheService,
|
||||
PersistentStateComponent<ResolutionAnchorCacheServiceImpl.State> {
|
||||
|
||||
data class State(
|
||||
var moduleNameToAnchorName: Map<String, String> = emptyMap()
|
||||
)
|
||||
|
||||
private val logger = logger<ResolutionAnchorCacheServiceImpl>()
|
||||
|
||||
@JvmField
|
||||
@Volatile
|
||||
var myState: State = State()
|
||||
|
||||
override fun getState(): State = myState
|
||||
|
||||
override fun loadState(state: State) {
|
||||
XmlSerializerUtil.copyBean(state, myState)
|
||||
}
|
||||
|
||||
object ResolutionAnchorMappingCacheKey
|
||||
object ResolutionAnchorDependenciesCacheKey
|
||||
|
||||
override val resolutionAnchorsForLibraries: Map<LibraryInfo, ModuleSourceInfo> by lazy {
|
||||
project.cacheByClassInvalidatingOnRootModifications(ResolutionAnchorMappingCacheKey::class.java) {
|
||||
mapResolutionAnchorForLibraries()
|
||||
}
|
||||
}
|
||||
|
||||
private val resolutionAnchorDependenciesCache: MutableMap<LibraryInfo, Set<ModuleSourceInfo>> =
|
||||
project.cacheByClassInvalidatingOnRootModifications(ResolutionAnchorDependenciesCacheKey::class.java) {
|
||||
ContainerUtil.createConcurrentWeakMap()
|
||||
}
|
||||
|
||||
override fun getDependencyResolutionAnchors(libraryInfo: LibraryInfo): Set<ModuleSourceInfo> {
|
||||
return resolutionAnchorDependenciesCache.getOrPut(libraryInfo) {
|
||||
val allTransitiveLibraryDependencies = with(LibraryDependenciesCache.getInstance(project)) {
|
||||
val (directDependenciesOnLibraries, _) = getLibrariesAndSdksUsedWith(libraryInfo)
|
||||
directDependenciesOnLibraries.closure { libraryDependency ->
|
||||
getLibrariesAndSdksUsedWith(libraryDependency).first
|
||||
}
|
||||
}
|
||||
allTransitiveLibraryDependencies.mapNotNull { resolutionAnchorsForLibraries[it] }.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
private fun associateModulesByNames(): Map<String, ModuleInfo> {
|
||||
return getModuleInfosFromIdeaModel(project).associateBy { moduleInfo ->
|
||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
|
||||
when (moduleInfo) {
|
||||
is LibraryInfo -> moduleInfo.library.name ?: "" // TODO: when does library have no name?
|
||||
is ModuleSourceInfo -> moduleInfo.module.name
|
||||
else -> moduleInfo.name.asString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapResolutionAnchorForLibraries(): Map<LibraryInfo, ModuleSourceInfo> {
|
||||
val modulesByNames: Map<String, ModuleInfo> = associateModulesByNames()
|
||||
|
||||
return myState.moduleNameToAnchorName.entries.mapNotNull { (libraryName, anchorName) ->
|
||||
val library: LibraryInfo = modulesByNames[libraryName]?.takeIf { it is LibraryInfo }?.cast()
|
||||
?: run {
|
||||
logger.warn("Resolution anchor mapping key doesn't point to a known library: $libraryName. Skipping this anchor")
|
||||
return@mapNotNull null
|
||||
}
|
||||
val anchor: ModuleSourceInfo = modulesByNames[anchorName]?.takeIf { it is ModuleSourceInfo }?.cast()
|
||||
?: run {
|
||||
logger.warn("Resolution anchor mapping value doesn't point to a source module: $anchorName. Skipping this anchor")
|
||||
return@mapNotNull null
|
||||
}
|
||||
|
||||
library to anchor
|
||||
}.toMap()
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -50,7 +50,8 @@ abstract class KotlinCodeBlockModificationListenerCompat(protected val project:
|
||||
|
||||
lateinit var kotlinOutOfCodeBlockTracker: ModificationTracker
|
||||
|
||||
internal val perModuleOutOfCodeBlockTrackerUpdater = KotlinModuleOutOfCodeBlockModificationTracker.Updater(project)
|
||||
// TODO: close back exposed API
|
||||
val perModuleOutOfCodeBlockTrackerUpdater = KotlinModuleOutOfCodeBlockModificationTracker.Updater(project)
|
||||
|
||||
protected fun init(
|
||||
treeAspect: TreeAspect,
|
||||
|
||||
+7
-2
@@ -32,6 +32,9 @@ class KotlinModuleOutOfCodeBlockModificationTracker private constructor(private
|
||||
ModuleRootManager.getInstance(module).orderEntries().recursively().forEachModule(
|
||||
CommonProcessors.CollectProcessor(resultModuleSet)
|
||||
)
|
||||
resultModuleSet.addAll(
|
||||
ModuleDependencyProviderExtension.getInstance(module.project).getAdditionalDependencyModules(module)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,7 +68,8 @@ class KotlinModuleOutOfCodeBlockModificationTracker private constructor(private
|
||||
}
|
||||
}
|
||||
|
||||
internal class Updater(project: Project) {
|
||||
// TODO: close back exposed API
|
||||
class Updater(project: Project) {
|
||||
private val kotlinOfOfCodeBlockTracker by lazy {
|
||||
KotlinCodeBlockModificationListener.getInstance(project).kotlinOutOfCodeBlockTracker
|
||||
}
|
||||
@@ -80,7 +84,8 @@ class KotlinModuleOutOfCodeBlockModificationTracker private constructor(private
|
||||
// perModuleModCount map
|
||||
private var perModuleChangesHighWatermark: Long? = null
|
||||
|
||||
internal fun getModificationCount(module: Module): Long {
|
||||
// TODO: close back exposed API
|
||||
fun getModificationCount(module: Module): Long {
|
||||
return perModuleModCount[module] ?: perModuleChangesHighWatermark ?: kotlinOfOfCodeBlockTracker.modificationCount
|
||||
}
|
||||
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.trackers
|
||||
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.project.Project
|
||||
|
||||
interface ModuleDependencyProviderExtension {
|
||||
fun getAdditionalDependencyModules(module: Module): Collection<Module>
|
||||
|
||||
companion object {
|
||||
val Default = object : ModuleDependencyProviderExtension {
|
||||
override fun getAdditionalDependencyModules(module: Module): Collection<Module> = emptySet()
|
||||
}
|
||||
|
||||
fun getInstance(project: Project): ModuleDependencyProviderExtension {
|
||||
return ServiceManager.getService(project, ModuleDependencyProviderExtension::class.java) ?: Default
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,9 +176,15 @@
|
||||
<projectService serviceInterface="org.jetbrains.kotlin.idea.util.ImportInsertHelper"
|
||||
serviceImplementation="org.jetbrains.kotlin.idea.util.ImportInsertHelperImpl"/>
|
||||
|
||||
<projectService serviceInterface="org.jetbrains.kotlin.idea.caches.resolve.ResolutionAnchorCacheService"
|
||||
serviceImplementation="org.jetbrains.kotlin.idea.caches.resolve.ResolutionAnchorCacheServiceImpl"/>
|
||||
|
||||
<projectService serviceInterface="org.jetbrains.kotlin.resolve.ResolutionAnchorProvider"
|
||||
serviceImplementation="org.jetbrains.kotlin.idea.caches.resolve.KotlinIdeResolutionAnchorService"/>
|
||||
|
||||
<projectService serviceInterface="org.jetbrains.kotlin.idea.caches.trackers.ModuleDependencyProviderExtension"
|
||||
serviceImplementation="org.jetbrains.kotlin.idea.caches.project.ResolutionAnchorModuleDependencyProviderExtension"/>
|
||||
|
||||
<projectService serviceImplementation="org.jetbrains.kotlin.idea.project.KotlinLibraryToSourceAnalysisStateComponent"/>
|
||||
|
||||
<projectService serviceInterface="org.jetbrains.kotlin.load.kotlin.MetadataFinderFactory"
|
||||
|
||||
Reference in New Issue
Block a user