[IDE] Move library dependency filtering to dependencies cache

The primary client of LibraryDependenciesCache is LibraryInfo,
but it is also used for maintaining modification trackers of
source-dependent libraries. Moving dependency filtering to
cache allows keeping all client in sync.

^KT-45908 In Progress
This commit is contained in:
Pavel Kirpichenkov
2021-04-14 16:46:58 +03:00
parent dfc6d85aee
commit 71365d2452
10 changed files with 120 additions and 26 deletions
@@ -35,7 +35,6 @@ import org.jetbrains.kotlin.idea.KotlinIdeaAnalysisBundle
import org.jetbrains.kotlin.idea.caches.resolve.util.enlargedSearchScope
import org.jetbrains.kotlin.idea.caches.trackers.KotlinModuleOutOfCodeBlockModificationTracker
import org.jetbrains.kotlin.idea.configuration.BuildSystemType
import org.jetbrains.kotlin.idea.configuration.IdeBuiltInsLoadingState
import org.jetbrains.kotlin.idea.configuration.getBuildSystemType
import org.jetbrains.kotlin.idea.core.isInTestSourceContentKotlinAware
import org.jetbrains.kotlin.idea.framework.KotlinSdkType
@@ -353,25 +352,7 @@ abstract class LibraryInfo(override val project: Project, val library: Library)
val (libraries, sdks) = LibraryDependenciesCache.getInstance(project).getLibrariesAndSdksUsedWith(this)
result.addAll(sdks)
/*
* When built-ins are created from module dependencies (as opposed to loading them from classloader)
* we must resolve Kotlin standard library containing some of the built-ins declarations in the same
* resolver for project as JDK. This comes from the following requirements:
* - JvmBuiltins need JDK and standard library descriptors -> resolver for project should be able to
* resolve them
* - Builtins are created in BuiltinsCache -> module descriptors should be resolved under lock of the
* SDK resolver to prevent deadlocks
* This means we have to maintain dependencies of the standard library manually or effectively drop
* resolver for SDK otherwise. Libraries depend on superset of their actual dependencies because of
* the inability to get real dependencies from IDEA model. So moving stdlib with all dependencies
* down is a questionable option.
*/
if (!IdeBuiltInsLoadingState.isFromClassLoader && this.isCoreKotlinLibrary(project)) {
libraries.filterTo(result) { it.isCoreKotlinLibrary(project) }
} else {
result.addAll(libraries)
}
result.addAll(libraries)
return result.toList()
}
@@ -17,6 +17,7 @@ import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import com.intellij.util.containers.ContainerUtil
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.idea.configuration.IdeBuiltInsLoadingState
import org.jetbrains.kotlin.idea.core.util.CachedValue
import org.jetbrains.kotlin.idea.core.util.getValue
import org.jetbrains.kotlin.idea.project.isHMPPEnabled
@@ -93,7 +94,9 @@ class LibraryDependenciesCacheImpl(private val project: Project) : LibraryDepend
}, Unit)
}
return Pair(libraries.toList(), sdks.toList())
val filteredLibraries = filterForBuiltins(libraryInfo, libraries)
return Pair(filteredLibraries.toList(), sdks.toList())
}
/**
@@ -109,6 +112,27 @@ class LibraryDependenciesCacheImpl(private val project: Project) : LibraryDepend
}!!
}
/*
* When built-ins are created from module dependencies (as opposed to loading them from classloader)
* we must resolve Kotlin standard library containing some of the built-ins declarations in the same
* resolver for project as JDK. This comes from the following requirements:
* - JvmBuiltins need JDK and standard library descriptors -> resolver for project should be able to
* resolve them
* - Builtins are created in BuiltinsCache -> module descriptors should be resolved under lock of the
* SDK resolver to prevent deadlocks
* This means we have to maintain dependencies of the standard library manually or effectively drop
* resolver for SDK otherwise. Libraries depend on superset of their actual dependencies because of
* the inability to get real dependencies from IDEA model. So moving stdlib with all dependencies
* down is a questionable option.
*/
private fun filterForBuiltins(libraryInfo: LibraryInfo, dependencyLibraries: Set<LibraryInfo>): Set<LibraryInfo> {
return if (!IdeBuiltInsLoadingState.isFromClassLoader && libraryInfo.isCoreKotlinLibrary(project)) {
dependencyLibraries.filterTo(mutableSetOf()) { it.isCoreKotlinLibrary(project) }
} else {
dependencyLibraries
}
}
private inner class LibraryUsageIndex {
private val modulesLibraryIsUsedIn: MultiMap<Library, Module> = MultiMap.createSet()
@@ -13,6 +13,7 @@ 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.annotations.TestOnly
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.caches.project.cacheByClassInvalidatingOnRootModifications
import org.jetbrains.kotlin.idea.caches.project.LibraryDependenciesCache
@@ -60,6 +61,11 @@ class ResolutionAnchorCacheServiceImpl(val project: Project) :
XmlSerializerUtil.copyBean(state, myState)
}
@TestOnly
fun setAnchors(mapping: Map<String, String>) {
myState = State(mapping)
}
object ResolutionAnchorMappingCacheKey
object ResolutionAnchorDependenciesCacheKey
@@ -0,0 +1 @@
class LibraryClass
@@ -0,0 +1 @@
class DependencyClass
@@ -0,0 +1 @@
class SourceDependentClass
@@ -0,0 +1 @@
class Test1
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.idea.caches.resolve
import com.intellij.facet.FacetManager
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
@@ -20,15 +21,13 @@ import org.jetbrains.kotlin.analyzer.ResolverForModuleComputationTracker
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersion
import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo
import org.jetbrains.kotlin.idea.caches.project.SdkInfo
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
import org.jetbrains.kotlin.idea.caches.trackers.KotlinModuleOutOfCodeBlockModificationTracker
import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder
import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCompilerSettings
import org.jetbrains.kotlin.idea.completion.test.withServiceRegistered
import org.jetbrains.kotlin.idea.core.util.toPsiFile
import org.jetbrains.kotlin.idea.facet.KotlinFacetConfiguration
import org.jetbrains.kotlin.idea.facet.KotlinFacetType
import org.jetbrains.kotlin.idea.framework.JSLibraryKind
@@ -37,12 +36,17 @@ import org.jetbrains.kotlin.idea.test.allKotlinFiles
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.idea.util.projectStructure.sdk
import org.jetbrains.kotlin.idea.util.sourceRoots
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.samWithReceiver.SamWithReceiverCommandLineProcessor.Companion.ANNOTATION_OPTION
import org.jetbrains.kotlin.samWithReceiver.SamWithReceiverCommandLineProcessor.Companion.PLUGIN_ID
import org.jetbrains.kotlin.test.*
import org.jetbrains.kotlin.test.TestJdkKind.FULL_JDK
import org.junit.Assert.assertNotEquals
import org.junit.runner.RunWith
import java.util.concurrent.ThreadLocalRandom
import kotlin.math.absoluteValue
@WithMutedInDatabaseRunTest
@RunWith(JUnit3WithIdeaConfigurationRunner::class)
@@ -258,6 +262,49 @@ open class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() {
checkHighlightingInProject()
}
fun testResolutionAnchorsAndBuiltins() {
val jarForCompositeLibrary = MockLibraryUtilExt.compileJvmLibraryToJar(
sourcesPath = "$testDataPath${getTestName(true)}/compositeLibraryPart",
jarName = "compositeLibraryPart"
)
val stdlibJarForCompositeLibrary = ForTestCompileRuntime.runtimeJarForTests()
val jarForSourceDependentLibrary = MockLibraryUtilExt.compileJvmLibraryToJar(
sourcesPath = "$testDataPath${getTestName(true)}/sourceDependentLibrary",
jarName = "sourceDependentLibrary"
)
val dependencyModule = module("dependencyModule")
val anchorModule = module("anchor")
val sourceModule = module("sourceModule")
val sourceDependentLibraryName = "sourceDependentLibrary"
sourceModule.addMultiJarLibrary(listOf(stdlibJarForCompositeLibrary, jarForCompositeLibrary), "compositeLibrary")
sourceModule.addLibrary(jarForSourceDependentLibrary, sourceDependentLibraryName)
anchorModule.addDependency(dependencyModule)
val anchorMapping = mapOf(sourceDependentLibraryName to anchorModule.name)
withResolutionAnchors(anchorMapping) {
checkHighlightingInProject()
dependencyModule.modifyTheOnlySourceFile()
checkHighlightingInProject()
}
}
private fun Module.modifyTheOnlySourceFile() {
val sourceRoot = sourceRoots.singleOrNull() ?: error("Expected single source root in a test module")
assert(sourceRoot.isDirectory) { "Source root of a test module is not a directory" }
val ktFile = sourceRoot.children.singleOrNull()?.toPsiFile(project) as? KtFile
?: error("Expected single .kt file in a test source module")
val stubFunctionName = "fn${System.currentTimeMillis()}_${ThreadLocalRandom.current().nextInt().toLong().absoluteValue}"
WriteCommandAction.runWriteCommandAction(project) {
ktFile.add(
KtPsiFactory(project).createFunction("fun $stubFunctionName() {}")
)
}
}
private fun Module.setupKotlinFacet(configure: KotlinFacetConfiguration.() -> Unit) = apply {
runWriteAction {
val facet = FacetManager.getInstance(this).addFacet(KotlinFacetType.INSTANCE, KotlinFacetType.NAME, null)
@@ -27,15 +27,18 @@ import com.intellij.util.ThrowableRunnable
import org.jetbrains.kotlin.config.CompilerSettings
import org.jetbrains.kotlin.config.KotlinFacetSettings
import org.jetbrains.kotlin.config.KotlinFacetSettingsProvider
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionAnchorCacheService
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionAnchorCacheServiceImpl
import org.jetbrains.kotlin.idea.facet.getOrCreateFacet
import org.jetbrains.kotlin.idea.facet.initializeIfNeeded
import org.jetbrains.kotlin.idea.project.KotlinLibraryToSourceAnalysisComponent
import org.jetbrains.kotlin.idea.test.*
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.test.TestJdkKind
import org.jetbrains.kotlin.test.WithMutedInDatabaseRunTest
import org.jetbrains.kotlin.test.runTest
import org.jetbrains.kotlin.test.util.KtTestUtil
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.junit.Assert
import java.io.File
@@ -111,10 +114,19 @@ abstract class AbstractMultiModuleTest : DaemonAnalyzerTestCase() {
jar: File,
name: String = KotlinJdkAndLibraryProjectDescriptor.LIBRARY_NAME,
kind: PersistentLibraryKind<*>? = null
) = addMultiJarLibrary(listOf(jar), name, kind)
fun Module.addMultiJarLibrary(
jars: Collection<File>,
name: String = KotlinJdkAndLibraryProjectDescriptor.LIBRARY_NAME,
kind: PersistentLibraryKind<*>? = null,
) {
assert(jars.isNotEmpty()) { "No JARs passed for a library" }
ConfigLibraryUtil.addLibrary(NewLibraryEditor().apply {
this.name = name
addRoot(VfsUtil.getUrlForLibraryRoot(jar), OrderRootType.CLASSES)
for (jar in jars) {
addRoot(VfsUtil.getUrlForLibraryRoot(jar), OrderRootType.CLASSES)
}
}, this, kind)
}
@@ -149,6 +161,26 @@ abstract class AbstractMultiModuleTest : DaemonAnalyzerTestCase() {
}
Assert.assertTrue(atLeastOneFile)
}
protected fun withResolutionAnchors(
anchors: Map<String, String>,
block: () -> Unit
) {
val resolutionAnchorService = ResolutionAnchorCacheService.getInstance(project).safeAs<ResolutionAnchorCacheServiceImpl>()
?: error("Anchor service missing")
val oldResolutionAnchorMappingState = resolutionAnchorService.state
val oldLibraryToSourceAnalysisState = KotlinLibraryToSourceAnalysisComponent.isEnabled(project)
resolutionAnchorService.setAnchors(anchors)
KotlinLibraryToSourceAnalysisComponent.setState(project, isEnabled = true)
try {
block()
} finally {
KotlinLibraryToSourceAnalysisComponent.setState(project, isEnabled = oldLibraryToSourceAnalysisState)
resolutionAnchorService.loadState(oldResolutionAnchorMappingState)
}
}
}
fun Module.createFacet(