mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IJPL-930 Implement ModuleExportedDependenciesGraph
GitOrigin-RevId: 02a6758a0d6bff6e0021cd1798d9c5f99c1620c2
This commit is contained in:
committed by
intellij-monorepo-bot
parent
42ef9c6517
commit
2fc7e1f910
@@ -20,7 +20,6 @@ import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.platform.backend.workspace.VirtualFileUrls;
|
||||
import com.intellij.platform.backend.workspace.WorkspaceModel;
|
||||
import com.intellij.platform.workspace.jps.entities.ExtensionsKt;
|
||||
import com.intellij.platform.workspace.jps.entities.InheritedSdkDependency;
|
||||
import com.intellij.platform.workspace.jps.entities.LibraryDependency;
|
||||
import com.intellij.platform.workspace.jps.entities.LibraryEntity;
|
||||
@@ -36,6 +35,8 @@ import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.util.ConcurrencyUtil;
|
||||
import com.intellij.util.containers.ConcurrentFactoryMap;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.graph.Graph;
|
||||
import com.intellij.workspaceModel.ide.impl.legacyBridge.module.ModuleExportedDependenciesGraph;
|
||||
import com.intellij.workspaceModel.ide.impl.legacyBridge.sdk.SdkBridgeImplKt;
|
||||
import com.intellij.workspaceModel.ide.legacyBridge.ModuleBridges;
|
||||
import kotlin.sequences.SequencesKt;
|
||||
@@ -160,9 +161,10 @@ public final class LibraryScopeCache {
|
||||
LibraryEntity lib = null;
|
||||
List<Module> modulesLibraryUsedIn = new ArrayList<>();
|
||||
|
||||
var exportedDependentsGraph = ModuleExportedDependenciesGraph.getInstance(myProject).exportedDependentsGraph();
|
||||
for (var library: libraries) {
|
||||
lib = library;
|
||||
modulesLibraryUsedIn.addAll(modulesWithLibraryId(library.getSymbolicId(), currentSnapshot));
|
||||
modulesLibraryUsedIn.addAll(findModulesWithLibraryId(library.getSymbolicId(), currentSnapshot, exportedDependentsGraph));
|
||||
}
|
||||
|
||||
if (lib != null) {
|
||||
@@ -271,8 +273,9 @@ public final class LibraryScopeCache {
|
||||
}
|
||||
|
||||
Set<Module> modulesWithLibrary = new HashSet<>();
|
||||
var exportedDependentsGraph = ModuleExportedDependenciesGraph.getInstance(myProject).exportedDependentsGraph();
|
||||
for (var library : libraries) {
|
||||
modulesWithLibrary.addAll(modulesWithLibraryId(library.getSymbolicId(), currentSnapshot));
|
||||
modulesWithLibrary.addAll(findModulesWithLibraryId(library.getSymbolicId(), currentSnapshot, exportedDependentsGraph));
|
||||
}
|
||||
modulesWithLibrary.addAll(index.getModulesForFile(virtualFile, false));
|
||||
|
||||
@@ -354,25 +357,17 @@ public final class LibraryScopeCache {
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<Module> modulesWithLibraryId(LibraryId libraryId, ImmutableEntityStorage currentSnapshot) {
|
||||
Set<Module> modulesWithLibrary = new HashSet<>();
|
||||
private static Set<Module> findModulesWithLibraryId(LibraryId libraryId, ImmutableEntityStorage currentSnapshot, Graph<ModuleEntity> exportedDependentsGraph) {
|
||||
Set<ModuleEntity> modulesWithLibrary = new HashSet<>();
|
||||
var ownerModules = SequencesKt.toList(currentSnapshot.referrers(libraryId, ModuleEntity.class));
|
||||
|
||||
for (var module : ownerModules) {
|
||||
var moduleBridge = ModuleBridges.findModule(module, currentSnapshot);
|
||||
if (moduleBridge != null) {
|
||||
modulesWithLibrary.add(moduleBridge);
|
||||
}
|
||||
modulesWithLibrary.add(module);
|
||||
if (exportsLibrary(module, libraryId)) {
|
||||
var transitiveModules = ExtensionsKt.collectTransitivelyDependentModules(module, currentSnapshot);
|
||||
for (var depModule : transitiveModules) {
|
||||
var depModuleBridge = ModuleBridges.findModule(depModule, currentSnapshot);
|
||||
if (depModuleBridge != null) {
|
||||
modulesWithLibrary.add(depModuleBridge);
|
||||
}
|
||||
}
|
||||
exportedDependentsGraph.getIn(module).forEachRemaining(modulesWithLibrary::add);
|
||||
}
|
||||
}
|
||||
return modulesWithLibrary;
|
||||
return ContainerUtil.map2Set(modulesWithLibrary, (moduleEntity) -> ModuleBridges.findModule(moduleEntity, currentSnapshot));
|
||||
}
|
||||
|
||||
private static boolean exportsLibrary(ModuleEntity module, LibraryId libraryId) {
|
||||
|
||||
+344
@@ -0,0 +1,344 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.workspaceModel.ide.legacyBridge
|
||||
|
||||
import com.intellij.openapi.roots.DependencyScope
|
||||
import com.intellij.openapi.roots.ModuleRootModificationUtil
|
||||
import com.intellij.testFramework.junit5.TestApplication
|
||||
import com.intellij.testFramework.rules.ProjectModelExtension
|
||||
import com.intellij.workspaceModel.ide.impl.legacyBridge.module.ModuleExportedDependenciesGraph
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.RegisterExtension
|
||||
|
||||
@TestApplication
|
||||
class ModuleExportedDependenciesGraphTest {
|
||||
@JvmField
|
||||
@RegisterExtension
|
||||
val projectModel: ProjectModelExtension = ProjectModelExtension()
|
||||
|
||||
private val graph: ModuleExportedDependenciesGraph
|
||||
get() = ModuleExportedDependenciesGraph.getInstance(projectModel.project)
|
||||
|
||||
@Test
|
||||
fun `empty graph has no nodes`() {
|
||||
val exportedGraph = graph.exportedDependentsGraph()
|
||||
|
||||
assertThat(exportedGraph.nodes)
|
||||
.describedAs("Empty graph should have no nodes")
|
||||
.isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `single module with no dependencies`() {
|
||||
val moduleA = projectModel.createModule("module-a").findModuleEntity()
|
||||
|
||||
val exportedGraph = graph.exportedDependentsGraph()
|
||||
|
||||
assertThat(exportedGraph.nodes)
|
||||
.describedAs("Graph should contain single module")
|
||||
.hasSize(1)
|
||||
assertThat(exportedGraph.getIn(moduleA).asSequence().toList())
|
||||
.describedAs("Module with no dependencies should have no incoming edges")
|
||||
.isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `graph nodes include all modules`() {
|
||||
projectModel.createModule("module-a")
|
||||
projectModel.createModule("module-b")
|
||||
projectModel.createModule("module-c")
|
||||
|
||||
val exportedGraph = graph.exportedDependentsGraph()
|
||||
|
||||
assertThat(exportedGraph.nodes.map { it.name })
|
||||
.describedAs("Graph should contain all created modules")
|
||||
.containsExactlyInAnyOrder("module-a", "module-b", "module-c")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `linear chain with all exported dependencies`() {
|
||||
// A ← B (exported) ← C (exported)
|
||||
val moduleA = projectModel.createModule("module-a")
|
||||
val moduleB = projectModel.createModule("module-b")
|
||||
val moduleC = projectModel.createModule("module-c")
|
||||
|
||||
ModuleRootModificationUtil.addDependency(moduleB, moduleA, DependencyScope.COMPILE, true)
|
||||
ModuleRootModificationUtil.addDependency(moduleC, moduleB, DependencyScope.COMPILE, true)
|
||||
|
||||
val exportedGraph = graph.exportedDependentsGraph()
|
||||
val moduleAEntity = moduleA.findModuleEntity()
|
||||
val moduleBEntity = moduleB.findModuleEntity()
|
||||
|
||||
assertThat(exportedGraph.getIn(moduleAEntity).asSequence().map { it.name }.toList())
|
||||
.describedAs("Module A should have B and C as transitive dependents (both exported)")
|
||||
.containsExactlyInAnyOrder("module-b", "module-c")
|
||||
|
||||
assertThat(exportedGraph.getIn(moduleBEntity).asSequence().map { it.name }.toList())
|
||||
.describedAs("Module B should have C as dependent")
|
||||
.containsExactlyInAnyOrder("module-c")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `non-exported dependency breaks transitive chain`() {
|
||||
// A ← B (NOT exported) ← C (exported)
|
||||
val moduleA = projectModel.createModule("module-a")
|
||||
val moduleB = projectModel.createModule("module-b")
|
||||
val moduleC = projectModel.createModule("module-c")
|
||||
|
||||
ModuleRootModificationUtil.addDependency(moduleB, moduleA, DependencyScope.COMPILE, false)
|
||||
ModuleRootModificationUtil.addDependency(moduleC, moduleB, DependencyScope.COMPILE, true)
|
||||
|
||||
val exportedGraph = graph.exportedDependentsGraph()
|
||||
val moduleAEntity = moduleA.findModuleEntity()
|
||||
val moduleBEntity = moduleB.findModuleEntity()
|
||||
|
||||
assertThat(exportedGraph.getIn(moduleAEntity).asSequence().map { it.name }.toList())
|
||||
.describedAs("Module A should only have B (C can't reach A transitively because B doesn't export A)")
|
||||
.containsExactlyInAnyOrder("module-b")
|
||||
|
||||
assertThat(exportedGraph.getIn(moduleBEntity).asSequence().map { it.name }.toList())
|
||||
.describedAs("Module B should have C as dependent")
|
||||
.containsExactlyInAnyOrder("module-c")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `diamond dependency with all exported`() {
|
||||
// A ← B (exported) ← D (exported)
|
||||
// A ← C (exported) ← D (exported)
|
||||
val moduleA = projectModel.createModule("module-a")
|
||||
val moduleB = projectModel.createModule("module-b")
|
||||
val moduleC = projectModel.createModule("module-c")
|
||||
val moduleD = projectModel.createModule("module-d")
|
||||
|
||||
ModuleRootModificationUtil.addDependency(moduleB, moduleA, DependencyScope.COMPILE, true)
|
||||
ModuleRootModificationUtil.addDependency(moduleC, moduleA, DependencyScope.COMPILE, true)
|
||||
ModuleRootModificationUtil.addDependency(moduleD, moduleB, DependencyScope.COMPILE, true)
|
||||
ModuleRootModificationUtil.addDependency(moduleD, moduleC, DependencyScope.COMPILE, true)
|
||||
|
||||
val exportedGraph = graph.exportedDependentsGraph()
|
||||
val moduleAEntity = moduleA.findModuleEntity()
|
||||
|
||||
assertThat(exportedGraph.getIn(moduleAEntity).asSequence().map { it.name }.toList())
|
||||
.describedAs("Module A should have B, C, and D as dependents (all paths exported)")
|
||||
.containsExactlyInAnyOrder("module-b", "module-c", "module-d")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `diamond dependency with mixed export flags`() {
|
||||
// A ← B (exported) ← D (exported)
|
||||
// A ← C (NOT exported) ← D (exported)
|
||||
val moduleA = projectModel.createModule("module-a")
|
||||
val moduleB = projectModel.createModule("module-b")
|
||||
val moduleC = projectModel.createModule("module-c")
|
||||
val moduleD = projectModel.createModule("module-d")
|
||||
|
||||
ModuleRootModificationUtil.addDependency(moduleB, moduleA, DependencyScope.COMPILE, true)
|
||||
ModuleRootModificationUtil.addDependency(moduleC, moduleA, DependencyScope.COMPILE, false)
|
||||
ModuleRootModificationUtil.addDependency(moduleD, moduleB, DependencyScope.COMPILE, true)
|
||||
ModuleRootModificationUtil.addDependency(moduleD, moduleC, DependencyScope.COMPILE, true)
|
||||
|
||||
val exportedGraph = graph.exportedDependentsGraph()
|
||||
val moduleAEntity = moduleA.findModuleEntity()
|
||||
val moduleCEntity = moduleC.findModuleEntity()
|
||||
|
||||
assertThat(exportedGraph.getIn(moduleAEntity).asSequence().map { it.name }.toList())
|
||||
.describedAs("Module A should have B, C, and D (D reaches A via B's exported path)")
|
||||
.containsExactlyInAnyOrder("module-b", "module-c", "module-d")
|
||||
|
||||
assertThat(exportedGraph.getIn(moduleCEntity).asSequence().map { it.name }.toList())
|
||||
.describedAs("Module C should only have D (no transitive propagation because C doesn't export A)")
|
||||
.containsExactlyInAnyOrder("module-d")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `multiple independent dependency chains`() {
|
||||
// Chain 1: A ← B (exported)
|
||||
// Chain 2: C ← D (exported)
|
||||
val moduleA = projectModel.createModule("module-a")
|
||||
val moduleB = projectModel.createModule("module-b")
|
||||
val moduleC = projectModel.createModule("module-c")
|
||||
val moduleD = projectModel.createModule("module-d")
|
||||
|
||||
ModuleRootModificationUtil.addDependency(moduleB, moduleA, DependencyScope.COMPILE, true)
|
||||
ModuleRootModificationUtil.addDependency(moduleD, moduleC, DependencyScope.COMPILE, true)
|
||||
|
||||
val exportedGraph = graph.exportedDependentsGraph()
|
||||
val moduleAEntity = moduleA.findModuleEntity()
|
||||
val moduleCEntity = moduleC.findModuleEntity()
|
||||
|
||||
assertThat(exportedGraph.getIn(moduleAEntity).asSequence().map { it.name }.toList())
|
||||
.describedAs("Module A should only have B from its chain")
|
||||
.containsExactlyInAnyOrder("module-b")
|
||||
|
||||
assertThat(exportedGraph.getIn(moduleCEntity).asSequence().map { it.name }.toList())
|
||||
.describedAs("Module C should only have D from its chain")
|
||||
.containsExactlyInAnyOrder("module-d")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `complex graph with multiple paths`() {
|
||||
// A ← B (exported) ← D (exported)
|
||||
// A ← C (exported) ← D (exported)
|
||||
// B ← E (exported)
|
||||
// C ← E (exported)
|
||||
val moduleA = projectModel.createModule("module-a")
|
||||
val moduleB = projectModel.createModule("module-b")
|
||||
val moduleC = projectModel.createModule("module-c")
|
||||
val moduleD = projectModel.createModule("module-d")
|
||||
val moduleE = projectModel.createModule("module-e")
|
||||
|
||||
ModuleRootModificationUtil.addDependency(moduleB, moduleA, DependencyScope.COMPILE, true)
|
||||
ModuleRootModificationUtil.addDependency(moduleC, moduleA, DependencyScope.COMPILE, true)
|
||||
ModuleRootModificationUtil.addDependency(moduleD, moduleB, DependencyScope.COMPILE, true)
|
||||
ModuleRootModificationUtil.addDependency(moduleD, moduleC, DependencyScope.COMPILE, true)
|
||||
ModuleRootModificationUtil.addDependency(moduleE, moduleB, DependencyScope.COMPILE, true)
|
||||
ModuleRootModificationUtil.addDependency(moduleE, moduleC, DependencyScope.COMPILE, true)
|
||||
|
||||
val exportedGraph = graph.exportedDependentsGraph()
|
||||
val moduleAEntity = moduleA.findModuleEntity()
|
||||
|
||||
assertThat(exportedGraph.getIn(moduleAEntity).asSequence().map { it.name }.toList())
|
||||
.describedAs("Module A should have B, C, D, and E (E reaches A via both B and C)")
|
||||
.containsExactlyInAnyOrder("module-b", "module-c", "module-d", "module-e")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `different dependency scopes with exported flag`() {
|
||||
// A ← B (exported, TEST scope) ← C (exported, RUNTIME scope)
|
||||
val moduleA = projectModel.createModule("module-a")
|
||||
val moduleB = projectModel.createModule("module-b")
|
||||
val moduleC = projectModel.createModule("module-c")
|
||||
|
||||
ModuleRootModificationUtil.addDependency(moduleB, moduleA, DependencyScope.TEST, true)
|
||||
ModuleRootModificationUtil.addDependency(moduleC, moduleB, DependencyScope.RUNTIME, true)
|
||||
|
||||
val exportedGraph = graph.exportedDependentsGraph()
|
||||
val moduleAEntity = moduleA.findModuleEntity()
|
||||
|
||||
assertThat(exportedGraph.getIn(moduleAEntity).asSequence().map { it.name }.toList())
|
||||
.describedAs("Exported flag should work regardless of dependency scope")
|
||||
.containsExactlyInAnyOrder("module-b", "module-c")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `circular dependency with all exported`() {
|
||||
// A ← B (exported) ← C (exported) ← A (exported)
|
||||
val moduleA = projectModel.createModule("module-a")
|
||||
val moduleB = projectModel.createModule("module-b")
|
||||
val moduleC = projectModel.createModule("module-c")
|
||||
|
||||
ModuleRootModificationUtil.addDependency(moduleB, moduleA, DependencyScope.COMPILE, true)
|
||||
ModuleRootModificationUtil.addDependency(moduleC, moduleB, DependencyScope.COMPILE, true)
|
||||
ModuleRootModificationUtil.addDependency(moduleA, moduleC, DependencyScope.COMPILE, true)
|
||||
|
||||
val exportedGraph = graph.exportedDependentsGraph()
|
||||
val moduleAEntity = moduleA.findModuleEntity()
|
||||
val moduleBEntity = moduleB.findModuleEntity()
|
||||
val moduleCEntity = moduleC.findModuleEntity()
|
||||
|
||||
// In a circular dependency with all exported, each module sees all modules in the cycle (including itself)
|
||||
assertThat(exportedGraph.getIn(moduleAEntity).asSequence().map { it.name }.toList())
|
||||
.describedAs("Module A should have B, C, and itself as dependents in circular chain")
|
||||
.containsExactlyInAnyOrder("module-a", "module-b", "module-c")
|
||||
|
||||
assertThat(exportedGraph.getIn(moduleBEntity).asSequence().map { it.name }.toList())
|
||||
.describedAs("Module B should have C, A, and itself as dependents in circular chain")
|
||||
.containsExactlyInAnyOrder("module-a", "module-b", "module-c")
|
||||
|
||||
assertThat(exportedGraph.getIn(moduleCEntity).asSequence().map { it.name }.toList())
|
||||
.describedAs("Module C should have A, B, and itself as dependents in circular chain")
|
||||
.containsExactlyInAnyOrder("module-a", "module-b", "module-c")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `circular dependency with non-exported break`() {
|
||||
// A ← B (exported) ← C (NOT exported) ← A (exported)
|
||||
val moduleA = projectModel.createModule("module-a")
|
||||
val moduleB = projectModel.createModule("module-b")
|
||||
val moduleC = projectModel.createModule("module-c")
|
||||
|
||||
ModuleRootModificationUtil.addDependency(moduleB, moduleA, DependencyScope.COMPILE, true)
|
||||
ModuleRootModificationUtil.addDependency(moduleC, moduleB, DependencyScope.COMPILE, false)
|
||||
ModuleRootModificationUtil.addDependency(moduleA, moduleC, DependencyScope.COMPILE, true)
|
||||
|
||||
val exportedGraph = graph.exportedDependentsGraph()
|
||||
val moduleBEntity = moduleB.findModuleEntity()
|
||||
|
||||
assertThat(exportedGraph.getIn(moduleBEntity).asSequence().map { it.name }.toList())
|
||||
.describedAs("Non-exported dependency breaks circular chain; C shouldn't propagate to A")
|
||||
.containsExactlyInAnyOrder("module-c")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `transitive exported dependencies validation`() {
|
||||
// A ← B (exported) ← C (exported) ← D (NOT exported)
|
||||
// D is added to A's dependents because it directly depends on C
|
||||
// But D is not added to the BFS queue because D doesn't export C
|
||||
val moduleA = projectModel.createModule("module-a")
|
||||
val moduleB = projectModel.createModule("module-b")
|
||||
val moduleC = projectModel.createModule("module-c")
|
||||
val moduleD = projectModel.createModule("module-d")
|
||||
|
||||
ModuleRootModificationUtil.addDependency(moduleB, moduleA, DependencyScope.COMPILE, true)
|
||||
ModuleRootModificationUtil.addDependency(moduleC, moduleB, DependencyScope.COMPILE, true)
|
||||
ModuleRootModificationUtil.addDependency(moduleD, moduleC, DependencyScope.COMPILE, false)
|
||||
|
||||
val exportedGraph = graph.exportedDependentsGraph()
|
||||
val moduleAEntity = moduleA.findModuleEntity()
|
||||
val moduleCEntity = moduleC.findModuleEntity()
|
||||
val moduleBEntity = moduleB.findModuleEntity()
|
||||
|
||||
// A's dependents: B (direct, exported), C (transitive via B), D (transitive via C, but D doesn't export C)
|
||||
assertThat(exportedGraph.getIn(moduleAEntity).asSequence().map { it.name }.toList())
|
||||
.describedAs("Module A should have B, C, and D as dependents (D is included because C is in the queue)")
|
||||
.containsExactlyInAnyOrder("module-b", "module-c", "module-d")
|
||||
|
||||
// B's dependents: C (direct, exported), D (transitive via C)
|
||||
assertThat(exportedGraph.getIn(moduleBEntity).asSequence().map { it.name }.toList())
|
||||
.describedAs("Module B should have C and D as dependents")
|
||||
.containsExactlyInAnyOrder("module-c", "module-d")
|
||||
|
||||
// C's dependents: only D (direct, not exported)
|
||||
assertThat(exportedGraph.getIn(moduleCEntity).asSequence().map { it.name }.toList())
|
||||
.describedAs("Module C should only have D (no transitive propagation because D doesn't export C)")
|
||||
.containsExactlyInAnyOrder("module-d")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getIn returns direct dependents`() {
|
||||
// A ← B (NOT exported)
|
||||
// A ← C (NOT exported)
|
||||
val moduleA = projectModel.createModule("module-a")
|
||||
val moduleB = projectModel.createModule("module-b")
|
||||
val moduleC = projectModel.createModule("module-c")
|
||||
|
||||
ModuleRootModificationUtil.addDependency(moduleB, moduleA, DependencyScope.COMPILE, false)
|
||||
ModuleRootModificationUtil.addDependency(moduleC, moduleA, DependencyScope.COMPILE, false)
|
||||
|
||||
val exportedGraph = graph.exportedDependentsGraph()
|
||||
val moduleAEntity = moduleA.findModuleEntity()
|
||||
|
||||
assertThat(exportedGraph.getIn(moduleAEntity).asSequence().map { it.name }.toList())
|
||||
.describedAs("getIn() should return direct dependents even when not exported")
|
||||
.containsExactlyInAnyOrder("module-b", "module-c")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getOut returns module dependencies`() {
|
||||
// A ← B (exported)
|
||||
// C ← B (NOT exported)
|
||||
val moduleA = projectModel.createModule("module-a")
|
||||
val moduleB = projectModel.createModule("module-b")
|
||||
val moduleC = projectModel.createModule("module-c")
|
||||
|
||||
ModuleRootModificationUtil.addDependency(moduleB, moduleA, DependencyScope.COMPILE, true)
|
||||
ModuleRootModificationUtil.addDependency(moduleB, moduleC, DependencyScope.COMPILE, false)
|
||||
|
||||
val exportedGraph = graph.exportedDependentsGraph()
|
||||
val moduleBEntity = moduleB.findModuleEntity()
|
||||
|
||||
assertThat(exportedGraph.getOut(moduleBEntity).asSequence().map { it.name }.toList())
|
||||
.describedAs("getOut() should return all dependencies")
|
||||
.containsExactlyInAnyOrder("module-a", "module-c")
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.workspaceModel.ide.impl.legacyBridge.module
|
||||
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.platform.backend.workspace.WorkspaceModel
|
||||
import com.intellij.platform.backend.workspace.impl.WorkspaceModelInternal
|
||||
import com.intellij.platform.workspace.jps.entities.ModuleDependency
|
||||
import com.intellij.platform.workspace.jps.entities.ModuleEntity
|
||||
import com.intellij.platform.workspace.storage.CachedValue
|
||||
import com.intellij.platform.workspace.storage.EntityStorage
|
||||
import com.intellij.platform.workspace.storage.VersionedEntityStorage
|
||||
import com.intellij.platform.workspace.storage.referrers
|
||||
import com.intellij.util.graph.CachingSemiGraph
|
||||
import com.intellij.util.graph.Graph
|
||||
import com.intellij.util.graph.GraphGenerator
|
||||
import com.intellij.util.graph.InboundSemiGraph
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
/**
|
||||
* [Graph.getIn] returns [ModuleEntity] which depend on the given module taking into account exported dependencies.
|
||||
*
|
||||
* [Graph.getOut] returns [ModuleEntity] on which the given module depends taking into account exported dependencies.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
@Service(Service.Level.PROJECT)
|
||||
class ModuleExportedDependenciesGraph(project: Project) {
|
||||
|
||||
private val entityStore: VersionedEntityStorage = (WorkspaceModel.getInstance(project) as WorkspaceModelInternal).entityStorage
|
||||
|
||||
fun exportedDependentsGraph(): Graph<ModuleEntity> = entityStore.cachedValue(exportedDependentsGraph)
|
||||
|
||||
private val exportedDependentsGraph = CachedValue { storage ->
|
||||
buildGraph(storage)
|
||||
}
|
||||
|
||||
private fun buildGraph(storage: EntityStorage): Graph<ModuleEntity> {
|
||||
return GraphGenerator.generate(CachingSemiGraph.cache(object : InboundSemiGraph<ModuleEntity> {
|
||||
override fun getNodes(): Collection<ModuleEntity> =
|
||||
storage.entities(ModuleEntity::class.java).toList()
|
||||
|
||||
override fun getIn(module: ModuleEntity): Iterator<ModuleEntity> {
|
||||
return collectDependentModules(module, storage).iterator()
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
private fun collectDependentModules(module: ModuleEntity, storage: EntityStorage): Set<ModuleEntity> {
|
||||
val queue = ArrayDeque<ModuleEntity>()
|
||||
val result = HashSet<ModuleEntity>()
|
||||
queue.add(module)
|
||||
while (queue.isNotEmpty()) {
|
||||
val currentModule = queue.removeFirst()
|
||||
for (referrer in storage.referrers<ModuleEntity>(currentModule.symbolicId)) {
|
||||
val dependency =
|
||||
referrer.dependencies.filterIsInstance<ModuleDependency>().find { it.module == currentModule.symbolicId } ?: continue
|
||||
if (result.add(referrer) && dependency.exported) {
|
||||
queue.add(referrer)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getInstance(project: Project): ModuleExportedDependenciesGraph = project.service()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user