[DependencySubstitution] cleanup: encapsulate coordinate matching inside DependencySubstitutionExtension

Encapsulated dependency coordinates allows to misunderstanding what is the dependency coordinates and how they should compare with each other. Also, it forbids dependency coordinate hijack by other extensions.

Maven coordinates should be specified only by the Library and Module Maven coordinate entities.

### Code Reviews
  * IJ-CR-164983

### Issues
  * IDEA-370483 Maven performance degradation related to library dependency substitution

GitOrigin-RevId: 01461ea4bf0fe44e60a80404a2cf08f16c0d5853
This commit is contained in:
Sergei Vorobyov
2025-06-13 21:26:49 +00:00
committed by intellij-monorepo-bot
parent 5a7b7533f6
commit f10a240e01
9 changed files with 82 additions and 112 deletions
@@ -1421,7 +1421,7 @@
<externalProjectDataService implementation="com.intellij.externalSystem.JavaModuleDataService"/>
<externalWorkspaceDataService implementation="com.intellij.externalSystem.JavaManifestDataService"/>
<libraryDataServiceExtension implementation="com.intellij.externalSystem.JavaLibraryDataServiceExtension"/>
<dependencySubstitutionCoordinateContributor implementation="com.intellij.java.impl.dependencySubstitution.DependencySubstitutionMavenCoordinateContributor"/>
<dependencySubstitutionCoordinateContributor implementation="com.intellij.java.impl.dependencySubstitution.MavenCoordinateDependencySubstitutionExtension"/>
<postStartupActivity implementation="com.intellij.pom.java.AcceptedLanguageLevelsSettingsStartupActivity"/>
<projectModelModifier implementation="com.intellij.openapi.roots.impl.IdeaProjectModelModifier" order="last"/>
<multiHostInjector implementation="com.intellij.psi.impl.source.tree.injected.JavaConcatenationToInjectorAdapter" order="first"/>
@@ -1,18 +0,0 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.impl.dependencySubstitution
import com.intellij.java.library.MavenCoordinates
import com.intellij.platform.externalSystem.impl.dependencySubstitution.DependencySubstitutionCoordinateContributor
import com.intellij.platform.workspace.jps.entities.LibraryEntity
import com.intellij.platform.workspace.jps.entities.ModuleEntity
private class DependencySubstitutionMavenCoordinateContributor : DependencySubstitutionCoordinateContributor {
override fun findModuleCoordinate(module: ModuleEntity): MavenCoordinates? {
return module.mavenCoordinates?.coordinates
}
override fun findLibraryCoordinate(library: LibraryEntity): MavenCoordinates? {
return library.mavenCoordinates?.coordinates
}
}
@@ -0,0 +1,20 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.impl.dependencySubstitution
import com.intellij.platform.externalSystem.impl.dependencySubstitution.DependencySubstitutionExtension
import com.intellij.platform.externalSystem.impl.dependencySubstitution.DependencySubstitutionUtil.intersect
import com.intellij.platform.workspace.jps.entities.LibraryId
import com.intellij.platform.workspace.jps.entities.ModuleId
import com.intellij.platform.workspace.storage.EntityStorage
import com.intellij.platform.workspace.storage.entities
private class MavenCoordinateDependencySubstitutionExtension : DependencySubstitutionExtension {
override fun buildLibraryToModuleMap(storage: EntityStorage): Map<LibraryId, ModuleId> {
val libraries = storage.entities<LibraryMavenCoordinateEntity>()
.associate { it.coordinates to it.library.symbolicId }
val modules = storage.entities<ModuleMavenCoordinateEntity>()
.associate { it.coordinates to it.module.symbolicId }
return libraries.intersect(modules)
}
}
@@ -75,8 +75,3 @@ f:com.intellij.openapi.externalSystem.service.ui.ExternalSystemJdkComboBox
- sf:INSTANCE:com.intellij.openapi.externalSystem.util.task.TaskExecutionUtil
- f:runTask(com.intellij.openapi.externalSystem.util.task.TaskExecutionSpec,kotlin.coroutines.Continuation):java.lang.Object
- f:runTask(com.intellij.openapi.externalSystem.util.task.TaskExecutionSpecBuilder,kotlin.coroutines.Continuation):java.lang.Object
*:com.intellij.platform.externalSystem.impl.dependencySubstitution.DependencySubstitutionCoordinateContributor
- *sf:Companion:com.intellij.platform.externalSystem.impl.dependencySubstitution.DependencySubstitutionCoordinateContributor$Companion
- findLibraryCoordinate(com.intellij.platform.workspace.jps.entities.LibraryEntity):java.lang.Object
- findModuleCoordinate(com.intellij.platform.workspace.jps.entities.ModuleEntity):java.lang.Object
*f:com.intellij.platform.externalSystem.impl.dependencySubstitution.DependencySubstitutionCoordinateContributor$Companion
@@ -10,7 +10,7 @@
interface="com.intellij.openapi.externalSystem.debugger.DebuggerBackendExtension"
dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.dependencySubstitutionCoordinateContributor"
interface="com.intellij.platform.externalSystem.impl.dependencySubstitution.DependencySubstitutionCoordinateContributor"
interface="com.intellij.platform.externalSystem.impl.dependencySubstitution.DependencySubstitutionExtension"
dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.externalProjectDataService"
interface="com.intellij.openapi.externalSystem.service.project.manage.ProjectDataService"
@@ -1,41 +0,0 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.externalSystem.impl.dependencySubstitution
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.platform.workspace.jps.entities.LibraryEntity
import com.intellij.platform.workspace.jps.entities.ModuleEntity
import org.jetbrains.annotations.ApiStatus
/**
* Defines mapping between ide entities and entities' global coordinates.
* The import can use that mapping for binary dependencies substitution on module dependencies
* across unrelated projects based on any build system or another kind of project modules generator.
*
* For example, maven coordinates can be used as global coordinates.
*/
@ApiStatus.Experimental
interface DependencySubstitutionCoordinateContributor {
/**
* Finds global module coordinates. For example, maven coordinates.
*
* @param module is a module to find artifact coordinates of a corresponding external project.
* @return found global module coordinates.
*/
fun findModuleCoordinate(module: ModuleEntity): Any? = null
/**
* Finds global library coordinates. For example, maven coordinates.
*
* @param library is a library to find artifact coordinates of a corresponding external library.
* @return found global library coordinates.
*/
fun findLibraryCoordinate(library: LibraryEntity): Any? = null
companion object {
@ApiStatus.Internal
val EP_NAME: ExtensionPointName<DependencySubstitutionCoordinateContributor> =
ExtensionPointName.create("com.intellij.dependencySubstitutionCoordinateContributor")
}
}
@@ -0,0 +1,31 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.externalSystem.impl.dependencySubstitution
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.platform.workspace.jps.entities.LibraryId
import com.intellij.platform.workspace.jps.entities.ModuleId
import com.intellij.platform.workspace.storage.EntityStorage
import org.jetbrains.annotations.ApiStatus
/**
* Defines mapping between ide library and module entities.
*
* The import can use that mapping for library dependencies substitution on module dependencies
* across all external projects.
*/
@ApiStatus.Internal
interface DependencySubstitutionExtension {
/**
* Builds relation between library and module dependencies based on any library and module characteristic.
*
* Note: this function may be executed under write action.
*/
fun buildLibraryToModuleMap(storage: EntityStorage): Map<LibraryId, ModuleId>
companion object {
val EP_NAME: ExtensionPointName<DependencySubstitutionExtension> =
ExtensionPointName.create("com.intellij.dependencySubstitutionCoordinateContributor")
}
}
@@ -9,7 +9,6 @@ import com.intellij.platform.workspace.storage.EntityStorage
import com.intellij.platform.workspace.storage.MutableEntityStorage
import com.intellij.platform.workspace.storage.entities
import org.jetbrains.annotations.ApiStatus
import kotlin.collections.iterator
@ApiStatus.Internal
object DependencySubstitutionUtil {
@@ -110,48 +109,25 @@ object DependencySubstitutionUtil {
return moduleDependency
}
private fun buildLibraryToCoordinateMap(storage: EntityStorage): Map<LibraryId, Any> {
TELEMETRY.spanBuilder("buildLibraryToCoordinateMap").use {
val result = HashMap<LibraryId, Any>()
DependencySubstitutionCoordinateContributor.EP_NAME.forEachExtensionSafe { contributor ->
TELEMETRY.spanBuilder("findLibraryCoordinate").use { span ->
span.setAttribute("contributor", contributor.javaClass.name)
for (library in storage.entities<LibraryEntity>()) {
val libraryCoordinate = contributor.findLibraryCoordinate(library) ?: continue
result[library.symbolicId] = libraryCoordinate
}
}
}
return result
}
}
private fun buildCoordinateToModuleMap(storage: EntityStorage): Map<Any, ModuleId> {
TELEMETRY.spanBuilder("buildCoordinateToModuleMap").use {
val result = HashMap<Any, ModuleId>()
DependencySubstitutionCoordinateContributor.EP_NAME.forEachExtensionSafe { contributor ->
TELEMETRY.spanBuilder("findModuleCoordinate").use { span ->
span.setAttribute("contributor", contributor.javaClass.name)
for (module in storage.entities<ModuleEntity>()) {
val moduleCoordinate = contributor.findModuleCoordinate(module) ?: continue
result[moduleCoordinate] = module.symbolicId
}
}
}
return result
}
}
private fun buildLibraryToModuleMap(storage: EntityStorage): Map<LibraryId, ModuleId> {
TELEMETRY.spanBuilder("buildLibraryToModuleMap").use {
val libraryToCoordinateMap = buildLibraryToCoordinateMap(storage)
val coordinateToModuleMap = buildCoordinateToModuleMap(storage)
val result = HashMap<LibraryId, ModuleId>()
for ((libraryId, libraryCoordinate) in libraryToCoordinateMap) {
val moduleId = coordinateToModuleMap[libraryCoordinate] ?: continue
result[libraryId] = moduleId
DependencySubstitutionExtension.EP_NAME.forEachExtensionSafe { contributor ->
TELEMETRY.spanBuilder("buildDependencyMap").use { span ->
span.setAttribute("contributor", contributor.javaClass.name)
result.putAll(contributor.buildLibraryToModuleMap(storage))
}
}
return result
}
}
@ApiStatus.Internal
fun <K: Any?, V1, V2> Map<K, V1>.intersect(other: Map<K, V2>): Map<V1, V2> {
val result = HashMap<V1, V2>()
for ((key, value1) in this) {
result[value1] = other[key ?: continue] ?: continue
}
return result
}
}
@@ -1,24 +1,31 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.externalSystem.dependencySubstitution
import com.intellij.platform.externalSystem.impl.dependencySubstitution.DependencySubstitutionCoordinateContributor
import com.intellij.platform.externalSystem.impl.dependencySubstitution.DependencySubstitutionExtension
import com.intellij.platform.externalSystem.impl.dependencySubstitution.DependencySubstitutionUtil.intersect
import com.intellij.platform.workspace.jps.entities.LibraryEntity
import com.intellij.platform.workspace.jps.entities.LibraryId
import com.intellij.platform.workspace.jps.entities.ModuleEntity
import com.intellij.platform.workspace.jps.entities.ModuleId
import com.intellij.platform.workspace.storage.EntityStorage
import com.intellij.platform.workspace.storage.entities
import com.intellij.testFramework.junit5.fixture.extensionPointFixture
abstract class DependencySubstitutionTestCase {
val coordinates by extensionPointFixture(DependencySubstitutionCoordinateContributor.EP_NAME, ::TestCoordinateContributor)
val coordinates by extensionPointFixture(DependencySubstitutionExtension.EP_NAME, ::TestDependencySubstitutionExtension)
class TestCoordinateContributor : DependencySubstitutionCoordinateContributor {
class TestDependencySubstitutionExtension : DependencySubstitutionExtension {
val modules = HashMap<String, String>()
val libraries = HashMap<String, String>()
override fun findModuleCoordinate(module: ModuleEntity): String? =
modules[module.name]
override fun findLibraryCoordinate(library: LibraryEntity): String? =
libraries[library.name]
override fun buildLibraryToModuleMap(storage: EntityStorage): Map<LibraryId, ModuleId> {
val libraries = storage.entities<LibraryEntity>()
.associate { libraries[it.name] to it.symbolicId }
val modules = storage.entities<ModuleEntity>()
.associate { modules[it.name] to it.symbolicId }
return libraries.intersect(modules)
}
}
}