diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PluginInitializationContext.kt b/platform/core-impl/src/com/intellij/ide/plugins/PluginInitializationContext.kt index 9ae33ce8ba8e..a50a73be5878 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PluginInitializationContext.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginInitializationContext.kt @@ -58,13 +58,43 @@ interface PluginInitializationContext { } /** - * Processed for all possible modules and "depends" sub-descriptors independently. - * @return a sequence of modules that should be deemed as additional dependencies of a given [descriptor]. + * Produces a sequence of modules that should be deemed as additional dependencies of a given [descriptor]. + * Note that the generated dependency is "strict", meaning that if the target gets excluded (e.g., if the target is a plugin that is marked disabled), + * then [descriptor] will also be excluded. + * + * Called for all possible modules and "depends" sub-descriptors independently. * * TODO Ideally, [pluginSet] should not be used, but it's required in the current [ProductPluginInitContext] implementation. + * + * @see [provideCompatibilityDependenciesForRemainingCandidates] */ fun provideCompatibilityDependencies(descriptor: IdeaPluginDescriptorImpl, pluginSet: UnambiguousPluginSet): Sequence + /** + * This method is different from [provideCompatibilityDependencies] in that it allows generating "soft" compatibility dependencies: + * imagine that several modules were extracted from the IDE's core and now form a separate plugin that can be disabled. + * Previously, these modules were available to external plugins via the Core classloader, i.e. without any explicit dependency, + * but now they are not available without an explicit dependency, which breaks compatibility. + * To remedy this, we want to supply additional dependencies on extracted modules. Producing a "strict" dependency + * (as in [provideCompatibilityDependencies]) may sometimes be too strict, e.g., if that new extracted plugin is disabled, external plugins + * that receive such a compatibility dependency (even those that don't actually need it) will be excluded since the dependency is "strict". + * However, this method is called when the preliminary set of remaining candidates is already constructed, i.e. when all regular module + * exclusion rules are processed, and it allows skipping generation of compatibility dependencies if the dependency target is already excluded. + * + * This method is called for every remaining candidate descriptor. + * + * Note that producing additional dependencies here still may cause exclusions (e.g., if a dependency cycle appears). + * + * Note that eventually every implicit dependency that is added through this method should become explicit in the affected plugins. + * This method should only work as a temporary compatibility mechanism, it should not grow indefinitely. + */ + fun provideCompatibilityDependenciesForRemainingCandidates(descriptor: IdeaPluginDescriptorImpl, remainingCandidates: RemainingCandidatesView): Sequence + + interface RemainingCandidatesView { + fun resolvePluginId(id: PluginId): PluginModuleDescriptor? + fun resolveContentModuleId(id: PluginModuleId): ContentModuleDescriptor? + } + fun provideModuleExclusionsImposedByProductRules(pluginSet: UnambiguousPluginSet): Sequence> /** @@ -109,4 +139,12 @@ fun PluginInitializationContext.validatePluginIsCompatible(plugin: PluginMainDes } @ApiStatus.Internal -data class PluginsPerProjectConfig(val isMainProcess: Boolean) \ No newline at end of file +data class PluginsPerProjectConfig(val isMainProcess: Boolean) + +@ApiStatus.Internal +fun PluginInitializationContext.RemainingCandidatesView.resolveReference(ref: DependencyRef): PluginModuleDescriptor? { + return when (ref) { + is DependencyRef.Plugin -> resolvePluginId(ref.pluginId) + is DependencyRef.ContentModule -> resolveContentModuleId(ref.moduleId) + } +} \ No newline at end of file diff --git a/platform/core-impl/src/com/intellij/ide/plugins/ProductPluginInitContext.kt b/platform/core-impl/src/com/intellij/ide/plugins/ProductPluginInitContext.kt index 6e8cc5f52cea..18d7951205dd 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/ProductPluginInitContext.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/ProductPluginInitContext.kt @@ -4,6 +4,7 @@ package com.intellij.ide.plugins import com.intellij.core.CoreBundle import com.intellij.ide.plugins.PluginDependencyAnalysis.DependencyRef import com.intellij.ide.plugins.PluginInitializationContext.EnvironmentConfiguredModuleData +import com.intellij.ide.plugins.PluginInitializationContext.RemainingCandidatesView import com.intellij.ide.plugins.PluginManagerCore.CORE_ID import com.intellij.ide.plugins.PluginManagerCore.JAVA_PLUGIN_ALIAS_ID import com.intellij.ide.plugins.PluginManagerCore.getPluginNameAndVendor @@ -104,6 +105,9 @@ class ProductPluginInitContext( override fun provideCompatibilityDependencies(descriptor: IdeaPluginDescriptorImpl, pluginSet: UnambiguousPluginSet): Sequence = defaultProductCompatibilityDependenciesProvider(descriptor, pluginSet) + override fun provideCompatibilityDependenciesForRemainingCandidates(descriptor: IdeaPluginDescriptorImpl, remainingCandidates: RemainingCandidatesView): Sequence = + defaultProductCompatibilityDependenciesForRemainingCandidatesProvider(descriptor, remainingCandidates) + override fun provideModuleExclusionsImposedByProductRules(pluginSet: UnambiguousPluginSet): Sequence> = defaultProductRulesImposedExclusions(pluginSet, expiredPlugins, thirdPartyPluginsWithoutConsentCheckResult) @@ -335,6 +339,16 @@ class ProductPluginInitContext( return null } + @VisibleForTesting + fun defaultProductCompatibilityDependenciesForRemainingCandidatesProvider( + descriptor: IdeaPluginDescriptorImpl, + remainingCandidates: RemainingCandidatesView, + ): Sequence { + return sequence { + + } + } + @VisibleForTesting fun defaultProductRulesImposedExclusions( pluginSet: UnambiguousPluginSet, diff --git a/platform/core-impl/src/com/intellij/ide/plugins/pluginInitContextConstraintResolver.kt b/platform/core-impl/src/com/intellij/ide/plugins/pluginInitContextConstraintResolver.kt index 97895fb26db2..2c30f21a62d7 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/pluginInitContextConstraintResolver.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/pluginInitContextConstraintResolver.kt @@ -179,7 +179,8 @@ private class PluginSetConstraintsResolver( } private fun sequenceAllDependenciesOfCandidateIncludingCompatibility(candidate: IdeaPluginDescriptorImpl): Sequence { - return PluginDependencyAnalysis.sequenceStrictDependencies(candidate) + initContext.provideCompatibilityDependencies(candidate, pluginSet) + return PluginDependencyAnalysis.sequenceStrictDependencies(candidate) + + initContext.provideCompatibilityDependencies(candidate, pluginSet) } /** diff --git a/platform/pluginSystem/testFramework/src/EmptyTestPluginInitContext.kt b/platform/pluginSystem/testFramework/src/EmptyTestPluginInitContext.kt index 759b8403a4bb..401caad72805 100644 --- a/platform/pluginSystem/testFramework/src/EmptyTestPluginInitContext.kt +++ b/platform/pluginSystem/testFramework/src/EmptyTestPluginInitContext.kt @@ -5,6 +5,7 @@ import com.intellij.ide.plugins.AmbiguousPluginSet import com.intellij.ide.plugins.IdeaPluginDescriptorImpl import com.intellij.ide.plugins.PluginDependencyAnalysis.DependencyRef import com.intellij.ide.plugins.PluginInitializationContext +import com.intellij.ide.plugins.PluginInitializationContext.RemainingCandidatesView import com.intellij.ide.plugins.PluginMainDescriptor import com.intellij.ide.plugins.PluginModuleDescriptor import com.intellij.ide.plugins.PluginModuleId @@ -30,6 +31,11 @@ abstract class EmptyTestPluginInitContext : PluginInitializationContext { pluginSet: UnambiguousPluginSet, ): Sequence = emptySequence() + override fun provideCompatibilityDependenciesForRemainingCandidates( + descriptor: IdeaPluginDescriptorImpl, + remainingCandidates: RemainingCandidatesView, + ): Sequence = emptySequence() + override fun provideModuleExclusionsImposedByProductRules(pluginSet: UnambiguousPluginSet): Sequence> = emptySequence() diff --git a/platform/pluginSystem/testFramework/src/PseudoProductTestPluginInitContext.kt b/platform/pluginSystem/testFramework/src/PseudoProductTestPluginInitContext.kt index ce5a3d99fc40..48ace2405177 100644 --- a/platform/pluginSystem/testFramework/src/PseudoProductTestPluginInitContext.kt +++ b/platform/pluginSystem/testFramework/src/PseudoProductTestPluginInitContext.kt @@ -8,6 +8,7 @@ import com.intellij.ide.plugins.PluginMainDescriptor import com.intellij.ide.plugins.PluginModuleDescriptor import com.intellij.ide.plugins.PluginModuleId import com.intellij.ide.plugins.ProductPluginInitContext.Companion.configureProductModeModules +import com.intellij.ide.plugins.ProductPluginInitContext.Companion.defaultProductCompatibilityDependenciesForRemainingCandidatesProvider import com.intellij.ide.plugins.ProductPluginInitContext.Companion.defaultProductCompatibilityDependenciesProvider import com.intellij.ide.plugins.ProductPluginInitContext.Companion.defaultProductRulesImposedExclusions import com.intellij.ide.plugins.ProductPluginInitContext.Companion.defaultRuntimeModuleGroupAffiliation @@ -42,6 +43,12 @@ abstract class PseudoProductTestPluginInitContext : EmptyTestPluginInitContext() ): PluginModuleDescriptor? = defaultRuntimeModuleGroupAffiliation(module, pluginSet) + override fun provideCompatibilityDependenciesForRemainingCandidates( + descriptor: IdeaPluginDescriptorImpl, + remainingCandidates: PluginInitializationContext.RemainingCandidatesView + ): Sequence = + defaultProductCompatibilityDependenciesForRemainingCandidatesProvider(descriptor, remainingCandidates) + override fun shouldIncludeContentModulesForDependsEdgeTarget(resolvedTarget: PluginMainDescriptor): Boolean = defaultShouldIncludeContentModulesForDependsEdgeTarget(resolvedTarget) }