diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.kt b/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.kt index e74f63bbdf75..b7eda84aa7d0 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.kt @@ -7,6 +7,7 @@ import com.intellij.diagnostic.CoroutineTracerShim import com.intellij.diagnostic.LoadingState import com.intellij.ide.plugins.DisabledPluginsState.Companion.invalidate import com.intellij.ide.plugins.PluginManagerCore.ULTIMATE_PLUGIN_ID +import com.intellij.ide.plugins.PluginManagerCore.getPluginSet import com.intellij.ide.plugins.PluginManagerCore.isDisabled import com.intellij.ide.plugins.PluginManagerCore.loadedPlugins import com.intellij.ide.plugins.PluginManagerCore.processAllNonOptionalDependencies @@ -483,7 +484,13 @@ object PluginManagerCore { loadingResult.getIncompleteIdMap().flatMap { (_, value) -> value.pluginAliases.map { it to value } }.toMap() - + val fullContentModuleIdMap = HashMap() + for (descriptor in loadingResult.getIncompleteIdMap().values) { + descriptor.contentModules.associateByTo(fullContentModuleIdMap) { it.moduleName } + } + for (descriptor in idMap.values) { + descriptor.contentModules.associateByTo(fullContentModuleIdMap) { it.moduleName } + } if (initContext.checkEssentialPlugins && !idMap.containsKey(CORE_ID)) { throw EssentialPluginMissingException(listOf("$CORE_ID (platform prefix: ${System.getProperty(PlatformUtils.PLATFORM_PREFIX_KEY)})")) @@ -513,7 +520,7 @@ object PluginManagerCore { incompletePlugins = loadingResult.getIncompleteIdMap().values, currentProductModeEvaluator = initContext::currentProductModeId, disabler = { descriptor, disabledModuleToProblematicPlugin -> - val loadingError = pluginSetBuilder.initEnableState(descriptor, idMap, fullIdMap, initContext::isPluginDisabled, pluginErrorsById, disabledModuleToProblematicPlugin) + val loadingError = pluginSetBuilder.initEnableState(descriptor, idMap, fullIdMap, fullContentModuleIdMap, initContext::isPluginDisabled, pluginErrorsById, disabledModuleToProblematicPlugin) if (loadingError != null) { registerLoadingError(loadingError) } @@ -560,7 +567,7 @@ object PluginManagerCore { } private fun selectPluginsForLoading( - descriptors: Collection, + descriptors: Collection, idMap: Map, errors: MutableMap, initContext: PluginInitializationContext, @@ -568,10 +575,14 @@ object PluginManagerCore { if (initContext.explicitPluginSubsetToLoad != null) { val rootPluginsToLoad: Set = initContext.explicitPluginSubsetToLoad!!.toHashSet() + initContext.essentialPlugins val pluginsToLoad = LinkedHashSet(rootPluginsToLoad.size) + val contentModuleIdMap = HashMap() + for (descriptor in descriptors) { + descriptor.contentModules.associateByTo(contentModuleIdMap) { it.moduleName } + } for (id in rootPluginsToLoad) { val descriptor = idMap[id] ?: continue pluginsToLoad.add(descriptor) - processAllNonOptionalDependencies(descriptor, idMap) { dependency -> + processAllNonOptionalDependencies(descriptor, idMap, contentModuleIdMap) { dependency -> pluginsToLoad.add(dependency) FileVisitResult.CONTINUE } @@ -758,8 +769,12 @@ object PluginManagerCore { } @ApiStatus.Internal - fun processAllNonOptionalDependencyIds(rootDescriptor: IdeaPluginDescriptorImpl, pluginIdMap: Map, consumer: (PluginId) -> FileVisitResult) { - processAllNonOptionalDependencies(rootDescriptor, depProcessed = HashSet(), pluginIdMap, consumer = { pluginId, _ -> consumer(pluginId) }) + fun processAllNonOptionalDependencyIds(rootDescriptor: IdeaPluginDescriptorImpl, pluginIdMap: Map, + contentModuleIdMap: Map, + consumer: (PluginId) -> FileVisitResult) { + processAllNonOptionalDependencies(rootDescriptor, depProcessed = HashSet(), pluginIdMap, contentModuleIdMap) { pluginId, _ -> + if (pluginId == null) FileVisitResult.CONTINUE else consumer(pluginId) + } } /** @@ -770,8 +785,9 @@ object PluginManagerCore { fun processAllNonOptionalDependencies( rootDescriptor: IdeaPluginDescriptorImpl, pluginIdMap: Map, + contentModuleIdMap: Map, consumer: (IdeaPluginDescriptorImpl) -> FileVisitResult, - ): Boolean = processAllNonOptionalDependencies(rootDescriptor, depProcessed = HashSet(), pluginIdMap, consumer = { _, descriptor -> + ): Boolean = processAllNonOptionalDependencies(rootDescriptor, depProcessed = HashSet(), pluginIdMap, contentModuleIdMap, consumer = { _, descriptor -> if (descriptor == null) FileVisitResult.CONTINUE else consumer(descriptor) }) @@ -779,25 +795,53 @@ object PluginManagerCore { rootDescriptor: IdeaPluginDescriptorImpl, depProcessed: MutableSet, pluginIdMap: Map, - consumer: (PluginId, IdeaPluginDescriptorImpl?) -> FileVisitResult, + contentModuleIdMap: Map, + consumer: (PluginId?, IdeaPluginDescriptorImpl?) -> FileVisitResult, ): Boolean { - for (dependencyId in getNonOptionalDependenciesIds(rootDescriptor)) { - val descriptor = pluginIdMap[dependencyId] - val pluginId = descriptor?.getPluginId() ?: dependencyId + fun processDependency(pluginId: PluginId?, moduleId: String?): Boolean { + val descriptor = if (pluginId != null) pluginIdMap[pluginId] else contentModuleIdMap[moduleId] + val pluginId = descriptor?.getPluginId() ?: pluginId when (consumer(pluginId, descriptor)) { FileVisitResult.TERMINATE -> return false FileVisitResult.CONTINUE -> { if (descriptor != null && depProcessed.add(descriptor)) { - if (!processAllNonOptionalDependencies(descriptor, depProcessed, pluginIdMap, consumer)) return false + if (!processAllNonOptionalDependencies(descriptor, depProcessed, pluginIdMap, contentModuleIdMap, consumer)) return false } } FileVisitResult.SKIP_SUBTREE -> {} FileVisitResult.SKIP_SIBLINGS -> throw UnsupportedOperationException("FileVisitResult.SKIP_SIBLINGS is not supported") } + return true + } + fun processModuleDependencies(moduleDependencies: ModuleDependencies): Boolean { + for (plugin in moduleDependencies.plugins) { + if (!processDependency(plugin.id, null)) return false + } + for (module in moduleDependencies.modules) { + if (!processDependency(null, module.name)) return false + } + return true + } + + for (dependency in rootDescriptor.dependencies) { + if (!dependency.isOptional) { + if (!processDependency(dependency.pluginId, null)) return false + } + } + + if (!processModuleDependencies(rootDescriptor.moduleDependencies)) return false + + if (rootDescriptor is PluginMainDescriptor) { + for (contentModule in rootDescriptor.contentModules) { + if (contentModule.moduleLoadingRule.required && !processModuleDependencies(contentModule.moduleDependencies)) { + return false + } + } } return true } + @Deprecated("Use [processAllNonOptionalDependencyIds] instead, this function doesn't process dependencies on modules") @ApiStatus.Internal fun getNonOptionalDependenciesIds(descriptor: IdeaPluginDescriptorImpl): Set { val dependencies = LinkedHashSet() @@ -857,9 +901,10 @@ object PluginManagerCore { @ApiStatus.Internal fun dependsOnUltimateOptionally(pluginDescriptor: IdeaPluginDescriptor?): Boolean { if (pluginDescriptor == null || pluginDescriptor !is IdeaPluginDescriptorImpl || !isDisabled(ULTIMATE_PLUGIN_ID)) return false - val idMap = buildPluginIdMap() + val pluginIdMap = buildPluginIdMap() + val contentModuleIdMap = getPluginSet().buildContentModuleIdMap() return pluginDescriptor.contentModules.any { contentModule -> - !contentModule.moduleLoadingRule.required && !processAllNonOptionalDependencies(contentModule, idMap) { descriptorImpl -> + !contentModule.moduleLoadingRule.required && !processAllNonOptionalDependencies(contentModule, pluginIdMap, contentModuleIdMap) { descriptorImpl -> when (descriptorImpl.pluginId) { ULTIMATE_PLUGIN_ID -> FileVisitResult.TERMINATE else -> FileVisitResult.CONTINUE @@ -928,20 +973,25 @@ fun getPluginDistDirByClass(aClass: Class<*>): Path? { @ApiStatus.Internal fun pluginRequiresUltimatePluginButItsDisabled(plugin: PluginId): Boolean { val idMap = PluginManagerCore.buildPluginIdMap() - return pluginRequiresUltimatePluginButItsDisabled(plugin, idMap) + val contentModuleIdMap = getPluginSet().buildContentModuleIdMap() + return pluginRequiresUltimatePluginButItsDisabled(plugin, idMap, contentModuleIdMap) } @ApiStatus.Internal -fun pluginRequiresUltimatePluginButItsDisabled(plugin: PluginId, pluginMap: Map): Boolean { +fun pluginRequiresUltimatePluginButItsDisabled(plugin: PluginId, pluginMap: Map, + contentModuleIdMap: Map): Boolean { if (!isDisabled(ULTIMATE_PLUGIN_ID)) return false - return pluginRequiresUltimatePlugin(plugin, pluginMap) + return pluginRequiresUltimatePlugin(plugin, pluginMap, contentModuleIdMap) } @ApiStatus.Internal -fun pluginRequiresUltimatePlugin(plugin: PluginId, pluginMap: Map = PluginManagerCore.buildPluginIdMap()): Boolean { +fun pluginRequiresUltimatePlugin(plugin: PluginId, + pluginMap: Map, + contentModuleMap: Map, +): Boolean { val rootDescriptor = pluginMap[plugin] if (rootDescriptor == null) return false - return !processAllNonOptionalDependencies(rootDescriptor, pluginMap) { descriptorImpl -> + return !processAllNonOptionalDependencies(rootDescriptor, pluginMap, contentModuleMap) { descriptorImpl -> when (descriptorImpl.pluginId) { ULTIMATE_PLUGIN_ID -> FileVisitResult.TERMINATE else -> FileVisitResult.CONTINUE diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PluginSet.kt b/platform/core-impl/src/com/intellij/ide/plugins/PluginSet.kt index cf3bc0409536..61d59d116d16 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PluginSet.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginSet.kt @@ -80,4 +80,21 @@ class PluginSet internal constructor( // FIXME this is a bad way to treat ambiguous plugin ids return pluginIdResolutionMap.asSequence().filter { it.value.size == 1 }.associateTo(HashMap()) { it.key to it.value[0] } } + + /** + * Returns a map from content module ID (name) to the corresponding descriptor from all plugins, not only enabled. + */ + fun buildContentModuleIdMap(): Map { + val result = HashMap() + val enabledPluginIds = enabledPlugins.mapTo(HashSet()) { it.pluginId } + for (plugin in allPlugins) { + if (plugin.pluginId !in enabledPluginIds) { + plugin.contentModules.associateByTo(result, ContentModuleDescriptor::moduleName) + } + } + for (plugin in enabledPlugins) { + plugin.contentModules.associateByTo(result, ContentModuleDescriptor::moduleName) + } + return result + } } \ No newline at end of file diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PluginSetBuilder.kt b/platform/core-impl/src/com/intellij/ide/plugins/PluginSetBuilder.kt index f7cad58ca3bc..e152ab26b1fa 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PluginSetBuilder.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginSetBuilder.kt @@ -286,11 +286,12 @@ class PluginSetBuilder(@JvmField val unsortedPlugins: Set) descriptor: IdeaPluginDescriptorImpl, idMap: Map, fullIdMap: Map, + fullContentModuleIdMap: Map, isPluginDisabled: (PluginId) -> Boolean, errors: MutableMap, disabledModuleToProblematicPlugin: Map, ): PluginNonLoadReason? { - val isNotifyUser = !descriptor.isImplementationDetail && !pluginRequiresUltimatePluginButItsDisabled(descriptor.pluginId, fullIdMap) + val isNotifyUser = !descriptor.isImplementationDetail && !pluginRequiresUltimatePluginButItsDisabled(descriptor.pluginId, fullIdMap, fullContentModuleIdMap) for (incompatibleId in descriptor.incompatiblePlugins) { if (!enabledPluginIds.containsKey(incompatibleId) || isPluginDisabled(incompatibleId)) { continue diff --git a/platform/platform-impl/src/com/intellij/diagnostic/DisablePluginsDialog.kt b/platform/platform-impl/src/com/intellij/diagnostic/DisablePluginsDialog.kt index 01aeaa4e95f7..615ebb61feb6 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/DisablePluginsDialog.kt +++ b/platform/platform-impl/src/com/intellij/diagnostic/DisablePluginsDialog.kt @@ -67,11 +67,13 @@ internal object DisablePluginsDialog { @JvmStatic private fun morePluginsAffected(pluginIdsToDisable: Set): Boolean { val pluginIdMap = PluginManagerCore.buildPluginIdMap() + val contentModuleIdMap = PluginManagerCore.getPluginSet().buildContentModuleIdMap() + for (rootDescriptor in PluginManagerCore.plugins) { if (!rootDescriptor.isEnabled || pluginIdsToDisable.contains(rootDescriptor.pluginId)) { continue } - if (!PluginManagerCore.processAllNonOptionalDependencies((rootDescriptor as IdeaPluginDescriptorImpl), pluginIdMap) { descriptor -> + if (!PluginManagerCore.processAllNonOptionalDependencies((rootDescriptor as IdeaPluginDescriptorImpl), pluginIdMap, contentModuleIdMap) { descriptor -> when { descriptor.isEnabled -> if (pluginIdsToDisable.contains(descriptor.pluginId)) FileVisitResult.TERMINATE else FileVisitResult.CONTINUE diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPaidPluginsService.kt b/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPaidPluginsService.kt index 4f634e7b10b7..f728a455569e 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPaidPluginsService.kt +++ b/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPaidPluginsService.kt @@ -75,13 +75,14 @@ class DynamicPaidPluginsService(private val cs: CoroutineScope) { val disabledPlugins = DisabledPluginsState.getDisabledIds() val pluginSet = PluginManagerCore.getPluginSet() val pluginIdMap = PluginManagerCore.buildPluginIdMap() + val contentModuleIdMap = pluginSet.buildContentModuleIdMap() val loadedPlugins = pluginSet.enabledPlugins.toSet() val pluginsToEnable = pluginSet.allPlugins.filter { !disabledPlugins.contains(it.pluginId) && !loadedPlugins.contains(it) && - pluginRequiresUltimatePlugin(it.pluginId, pluginIdMap) && - !pluginRequiresDisabledPlugin(it.pluginId, pluginIdMap, disabledPlugins) + pluginRequiresUltimatePlugin(it.pluginId, pluginIdMap, contentModuleIdMap) && + !pluginRequiresDisabledPlugin(it.pluginId, pluginIdMap, contentModuleIdMap, disabledPlugins) } if (pluginsToEnable.isEmpty()) { @@ -177,10 +178,11 @@ class DynamicPaidPluginsService(private val cs: CoroutineScope) { } } -private fun pluginRequiresDisabledPlugin(plugin: PluginId, pluginMap: Map, disabledPluginIds: Set): Boolean { +private fun pluginRequiresDisabledPlugin(plugin: PluginId, pluginMap: Map, + contentModuleIdMap: Map, disabledPluginIds: Set): Boolean { if (disabledPluginIds.isEmpty()) return false val rootDescriptor = pluginMap[plugin] ?: return false - return !processAllNonOptionalDependencies(rootDescriptor, pluginMap) { descriptorImpl -> + return !processAllNonOptionalDependencies(rootDescriptor, pluginMap, contentModuleIdMap) { descriptorImpl -> if (disabledPluginIds.contains(descriptorImpl.pluginId)) FileVisitResult.TERMINATE else FileVisitResult.CONTINUE } diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/newui/DefaultUiPluginManagerController.kt b/platform/platform-impl/src/com/intellij/ide/plugins/newui/DefaultUiPluginManagerController.kt index bd862f48c117..8bc6a64cb037 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/newui/DefaultUiPluginManagerController.kt +++ b/platform/platform-impl/src/com/intellij/ide/plugins/newui/DefaultUiPluginManagerController.kt @@ -125,7 +125,8 @@ object DefaultUiPluginManagerController : UiPluginManagerController { needRestart = true } val pluginIdMap = buildPluginIdMap() - val pluginsToEnable = updatePluginDependencies(session, pluginIdMap) + val contentModuleIdMap = getPluginSet().buildContentModuleIdMap() + val pluginsToEnable = updatePluginDependencies(session, pluginIdMap, contentModuleIdMap) assertCanApply(session, pluginIdMap) val pluginEnabler = PluginEnabler.getInstance() @@ -342,7 +343,8 @@ object DefaultUiPluginManagerController : UiPluginManagerController { override fun prepareToUninstall(pluginsToUninstall: List): PrepareToUninstallResult { val applicationInfo = ApplicationInfoEx.getInstanceEx() val idMap = buildPluginIdMap() - val dependentsMap = pluginsToUninstall.associateWith { getDependents(it, applicationInfo, idMap).map { it.name } } + val contentModuleIdMap = getPluginSet().buildContentModuleIdMap() + val dependentsMap = pluginsToUninstall.associateWith { getDependents(it, applicationInfo, idMap, contentModuleIdMap).map { it.name } } val bundledPlugins = pluginsToUninstall .mapNotNull { idMap[it] } .filter { isBundledUpdate(it) } @@ -369,10 +371,11 @@ object DefaultUiPluginManagerController : UiPluginManagerController { setNewEnabled(descriptors, tempEnabled, action) val pluginIdMap: Map = buildPluginIdMap() + val contentModuleIdMap = getPluginSet().buildContentModuleIdMap() val descriptorsToUpdate = if (action.isEnable) { - getDependenciesToEnable(descriptors, tempEnabled, pluginIdMap) + getDependenciesToEnable(descriptors, tempEnabled, pluginIdMap, contentModuleIdMap) } - else getDependentsToDisable(descriptorIds, tempEnabled, pluginIdMap) + else getDependentsToDisable(descriptorIds, tempEnabled, pluginIdMap, contentModuleIdMap) val pluginNamesToUpdate = descriptorsToUpdate .filter { !InstalledPluginsTableModel.isHiddenImplementationDetail(it) } @@ -384,7 +387,7 @@ object DefaultUiPluginManagerController : UiPluginManagerController { return SetEnabledStateResult(pluginNamesToUpdate, allDescriptorsToUpdate.map { it.pluginId }.toSet()) } else { - return enableDependencies(session, allDescriptorsToUpdate, action, pluginIdMap) + return enableDependencies(session, allDescriptorsToUpdate, action, pluginIdMap, contentModuleIdMap) } } @@ -396,7 +399,7 @@ object DefaultUiPluginManagerController : UiPluginManagerController { val session = findSession(sessionId) ?: return SetEnabledStateResult() val descriptors = descriptorIds.toPluginDescriptors() val action = if (enable) PluginEnableDisableAction.ENABLE_GLOBALLY else PluginEnableDisableAction.DISABLE_GLOBALLY - return enableDependencies(session, descriptors, action, buildPluginIdMap()) + return enableDependencies(session, descriptors, action, buildPluginIdMap(), getPluginSet().buildContentModuleIdMap()) } override fun enableRequiredPlugins(sessionId: String, pluginId: PluginId): Set { @@ -441,22 +444,26 @@ object DefaultUiPluginManagerController : UiPluginManagerController { override fun hasPluginsAvailableForEnableDisable(pluginIds: List): Boolean { val idMap = buildPluginIdMap() - return pluginIds.any { !pluginRequiresUltimatePluginButItsDisabled(it, idMap) } + val contentModuleIdMap = getPluginSet().buildContentModuleIdMap() + return pluginIds.any { !pluginRequiresUltimatePluginButItsDisabled(it, idMap, contentModuleIdMap) } } override fun isPluginRequiresUltimateButItIsDisabled(pluginId: PluginId): Boolean { val idMap = buildPluginIdMap() - return pluginRequiresUltimatePluginButItsDisabled(pluginId, idMap) + val contentModuleIdMap = getPluginSet().buildContentModuleIdMap() + return pluginRequiresUltimatePluginButItsDisabled(pluginId, idMap, contentModuleIdMap) } override fun hasPluginRequiresUltimateButItsDisabled(pluginIds: List): Boolean { val idMap = buildPluginIdMap() - return pluginIds.any { pluginRequiresUltimatePluginButItsDisabled(it, idMap) } + val contentModuleIdMap = getPluginSet().buildContentModuleIdMap() + return pluginIds.any { pluginRequiresUltimatePluginButItsDisabled(it, idMap, contentModuleIdMap) } } override fun filterPluginsRequiringUltimateButItsDisabled(pluginIds: List): List { val idMap = buildPluginIdMap() - return pluginIds.filter { pluginRequiresUltimatePluginButItsDisabled(it, idMap) } + val contentModuleIdMap = getPluginSet().buildContentModuleIdMap() + return pluginIds.filter { pluginRequiresUltimatePluginButItsDisabled(it, idMap, contentModuleIdMap) } } override fun allowLoadUnloadWithoutRestart(pluginId: PluginId): Boolean { @@ -471,7 +478,7 @@ object DefaultUiPluginManagerController : UiPluginManagerController { override fun updatePluginDependencies(sessionId: String): Set { val session = findSession(sessionId) ?: return emptySet() - return updatePluginDependencies(session, null) + return updatePluginDependencies(session, null, null) } override fun executePluginsSearch(query: String, count: Int, includeIncompatible: Boolean): PluginSearchResult { @@ -604,10 +611,11 @@ object DefaultUiPluginManagerController : UiPluginManagerController { allDescriptorsToUpdate: List, action: PluginEnableDisableAction, pluginIdMap: Map, + contentModuleIdMap: Map, ): SetEnabledStateResult { val changedStates = setNewEnabled(allDescriptorsToUpdate, session.pluginStates, action, { descriptor, pair -> handleBeforeChangeEnableState(session, descriptor, pair) }) - val pluginsToEnable = updatePluginDependencies(session, pluginIdMap) + val pluginsToEnable = updatePluginDependencies(session, pluginIdMap, contentModuleIdMap) pluginsToEnable.forEach { changedStates[it] = true } return SetEnabledStateResult(changedStates = changedStates) } @@ -792,10 +800,11 @@ object DefaultUiPluginManagerController : UiPluginManagerController { descriptors: List, enabledMap: Map, pluginIdMap: Map, + contentModuleIdMap: Map, ): List { val result = mutableListOf() for (descriptor in descriptors) { - PluginManagerCore.processAllNonOptionalDependencies(descriptor, pluginIdMap) { dependency -> + PluginManagerCore.processAllNonOptionalDependencies(descriptor, pluginIdMap, contentModuleIdMap) { dependency -> val dependencyId = dependency.pluginId val state = enabledMap[dependencyId] @@ -812,9 +821,11 @@ object DefaultUiPluginManagerController : UiPluginManagerController { private fun updatePluginDependencies( session: PluginManagerSession, pluginIdMap: Map?, + contentModuleIdMap: Map?, ): Set { val pluginsToEnable = mutableSetOf() var pluginIdMap = pluginIdMap + var contentModuleIdMap = contentModuleIdMap session.dependentToRequiredListMap.clear() val pluginsState = InstalledPluginsState.getInstance() @@ -828,10 +839,13 @@ object DefaultUiPluginManagerController : UiPluginManagerController { if (pluginIdMap == null) { pluginIdMap = buildPluginIdMap() } + if (contentModuleIdMap == null) { + contentModuleIdMap = getPluginSet().buildContentModuleIdMap() + } val loaded: Boolean = session.pluginStates.contains(pluginId) if (rootDescriptor is IdeaPluginDescriptorImpl) { - PluginManagerCore.processAllNonOptionalDependencyIds(rootDescriptor, pluginIdMap) { depId: PluginId -> + PluginManagerCore.processAllNonOptionalDependencyIds(rootDescriptor, pluginIdMap, contentModuleIdMap) { depId: PluginId -> if (depId == pluginId) { return@processAllNonOptionalDependencyIds FileVisitResult.CONTINUE } @@ -877,6 +891,7 @@ object DefaultUiPluginManagerController : UiPluginManagerController { pluginIds: List, enabledMap: MutableMap, pluginIdMap: Map, + contentModuleIdMap: Map, ): List { val result = mutableListOf() @@ -887,7 +902,7 @@ object DefaultUiPluginManagerController : UiPluginManagerController { continue } - PluginManagerCore.processAllNonOptionalDependencies(descriptor, pluginIdMap) { dependency: IdeaPluginDescriptorImpl? -> + PluginManagerCore.processAllNonOptionalDependencies(descriptor, pluginIdMap, contentModuleIdMap) { dependency: IdeaPluginDescriptorImpl? -> val dependencyId = dependency!!.getPluginId() if (!enabledMap.contains(dependencyId)) { return@processAllNonOptionalDependencies FileVisitResult.TERMINATE @@ -908,6 +923,7 @@ object DefaultUiPluginManagerController : UiPluginManagerController { rootId: PluginId, applicationInfo: ApplicationInfoEx, pluginIdMap: Map, + contentModuleIdMap: Map, ): List { val result = mutableListOf() for (entry in pluginIdMap.entries) { @@ -920,7 +936,7 @@ object DefaultUiPluginManagerController : UiPluginManagerController { continue } - PluginManagerCore.processAllNonOptionalDependencies(descriptor, pluginIdMap) { + PluginManagerCore.processAllNonOptionalDependencies(descriptor, pluginIdMap, contentModuleIdMap) { if (it.pluginId == rootId) { result.add(descriptor) return@processAllNonOptionalDependencies FileVisitResult.TERMINATE diff --git a/platform/platform-impl/src/com/intellij/ide/ui/PluginBooleanOptionDescriptor.java b/platform/platform-impl/src/com/intellij/ide/ui/PluginBooleanOptionDescriptor.java index bea331bd61b8..9ac896d42f6a 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/PluginBooleanOptionDescriptor.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/PluginBooleanOptionDescriptor.java @@ -63,9 +63,10 @@ public final class PluginBooleanOptionDescriptor extends BooleanOptionDescriptio } Map pluginIdMap = PluginManagerCore.INSTANCE.buildPluginIdMap(); + Map<@NotNull String, @NotNull ContentModuleDescriptor> contentModuleIdMap = PluginManagerCore.getPluginSet().buildContentModuleIdMap(); Collection autoSwitchedDescriptors = enable ? - getDependenciesToEnable(descriptors, pluginIdMap) : - getDependentsToDisable(descriptors, pluginIdMap); + getDependenciesToEnable(descriptors, pluginIdMap, contentModuleIdMap) : + getDependentsToDisable(descriptors, pluginIdMap, contentModuleIdMap); PluginEnabler pluginEnabler = PluginEnabler.getInstance(); boolean appliedWithoutRestart = enable ? @@ -131,7 +132,8 @@ public final class PluginBooleanOptionDescriptor extends BooleanOptionDescriptio } private static @NotNull Collection getDependenciesToEnable(@NotNull Collection descriptors, - @NotNull Map pluginIdMap) { + @NotNull Map pluginIdMap, + @NotNull Map contentModuleIdMap) { Set result = new LinkedHashSet<>(); for (IdeaPluginDescriptor descriptor : descriptors) { @@ -141,7 +143,7 @@ public final class PluginBooleanOptionDescriptor extends BooleanOptionDescriptio continue; } - PluginManagerCore.INSTANCE.processAllNonOptionalDependencies((IdeaPluginDescriptorImpl)descriptor, pluginIdMap, dependency -> + PluginManagerCore.INSTANCE.processAllNonOptionalDependencies((IdeaPluginDescriptorImpl)descriptor, pluginIdMap, contentModuleIdMap, dependency -> PluginManagerCore.CORE_ID.equals(dependency.getPluginId()) || (PluginManagerCore.ULTIMATE_PLUGIN_ID.equals(dependency.getPluginId()) && PluginManagerCore.isDisabled(PluginManagerCore.ULTIMATE_PLUGIN_ID)) || @@ -155,14 +157,15 @@ public final class PluginBooleanOptionDescriptor extends BooleanOptionDescriptio } private static @NotNull Collection getDependentsToDisable(@NotNull Collection descriptors, - @NotNull Map pluginIdMap) { + @NotNull Map pluginIdMap, + @NotNull Map contentModuleIdMap) { Set result = new LinkedHashSet<>(); ApplicationInfoEx applicationInfo = ApplicationInfoEx.getInstanceEx(); for (IdeaPluginDescriptor descriptor : descriptors) { result.add(descriptor); - result.addAll(DefaultUiPluginManagerController.INSTANCE.getDependents(descriptor.getPluginId(), applicationInfo, pluginIdMap)); + result.addAll(DefaultUiPluginManagerController.INSTANCE.getDependents(descriptor.getPluginId(), applicationInfo, pluginIdMap, contentModuleIdMap)); } return Collections.unmodifiableSet(result); diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginDependenciesTest.kt b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginDependenciesTest.kt index e1511915d445..56ba3fc105d7 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginDependenciesTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginDependenciesTest.kt @@ -172,10 +172,31 @@ internal class PluginDependenciesTest { assertFirstErrorContains("sample.plugin", "requires plugin", "bar") assertNonOptionalDependenciesIds(result, "sample.plugin", "bar") } + + @Test + fun `plugin is not loaded if required module depends on a module from disabled plugin`() { + `bar-plugin with module bar`() + plugin("sample.plugin") { + content { + module("required.module", ModuleLoadingRule.REQUIRED) { + packagePrefix = "required" + dependencies { + module("bar") + } + } + } + }.buildDir(pluginDirPath.resolve("sample-plugin")) + val result = buildPluginSet(disabledPluginIds = arrayOf("bar-plugin")) + assertThat(result).doesNotHaveEnabledPlugins() + assertFirstErrorContains("sample.plugin", "requires plugin", "bar-plugin"/*, "to be enabled"*/) //todo fix not loading reason + assertNonOptionalDependenciesIds(result, "sample.plugin", "bar-plugin") + } private fun assertNonOptionalDependenciesIds(result: PluginSet, pluginId: String, vararg dependencyPluginId: String) { - val actualDependencies = HashSet() - PluginManagerCore.processAllNonOptionalDependencyIds(result.getPlugin(pluginId), result.buildPluginIdMap()) { + val actualDependencies = HashSet() + val pluginIdMap = result.buildPluginIdMap() + val contentModuleIdMap = result.buildContentModuleIdMap() + PluginManagerCore.processAllNonOptionalDependencyIds(result.getPlugin(pluginId), pluginIdMap, contentModuleIdMap) { actualDependencies.add(it.idString) FileVisitResult.CONTINUE } diff --git a/platform/settings-sync-core/src/com/intellij/settingsSync/core/plugins/SettingsSyncPluginManager.kt b/platform/settings-sync-core/src/com/intellij/settingsSync/core/plugins/SettingsSyncPluginManager.kt index adaecc7dcdf2..3642196cdc12 100644 --- a/platform/settings-sync-core/src/com/intellij/settingsSync/core/plugins/SettingsSyncPluginManager.kt +++ b/platform/settings-sync-core/src/com/intellij/settingsSync/core/plugins/SettingsSyncPluginManager.kt @@ -1,5 +1,6 @@ package com.intellij.settingsSync.core.plugins +import com.intellij.ide.plugins.ContentModuleDescriptor import com.intellij.ide.plugins.IdeaPluginDescriptor import com.intellij.ide.plugins.IdeaPluginDescriptorImpl import com.intellij.ide.plugins.PluginEnableStateChangedListener @@ -88,7 +89,8 @@ internal class SettingsSyncPluginManager(private val cs: CoroutineScope) : Dispo } val pluginIdMap = PluginManagerCore.buildPluginIdMap() - + val contentModuleIdMap = PluginManagerCore.getPluginSet().buildContentModuleIdMap() + for (plugin in currentIdePlugins) { val id = plugin.pluginId if (!isPluginSynceable(id) || PluginManagerProxy.getInstance().isIncompatible(plugin)) { @@ -102,7 +104,7 @@ internal class SettingsSyncPluginManager(private val cs: CoroutineScope) : Dispo } else if ( PluginManagerCore.isDisabled(PluginManagerCore.ULTIMATE_PLUGIN_ID) && - isUltimate(plugin, pluginIdMap) + isUltimate(plugin, pluginIdMap, contentModuleIdMap) ) { if (LOG.isDebugEnabled) { LOG.debug("Skipped syncing ultimate plugin ${plugin.pluginId}") @@ -125,9 +127,10 @@ internal class SettingsSyncPluginManager(private val cs: CoroutineScope) : Dispo private fun isUltimate( plugin: IdeaPluginDescriptor, pluginIdMap: Map, + contentModuleIdMap: Map, ): Boolean { var isUltimate = false - PluginManagerCore.processAllNonOptionalDependencyIds(plugin as IdeaPluginDescriptorImpl, pluginIdMap) { + PluginManagerCore.processAllNonOptionalDependencyIds(plugin as IdeaPluginDescriptorImpl, pluginIdMap, contentModuleIdMap) { if (it == PluginManagerCore.ULTIMATE_PLUGIN_ID) { isUltimate = true }