From c0b2f613a4109bce9f95f49f6adf3ddbccdfe594 Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Fri, 30 Jul 2021 21:32:24 +0200 Subject: [PATCH] configuration and sorting using modules and not plugins GitOrigin-RevId: 632049b4db917b88f453d774d8a1e404cb4641e6 --- .../intellij/util/lang/PathClassLoader.java | 14 - .../intellij/ide/plugins/CachingSemiGraph.kt | 245 ++++---- .../ide/plugins/ClassLoaderConfigurator.kt | 585 ++++++----------- .../ide/plugins/ClassPathXmlPathResolver.kt | 8 +- .../plugins/DescriptorListLoadingContext.kt | 2 +- .../ide/plugins/DynamicPluginListener.kt | 4 +- .../ide/plugins/IdeaPluginDescriptorImpl.kt | 103 ++- .../ide/plugins/ImmutableZipFileDataLoader.kt | 15 +- .../plugins/ModuleDependenciesDescriptor.kt | 8 +- .../ide/plugins/PluginDescriptorLoader.kt | 234 ++++--- .../ide/plugins/PluginLoadingResult.kt | 60 +- .../intellij/ide/plugins/PluginManager.java | 8 +- .../ide/plugins/PluginManagerCore.java | 439 +++---------- .../ide/plugins/PluginManagerState.java | 22 - .../src/com/intellij/ide/plugins/PluginSet.kt | 132 +--- .../intellij/ide/plugins/PluginSetBuilder.kt | 314 ++++++++++ .../ide/plugins/PluginXmlPathResolver.kt | 4 +- .../src/com/intellij/ide/plugins/XmlReader.kt | 9 +- .../ide/plugins/cl/PluginClassLoader.java | 45 +- .../PrecomputedExtensionModel.kt | 30 +- .../IdeIdeaFormatWriter.kt | 18 +- .../impl/IntentionHintComponent.java | 5 +- .../impl/FileTemplatesLoader.java | 5 +- .../openapi/module/impl/ModuleImpl.java | 5 +- .../legacyBridge/module/ModuleBridgeImpl.kt | 22 +- .../impl/CheckRequiredPluginsActivity.java | 2 +- .../ide/plugins/ClassLoaderTreeChecker.kt | 2 +- .../intellij/ide/plugins/DynamicPlugins.kt | 592 +++++++++--------- .../intellij/ide/plugins/PluginInstaller.java | 2 +- .../ide/plugins/newui/MyPluginModel.java | 2 +- .../com/intellij/idea/ApplicationLoader.kt | 25 +- .../com/intellij/idea/CommandLineArgs.java | 6 +- .../src/com/intellij/idea/StartupUtil.java | 7 +- .../com/intellij/idea/ZipFilePoolImpl.java | 7 +- .../actionSystem/impl/ActionManagerImpl.java | 340 +++++----- .../application/impl/ApplicationImpl.java | 12 +- .../client/ClientAwareComponentManager.kt | 16 +- .../openapi/client/ClientSessionImpl.kt | 28 +- .../openapi/project/impl/DefaultProject.java | 6 +- .../openapi/project/impl/ProjectExImpl.kt | 2 +- .../openapi/project/impl/projectLoader.kt | 7 +- .../testData/plugins/sort/moduleSort.txt | 4 + .../testData/plugins/sort/moduleSort.xml | 35 ++ .../plugins/sort/simplePluginSort.txt | 52 +- .../testData/plugins/sort/ultimatePlugins.txt | 4 +- .../plugins/ClassLoaderConfiguratorTest.kt | 17 +- .../ide/plugins/DynamicPluginsTest.kt | 10 +- .../ide/plugins/DynamicPluginsTestUtil.kt | 8 +- .../ide/plugins/PluginDescriptorTest.kt | 14 +- .../ide/plugins/PluginManagerTest.java | 92 ++- .../intellij/util/graph/ChunkGraphTest.java | 24 +- .../module/ModuleManagerBridgeImpl.kt | 9 +- .../ide/legacyBridge/ModuleBridge.kt | 10 +- .../serviceContainer/ComponentManagerImpl.kt | 61 +- .../serviceContainer/containerUtil.kt | 9 - .../intellij/simpleApplicationBootstrapper.kt | 21 +- .../LightPlatformCodeInsightTestCase.java | 14 +- .../com/intellij/util/lang/Java11Shim.java | 11 +- .../intellij/util/lang/UrlClassLoader.java | 8 +- .../com/intellij/util/lang/ZipFilePool.java | 5 +- .../platform/util/plugins/dataLoader.kt | 11 +- .../com/intellij/util/graph/DFSTBuilder.java | 197 ++++-- .../util/graph/OutboundSemiGraph.java | 22 +- .../performance/TailRecursionInspection.java | 6 +- ...ResourceBundlePropertiesUpdateManager.java | 16 +- 65 files changed, 2008 insertions(+), 2044 deletions(-) delete mode 100644 platform/core-impl/src/com/intellij/ide/plugins/PluginManagerState.java create mode 100644 platform/core-impl/src/com/intellij/ide/plugins/PluginSetBuilder.kt create mode 100644 platform/platform-tests/testData/plugins/sort/moduleSort.txt create mode 100644 platform/platform-tests/testData/plugins/sort/moduleSort.xml diff --git a/platform/bootstrap/src/com/intellij/util/lang/PathClassLoader.java b/platform/bootstrap/src/com/intellij/util/lang/PathClassLoader.java index 08d774b4370a..617736b25680 100644 --- a/platform/bootstrap/src/com/intellij/util/lang/PathClassLoader.java +++ b/platform/bootstrap/src/com/intellij/util/lang/PathClassLoader.java @@ -15,7 +15,6 @@ public final class PathClassLoader extends UrlClassLoader { private static final Function RESOURCE_FILE_FACTORY = file -> new ZipResourceFile(file); private static final boolean isParallelCapable = registerAsParallelCapable(); - private static final ClassLoader appClassLoader = PathClassLoader.class.getClassLoader(); private final BytecodeTransformer transformer; @@ -51,19 +50,6 @@ public final class PathClassLoader extends UrlClassLoader { transformer = null; registerInClassLoaderValueMap(parent, this); - - // who knows - assert appClassLoader != this; - } - - @Override - protected Class findClass(@NotNull String name) throws ClassNotFoundException { - if (name.startsWith("com.intellij.util.lang.")) { - return appClassLoader.loadClass(name); - } - else { - return super.findClass(name); - } } @Override diff --git a/platform/core-impl/src/com/intellij/ide/plugins/CachingSemiGraph.kt b/platform/core-impl/src/com/intellij/ide/plugins/CachingSemiGraph.kt index aadc140f2c3a..84ba216235ed 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/CachingSemiGraph.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/CachingSemiGraph.kt @@ -1,26 +1,23 @@ // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -@file:Suppress("ReplacePutWithAssignment") +@file:Suppress("ReplacePutWithAssignment", "ReplaceGetOrSet") package com.intellij.ide.plugins -import com.intellij.openapi.extensions.PluginId -import com.intellij.util.graph.Graph +import com.intellij.util.ArrayUtilRt +import com.intellij.util.graph.DFSTBuilder import com.intellij.util.lang.Java11Shim -import org.jetbrains.annotations.ApiStatus import java.util.* -import java.util.function.Supplier -import java.util.stream.Stream +import java.util.function.ToIntFunction -@ApiStatus.Internal -class CachingSemiGraph private constructor( - private val nodes: Collection, - private val pluginToDirectDependencies: Map>, -) : Graph { +internal class CachingSemiGraph private constructor( + @JvmField val nodes: Collection, + @JvmField val moduleToDirectDependencies: Map>, +) : DFSTBuilder.DFSTBuilderAwareGraph { private val outs = IdentityHashMap>() init { val edges = Collections.newSetFromMap>(HashMap()) for (node in nodes) { - for (inNode in (pluginToDirectDependencies[node] ?: continue)) { + for (inNode in (moduleToDirectDependencies.get(node) ?: continue)) { if (edges.add(AbstractMap.SimpleImmutableEntry(inNode, node))) { // not a duplicate edge outs.computeIfAbsent(inNode) { ArrayList() }.add(node) @@ -30,132 +27,152 @@ class CachingSemiGraph private constructor( } companion object { - fun createPluginIdGraph( - descriptors: Collection, - pluginSet: PluginSet, - withOptional: Boolean, - ): CachingSemiGraph { - val hasAllModules = pluginSet.isPluginEnabled(PluginManagerCore.ALL_MODULES_MARKER) - val javaDep = Supplier { - pluginSet.findEnabledPlugin(PluginManagerCore.JAVA_MODULE_ID) - } + fun createModuleGraph(plugins: List): CachingSemiGraph { + val moduleMap = HashMap(plugins.size * 2) + val modules = ArrayList(moduleMap.size) + for (module in plugins) { + moduleMap.put(module.pluginId.idString, module) + for (v1Module in module.modules) { + moduleMap.put(v1Module.idString, module) + } - val uniqueCheck = Collections.newSetFromMap(IdentityHashMap()) - val pluginToDirectDependencies = IdentityHashMap>(descriptors.size) - val list = ArrayList(32) - val ids = arrayOfNulls(descriptors.size) - var index = 0 - for (descriptor in descriptors) { - ids[index++] = descriptor.id - collectDirectDependencies(descriptor, pluginSet, withOptional, hasAllModules, javaDep, uniqueCheck, list) - if (!list.isEmpty()) { - pluginToDirectDependencies.put(descriptor.id, Java11Shim.INSTANCE.copyOf(list)) - list.clear() + modules.add(module) + for (item in module.content.modules) { + val subModule = item.requireDescriptor() + modules.add(subModule) + moduleMap.put(item.name, subModule) } } - Arrays.sort(ids) - return CachingSemiGraph( - Java11Shim.INSTANCE.listOf(ids), - pluginToDirectDependencies, - ) - } - } - override fun getOut(n: Node): Iterator = outs.get(n)?.iterator() ?: Collections.emptyIterator() + val hasAllModules = moduleMap.containsKey(PluginManagerCore.ALL_MODULES_MARKER.idString) + val result: MutableSet = Collections.newSetFromMap(IdentityHashMap()) + val moduleToDirectDependencies = IdentityHashMap>(modules.size) + for (module in modules) { + val implicitDep = if (hasAllModules) getImplicitDependency(module, moduleMap) else null + if (implicitDep != null) { + if (module === implicitDep) { + PluginManagerCore.getLogger().error("Plugin $module depends on self") + } + else { + result.add(implicitDep) + } + } - override fun getNodes() = nodes + collectDirectDependenciesInOldFormat(module, moduleMap, result) + collectDirectDependenciesInNewFormat(module, moduleMap, result) - override fun getIn(node: Node): Iterator { - return pluginToDirectDependencies.get(node)?.iterator() ?: Collections.emptyIterator() - } + if (module.moduleName != null && module.pluginId != PluginManagerCore.CORE_ID) { + // add main as implicit dependency + val main = moduleMap.get(module.pluginId.idString)!! + assert(main !== module) + result.add(main) + } - fun getInStream(node: Node): Stream { - return pluginToDirectDependencies.get(node)?.stream() ?: Stream.empty() - } -} - -private fun collectDirectDependencies(rootDescriptor: IdeaPluginDescriptorImpl, - pluginSet: PluginSet, - withOptional: Boolean, - hasAllModules: Boolean, - javaDep: Supplier, - uniqueCheck: MutableSet, - result: MutableList) { - val implicitDep = if (hasAllModules) PluginManagerCore.getImplicitDependency(rootDescriptor, javaDep) else null - uniqueCheck.clear() - if (implicitDep != null) { - if (rootDescriptor === implicitDep) { - PluginManagerCore.getLogger().error("Plugin $rootDescriptor depends on self") - } - else { - uniqueCheck.add(implicitDep.id) - result.add(implicitDep.id) - } - } - - for (dependency in rootDescriptor.pluginDependencies) { - if (!withOptional && dependency.isOptional) { - continue + if (!result.isEmpty()) { + moduleToDirectDependencies.put(module, Java11Shim.INSTANCE.copyOfCollection(result)) + result.clear() + } + } + return CachingSemiGraph(modules, moduleToDirectDependencies) } - // check for missing optional dependency - val dep = pluginSet.findEnabledPlugin(dependency.pluginId) ?: continue - - // if 'dep' refers to a module we need to check the real plugin containing this module only if it's still enabled, - // otherwise the graph will be inconsistent - - // ultimate plugin it is combined plugin, where some included XML can define dependency on ultimate explicitly and for now not clear, - // can be such requirements removed or not - if (rootDescriptor === dep) { - if (PluginManagerCore.CORE_ID != rootDescriptor.pluginId) { - PluginManagerCore.getLogger().error("Plugin $rootDescriptor depends on self") + fun getTopologicalComparator(builder: DFSTBuilder): Comparator { + val comparator = builder.comparator() + // there is circular reference between core and implementation-detail plugin, as not all such plugins extracted from core, + // so, ensure that core plugin is always first (otherwise not possible to register actions - parent group not defined) + // don't use sortWith here - avoid loading kotlin stdlib + return Comparator { o1, o2 -> + when { + o1.moduleName == null && o1.pluginId == PluginManagerCore.CORE_ID -> -1 + o2.moduleName == null && o2.pluginId == PluginManagerCore.CORE_ID -> 1 + else -> comparator.compare(o1, o2) + } } } - else if (uniqueCheck.add(dep.id)) { - result.add(dep.id) + } + + fun getIn(node: Node): List = moduleToDirectDependencies.get(node) ?: Collections.emptyList() + + override fun buildOuts(nodeIndex: ToIntFunction, node: Node): IntArray { + val out = outs.get(node) ?: return ArrayUtilRt.EMPTY_INT_ARRAY + return IntArray(out.size) { + nodeIndex.applyAsInt(out.get(it)) + } + } +} + +/** + * In 191.* and earlier builds Java plugin was part of the platform, so any plugin installed in IntelliJ IDEA might be able to use its + * classes without declaring explicit dependency on the Java module. This method is intended to add implicit dependency on the Java plugin + * for such plugins to avoid breaking compatibility with them. + */ +private fun getImplicitDependency(descriptor: IdeaPluginDescriptorImpl, + idMap: Map): IdeaPluginDescriptorImpl? { + // skip our plugins as expected to be up-to-date whether bundled or not + if (descriptor.isBundled || descriptor.packagePrefix != null || descriptor.implementationDetail) { + return null + } + + val pluginId = descriptor.pluginId + if (PluginManagerCore.CORE_ID == pluginId || PluginManagerCore.JAVA_PLUGIN_ID == pluginId || + PluginManagerCore.hasModuleDependencies(descriptor)) { + return null + } + + // If a plugin does not include any module dependency tags in its plugin.xml, it's assumed to be a legacy plugin + // and is loaded only in IntelliJ IDEA, so it may use classes from Java plugin. + return idMap.get(PluginManagerCore.JAVA_MODULE_ID.idString) +} + + +private fun collectDirectDependenciesInOldFormat(rootDescriptor: IdeaPluginDescriptorImpl, + idMap: Map, + result: MutableSet) { + for (dependency in rootDescriptor.pluginDependencies) { + // check for missing optional dependency + val dep = idMap.get(dependency.pluginId.idString) ?: continue + if (dep.pluginId != PluginManagerCore.CORE_ID) { + // ultimate plugin it is combined plugin, where some included XML can define dependency on ultimate explicitly and for now not clear, + // can be such requirements removed or not + if (rootDescriptor === dep) { + if (rootDescriptor.pluginId != PluginManagerCore.CORE_ID) { + PluginManagerCore.getLogger().error("Plugin $rootDescriptor depends on self") + } + } + else { + // e.g. `.env` plugin in an old format and doesn't explicitly specify dependency on a new extracted modules + dep.content.modules.mapTo(result) { it.requireDescriptor() } + + result.add(dep) + } + } + + dependency.subDescriptor?.let { + collectDirectDependenciesInOldFormat(it, idMap, result) } } - directDependenciesOfModule(rootDescriptor, pluginSet, uniqueCheck, result) - - // graph for plugins, not for modules - so, dependency of content must be taken into account - if (rootDescriptor.id != PluginManagerCore.CORE_ID) { - for (module in rootDescriptor.content.modules) { - directDependenciesOfModule(module.requireDescriptor(), pluginSet, uniqueCheck, result) - } - } for (moduleId in rootDescriptor.incompatibilities) { - val dep = pluginSet.findEnabledPlugin(moduleId) - if (dep != null && uniqueCheck.add(dep.id)) { - result.add(dep.id) + idMap.get(moduleId.idString)?.let { + result.add(it) } } } -private fun directDependenciesOfModule(module: IdeaPluginDescriptorImpl, - pluginSet: PluginSet, - uniqueCheck: MutableSet, - result: MutableList) { - processDirectDependencies(module, pluginSet) { - if (uniqueCheck.add(it.id)) { - result.add(it.id) - } - } -} - -inline fun processDirectDependencies(module: IdeaPluginDescriptorImpl, - pluginSet: PluginSet, - processor: (IdeaPluginDescriptorImpl) -> Unit) { +private fun collectDirectDependenciesInNewFormat(module: IdeaPluginDescriptorImpl, + idMap: Map, + result: MutableCollection) { for (item in module.dependencies.modules) { - val descriptor = pluginSet.findEnabledModule(item.name) + val descriptor = idMap.get(item.name) if (descriptor != null) { - processor(descriptor) + result.add(descriptor) } } for (item in module.dependencies.plugins) { - val descriptor = pluginSet.findEnabledPlugin(item.id) - if (descriptor != null) { - processor(descriptor) + val descriptor = idMap.get(item.id.idString) + // fake v1 module maybe located in a core plugin + if (descriptor != null && descriptor.pluginId != PluginManagerCore.CORE_ID) { + result.add(descriptor) } } } \ No newline at end of file diff --git a/platform/core-impl/src/com/intellij/ide/plugins/ClassLoaderConfigurator.kt b/platform/core-impl/src/com/intellij/ide/plugins/ClassLoaderConfigurator.kt index 7acd875c1c37..68447909d0ef 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/ClassLoaderConfigurator.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/ClassLoaderConfigurator.kt @@ -1,10 +1,11 @@ // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -@file:Suppress("ReplaceNegatedIsEmptyWithIsNotEmpty") +@file:Suppress("ReplaceNegatedIsEmptyWithIsNotEmpty", "ReplaceGetOrSet") package com.intellij.ide.plugins import com.intellij.diagnostic.PluginException import com.intellij.ide.plugins.cl.PluginClassLoader import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.extensions.PluginId import com.intellij.util.SmartList import com.intellij.util.lang.ClassPath import com.intellij.util.lang.ResourceFile @@ -26,20 +27,19 @@ class ClassLoaderConfigurator( val pluginSet: PluginSet, private val coreLoader: ClassLoader = ClassLoaderConfigurator::class.java.classLoader, ) { - - private var javaDep: Optional? = null - - // temporary set to produce arrays (avoid allocation for each plugin) - // set to remove duplicated classloaders - private val dependencies = LinkedHashSet() - - private val hasAllModules = pluginSet.isPluginEnabled(PluginManagerCore.ALL_MODULES_MARKER) - // todo for dynamic reload this guard doesn't contain all used plugin prefixes private val pluginPackagePrefixUniqueGuard = HashMap() @Suppress("JoinDeclarationAndAssignment") private val resourceFileFactory: Function? + private val mainToClassPath = IdentityHashMap() + + private class MainInfo( + @JvmField val classPath: ClassPath, + @JvmField val files: List, + @JvmField val libDirectories: MutableList + ) + init { resourceFileFactory = try { @Suppress("UNCHECKED_CAST") @@ -56,199 +56,177 @@ class ClassLoaderConfigurator( } } - fun configureDependenciesIfNeeded(mainToModule: Map>, - dependencyPlugin: IdeaPluginDescriptorImpl) { + fun configureDependenciesIfNeeded(mainToModule: Map>) { for ((mainDependent, modules) in mainToModule) { val mainDependentClassLoader = mainDependent.classLoader as PluginClassLoader - if (mainDependent.packagePrefix != null) { - for (module in modules) { - assert(module.packagePrefix != null) - configureModule(module, mainDependent, - files = mainDependentClassLoader.files, - libDirectories = mainDependentClassLoader.libDirectories, - classPath = mainDependentClassLoader.classPath) - } - } - else { - mainDependentClassLoader.attachParent(dependencyPlugin) + mainToClassPath.put(mainDependent.pluginId, MainInfo(classPath = mainDependentClassLoader.classPath, + files = mainDependentClassLoader.files, + libDirectories = mainDependentClassLoader.libDirectories)) + if (mainDependent.packagePrefix == null) { for (module in modules) { module.classLoader = mainDependentClassLoader } } + else { + for (module in modules) { + configureModule(module) + } + } } - dependencies.clear() } - fun configureAll() { - val postTasks = ArrayList<() -> Unit>() - for (plugin in pluginSet.enabledPlugins) { - // not only for core plugin, but also for a plugin from classpath (run TraverseUi) - if (plugin.pluginId == PluginManagerCore.CORE_ID || (plugin.isUseCoreClassLoader && !plugin.content.modules.isEmpty())) { - configureCorePlugin(plugin, postTasks) + fun configure() { + for (module in pluginSet.getRawListOfEnabledModules()) { + configureModule(module) + } + } + + fun configureModule(module: IdeaPluginDescriptorImpl) { + checkPackagePrefixUniqueness(module) + + val isMain = module.moduleName == null + val dependencies = pluginSet.moduleToDirectDependencies.get(module) ?: EMPTY_DESCRIPTOR_ARRAY + if (isMain) { + if (module.useCoreClassLoader || module.pluginId == PluginManagerCore.CORE_ID) { + setPluginClassLoaderForModuleAndOldSubDescriptors(module, coreLoader) + return + } + + var files = module.jarFiles + if (files == null) { + if (!module.isUseIdeaClassLoader) { + log.error("jarFiles is not set for $module") + } + files = Collections.emptyList()!! + } + + val libDirectories: MutableList = SmartList() + val libDir = module.path.resolve("lib") + if (Files.exists(libDir)) { + libDirectories.add(libDir.toAbsolutePath().toString()) + } + + val mimicJarUrlConnection = !module.isBundled && module.vendor != "JetBrains" + val pluginClassPath = ClassPath(files, Collections.emptySet(), DEFAULT_CLASSLOADER_CONFIGURATION, resourceFileFactory, + mimicJarUrlConnection) + val mainInfo = MainInfo(classPath = pluginClassPath, files = files, libDirectories = libDirectories) + val existing = mainToClassPath.put(module.pluginId, mainInfo) + if (existing != null) { + log.error(PluginException("Main module with ${module.pluginId} is already added (existingClassPath=${existing.files}", + module.pluginId)) + } + + val mainDependentClassLoader = if (module.isUseIdeaClassLoader) { + configureUsingIdeaClassloader(mainInfo.files, module) } else { - configure(plugin) + createPluginClassLoader(module, mainInfo = mainInfo, dependencies = dependencies) } - } - - // intellij.profiler.ultimate depends on com.intellij.java but com.intellij.java depends on core plugin - for (postTask in postTasks) { - postTask() - } - } - - fun configure(plugin: IdeaPluginDescriptorImpl) { - checkPackagePrefixUniqueness(plugin) - - if (plugin.pluginId == PluginManagerCore.CORE_ID) { - throw IllegalStateException("Core plugin cannot be configured dynamically") - } - else if (plugin.isUseCoreClassLoader) { - setPluginClassLoaderForMainAndSubPlugins(plugin, coreLoader) - return - } - - dependencies.clear() - - // first, set class loader for main descriptor - if (hasAllModules) { - val implicitDependency = PluginManagerCore.getImplicitDependency(plugin) { - // first, set class loader for main descriptor - if (javaDep == null) { - javaDep = Optional.ofNullable(pluginSet.findEnabledPlugin(PluginManagerCore.JAVA_PLUGIN_ID)) - } - javaDep!!.orElse(null) - } - implicitDependency?.let { - if (it.classLoader !== coreLoader) { - dependencies.add(it) - } - Unit - } - } - - var files = plugin.jarFiles - if (files == null) { - log.error("jarFiles is not set for $plugin") - files = Collections.emptyList()!! - } - - var oldActiveSubModules: MutableList? = null - for (dependency in plugin.pluginDependencies) { - val p = pluginSet.findEnabledPlugin(dependency.pluginId) ?: continue - val loader = p.classLoader - if (loader == null) { - log.error(PluginLoadingError.formatErrorMessage(plugin, "requires missing class loader for $p")) - } - else if (loader !== coreLoader) { - // e.g. `.env` plugin in an old format and doesn't explicitly specify dependency on a new extracted modules - p.content.modules.mapTo(dependencies) { it.requireDescriptor() } - // must be after adding implicit module class loaders - dependencies.add(p) - } - - dependency.subDescriptor?.let { - if (oldActiveSubModules == null) { - oldActiveSubModules = ArrayList() - } - oldActiveSubModules!!.add(it) - } - } - - // new format - processDirectDependencies(plugin, pluginSet) { - if (it.classLoader !== coreLoader) { - dependencies.add(it) - } - } - - val mimicJarUrlConnection = !plugin.isBundled && plugin.vendor != "JetBrains" - val pluginClassPath = ClassPath(files, Collections.emptySet(), DEFAULT_CLASSLOADER_CONFIGURATION, resourceFileFactory, mimicJarUrlConnection) - - val libDirectories: MutableList = SmartList() - val libDir = plugin.path.resolve("lib") - if (Files.exists(libDir)) { - libDirectories.add(libDir.toAbsolutePath().toString()) - } - - val mainDependentClassLoader = if (plugin.isUseIdeaClassLoader) { - configureUsingIdeaClassloader(files, plugin) + module.classLoader = mainDependentClassLoader + configureDependenciesInOldFormat(module, mainDependentClassLoader) } else { - createPluginClassLoader(plugin, files = files, libDirectories = libDirectories, classPath = pluginClassPath) - } + if (module.packagePrefix == null) { + throw PluginException("Package is not specified (module=$module)", module.pluginId) + } - // second, set class loaders for sub descriptors - plugin.classLoader = mainDependentClassLoader - for (module in plugin.content.modules) { - configureModule(module = module.requireDescriptor(), - plugin = plugin, - files = files, - libDirectories = libDirectories, - classPath = pluginClassPath) - } + assert(module.pluginDependencies.isEmpty()) { "Module $module shouldn't have plugin dependencies: ${module.pluginDependencies}" } + for (dependency in dependencies) { + // if the module depends on an unavailable plugin, it will not be loaded + if (dependency.classLoader == null) { + return + } + } - for (subDescriptor in (oldActiveSubModules ?: Collections.emptyList())) { - // classLoader must be set - otherwise sub descriptor considered as inactive - subDescriptor.classLoader = mainDependentClassLoader - } + if (module.useCoreClassLoader) { + module.classLoader = coreLoader + return + } - // reset to ensure that stalled data will be not reused somehow later - dependencies.clear() - } - - private fun configureCorePlugin(plugin: IdeaPluginDescriptorImpl, postTasks: MutableList<() -> Unit>) { - plugin.classLoader = coreLoader - // do we really have pluginDependencies for core plugin? - for (dependency in plugin.pluginDependencies) { - if (dependency.subDescriptor != null && pluginSet.isPluginEnabled(dependency.pluginId)) { - configureCorePlugin(dependency.subDescriptor!!, postTasks) + val mainInfo = mainToClassPath.get(module.pluginId) + if (mainInfo == null) { + if (module.pluginId == PluginManagerCore.CORE_ID) { + configureCorePluginModuleClassLoader(module, dependencies) + } + else { + throw PluginException("Cannot find containing plugin ${module.pluginId} for module ${module.moduleName} ", module.pluginId) + } + } + else { + module.classLoader = PluginClassLoader( + mainInfo.files, + mainInfo.classPath, + dependencies, + module, + coreLoader, + createModuleResolveScopeManager(), + module.packagePrefix, + mainInfo.libDirectories + ) } } + } - if (plugin.content.modules.isEmpty()) { + private fun configureDependenciesInOldFormat(module: IdeaPluginDescriptorImpl, mainDependentClassLoader: ClassLoader) { + for (dependency in module.pluginDependencies) { + val subDescriptor = dependency.subDescriptor ?: continue + if (pluginSet.findEnabledPlugin(dependency.pluginId)?.takeIf { it !== module } == null) { + continue + } + // classLoader must be set - otherwise sub descriptor considered as inactive + subDescriptor.classLoader = mainDependentClassLoader + configureDependenciesInOldFormat(subDescriptor, mainDependentClassLoader) + } + } + + private fun configureCorePluginModuleClassLoader(module: IdeaPluginDescriptorImpl, deps: Array) { + val coreUrlClassLoader = getCoreUrlClassLoaderIfPossible(module) + if (coreUrlClassLoader == null) { + setPluginClassLoaderForModuleAndOldSubDescriptors(module, coreLoader) return } + module.classLoader = PluginClassLoader( + Collections.emptyList(), + coreUrlClassLoader.classPath, + deps, + module, + coreLoader, + createModuleResolveScopeManager(), + module.packagePrefix, + ArrayList() + ) + } + + private fun getCoreUrlClassLoaderIfPossible(module: IdeaPluginDescriptorImpl): UrlClassLoader? { val coreUrlClassLoader = coreLoader as? UrlClassLoader - for (item in plugin.content.modules) { - val module = item.requireDescriptor() - // skip if some dependency is not available - if (module.dependencies.modules.any { !pluginSet.isModuleEnabled(it.name) } || - module.dependencies.plugins.any { !pluginSet.isPluginEnabled(it.id) }) { - continue - } + if (coreUrlClassLoader == null) { + @Suppress("SpellCheckingInspection") + log.error("You should run JVM with -Djava.system.class.loader=com.intellij.util.lang.PathClassLoader") + setPluginClassLoaderForModuleAndOldSubDescriptors(module, coreLoader) + return null + } - assert(module.content.modules.isEmpty()) - if (coreUrlClassLoader == null) { - module.classLoader = coreLoader + if (coreUrlClassLoader.resolveScopeManager == null) { + val corePlugin = pluginSet.enabledPlugins.first() + assert(corePlugin.pluginId == PluginManagerCore.CORE_ID) + val resolveScopeManager = createPluginDependencyAndContentBasedScope(descriptor = corePlugin, pluginSet = pluginSet) + if (resolveScopeManager != null) { + coreUrlClassLoader.resolveScopeManager = BiPredicate { name, force -> + resolveScopeManager.isDefinitelyAlienClass(name, "", force) != null + } } - else { - if (coreUrlClassLoader.resolveScopeManager == null) { - val resolveScopeManager = createPluginDependencyAndContentBasedScope(descriptor = plugin, pluginSet = pluginSet) - if (resolveScopeManager != null) { - coreUrlClassLoader.resolveScopeManager = BiPredicate { name, force -> - resolveScopeManager.isDefinitelyAlienClass(name, "", force) != null - } - } - } + } - if (module.dependencies.plugins.isEmpty()) { - configureModule(module = module, - plugin = plugin, - files = Collections.emptyList(), - libDirectories = ArrayList(), - classPath = coreUrlClassLoader.classPath) - } - else { - postTasks.add { - configureModule(module = module, - plugin = plugin, - files = Collections.emptyList(), - libDirectories = ArrayList(), - classPath = coreUrlClassLoader.classPath) - } - } + return coreUrlClassLoader + } + + private fun setPluginClassLoaderForModuleAndOldSubDescriptors(rootDescriptor: IdeaPluginDescriptorImpl, classLoader: ClassLoader) { + rootDescriptor.classLoader = classLoader + for (dependency in rootDescriptor.pluginDependencies) { + val subDescriptor = dependency.subDescriptor + if (subDescriptor != null && pluginSet.isPluginEnabled(dependency.pluginId)) { + setPluginClassLoaderForModuleAndOldSubDescriptors(subDescriptor, classLoader) } } } @@ -263,94 +241,54 @@ class ClassLoaderConfigurator( } } - private fun createPluginClassLoader(descriptor: IdeaPluginDescriptorImpl, - files: List, - libDirectories: MutableList, - classPath: ClassPath): PluginClassLoader { - val parents: Array = if (dependencies.isEmpty()) { - EMPTY_DESCRIPTOR_ARRAY + private fun createPluginClassLoader(module: IdeaPluginDescriptorImpl, + dependencies: Array, + mainInfo: MainInfo): PluginClassLoader { + val resolveScopeManager: PluginClassLoader.ResolveScopeManager? + // main plugin descriptor + if (module.moduleName == null) { + resolveScopeManager = when (module.pluginId.idString) { + "com.intellij.diagram" -> { + // multiple packages - intellij.diagram and intellij.diagram.impl modules + createScopeWithExtraPackage("com.intellij.diagram.") + } + "com.intellij.struts2" -> createScopeWithExtraPackage("com.intellij.lang.ognl.") + "com.intellij.properties" -> { + // todo ability to customize (cannot move due to backward compatibility) + PluginClassLoader.ResolveScopeManager { name, packagePrefix, force -> + if (force) { + null + } + else if (!name.startsWith(packagePrefix) && + !name.startsWith("com.intellij.ultimate.PluginVerifier") && + name != "com.intellij.codeInspection.unused.ImplicitPropertyUsageProvider") { + "" + } + else { + null + } + } + } + else -> createPluginDependencyAndContentBasedScope(descriptor = module, pluginSet = pluginSet) + } } else { - dependencies.toArray(arrayOfNulls(dependencies.size)) - } - - return createPluginClassLoader(parents = parents, - descriptor = descriptor, - files = files, - coreLoader = coreLoader, - classPath = classPath, - libDirectories = libDirectories, - pluginSet = pluginSet) - } - - private fun configureModule(module: IdeaPluginDescriptorImpl, - plugin: IdeaPluginDescriptorImpl, - files: List, - libDirectories: MutableList, - classPath: ClassPath) { - if (module.packagePrefix == null) { - throw PluginException("Package is not specified (module=$module)", module.pluginId) - } - - checkPackagePrefixUniqueness(module) - dependencies.clear() - - // must be before main descriptor classloader - - for (item in module.dependencies.modules) { - // Module dependency is always optional. If the module depends on an unavailable plugin, it will not be loaded. - dependencies.add(pluginSet.findEnabledModule(item.name) ?: return) - } - for (item in module.dependencies.plugins) { - val descriptor = pluginSet.findEnabledPlugin(item.id) ?: return - if (descriptor.classLoader !== coreLoader) { - dependencies.add(descriptor) + resolveScopeManager = if (module.content.modules.isEmpty()) { + createModuleResolveScopeManager() + } + else { + // see "The `content.module` element" section about content handling for a module + createModuleContentBasedScope(descriptor = module) } } - - // add main descriptor classloader as parent - if (plugin.id != PluginManagerCore.CORE_ID) { - dependencies.add(plugin) - } - - assert(module.pluginDependencies.isEmpty()) { "Module $module shouldn't have plugin dependencies: ${module.pluginDependencies}" } - - val array = if (dependencies.isEmpty()) { - EMPTY_DESCRIPTOR_ARRAY - } - else { - dependencies.toArray(arrayOfNulls(dependencies.size)) - } - - module.classLoader = PluginClassLoader( - files, - classPath, - array, - module, - coreLoader, - createModuleResolveScopeManager(), module.packagePrefix, - libDirectories - ) - } - - private fun setPluginClassLoaderForMainAndSubPlugins(rootDescriptor: IdeaPluginDescriptorImpl, classLoader: ClassLoader?) { - rootDescriptor.classLoader = classLoader - for (dependency in rootDescriptor.pluginDependencies) { - if (dependency.subDescriptor != null && pluginSet.isPluginEnabled(dependency.pluginId)) { - setPluginClassLoaderForMainAndSubPlugins(dependency.subDescriptor!!, classLoader) - } - } - - for (item in rootDescriptor.content.modules) { - val module = item.requireDescriptor() - // skip if some dependency is not available - if (module.dependencies.modules.any { !pluginSet.isModuleEnabled(it.name) } || - module.dependencies.plugins.any { !pluginSet.isPluginEnabled(it.id) }) { - continue - } - - setPluginClassLoaderForMainAndSubPlugins(module, classLoader) - } + return PluginClassLoader(mainInfo.files, + mainInfo.classPath, + dependencies, + module, + coreLoader, + resolveScopeManager, + module.packagePrefix, + mainInfo.libDirectories) } } @@ -359,91 +297,6 @@ class ClassLoaderConfigurator( private val log: Logger get() = Logger.getInstance("#com.intellij.ide.plugins.PluginManager") -// static to ensure that anonymous classes will not hold ClassLoaderConfigurator -private fun createPluginClassLoader(parents: Array, - descriptor: IdeaPluginDescriptorImpl, - files: List, - libDirectories: MutableList, - coreLoader: ClassLoader, - classPath: ClassPath, - pluginSet: PluginSet): PluginClassLoader { - // main plugin descriptor - if (descriptor.descriptorPath == null) { - when (descriptor.id.idString) { - "com.intellij.diagram" -> { - // multiple packages - intellij.diagram and intellij.diagram.impl modules - return createPluginClassLoaderWithExtraPackage(parents = parents, - descriptor = descriptor, - files = files, - coreLoader = coreLoader, - classPath = classPath, - libDirectories = libDirectories, - customPackage = "com.intellij.diagram.") - } - "com.intellij.struts2" -> { - return createPluginClassLoaderWithExtraPackage(parents = parents, - descriptor = descriptor, - files = files, - coreLoader = coreLoader, - classPath = classPath, - libDirectories = libDirectories, - customPackage = "com.intellij.lang.ognl.") - } - "com.intellij.properties" -> { - // todo ability to customize (cannot move due to backward compatibility) - return createPluginClassloader(parents = parents, - descriptor = descriptor, - files = files, - coreLoader = coreLoader, - classPath = classPath, - libDirectories = libDirectories) { name, packagePrefix, force -> - if (force) { - null - } - else if (!name.startsWith(packagePrefix) && - !name.startsWith("com.intellij.ultimate.PluginVerifier") && - name != "com.intellij.codeInspection.unused.ImplicitPropertyUsageProvider") { - "" - } - else { - null - } - } - } - } - } - else { - if (!descriptor.content.modules.isEmpty()) { - // see "The `content.module` element" section about content handling for a module - return createPluginClassloader(parents = parents, - descriptor = descriptor, - files = files, - coreLoader = coreLoader, - classPath = classPath, - libDirectories = libDirectories, - resolveScopeManager = createModuleContentBasedScope(descriptor)) - } - else if (descriptor.packagePrefix != null) { - return createPluginClassloader(parents = parents, - descriptor = descriptor, - files = files, - coreLoader = coreLoader, - classPath = classPath, - libDirectories = libDirectories, resolveScopeManager = createModuleResolveScopeManager()) - } - } - - return createPluginClassloader( - parents = parents, - descriptor = descriptor, - files = files, - coreLoader = coreLoader, - classPath = classPath, - libDirectories = libDirectories, - resolveScopeManager = createPluginDependencyAndContentBasedScope(descriptor = descriptor, pluginSet = pluginSet) - ) -} - private fun createModuleResolveScopeManager(): PluginClassLoader.ResolveScopeManager { return PluginClassLoader.ResolveScopeManager { name, packagePrefix, _ -> // force flag is ignored for module - e.g., RailsViewLineMarkerProvider is referenced @@ -452,40 +305,16 @@ private fun createModuleResolveScopeManager(): PluginClassLoader.ResolveScopeMan } } -private fun createPluginClassloader(parents: Array, - descriptor: IdeaPluginDescriptorImpl, - files: List, - libDirectories: MutableList, - coreLoader: ClassLoader, - classPath: ClassPath, - resolveScopeManager: PluginClassLoader.ResolveScopeManager?): PluginClassLoader { - return PluginClassLoader(files, classPath, parents, descriptor, coreLoader, resolveScopeManager, descriptor.packagePrefix, - libDirectories) -} - -private fun createPluginClassLoaderWithExtraPackage(parents: Array, - descriptor: IdeaPluginDescriptorImpl, - files: List, - libDirectories: MutableList, - coreLoader: ClassLoader, - classPath: ClassPath, - customPackage: String): PluginClassLoader { - return createPluginClassloader(parents = parents, - descriptor = descriptor, - files = files, - coreLoader = coreLoader, - classPath = classPath, - libDirectories = libDirectories) { name, packagePrefix, force -> - if (force) { - null +private fun createScopeWithExtraPackage(customPackage: String): PluginClassLoader.ResolveScopeManager { + return PluginClassLoader.ResolveScopeManager { name, packagePrefix, force -> + if (!force && + !name.startsWith(packagePrefix) && + !name.startsWith("com.intellij.ultimate.PluginVerifier") && + !name.startsWith(customPackage)) { + "" } else { - if (!name.startsWith(packagePrefix) && !name.startsWith("com.intellij.ultimate.PluginVerifier") && !name.startsWith(customPackage)) { - "" - } - else { - null - } + null } } } @@ -501,7 +330,7 @@ private fun createPluginDependencyAndContentBasedScope(descriptor: IdeaPluginDes } val pluginId = descriptor.pluginId.idString - return PluginClassLoader.ResolveScopeManager { name, _, force -> + return PluginClassLoader.ResolveScopeManager { name, packagePrefix, force -> if (force) { return@ResolveScopeManager null } diff --git a/platform/core-impl/src/com/intellij/ide/plugins/ClassPathXmlPathResolver.kt b/platform/core-impl/src/com/intellij/ide/plugins/ClassPathXmlPathResolver.kt index e4e18982d152..fd2186fc2e5a 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/ClassPathXmlPathResolver.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/ClassPathXmlPathResolver.kt @@ -5,7 +5,7 @@ import com.intellij.platform.util.plugins.DataLoader import com.intellij.platform.util.plugins.LocalFsDataLoader import java.nio.file.Files -internal class ClassPathXmlPathResolver(private val classLoader: ClassLoader, private val isRunningFromSources: Boolean) : PathResolver { +internal class ClassPathXmlPathResolver(private val classLoader: ClassLoader, val isRunningFromSources: Boolean) : PathResolver { override val isFlat: Boolean get() = true @@ -15,7 +15,7 @@ internal class ClassPathXmlPathResolver(private val classLoader: ClassLoader, pr base: String?, relativePath: String): Boolean { val path = PluginXmlPathResolver.toLoadPath(relativePath, base) - readModuleDescriptor(inputStream = classLoader.getResourceAsStream(path) ?: return false, + readModuleDescriptor(input = classLoader.getResourceAsStream(path) ?: return false, readContext = readContext, pathResolver = this, dataLoader = dataLoader, @@ -51,7 +51,7 @@ internal class ClassPathXmlPathResolver(private val classLoader: ClassLoader, pr throw RuntimeException("Cannot resolve $path (dataLoader=$dataLoader, classLoader=$classLoader)") } } - return readModuleDescriptor(inputStream = resource, + return readModuleDescriptor(input = resource, readContext = readContext, pathResolver = this, dataLoader = dataLoader, @@ -66,7 +66,7 @@ internal class ClassPathXmlPathResolver(private val classLoader: ClassLoader, pr readInto: RawPluginDescriptor?): RawPluginDescriptor? { val path = PluginXmlPathResolver.toLoadPath(relativePath, null) val resource = classLoader.getResourceAsStream(path) - return readModuleDescriptor(inputStream = resource ?: return null, + return readModuleDescriptor(input = resource ?: return null, readContext = readContext, pathResolver = this, dataLoader = dataLoader, diff --git a/platform/core-impl/src/com/intellij/ide/plugins/DescriptorListLoadingContext.kt b/platform/core-impl/src/com/intellij/ide/plugins/DescriptorListLoadingContext.kt index 2853f463dd94..a01284482b54 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/DescriptorListLoadingContext.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/DescriptorListLoadingContext.kt @@ -67,12 +67,12 @@ class DescriptorListLoadingContext constructor( get() = threadLocalXmlFactory.get()[0]!!.visitedFiles fun checkOptionalConfigShortName(configFile: String, descriptor: IdeaPluginDescriptor): Boolean { - val pluginId = descriptor.pluginId ?: return false val configNames = optionalConfigNames if (configNames == null || configFile.startsWith("intellij.")) { return false } + val pluginId = descriptor.pluginId val oldPluginId = configNames.put(configFile, pluginId) if (oldPluginId == null || oldPluginId == pluginId) { return false diff --git a/platform/core-impl/src/com/intellij/ide/plugins/DynamicPluginListener.kt b/platform/core-impl/src/com/intellij/ide/plugins/DynamicPluginListener.kt index becabeb883ed..e639208f4a06 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/DynamicPluginListener.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/DynamicPluginListener.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ide.plugins import com.intellij.openapi.progress.ProcessCanceledException @@ -34,6 +34,8 @@ interface DynamicPluginListener { /** * Checks if the plugin can be dynamically unloaded at this moment. * Method should throw [CannotUnloadPluginException] if it isn't possible for some reason. + * + * Not dispatched for a content modules (plugin model V2). */ @Throws(CannotUnloadPluginException::class) @JvmDefault diff --git a/platform/core-impl/src/com/intellij/ide/plugins/IdeaPluginDescriptorImpl.kt b/platform/core-impl/src/com/intellij/ide/plugins/IdeaPluginDescriptorImpl.kt index b3686b67d714..043c96313dba 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/IdeaPluginDescriptorImpl.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/IdeaPluginDescriptorImpl.kt @@ -1,4 +1,5 @@ // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +@file:Suppress("ReplaceGetOrSet") package com.intellij.ide.plugins import com.intellij.AbstractBundle @@ -24,10 +25,12 @@ private val checkCompatibilityFlag = System.getProperty("idea.plugin.check.compa @ApiStatus.Internal class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor, - val path: Path, + @JvmField val path: Path, private val isBundled: Boolean, - id: PluginId?) : IdeaPluginDescriptor { - val id: PluginId = id ?: PluginId.getId(raw.id ?: raw.name ?: throw RuntimeException("Neither id nor name are specified")) + id: PluginId?, + @JvmField val moduleName: String?, + @JvmField val useCoreClassLoader: Boolean = false) : IdeaPluginDescriptor { + private val id: PluginId = id ?: PluginId.getId(raw.id ?: raw.name ?: throw RuntimeException("Neither id nor name are specified")) private val name = raw.name ?: id?.idString ?: raw.id @Suppress("EnumEntryName") @@ -74,10 +77,15 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor, pluginDependencies = list ?: Collections.emptyList() } + companion object { + @ApiStatus.Internal + @JvmField var disableNonBundledPlugins = false + } + @Transient @JvmField var jarFiles: List? = null @JvmField var classLoader: ClassLoader? = null - @JvmField val actions: List? = raw.actions + @JvmField val actions: List = raw.actions ?: Collections.emptyList() // extension point name -> list of extension descriptors val epNameToExtensions: Map>? = raw.epNameToExtensions @@ -94,9 +102,6 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor, @JvmField val isUseIdeaClassLoader = raw.isUseIdeaClassLoader - var isUseCoreClassLoader = false - private set - @JvmField val isBundledUpdateAllowed = raw.isBundledUpdateAllowed @JvmField internal val implementationDetail = raw.implementationDetail @@ -125,10 +130,12 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor, descriptorPath: String, pathResolver: PathResolver, context: DescriptorListLoadingContext, - dataLoader: DataLoader): IdeaPluginDescriptorImpl { + dataLoader: DataLoader, + moduleName: String?): IdeaPluginDescriptorImpl { raw.name = name @Suppress("TestOnlyProblems") - val result = IdeaPluginDescriptorImpl(raw, path = path, isBundled = isBundled, id = id) + val result = IdeaPluginDescriptorImpl(raw, path = path, isBundled = isBundled, id = id, moduleName = moduleName, + useCoreClassLoader = useCoreClassLoader) result.descriptorPath = descriptorPath result.vendor = vendor result.version = version @@ -155,7 +162,8 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor, descriptorPath = subDescriptorFile, pathResolver = pathResolver, context = context, - dataLoader = dataLoader) + dataLoader = dataLoader, + moduleName = module.name) module.descriptor = subDescriptor } } @@ -183,6 +191,11 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor, markAsIncomplete(context, disabledDependency = null, shortMessage = null) } else { + checkCompatibility(context) + if (isIncomplete) { + return + } + for (pluginDependency in dependencies.plugins) { if (context.isPluginDisabled(pluginDependency.id)) { markAsIncomplete(context, pluginDependency.id, shortMessage = "plugin.loading.error.short.depends.on.disabled.plugin") @@ -197,13 +210,13 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor, } } - processOldDependencies(descriptor = this, - context = context, - pathResolver = pathResolver, - dependencies = pluginDependencies, - dataLoader = dataLoader) - - checkCompatibility(context) + if (!isIncomplete && moduleName == null) { + processOldDependencies(descriptor = this, + context = context, + pathResolver = pathResolver, + dependencies = pluginDependencies, + dataLoader = dataLoader) + } } private fun processOldDependencies(descriptor: IdeaPluginDescriptorImpl, @@ -268,22 +281,53 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor, descriptorPath = configFile, pathResolver = pathResolver, context = context, - dataLoader = dataLoader) + dataLoader = dataLoader, + moduleName = null) dependency.subDescriptor = subDescriptor visitedFiles.clear() } } private fun checkCompatibility(context: DescriptorListLoadingContext) { - if (isBundled || !checkCompatibilityFlag || (sinceBuild == null && untilBuild == null)) { + if (isBundled) { return } - val error = PluginManagerCore.checkBuildNumberCompatibility(this, context.result.productBuildNumber.get()) ?: return + fun markAsIncompatible(error: PluginLoadingError) { + if (isIncomplete) { + return + } - // error will be added by reportIncompatiblePlugin - markAsIncomplete(context = context, disabledDependency = null, shortMessage = null) - context.result.reportIncompatiblePlugin(this, error) + isIncomplete = true + isEnabled = false + context.result.addIncompletePlugin(plugin = this, error = error) + } + + if (disableNonBundledPlugins) { + markAsIncompatible(PluginLoadingError( + plugin = this, + detailedMessageSupplier = { CoreBundle.message("plugin.loading.error.long.custom.plugin.loading.disabled", getName()) }, + shortMessageSupplier = { CoreBundle.message("plugin.loading.error.short.custom.plugin.loading.disabled") }, + isNotifyUser = false + )) + return + } + + if (checkCompatibilityFlag && (sinceBuild != null || untilBuild != null)) { + PluginManagerCore.checkBuildNumberCompatibility(this, context.result.productBuildNumber.get())?.let { + markAsIncompatible(it) + return + } + } + + // "Show broken plugins in Settings | Plugins so that users can uninstall them and resolve "Plugin Error" (IDEA-232675)" + if (context.result.isBroken(this)) { + markAsIncompatible(PluginLoadingError( + plugin = this, + detailedMessageSupplier = { CoreBundle.message("plugin.loading.error.long.marked.as.broken", name, version) }, + shortMessageSupplier = { CoreBundle.message("plugin.loading.error.short.marked.as.broken") } + )) + } } private fun markAsIncomplete(context: DescriptorListLoadingContext, @@ -303,19 +347,13 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor, else { PluginLoadingError(plugin = this, detailedMessageSupplier = null, - shortMessageSupplier = { - CoreBundle.message(shortMessage, pluginId!!) - }, + shortMessageSupplier = { CoreBundle.message(shortMessage, pluginId!!) }, isNotifyUser = false, disabledDependency = disabledDependency) } context.result.addIncompletePlugin(this, pluginError) } - fun collectExtensionPoints() { - - } - @ApiStatus.Internal fun registerExtensions(nameToPoint: Map>, containerDescriptor: ContainerDescriptor, @@ -459,10 +497,6 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor, override fun getPluginClassLoader(): ClassLoader = classLoader ?: javaClass.classLoader - fun setUseCoreClassLoader() { - isUseCoreClassLoader = true - } - override fun isEnabled() = isEnabled override fun setEnabled(enabled: Boolean) { @@ -499,6 +533,7 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor, return "PluginDescriptor(" + "name=$name, " + "id=$id, " + + (if (moduleName == null) "" else "moduleName=$moduleName, ") + "descriptorPath=${descriptorPath ?: "plugin.xml"}, " + "path=${pluginPathToUserString(path)}, " + "version=$version, " + diff --git a/platform/core-impl/src/com/intellij/ide/plugins/ImmutableZipFileDataLoader.kt b/platform/core-impl/src/com/intellij/ide/plugins/ImmutableZipFileDataLoader.kt index f2b53215d044..23830427dd5b 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/ImmutableZipFileDataLoader.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/ImmutableZipFileDataLoader.kt @@ -1,18 +1,17 @@ -// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ide.plugins import com.intellij.platform.util.plugins.DataLoader import com.intellij.util.lang.ZipFilePool import com.intellij.util.lang.ZipFilePool.EntryResolver -import org.jetbrains.annotations.ApiStatus +import java.io.InputStream import java.nio.file.Path -@ApiStatus.Internal -class ImmutableZipFileDataLoader(private val resolver: EntryResolver, - private val zipPath: Path, - override val pool: ZipFilePool) : DataLoader { - override fun load(path: String): ByteArray? { - // well, path maybe specified as `/META-INF/*` in plugin descriptor and +internal class ImmutableZipFileDataLoader(private val resolver: EntryResolver, + private val zipPath: Path, + override val pool: ZipFilePool) : DataLoader { + override fun load(path: String): InputStream? { + // well, path maybe specified as `/META-INF/*` in plugin descriptor, and // it is our responsibility to normalize path for ImmutableZipFile API // do not use kotlin stdlib here return resolver.loadZipEntry(if (path[0] == '/') path.substring(1) else path) diff --git a/platform/core-impl/src/com/intellij/ide/plugins/ModuleDependenciesDescriptor.kt b/platform/core-impl/src/com/intellij/ide/plugins/ModuleDependenciesDescriptor.kt index 80a84d4dda7d..6c4b8fd41862 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/ModuleDependenciesDescriptor.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/ModuleDependenciesDescriptor.kt @@ -6,6 +6,7 @@ import com.intellij.openapi.extensions.PluginId import org.jetbrains.annotations.ApiStatus import java.util.* +@ApiStatus.Internal class ModuleDependenciesDescriptor(@JvmField val modules: List, @JvmField val plugins: List) { companion object { @JvmField val EMPTY = ModuleDependenciesDescriptor(Collections.emptyList(), Collections.emptyList()) @@ -29,8 +30,7 @@ class PluginContentDescriptor(@JvmField val modules: List) { } @ApiStatus.Internal - class ModuleItem(@JvmField val name: String, - @JvmField val configFile: String?) { + class ModuleItem(@JvmField val name: String, @JvmField val configFile: String?) { @JvmField internal var descriptor: IdeaPluginDescriptorImpl? = null fun requireDescriptor() = descriptor ?: throw IllegalStateException("Descriptor is not set for $this") @@ -38,7 +38,5 @@ class PluginContentDescriptor(@JvmField val modules: List) { override fun toString() = "ModuleItem(name=$name, descriptor=$descriptor, configFile=$configFile)" } - override fun toString(): String { - return "PluginContentDescriptor(modules=$modules)" - } + override fun toString() = "PluginContentDescriptor(modules=$modules)" } \ No newline at end of file diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PluginDescriptorLoader.kt b/platform/core-impl/src/com/intellij/ide/plugins/PluginDescriptorLoader.kt index c9cd3d184c2b..dbc941bcaacf 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PluginDescriptorLoader.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginDescriptorLoader.kt @@ -11,7 +11,6 @@ import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.extensions.PluginId import com.intellij.openapi.util.BuildNumber import com.intellij.openapi.util.io.NioFiles -import com.intellij.openapi.util.text.Strings import com.intellij.platform.util.plugins.DataLoader import com.intellij.platform.util.plugins.LocalFsDataLoader import com.intellij.util.PlatformUtils @@ -24,6 +23,7 @@ import org.jetbrains.annotations.TestOnly import java.io.Closeable import java.io.File import java.io.IOException +import java.io.InputStream import java.net.URL import java.nio.charset.StandardCharsets import java.nio.file.Files @@ -32,6 +32,7 @@ import java.nio.file.Path import java.nio.file.Paths import java.util.* import java.util.concurrent.* +import java.util.function.BiConsumer import java.util.function.Supplier import java.util.zip.ZipFile import javax.xml.stream.XMLStreamException @@ -44,16 +45,15 @@ internal fun createPluginLoadingResult(buildNumber: BuildNumber?): PluginLoading productBuildNumber = { buildNumber ?: PluginManagerCore.getBuildNumber() }) } -fun loadDescriptor(file: Path, - isBundled: Boolean, - parentContext: DescriptorListLoadingContext): IdeaPluginDescriptorImpl? { +@TestOnly +fun loadDescriptor(file: Path, parentContext: DescriptorListLoadingContext): IdeaPluginDescriptorImpl? { return loadDescriptorFromFileOrDir(file = file, - pathName = PluginManagerCore.PLUGIN_XML, context = parentContext, pathResolver = PluginXmlPathResolver.DEFAULT_PATH_RESOLVER, - isBundled = isBundled, + isBundled = false, isEssential = false, - isDirectory = Files.isDirectory(file)) + isDirectory = Files.isDirectory(file), + useCoreClassLoader = false) } internal fun loadForCoreEnv(pluginRoot: Path, fileName: String): IdeaPluginDescriptorImpl? { @@ -66,7 +66,8 @@ internal fun loadForCoreEnv(pluginRoot: Path, fileName: String): IdeaPluginDescr context = parentContext, isBundled = true, isEssential = true, - pathResolver = pathResolver) + pathResolver = pathResolver, + useCoreClassLoader = false) } else { return loadDescriptorFromJar(file = pluginRoot, @@ -75,7 +76,8 @@ internal fun loadForCoreEnv(pluginRoot: Path, fileName: String): IdeaPluginDescr parentContext = parentContext, isBundled = true, isEssential = true, - pluginPath = null) + pluginPath = null, + useCoreClassLoader = false) } } @@ -85,6 +87,7 @@ private fun loadDescriptorFromDir(file: Path, context: DescriptorListLoadingContext, isBundled: Boolean, isEssential: Boolean, + useCoreClassLoader: Boolean, pathResolver: PathResolver): IdeaPluginDescriptorImpl? { try { val input = Files.readAllBytes(file.resolve(descriptorRelativePath)) @@ -96,7 +99,8 @@ private fun loadDescriptorFromDir(file: Path, includeBase = null, readInto = null, locationSource = file.toString()) - val descriptor = IdeaPluginDescriptorImpl(raw = raw, path = pluginPath ?: file, isBundled = isBundled, id = null) + val descriptor = IdeaPluginDescriptorImpl(raw = raw, path = pluginPath ?: file, isBundled = isBundled, id = null, moduleName = null, + useCoreClassLoader = useCoreClassLoader) descriptor.readExternal(raw = raw, pathResolver = pathResolver, context = context, isSub = false, dataLoader = dataLoader) descriptor.jarFiles = Collections.singletonList(file) return descriptor @@ -119,6 +123,7 @@ private fun loadDescriptorFromJar(file: Path, parentContext: DescriptorListLoadingContext, isBundled: Boolean, isEssential: Boolean, + useCoreClassLoader: Boolean, pluginPath: Path?): IdeaPluginDescriptorImpl? { var closeable: Closeable? = null try { @@ -141,7 +146,8 @@ private fun loadDescriptorFromJar(file: Path, readInto = null, locationSource = file.toString()) - val descriptor = IdeaPluginDescriptorImpl(raw = raw, path = pluginPath ?: file, isBundled = isBundled, id = null) + val descriptor = IdeaPluginDescriptorImpl(raw = raw, path = pluginPath ?: file, isBundled = isBundled, id = null, moduleName = null, + useCoreClassLoader = useCoreClassLoader) descriptor.readExternal(raw = raw, pathResolver = pathResolver, context = parentContext, isSub = false, dataLoader = dataLoader) descriptor.jarFiles = Collections.singletonList(descriptor.pluginPath) return descriptor @@ -162,44 +168,49 @@ private class JavaZipFileDataLoader(private val file: ZipFile) : DataLoader { override val pool: ZipFilePool? get() = null - override fun load(path: String): ByteArray? { + override fun load(path: String): InputStream? { val entry = file.getEntry(if (path[0] == '/') path.substring(1) else path) ?: return null - return file.getInputStream(entry).use { it.readBytes() } + return file.getInputStream(entry) } override fun toString() = file.toString() } fun loadDescriptorFromFileOrDir(file: Path, - pathName: String, context: DescriptorListLoadingContext, pathResolver: PathResolver, isBundled: Boolean, isEssential: Boolean, - isDirectory: Boolean): IdeaPluginDescriptorImpl? { + isDirectory: Boolean, + useCoreClassLoader: Boolean): IdeaPluginDescriptorImpl? { return when { isDirectory -> { - val descriptorRelativePath = "${PluginManagerCore.META_INF}$pathName" loadDescriptorFromDir(file = file, - descriptorRelativePath = descriptorRelativePath, + descriptorRelativePath = PluginManagerCore.PLUGIN_XML_PATH, pluginPath = null, context = context, isBundled = isBundled, isEssential = isEssential, - pathResolver = pathResolver)?.let { + pathResolver = pathResolver, + useCoreClassLoader = useCoreClassLoader)?.let { return it } - loadFromPluginDir(file = file, pathName = pathName, parentContext = context, isBundled = isBundled, - isEssential = isEssential, descriptorRelativePath = descriptorRelativePath, pathResolver = pathResolver) + loadFromPluginDir(file = file, + parentContext = context, + isBundled = isBundled, + isEssential = isEssential, + useCoreClassLoader = useCoreClassLoader, + pathResolver = pathResolver) } file.fileName.toString().endsWith(".jar", ignoreCase = true) -> { loadDescriptorFromJar(file = file, - fileName = pathName, + fileName = PluginManagerCore.PLUGIN_XML, pathResolver = pathResolver, parentContext = context, isBundled = isBundled, isEssential = isEssential, - pluginPath = null) + pluginPath = null, + useCoreClassLoader = useCoreClassLoader) } else -> null } @@ -209,11 +220,10 @@ fun loadDescriptorFromFileOrDir(file: Path, // or // lib/*.jar private fun loadFromPluginDir(file: Path, - pathName: String, parentContext: DescriptorListLoadingContext, isBundled: Boolean, isEssential: Boolean, - descriptorRelativePath: String, + useCoreClassLoader: Boolean, pathResolver: PathResolver): IdeaPluginDescriptorImpl? { val pluginJarFiles = ArrayList() try { @@ -235,12 +245,13 @@ private fun loadFromPluginDir(file: Path, val pluginPathResolver = PluginXmlPathResolver(pluginJarFiles) for (jarFile in pluginJarFiles) { loadDescriptorFromJar(file = jarFile, - fileName = pathName, + fileName = PluginManagerCore.PLUGIN_XML, pathResolver = pluginPathResolver, parentContext = parentContext, isBundled = isBundled, isEssential = isEssential, - pluginPath = file)?.let { + pluginPath = file, + useCoreClassLoader = useCoreClassLoader)?.let { it.jarFiles = pluginJarFiles return it } @@ -251,12 +262,13 @@ private fun loadFromPluginDir(file: Path, if (!isBundled) { val classesDir = file.resolve("classes") loadDescriptorFromDir(file = classesDir, - descriptorRelativePath = descriptorRelativePath, + descriptorRelativePath = PluginManagerCore.PLUGIN_XML_PATH, pluginPath = file, context = parentContext, - isBundled = isBundled, + isBundled = false, isEssential = isEssential, - pathResolver = pathResolver)?.let { + pathResolver = pathResolver, + useCoreClassLoader = useCoreClassLoader)?.let { val classPath = ArrayList(pluginJarFiles.size + 1) classPath.add(classesDir) classPath.addAll(pluginJarFiles) @@ -327,15 +339,19 @@ private fun loadDescriptorsFromProperty(result: PluginLoadingResult, context: De // gradle-intellij-plugin heavily depends on this property in order to have core class loader plugins during tests val useCoreClassLoaderForPluginsFromProperty = java.lang.Boolean.parseBoolean( System.getProperty("idea.use.core.classloader.for.plugin.path")) - val t = StringTokenizer(pathProperty, File.pathSeparatorChar.toString() + ",") + val t = StringTokenizer(pathProperty, File.pathSeparatorChar + ",") while (t.hasMoreTokens()) { val s = t.nextToken() - loadDescriptor(Paths.get(s), false, context)?.let { + val file = Paths.get(s) + loadDescriptorFromFileOrDir(file = file, + context = context, + pathResolver = PluginXmlPathResolver.DEFAULT_PATH_RESOLVER, + isBundled = false, + isEssential = false, + isDirectory = Files.isDirectory(file), + useCoreClassLoader = useCoreClassLoaderForPluginsFromProperty)?.let { // plugins added via property shouldn't be overridden to avoid plugin root detection issues when running external plugin tests - result.add(it, /* overrideUseIfCompatible = */true) - if (useCoreClassLoaderForPluginsFromProperty) { - it.setUseCoreClassLoader() - } + result.add(it, overrideUseIfCompatible = true) } } } @@ -385,9 +401,10 @@ private fun loadBundledDescriptorsAndDescriptorsFromDir(context: DescriptorListL val platformPrefix = PlatformUtils.getPlatformPrefix() // should be the only plugin in lib (only for Ultimate and WebStorm for now) - val pathResolver = ClassPathXmlPathResolver(classLoader, isRunningFromSources = isRunningFromSources) + val isInDevServerMode = java.lang.Boolean.getBoolean("idea.use.dev.build.server") + val pathResolver = ClassPathXmlPathResolver(classLoader, isRunningFromSources = isRunningFromSources && !isInDevServerMode) if ((platformPrefix == PlatformUtils.IDEA_PREFIX || platformPrefix == PlatformUtils.WEB_PREFIX) && - (java.lang.Boolean.getBoolean("idea.use.dev.build.server") || (!isUnitTestMode && !isRunningFromSources))) { + (isInDevServerMode || (!isUnitTestMode && !isRunningFromSources))) { val dataLoader = object : DataLoader { override val pool: ZipFilePool get() = throw IllegalStateException("must be not called") @@ -397,24 +414,28 @@ private fun loadBundledDescriptorsAndDescriptorsFromDir(context: DescriptorListL override fun toString() = "product classpath" } - val raw = readModuleDescriptor(inputStream = classLoader.getResourceAsStream(PluginManagerCore.PLUGIN_XML_PATH)!!, + val raw = readModuleDescriptor(input = classLoader.getResourceAsStream(PluginManagerCore.PLUGIN_XML_PATH)!!, readContext = context, pathResolver = pathResolver, dataLoader = dataLoader, includeBase = null, readInto = null, locationSource = null) - val descriptor = IdeaPluginDescriptorImpl(raw = raw, path = Paths.get(PathManager.getLibPath()), isBundled = true, id = null) + val descriptor = IdeaPluginDescriptorImpl(raw = raw, + path = Paths.get(PathManager.getLibPath()), + isBundled = true, + id = null, + moduleName = null, + useCoreClassLoader = pathResolver.isRunningFromSources) descriptor.readExternal(raw = raw, pathResolver = pathResolver, context = context, isSub = false, dataLoader = dataLoader) - descriptor.setUseCoreClassLoader() - context.result.add(descriptor, /* overrideUseIfCompatible = */false) + context.result.add(descriptor, overrideUseIfCompatible = false) } else { - val urlsFromClassPath = LinkedHashMap() - val platformPluginURL = computePlatformPluginUrlAndCollectPluginUrls(classLoader, urlsFromClassPath, platformPrefix) - if (!urlsFromClassPath.isEmpty()) { + val urlToFilename = LinkedHashMap() + val platformPluginURL = computePlatformPluginUrlAndCollectPluginUrls(classLoader, urlToFilename, platformPrefix) + if (!urlToFilename.isEmpty()) { activity = activity.endAndStart("plugin from classpath loading") - pool.invoke(LoadDescriptorsFromClassPathAction(urls = urlsFromClassPath, + pool.invoke(LoadDescriptorsFromClassPathAction(urlToFilename = urlToFilename, context = context, platformPluginURL = platformPluginURL, pathResolver = pathResolver)) @@ -474,12 +495,12 @@ fun loadDescriptorFromArtifact(file: Path, buildNumber: BuildNumber?): IdeaPlugi transient = true) val descriptor = loadDescriptorFromFileOrDir(file = file, - pathName = PluginManagerCore.PLUGIN_XML, context = context, pathResolver = PluginXmlPathResolver.DEFAULT_PATH_RESOLVER, isBundled = false, isEssential = false, - isDirectory = false) + isDirectory = false, + useCoreClassLoader = false) if (descriptor != null || !file.toString().endsWith(".zip")) { return descriptor } @@ -491,12 +512,12 @@ fun loadDescriptorFromArtifact(file: Path, buildNumber: BuildNumber?): IdeaPlugi val rootDir = NioFiles.list(outputDir).firstOrNull() if (rootDir != null) { return loadDescriptorFromFileOrDir(file = rootDir, - pathName = PluginManagerCore.PLUGIN_XML, context = context, pathResolver = PluginXmlPathResolver.DEFAULT_PATH_RESOLVER, isBundled = false, isEssential = false, - isDirectory = true) + isDirectory = true, + useCoreClassLoader = false) } } catch (ignore: NoSuchFileException) { } @@ -514,12 +535,12 @@ fun loadDescriptor(file: Path, pathResolver: PathResolver): IdeaPluginDescriptorImpl? { DescriptorListLoadingContext(disabledPlugins = disabledPlugins).use { context -> return loadDescriptorFromFileOrDir(file = file, - pathName = PluginManagerCore.PLUGIN_XML, context = context, pathResolver = pathResolver, isBundled = isBundled, isEssential = false, - isDirectory = Files.isDirectory(file)) + isDirectory = Files.isDirectory(file), + useCoreClassLoader = false) } } @@ -567,14 +588,14 @@ fun getDescriptorsToMigrate(dir: Path, @TestOnly fun testLoadDescriptorsFromClassPath(loader: ClassLoader): List { - val urlsFromClassPath = LinkedHashMap() - collectPluginFilesInClassPath(loader, urlsFromClassPath) + val urlToFilename = LinkedHashMap() + collectPluginFilesInClassPath(loader, urlToFilename) val buildNumber = BuildNumber.fromString("2042.42")!! val context = DescriptorListLoadingContext(disabledPlugins = emptySet(), result = PluginLoadingResult(brokenPluginVersions = emptyMap(), productBuildNumber = Supplier { buildNumber }, checkModuleDependencies = false)) - LoadDescriptorsFromClassPathAction(urlsFromClassPath, context, null, ClassPathXmlPathResolver(loader, isRunningFromSources = false)).compute() + LoadDescriptorsFromClassPathAction(urlToFilename, context, null, ClassPathXmlPathResolver(loader, isRunningFromSources = false)).compute() context.result.finishLoading() return context.result.getEnabledPlugins() } @@ -591,12 +612,11 @@ private class LoadDescriptorsFromDirAction(private val dir: Path, override fun compute(): IdeaPluginDescriptorImpl? { if (Files.isDirectory(file)) { return loadFromPluginDir(file = file, - pathName = PluginManagerCore.PLUGIN_XML, parentContext = context, isBundled = isBundled, isEssential = false, pathResolver = PluginXmlPathResolver.DEFAULT_PATH_RESOLVER, - descriptorRelativePath = "${PluginManagerCore.META_INF}${PluginManagerCore.PLUGIN_XML}") + useCoreClassLoader = false) } else if (file.fileName.toString().endsWith(".jar", ignoreCase = true)) { return loadDescriptorFromJar(file = file, @@ -605,7 +625,8 @@ private class LoadDescriptorsFromDirAction(private val dir: Path, parentContext = context, isBundled = isBundled, isEssential = false, - pluginPath = null) + pluginPath = null, + useCoreClassLoader = false) } else { return null @@ -628,82 +649,91 @@ private class LoadDescriptorsFromDirAction(private val dir: Path, } } -private class LoadDescriptorsFromClassPathAction(private val urls: Map, +// urls here expected to be a file urls to plugin.xml +private class LoadDescriptorsFromClassPathAction(private val urlToFilename: Map, private val context: DescriptorListLoadingContext, private val platformPluginURL: URL?, - private val pathResolver: PathResolver) : RecursiveAction() { + private val pathResolver: ClassPathXmlPathResolver) : RecursiveAction() { public override fun compute() { - val tasks = ArrayList>(urls.size) - for ((url, value) in urls) { + val tasks = ArrayList>(urlToFilename.size) + urlToFilename.forEach(BiConsumer { url, filename -> tasks.add(object : RecursiveTask() { override fun compute(): IdeaPluginDescriptorImpl? { val isEssential = url == platformPluginURL try { - return loadDescriptorFromResource(resource = url, pathName = value, isEssential = isEssential) + return loadDescriptorFromResource(resource = url, filename = filename) } catch (e: Throwable) { if (isEssential) { throw e } + LOG.info("Cannot load $url", e) return null } } }) - } + }) val result = context.result ForkJoinTask.invokeAll(tasks) for (task in tasks) { task.rawResult?.let { - it.setUseCoreClassLoader() result.add(it, overrideUseIfCompatible = false) } } } - private fun loadDescriptorFromResource(resource: URL, pathName: String, isEssential: Boolean): IdeaPluginDescriptorImpl? { - when { - URLUtil.FILE_PROTOCOL == resource.protocol -> { - val file = Paths.get(Strings.trimEnd(UrlClassLoader.urlToFilePath(resource.path).replace('\\', '/'), pathName)).parent - return loadDescriptorFromFileOrDir(file = file, - pathName = pathName, - context = context, - pathResolver = pathResolver, - isBundled = true, - isEssential = isEssential, - isDirectory = Files.isDirectory(file)) - } - URLUtil.JAR_PROTOCOL == resource.protocol -> { - val file = Paths.get(UrlClassLoader.urlToFilePath(resource.path)) - val parentFile = file.parent - if (parentFile == null || !parentFile.endsWith("lib")) { - return loadDescriptorFromJar(file = file, - fileName = pathName, - pathResolver = pathResolver, - parentContext = context, - isBundled = true, - isEssential = isEssential, - pluginPath = null) + // filename - plugin.xml or ${platformPrefix}Plugin.xml + private fun loadDescriptorFromResource(resource: URL, filename: String): IdeaPluginDescriptorImpl? { + val file = Paths.get(UrlClassLoader.urlToFilePath(resource.path)) + var closeable: Closeable? = null + val dataLoader: DataLoader + val basePath: Path + try { + val input: InputStream + when { + URLUtil.FILE_PROTOCOL == resource.protocol -> { + basePath = file.parent.parent + dataLoader = LocalFsDataLoader(basePath) + input = Files.newInputStream(file) } - else { - // Support for unpacked plugins in classpath. E.g. .../community/build/dependencies/build/kotlin/Kotlin/lib/kotlin-plugin.jar - val descriptor = loadDescriptorFromJar(file = file, - fileName = pathName, - pathResolver = pathResolver, - parentContext = context, - isBundled = true, - isEssential = isEssential, - pluginPath = file.parent.parent) - if (descriptor != null) { - descriptor.jarFiles = Collections.singletonList(file) + URLUtil.JAR_PROTOCOL == resource.protocol -> { + // support for unpacked plugins in classpath, e.g. .../community/build/dependencies/build/kotlin/Kotlin/lib/kotlin-plugin.jar + basePath = file.parent?.takeIf { !it.endsWith("lib") }?.parent ?: file + + val pool = if (context.transient) null else ZipFilePool.POOL + if (pool == null) { + val zipFile = ZipFile(file.toFile(), StandardCharsets.UTF_8) + closeable = zipFile + dataLoader = JavaZipFileDataLoader(zipFile) } - return descriptor + else { + dataLoader = ImmutableZipFileDataLoader(pool.load(file), file, pool) + } + + input = dataLoader.load("META-INF/$filename") ?: return null } + else -> return null } - else -> { - return null - } + + val raw = readModuleDescriptor(input = input, + readContext = context, + pathResolver = pathResolver, + dataLoader = dataLoader, + includeBase = null, + readInto = null, + locationSource = file.toString()) + // it is very important to not set useCoreClassLoader = true blindly + // - product modules must uses own class loader if not running from sources + val descriptor = IdeaPluginDescriptorImpl(raw = raw, path = basePath, isBundled = true, id = null, moduleName = null, + useCoreClassLoader = pathResolver.isRunningFromSources) + descriptor.readExternal(raw = raw, pathResolver = pathResolver, context = context, isSub = false, dataLoader = dataLoader) + // do not set jarFiles by intention - doesn't make sense + return descriptor + } + finally { + closeable?.close() } } } diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PluginLoadingResult.kt b/platform/core-impl/src/com/intellij/ide/plugins/PluginLoadingResult.kt index 6a63600fa85b..16d7ee6b6349 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PluginLoadingResult.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginLoadingResult.kt @@ -1,10 +1,12 @@ // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +@file:Suppress("ReplaceGetOrSet") package com.intellij.ide.plugins import com.intellij.core.CoreBundle import com.intellij.openapi.extensions.PluginId import com.intellij.openapi.util.BuildNumber import com.intellij.util.PlatformUtils +import com.intellij.util.lang.Java11Shim import com.intellij.util.text.VersionComparatorUtil import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.TestOnly @@ -55,6 +57,10 @@ class PluginLoadingResult(private val brokenPluginVersions: Map = Collections.unmodifiableMap(pluginErrors) fun getGlobalErrors(): List> { @@ -64,22 +70,22 @@ class PluginLoadingResult(private val brokenPluginVersions: Map 0) { - incompletePlugins.put(plugin.pluginId, plugin) - } - } - if (error != null) { - pluginErrors.put(plugin.pluginId, error) - } - } - - internal fun reportIncompatiblePlugin(plugin: IdeaPluginDescriptorImpl, error: PluginLoadingError) { // do not report if some compatible plugin were already added // no race condition here: plugins from classpath are loaded before and not in parallel to loading from plugin dir - if (!idMap.containsKey(plugin.pluginId)) { - pluginErrors.put(plugin.pluginId, error) + if (idMap.containsKey(plugin.pluginId)) { + return + } + + val existingIncompletePlugin = incompletePlugins.putIfAbsent(plugin.pluginId, plugin) + if (existingIncompletePlugin != null && VersionComparatorUtil.compare(plugin.version, existingIncompletePlugin.version) > 0) { + incompletePlugins.put(plugin.pluginId, plugin) + if (error != null) { + // force put + pluginErrors.put(plugin.pluginId, error) + } + } + else if (error != null) { + pluginErrors.putIfAbsent(plugin.pluginId, error) } } @@ -99,15 +105,12 @@ class PluginLoadingResult(private val brokenPluginVersions: Map, + effectiveDisabledIds: Set) { + @JvmField val effectiveDisabledIds: Set = + Java11Shim.INSTANCE.copyOf(effectiveDisabledIds.mapTo(HashSet(effectiveDisabledIds.size), IdeaPluginDescriptorImpl::getPluginId)) + @JvmField val disabledRequiredIds: Set = + Java11Shim.INSTANCE.copyOf(disabledRequiredIds.mapTo(HashSet(disabledRequiredIds.size), IdeaPluginDescriptorImpl::getPluginId)) } \ No newline at end of file diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PluginManager.java b/platform/core-impl/src/com/intellij/ide/plugins/PluginManager.java index 500f56c5340e..63cd62b51767 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PluginManager.java +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginManager.java @@ -164,13 +164,7 @@ public final class PluginManager { } public @Nullable IdeaPluginDescriptor findEnabledPlugin(@NotNull PluginId id) { - List result = PluginManagerCore.getLoadedPlugins(null); - for (IdeaPluginDescriptor plugin : result) { - if (id.equals(plugin.getPluginId())) { - return plugin; - } - } - return null; + return PluginManagerCore.getPluginSet().findEnabledPlugin(id); } public boolean hideImplementationDetails() { diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java b/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java index 4a827d37a6a0..51cb78e6df8a 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java @@ -14,14 +14,11 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.BuildNumber; import com.intellij.openapi.util.NlsContexts; import com.intellij.openapi.util.NlsSafe; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.util.text.HtmlChunk; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.PlatformUtils; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.graph.DFSTBuilder; -import com.intellij.util.lang.Java11Shim; import com.intellij.util.lang.UrlClassLoader; import org.jetbrains.annotations.*; @@ -88,10 +85,6 @@ public final class PluginManagerCore { private static Set ourPluginsToDisable; private static Set ourPluginsToEnable; - @SuppressWarnings("StaticNonFinalField") - @ApiStatus.Internal - public static boolean ourDisableNonBundledPlugins; - /** * Bundled plugins that were updated. * When we update bundled plugin it becomes not bundled, so it is more difficult for analytics to use that data. @@ -114,7 +107,8 @@ public final class PluginManagerCore { return getPluginSet().allPlugins.toArray(new IdeaPluginDescriptor[0]); } - static @NotNull PluginSet getPluginSet() { + @ApiStatus.Internal + public static @NotNull PluginSet getPluginSet() { return Objects.requireNonNull(pluginSet); } @@ -123,7 +117,7 @@ public final class PluginManagerCore { * the plugins it depends on. */ public static @NotNull List getLoadedPlugins() { - return getLoadedPlugins(null); + return getPluginSet().enabledPlugins; } @ApiStatus.Internal @@ -394,33 +388,6 @@ public final class PluginManagerCore { return false; } - /** - * In 191.* and earlier builds Java plugin was part of the platform, so any plugin installed in IntelliJ IDEA might be able to use its - * classes without declaring explicit dependency on the Java module. This method is intended to add implicit dependency on the Java plugin - * for such plugins to avoid breaking compatibility with them. - */ - static @Nullable IdeaPluginDescriptorImpl getImplicitDependency(@NotNull IdeaPluginDescriptorImpl descriptor, - @NotNull Supplier javaDepGetter) { - // skip our plugins as expected to be up-to-date whether bundled or not - if (descriptor.isBundled() || descriptor.packagePrefix != null || descriptor.implementationDetail) { - return null; - } - - PluginId pluginId = descriptor.getPluginId(); - if (CORE_ID.equals(pluginId) || JAVA_PLUGIN_ID.equals(pluginId)) { - return null; - } - - IdeaPluginDescriptorImpl javaDep = javaDepGetter.get(); - if (javaDep == null) { - return null; - } - - // If a plugin does not include any module dependency tags in its plugin.xml, it's assumed to be a legacy plugin - // and is loaded only in IntelliJ IDEA, so it may use classes from Java plugin. - return hasModuleDependencies(descriptor) ? null : javaDep; - } - static boolean hasModuleDependencies(@NotNull IdeaPluginDescriptorImpl descriptor) { for (PluginDependency dependency : descriptor.pluginDependencies) { PluginId dependencyPluginId = dependency.getPluginId(); @@ -435,6 +402,12 @@ public final class PluginManagerCore { public static synchronized void invalidatePlugins() { pluginSet = null; + + CompletableFuture future = descriptorListFuture; + if (future != null) { + descriptorListFuture = null; + future.cancel(false); + } DisabledPluginsState.invalidate(); ourShadowedBundledPlugins = null; } @@ -549,93 +522,39 @@ public final class PluginManagerCore { return pluginLoadingErrors.get(pluginId); } - private static void checkPluginCycles(@NotNull List descriptors, - @NotNull PluginSet pluginSet, - @NotNull List> errors, - @NotNull Map idMap) { - CachingSemiGraph graph = CachingSemiGraph.Companion.createPluginIdGraph(descriptors, pluginSet, true); - DFSTBuilder builder = new DFSTBuilder<>(graph); - if (builder.isAcyclic()) { - return; + private static @NotNull List> prepareActions(@NotNull Set disabledIds, + @NotNull Set disabledRequiredIds) { + if (disabledIds.isEmpty()) { + return Collections.emptyList(); } - for (Collection component : builder.getComponents()) { - if (component.size() < 2) { - continue; - } - for (PluginId id : component) { - IdeaPluginDescriptorImpl plugin = pluginSet.findEnabledPlugin(id); - if (plugin != null) { - plugin.setEnabled(false); - } - } - - String pluginsString = component.stream().map(it -> "'" + idMap.get(it).getName() + "'").collect(Collectors.joining(", ")); - errors.add(message("plugin.loading.error.plugins.cannot.be.loaded.because.they.form.a.dependency.cycle", pluginsString)); - - StringBuilder detailedMessage = new StringBuilder(); - Function pluginToString = id -> { - IdeaPluginDescriptorImpl descriptor = idMap.get(id); - return "id = " + id.getIdString() + " (" + descriptor.getName() + ")"; - }; - - detailedMessage.append("Detected plugin dependencies cycle details (only related dependencies are included):\n"); - component.stream() - .map(p -> new Pair<>(p, pluginToString.apply(p))) - .sorted(Comparator.comparing(p -> p.second, String.CASE_INSENSITIVE_ORDER)) - .forEach(p -> { - detailedMessage.append(" ").append(p.getSecond()).append(" depends on:\n"); - graph.getInStream(p.first) - .filter(component::contains) - .map(pluginToString) - .sorted(String.CASE_INSENSITIVE_ORDER) - .forEach(dep -> detailedMessage.append(" ").append(dep).append("\n")); - }); - - getLogger().info(detailedMessage.toString()); - } - } - - private static @NotNull List> prepareActions(@NotNull Set disabledIds, - @NotNull Set disabledRequiredIds, - @NotNull Map idMap) { List> actions = new ArrayList<>(); - if (!disabledIds.isEmpty()) { - String nameToDisable = getFirstPluginName(disabledIds, idMap, PluginId::getIdString); + String nameToDisable = getFirstPluginName(disabledIds); + actions.add(() -> { + return HtmlChunk.link(DISABLE, nameToDisable == null + ? CoreBundle.message("link.text.disable.not.loaded.plugins") + : CoreBundle.message("link.text.disable.plugin", nameToDisable)); + }); + if (!disabledRequiredIds.isEmpty()) { + String nameToEnable = getFirstPluginName(disabledRequiredIds); actions.add(() -> { - String text = nameToDisable != null ? - CoreBundle.message("link.text.disable.plugin", nameToDisable) : - CoreBundle.message("link.text.disable.not.loaded.plugins"); - return HtmlChunk.link(DISABLE, text); + return HtmlChunk.link(ENABLE, nameToEnable == null + ? CoreBundle.message("link.text.enable.all.necessary.plugins") + : CoreBundle.message("link.text.enable.plugin", nameToEnable)); }); - if (!disabledRequiredIds.isEmpty()) { - String nameToEnable = getFirstPluginName(disabledRequiredIds, idMap, __ -> null); - actions.add(() -> { - String text = nameToEnable != null ? - CoreBundle.message("link.text.enable.plugin", nameToEnable) : - CoreBundle.message("link.text.enable.all.necessary.plugins"); - return HtmlChunk.link(ENABLE, text); - }); - } - actions.add(() -> HtmlChunk.link(EDIT, CoreBundle.message("link.text.open.plugin.manager"))); } + actions.add(() -> HtmlChunk.link(EDIT, CoreBundle.message("link.text.open.plugin.manager"))); return actions; } - private static @Nullable @NlsSafe String getFirstPluginName(@NotNull Set pluginIds, - @NotNull Map idMap, - @NotNull Function defaultName) { - int size = pluginIds.size(); - if (size == 0) { - throw new IllegalArgumentException("Plugins set should not be empty"); - } - else if (size == 1) { - PluginId pluginId = pluginIds.iterator().next(); - IdeaPluginDescriptor descriptor = idMap.get(pluginId); - return descriptor != null ? descriptor.getName() : defaultName.apply(pluginId); - } - else { - return null; + private static @Nullable @NlsSafe String getFirstPluginName(@NotNull Set modules) { + switch (modules.size()) { + case 0: + throw new IllegalArgumentException("Plugins set should not be empty"); + case 1: + return modules.iterator().next().getName(); + default: + return null; } } @@ -647,11 +566,9 @@ public final class PluginManagerCore { boolean applied = pluginIds != null; if (applied) { - Map pluginIdMap = buildPluginIdMap(); - for (PluginId pluginId : pluginIds) { - IdeaPluginDescriptor descriptor = pluginIdMap.get(pluginId); - if (descriptor != null) { - descriptor.setEnabled(enabled); + for (IdeaPluginDescriptorImpl module : getPluginSet().allPlugins) { + if (pluginIds.contains(module.getPluginId())) { + module.setEnabled(enabled); } } @@ -690,13 +607,13 @@ public final class PluginManagerCore { } @ApiStatus.Internal - public static @NotNull CompletableFuture> initPlugins(@NotNull ClassLoader coreClassLoader) { + public static @NotNull CompletableFuture initPlugins(@NotNull ClassLoader coreClassLoader) { CompletableFuture future = descriptorListFuture; if (future == null) { throw new IllegalStateException("Call scheduleDescriptorLoading() first"); } return future.thenApply(context -> { - return loadAndInitializePlugins(context, coreClassLoader).enabledPlugins; + return loadAndInitializePlugins(context, coreClassLoader); }); } @@ -726,14 +643,9 @@ public final class PluginManagerCore { private static void disableIncompatiblePlugins(@NotNull List descriptors, @NotNull Map idMap, @NotNull Map errors) { - boolean isNonBundledPluginDisabled = ourDisableNonBundledPlugins; - if (isNonBundledPluginDisabled) { - getLogger().info("Running with disableThirdPartyPlugins argument, third-party plugins will be disabled"); - } String selectedIds = System.getProperty("idea.load.plugins.id"); String selectedCategory = System.getProperty("idea.load.plugins.category"); - IdeaPluginDescriptorImpl coreDescriptor = idMap.get(CORE_ID); Set explicitlyEnabled = null; if (selectedIds != null) { Set set = new HashSet<>(); @@ -772,21 +684,14 @@ public final class PluginManagerCore { explicitlyEnabled.addAll(nonOptionalDependencies); } - Map> brokenPluginVersions = getBrokenPluginVersions(); + IdeaPluginDescriptorImpl coreDescriptor = idMap.get(CORE_ID); boolean shouldLoadPlugins = Boolean.parseBoolean(System.getProperty("idea.load.plugins", "true")); for (IdeaPluginDescriptorImpl descriptor : descriptors) { if (descriptor == coreDescriptor) { continue; } - Set set = brokenPluginVersions.get(descriptor.getPluginId()); - if (set != null && set.contains(descriptor.getVersion())) { - descriptor.setEnabled(false); - errors.put(descriptor.getPluginId(), new PluginLoadingError(descriptor, - message("plugin.loading.error.long.marked.as.broken", descriptor.getName(), descriptor.getVersion()), - message("plugin.loading.error.short.marked.as.broken"))); - } - else if (explicitlyEnabled != null) { + if (explicitlyEnabled != null) { if (!explicitlyEnabled.contains(descriptor)) { descriptor.setEnabled(false); getLogger().info("Plugin '" + descriptor.getName() + "' " + @@ -802,15 +707,6 @@ public final class PluginManagerCore { descriptor.getName()), message("plugin.loading.error.short.plugin.loading.disabled"))); } - else if (isNonBundledPluginDisabled && !descriptor.isBundled()) { - descriptor.setEnabled(false); - errors.put(descriptor.getPluginId(), new PluginLoadingError(descriptor, - message("plugin.loading.error.long.custom.plugin.loading.disabled", - descriptor.getName()), - message("plugin.loading.error.short.custom.plugin.loading.disabled"), - false, - null)); - } } } @@ -900,45 +796,25 @@ public final class PluginManagerCore { } List descriptors = loadingResult.getEnabledPlugins(); - PluginSet rawPluginSet = PluginSet.Companion.createRawPluginSet(descriptors); disableIncompatiblePlugins(descriptors, idMap, pluginErrorsById); - checkPluginCycles(descriptors, rawPluginSet, globalErrors, idMap); + PluginSetBuilder pluginSetBuilder = new PluginSetBuilder(descriptors); + pluginSetBuilder.checkPluginCycles(globalErrors); - Map disabledIds = new HashMap<>(); + Set disabledAfterInit = new HashSet<>(); + Set disabledRequired = new HashSet<>(); - // topological sort based on required dependencies only - List sortedRequired = rawPluginSet.sortTopologically(descriptors, false); - - Map enabledPluginIds = new HashMap<>(); - Map enabledModuleV2Ids = new HashMap<>(); - Set disabledRequiredIds = new HashSet<>(); - - Logger logger = getLogger(); - boolean isDebugLogEnabled = logger.isDebugEnabled() || !System.getProperty("plugin.classloader.debug", "").isEmpty() || isUnitTestMode; - for (IdeaPluginDescriptorImpl descriptor : sortedRequired) { - boolean wasEnabled = descriptor.isEnabled(); - if (wasEnabled && computePluginEnabled(descriptor, - enabledPluginIds, enabledModuleV2Ids, - idMap, disabledRequiredIds, context.disabledPlugins, pluginErrorsById)) { - PluginSet.Companion.addWithV1Modules(enabledPluginIds, descriptor); - if (!CORE_ID.equals(descriptor.getPluginId())) { - PluginSet.Companion.checkModules(descriptor, enabledPluginIds, enabledModuleV2Ids, isDebugLogEnabled, logger); - } - else { - for (PluginContentDescriptor.ModuleItem item : descriptor.content.modules) { - enabledModuleV2Ids.put(item.name, item); - } - } + pluginSetBuilder.computeEnabledModuleMap(descriptor -> { + if (pluginSetBuilder.initEnableState$intellij_platform_core_impl(descriptor, idMap, disabledRequired, + context.disabledPlugins, pluginErrorsById)) { + return false; } - else { - descriptor.setEnabled(false); - if (wasEnabled) { - disabledIds.put(descriptor.getPluginId(), descriptor.getName()); - } - } - } - List> actions = prepareActions(disabledIds.keySet(), disabledRequiredIds, idMap); + descriptor.setEnabled(false); + disabledAfterInit.add(descriptor); + return true; + }); + + List> actions = prepareActions(disabledAfterInit, disabledRequired); pluginLoadingErrors = pluginErrorsById; List> errorsList = preparePluginsError(globalErrors); @@ -949,47 +825,13 @@ public final class PluginManagerCore { } } - // topological sort based on all (required and optional) dependencies - List allPlugins = rawPluginSet.sortTopologically(sortedRequired, true); - List enabledPlugins = PluginSet.Companion.getOnlyEnabledPlugins(allPlugins); - Java11Shim java11Shim = Java11Shim.INSTANCE; - if (!context.result.incompletePlugins.isEmpty()) { - List result = new ArrayList<>(allPlugins.size() + context.result.incompletePlugins.size()); - result.addAll(allPlugins); - result.addAll(context.result.incompletePlugins.values()); - allPlugins = java11Shim.copyOf(result); - } - - PluginSet pluginSet = new PluginSet(allPlugins, java11Shim.copyOf(enabledPlugins), - java11Shim.copyOf(enabledModuleV2Ids), java11Shim.copyOf(enabledPluginIds)); - new ClassLoaderConfigurator(pluginSet, coreLoader).configureAll(); - if (checkEssentialPlugins) { checkEssentialPluginsAreAvailable(idMap); } - return new PluginManagerState(pluginSet, disabledRequiredIds, java11Shim.copyOf(disabledIds.keySet())); - } - - @ApiStatus.Internal - public static @NotNull Map buildPluginIdMap(@NotNull List descriptors) { - Map idMap = new HashMap<>(descriptors.size()); - Map> duplicateMap = null; - for (IdeaPluginDescriptorImpl descriptor : descriptors) { - Map> newDuplicateMap = checkAndPut(descriptor, descriptor.getPluginId(), idMap, duplicateMap); - if (newDuplicateMap != null) { - duplicateMap = newDuplicateMap; - continue; - } - - for (PluginId module : descriptor.modules) { - newDuplicateMap = checkAndPut(descriptor, module, idMap, duplicateMap); - if (newDuplicateMap != null) { - duplicateMap = newDuplicateMap; - } - } - } - return idMap; + PluginSet pluginSet = pluginSetBuilder.createPluginSet(context.result.incompletePlugins.values()); + new ClassLoaderConfigurator(pluginSet, coreLoader).configure(); + return new PluginManagerState(pluginSet, disabledRequired, disabledAfterInit); } @SuppressWarnings("DuplicatedCode") @@ -1023,119 +865,6 @@ public final class PluginManagerCore { return duplicateMap; } - private static boolean computePluginEnabled(@NotNull IdeaPluginDescriptorImpl descriptor, - @NotNull Map enabledPluginIds, - @NotNull Map enabledModuleV2Ids, - @NotNull Map idMap, - @NotNull Set disabledRequiredIds, - @NotNull Set disabledPlugins, - @NotNull Map errors) { - if (CORE_ID.equals(descriptor.getPluginId())) { - return true; - } - - boolean notifyUser = !descriptor.isImplementationDetail(); - boolean result = true; - for (PluginId incompatibleId : descriptor.incompatibilities) { - if (!enabledPluginIds.containsKey(incompatibleId) || disabledPlugins.contains(incompatibleId)) { - continue; - } - - result = false; - String presentableName = incompatibleId.getIdString(); - errors.put(descriptor.getPluginId(), - new PluginLoadingError( - descriptor, - message("plugin.loading.error.long.ide.contains.conflicting.module", descriptor.getName(), presentableName), - message("plugin.loading.error.short.ide.contains.conflicting.module", presentableName), notifyUser, null - )); - } - - for (PluginDependency dependency : descriptor.pluginDependencies) { - PluginId depId = dependency.getPluginId(); - if (dependency.isOptional() || enabledPluginIds.containsKey(depId)) { - continue; - } - - result = false; - IdeaPluginDescriptor dep = idMap.get(depId); - if (dep != null && disabledPlugins.contains(depId)) { - // broken/incompatible plugins can be updated, add them anyway - disabledRequiredIds.add(dep.getPluginId()); - } - - addCannotLoadError(descriptor, errors, notifyUser, depId, dep); - } - - for (ModuleDependenciesDescriptor.PluginReference item : descriptor.dependencies.plugins) { - if (enabledPluginIds.containsKey(item.id)) { - continue; - } - - result = false; - IdeaPluginDescriptorImpl dep = idMap.get(item.id); - if (dep != null && disabledPlugins.contains(item.id)) { - // broken/incompatible plugins can be updated, add them anyway - disabledRequiredIds.add(dep.getPluginId()); - } - - addCannotLoadError(descriptor, errors, notifyUser, item.id, dep); - } - - for (ModuleDependenciesDescriptor.ModuleReference item : descriptor.dependencies.modules) { - if (enabledModuleV2Ids.containsKey(item.name)) { - continue; - } - - result = false; - errors.put(descriptor.getPluginId(), - new PluginLoadingError( - descriptor, - message("plugin.loading.error.long.depends.on.not.installed.plugin", descriptor.getName(), item.name), - message("plugin.loading.error.short.depends.on.not.installed.plugin", item.name), notifyUser, null - )); - - } - return result; - } - - private static void addCannotLoadError(@NotNull IdeaPluginDescriptorImpl descriptor, - @NotNull Map errors, - boolean notifyUser, - PluginId depId, - @Nullable IdeaPluginDescriptor dep) { - String depName = dep == null ? null : dep.getName(); - if (depName == null) { - @NlsSafe String depPresentableId = depId.getIdString(); - if (errors.containsKey(depId)) { - PluginLoadingError depError = errors.get(depId); - String depNameFromError = depError.getPlugin().getName(); - errors.put(descriptor.getPluginId(), - new PluginLoadingError( - descriptor, - message("plugin.loading.error.long.depends.on.failed.to.load.plugin", descriptor.getName(), depNameFromError != null ? depNameFromError : depPresentableId), - message("plugin.loading.error.short.depends.on.failed.to.load.plugin", depPresentableId), notifyUser, null - )); - } - else { - errors.put(descriptor.getPluginId(), - new PluginLoadingError( - descriptor, - message("plugin.loading.error.long.depends.on.not.installed.plugin", descriptor.getName(), depPresentableId), - message("plugin.loading.error.short.depends.on.not.installed.plugin", depPresentableId), notifyUser, null - )); - } - } - else { - errors.put(descriptor.getPluginId(), - new PluginLoadingError( - descriptor, - message("plugin.loading.error.long.depends.on.disabled.plugin", descriptor.getName(), depName), - message("plugin.loading.error.short.depends.on.disabled.plugin", depName), notifyUser, dep.getPluginId() - )); - } - } - private static @NotNull @Nls Supplier message(@NotNull @PropertyKey(resourceBundle = CoreBundle.BUNDLE) String key, Object @NotNull ... params) { //noinspection Convert2Lambda @@ -1150,26 +879,24 @@ public final class PluginManagerCore { @SuppressWarnings("NonPrivateFieldAccessedInSynchronizedContext") private static synchronized @NotNull PluginSet loadAndInitializePlugins(@NotNull DescriptorListLoadingContext context, @NotNull ClassLoader coreLoader) { - try { - Activity activity = StartUpMeasurer.startActivity("plugin initialization", ActivityCategory.DEFAULT); - PluginManagerState initResult = initializePlugins(context, coreLoader, !isUnitTestMode); - PluginLoadingResult result = context.result; - - ourPluginsToDisable = initResult.effectiveDisabledIds; - ourPluginsToEnable = initResult.disabledRequiredIds; - - ourShadowedBundledPlugins = result.shadowedBundledIds; - - activity.end(); - activity.setDescription("plugin count: " + initResult.pluginSet.enabledPlugins.size()); - logPlugins(initResult.pluginSet.allPlugins, result.incompletePlugins.values()); - pluginSet = initResult.pluginSet; - return initResult.pluginSet; - } - catch (RuntimeException e) { - getLogger().error(e); - throw e; + if (IdeaPluginDescriptorImpl.disableNonBundledPlugins) { + getLogger().info("Running with disableThirdPartyPlugins argument, third-party plugins will be disabled"); } + + Activity activity = StartUpMeasurer.startActivity("plugin initialization", ActivityCategory.DEFAULT); + PluginManagerState initResult = initializePlugins(context, coreLoader, !isUnitTestMode); + PluginLoadingResult result = context.result; + + ourPluginsToDisable = initResult.effectiveDisabledIds; + ourPluginsToEnable = initResult.disabledRequiredIds; + + ourShadowedBundledPlugins = result.shadowedBundledIds; + + activity.end(); + activity.setDescription("plugin count: " + initResult.pluginSet.enabledPlugins.size()); + logPlugins(initResult.pluginSet.allPlugins, result.incompletePlugins.values()); + pluginSet = initResult.pluginSet; + return initResult.pluginSet; } @SuppressWarnings("RedundantSuppression") @@ -1241,7 +968,23 @@ public final class PluginManagerCore { @ApiStatus.Internal public static @NotNull Map buildPluginIdMap() { LoadingState.COMPONENTS_REGISTERED.checkOccurred(); - return buildPluginIdMap(getPluginSet().allPlugins); + Map idMap = new HashMap<>(getPluginSet().allPlugins.size()); + Map> duplicateMap = null; + for (IdeaPluginDescriptorImpl descriptor : getPluginSet().allPlugins) { + Map> newDuplicateMap = checkAndPut(descriptor, descriptor.getPluginId(), idMap, duplicateMap); + if (newDuplicateMap != null) { + duplicateMap = newDuplicateMap; + continue; + } + + for (PluginId module : descriptor.modules) { + newDuplicateMap = checkAndPut(descriptor, module, idMap, duplicateMap); + if (newDuplicateMap != null) { + duplicateMap = newDuplicateMap; + } + } + } + return idMap; } @SuppressWarnings("UnusedReturnValue") diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerState.java b/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerState.java deleted file mode 100644 index d1a5f60e9f78..000000000000 --- a/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerState.java +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -package com.intellij.ide.plugins; - -import com.intellij.openapi.extensions.PluginId; -import org.jetbrains.annotations.NotNull; - -import java.util.Set; - -// todo merge into PluginSetState? -public final class PluginManagerState { - final Set effectiveDisabledIds; - final Set disabledRequiredIds; - final PluginSet pluginSet; - - PluginManagerState(@NotNull PluginSet pluginSet, - @NotNull Set disabledRequiredIds, - @NotNull Set effectiveDisabledIds) { - this.pluginSet = pluginSet; - this.disabledRequiredIds = disabledRequiredIds; - this.effectiveDisabledIds = effectiveDisabledIds; - } -} 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 3ecb1a3cf40c..0711b984396c 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PluginSet.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginSet.kt @@ -2,149 +2,57 @@ @file:Suppress("ReplacePutWithAssignment", "ReplaceGetOrSet") package com.intellij.ide.plugins -import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.extensions.PluginId import com.intellij.util.graph.DFSTBuilder -import com.intellij.util.lang.Java11Shim import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.TestOnly -import java.util.* // if otherwise not specified, `module` in terms of v2 plugin model @ApiStatus.Internal class PluginSet internal constructor( @JvmField val allPlugins: List, @JvmField val enabledPlugins: List, - private val enabledModuleMap: Map, + private val enabledModuleMap: Map, private val enabledPluginAndV1ModuleMap: Map, + private val enabledModules: List, + @JvmField val moduleToDirectDependencies: Map> ) { companion object { - // special case - raw plugin set where everything is enabled and resolved - fun createRawPluginSet(plugins: List): PluginSet { - val java11Shim = Java11Shim.INSTANCE - val enabledModuleV2Ids = HashMap() - val enabledPluginAndModuleV1Map = HashMap(plugins.size) - for (descriptor in plugins) { - addWithV1Modules(enabledPluginAndModuleV1Map, descriptor) - for (item in descriptor.content.modules) { - enabledModuleV2Ids.put(item.name, item) - } - } - return PluginSet( - allPlugins = plugins, - enabledPlugins = plugins, - enabledModuleMap = java11Shim.copyOf(enabledModuleV2Ids), - enabledPluginAndV1ModuleMap = java11Shim.copyOf(enabledPluginAndModuleV1Map), - ) - } - - fun createPluginSet(allPlugins: List, enabledPlugins: List): PluginSet { - val enabledModuleV2Ids = HashMap() - val enabledPluginAndModuleV1Map = HashMap(enabledPlugins.size) - - val log = PluginManagerCore.getLogger() - val isDebugLogEnabled = log.isDebugEnabled || !System.getProperty("plugin.classloader.debug", "").isEmpty() - for (descriptor in enabledPlugins) { - addWithV1Modules(enabledPluginAndModuleV1Map, descriptor) - checkModules(descriptor, enabledPluginAndModuleV1Map, enabledModuleV2Ids, isDebugLogEnabled, log) - } - - val java11Shim = Java11Shim.INSTANCE - return PluginSet( - allPlugins = java11Shim.copyOf(allPlugins), - enabledPlugins = java11Shim.copyOf(enabledPlugins), - enabledModuleMap = java11Shim.copyOf(enabledModuleV2Ids), - enabledPluginAndV1ModuleMap = java11Shim.copyOf(enabledPluginAndModuleV1Map), - ) - } - - fun addWithV1Modules(result: MutableMap, descriptor: IdeaPluginDescriptorImpl) { - result.put(descriptor.id, descriptor) - for (module in descriptor.modules) { - result.put(module, descriptor) - } - } - - fun getOnlyEnabledPlugins(sortedAll: Collection): List { - return sortedAll.filterTo(ArrayList(sortedAll.size)) { it.isEnabled } - } - - fun checkModules(descriptor: IdeaPluginDescriptorImpl, - enabledPluginIds: Map, - enabledModuleV2Ids: MutableMap, - isDebugLogEnabled: Boolean, - log: Logger) { - m@ for (item in descriptor.content.modules) { - for (ref in item.requireDescriptor().dependencies.modules) { - if (!enabledModuleV2Ids.containsKey(ref.name)) { - if (isDebugLogEnabled) { - log.info("Module ${item.name} is not enabled because dependency ${ref.name} is not available") - } - continue@m - } - } - for (ref in item.requireDescriptor().dependencies.plugins) { - if (!enabledPluginIds.containsKey(ref.id)) { - if (isDebugLogEnabled) { - log.info("Module ${item.name} is not enabled because dependency ${ref.id} is not available") - } - continue@m - } - } - enabledModuleV2Ids.put(item.name, item) - } + fun getTopologicalComparator(allPlugins: List): Comparator { + val graph = CachingSemiGraph.createModuleGraph(allPlugins) + return CachingSemiGraph.getTopologicalComparator(DFSTBuilder(graph.nodes.toTypedArray(), null, true, graph)) } } + fun getRawListOfEnabledModules() = enabledModules + + /** + * You must not use this method before [ClassLoaderConfigurator.configure]. + */ + fun getEnabledModules(): Sequence = enabledModules.asSequence() + @TestOnly - fun getUnsortedEnabledModules(): Collection = ArrayList(enabledModuleMap.values) + fun getUnsortedEnabledModules(): Collection = ArrayList(enabledModuleMap.values) fun isPluginEnabled(id: PluginId) = enabledPluginAndV1ModuleMap.containsKey(id) fun findEnabledPlugin(id: PluginId): IdeaPluginDescriptorImpl? = enabledPluginAndV1ModuleMap.get(id) - fun findEnabledModule(id: String): IdeaPluginDescriptorImpl? = enabledModuleMap.get(id)?.requireDescriptor() + fun findEnabledModule(id: String): IdeaPluginDescriptorImpl? = enabledModuleMap.get(id) fun isModuleEnabled(id: String) = enabledModuleMap.containsKey(id) - fun enablePlugin(descriptor: IdeaPluginDescriptorImpl): PluginSet { + fun enablePlugin(toEnable: IdeaPluginDescriptorImpl): PluginSet { // in tests or on install plugin is not in all plugins // linear search is ok here - not a hot method - PluginManagerCore.getLogger().assertTrue(!enabledPlugins.contains(descriptor)) - - return createPluginSet( - allPlugins = if (descriptor in allPlugins) allPlugins else sortTopologically(allPlugins + descriptor), - enabledPlugins = sortTopologically(enabledPlugins + descriptor), - ) + PluginManagerCore.getLogger().assertTrue(!enabledPlugins.contains(toEnable) && toEnable.isEnabled) + return PluginSetBuilder(if (toEnable in allPlugins) allPlugins else allPlugins + toEnable).computeEnabledModuleMap().createPluginSet() } - fun sortTopologically(descriptors: List, withOptional: Boolean = true): List { - val graph = CachingSemiGraph.createPluginIdGraph(descriptors, pluginSet = this, withOptional) - - val comparator = DFSTBuilder(graph).comparator() - // there is circular reference between core and implementation-detail plugin, as not all such plugins extracted from core, - // so, ensure that core plugin is always first (otherwise not possible to register actions - parent group not defined) - // don't use sortWith here - avoid loading kotlin stdlib - val sortedRequired = descriptors.toTypedArray() - Arrays.sort(sortedRequired, Comparator { o1, o2 -> - when (PluginManagerCore.CORE_ID) { - o1.id -> -1 - o2.id -> 1 - else -> comparator.compare(o1.id, o2.id) - } - }) - @Suppress("ReplaceJavaStaticMethodWithKotlinAnalog", "UNCHECKED_CAST") - return Java11Shim.INSTANCE.listOf(sortedRequired) - } - - fun updateEnabledPlugins() = updateEnabledPlugins(allPlugins) + fun updateEnabledPlugins() = PluginSetBuilder(allPlugins).computeEnabledModuleMap().createPluginSet() fun removePluginAndUpdateEnabledPlugins(descriptor: IdeaPluginDescriptorImpl): PluginSet { // not just remove from enabledPlugins - maybe another plugins in list also disabled as result of plugin unloading - return updateEnabledPlugins(allPlugins = allPlugins - descriptor) - } - - private fun updateEnabledPlugins(allPlugins: List): PluginSet { - return createPluginSet(allPlugins = allPlugins, enabledPlugins = getOnlyEnabledPlugins(allPlugins)) + return PluginSetBuilder(unsortedPlugins = allPlugins - descriptor).computeEnabledModuleMap().createPluginSet() } } \ 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 new file mode 100644 index 000000000000..fab8bfd8781d --- /dev/null +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginSetBuilder.kt @@ -0,0 +1,314 @@ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +@file:Suppress("ReplaceGetOrSet", "ReplaceGetOrSet") +package com.intellij.ide.plugins + +import com.intellij.core.CoreBundle +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.extensions.PluginId +import com.intellij.util.containers.ContainerUtil +import com.intellij.util.graph.DFSTBuilder +import com.intellij.util.lang.Java11Shim +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.Nls +import org.jetbrains.annotations.PropertyKey +import java.util.* +import java.util.function.BiConsumer +import java.util.function.Predicate +import java.util.function.Supplier + +@ApiStatus.Internal +class PluginSetBuilder(val unsortedPlugins: List) { + private val graph = CachingSemiGraph.createModuleGraph(unsortedPlugins) + private val sortedModules = graph.nodes.toTypedArray() + // useIdentityStrategy - performance, avoid equals + private val topologicalSorter = DFSTBuilder(sortedModules.clone(), null, true, graph) + + private val topologicalComparator = CachingSemiGraph.getTopologicalComparator(topologicalSorter) + + private val enabledPluginIds = HashMap(unsortedPlugins.size) + private val enabledModuleV2Ids = HashMap(unsortedPlugins.size * 2) + + init { + Arrays.sort(sortedModules, topologicalComparator) + } + + fun checkPluginCycles(errors: MutableList>) { + if (topologicalSorter.isAcyclic) { + return + } + + for (component in topologicalSorter.components) { + if (component.size < 2) { + continue + } + + for (plugin in component) { + plugin.isEnabled = false + } + + val pluginsString = component.joinToString(separator = ", ") { "'${it.name}'" } + errors.add(message("plugin.loading.error.plugins.cannot.be.loaded.because.they.form.a.dependency.cycle", pluginsString)) + val detailedMessage = StringBuilder() + val pluginToString: (IdeaPluginDescriptorImpl) -> String = { "id = ${it.pluginId.idString} (${it.name})" } + detailedMessage.append("Detected plugin dependencies cycle details (only related dependencies are included):\n") + component + .asSequence() + .map { Pair(it, pluginToString(it)) } + .sortedWith(Comparator.comparing({ it.second }, String.CASE_INSENSITIVE_ORDER)) + .forEach { + detailedMessage.append(" ").append(it.second).append(" depends on:\n") + graph.getIn(it.first).asSequence() + .filter { o: IdeaPluginDescriptorImpl -> component.contains(o) } + .map(pluginToString) + .sortedWith(java.lang.String.CASE_INSENSITIVE_ORDER) + .forEach { dep: String? -> + detailedMessage.append(" ").append(dep).append("\n") + } + } + PluginManagerCore.getLogger().info(detailedMessage.toString()) + } + } + + // Only plugins returned. Not modules. See PluginManagerTest.moduleSort test to understand the issue. + private fun getSortedPlugins(): Array { + val pluginToNumber = Object2IntOpenHashMap(unsortedPlugins.size) + pluginToNumber.put(PluginManagerCore.CORE_ID, 0) + var number = 0 + for (module in sortedModules) { + // no content, so will be no modules, add it + if (module.descriptorPath != null || module.content.modules.isEmpty()) { + pluginToNumber.putIfAbsent(module.pluginId, number++) + } + } + val sorted = unsortedPlugins.toTypedArray() + Arrays.sort(sorted, Comparator { o1, o2 -> + pluginToNumber.getInt(o1.pluginId) - pluginToNumber.getInt(o2.pluginId) + }) + return sorted + } + + private fun getEnabledModules(): List { + val result = ArrayList(sortedModules.size) + for (module in sortedModules) { + if (if (module.moduleName == null) module.isEnabled else enabledModuleV2Ids.containsKey(module.moduleName)) { + result.add(module) + } + } + return result + } + + fun computeEnabledModuleMap(disabler: Predicate? = null): PluginSetBuilder { + val logMessages = ArrayList() + + m@ for (module in sortedModules) { + if (module.moduleName == null) { + if (module.pluginId != PluginManagerCore.CORE_ID && (!module.isEnabled || (disabler != null && disabler.test(module)))) { + continue + } + } + else if (!enabledPluginIds.containsKey(module.pluginId)) { + continue + } + + for (ref in module.dependencies.modules) { + if (!enabledModuleV2Ids.containsKey(ref.name)) { + logMessages.add("Module ${module.moduleName ?: module.pluginId} is not enabled because dependency ${ref.name} is not available") + continue@m + } + } + for (ref in module.dependencies.plugins) { + if (!enabledPluginIds.containsKey(ref.id)) { + logMessages.add("Module ${module.moduleName ?: module.pluginId} is not enabled because dependency ${ref.id} is not available") + continue@m + } + } + + if (module.moduleName == null) { + enabledPluginIds.put(module.pluginId, module) + for (v1Module in module.modules) { + enabledPluginIds.put(v1Module, module) + } + if (module.packagePrefix != null) { + enabledModuleV2Ids.put(module.pluginId.idString, module) + } + } + else { + enabledModuleV2Ids.put(module.moduleName, module) + } + } + + if (!logMessages.isEmpty()) { + PluginManagerCore.getLogger().info(logMessages.joinToString(separator = "\n")) + } + return this + } + + fun createPluginSet(incompletePlugins: Collection = Collections.emptyList()): PluginSet { + val java11Shim = Java11Shim.INSTANCE + + val allPlugins: List + val sortedPlugins = getSortedPlugins() + if (incompletePlugins.isEmpty()) { + allPlugins = ContainerUtil.immutableList(*sortedPlugins) + } + else { + val result = ArrayList(sortedPlugins.size + incompletePlugins.size) + result.addAll(sortedPlugins) + result.addAll(incompletePlugins) + allPlugins = java11Shim.copyOfCollection(result) + } + + val enabledPlugins = java11Shim.copyOfCollection(sortedPlugins.filterTo(ArrayList(sortedPlugins.size)) { it.isEnabled }) + + val moduleToSortedDirectDependencies = + IdentityHashMap>(graph.moduleToDirectDependencies.size) + graph.moduleToDirectDependencies.forEach(BiConsumer { k, v -> + val list = v.toTypedArray() + Arrays.sort(list, topologicalComparator) + moduleToSortedDirectDependencies.put(k, list) + }) + + return PluginSet( + allPlugins = allPlugins, + enabledPlugins = enabledPlugins, + enabledModuleMap = java11Shim.copyOf(enabledModuleV2Ids), + enabledPluginAndV1ModuleMap = java11Shim.copyOf(enabledPluginIds), + enabledModules = java11Shim.copyOfCollection(getEnabledModules()), + moduleToDirectDependencies = java11Shim.copyOf(moduleToSortedDirectDependencies), + ) + } + + fun checkModules(descriptor: IdeaPluginDescriptorImpl, isDebugLogEnabled: Boolean, log: Logger) { + m@ for (item in descriptor.content.modules) { + for (ref in item.requireDescriptor().dependencies.modules) { + if (!enabledModuleV2Ids.containsKey(ref.name)) { + if (isDebugLogEnabled) { + log.info("Module ${item.name} is not enabled because dependency ${ref.name} is not available") + } + continue@m + } + } + for (ref in item.requireDescriptor().dependencies.plugins) { + if (!enabledPluginIds.containsKey(ref.id)) { + if (isDebugLogEnabled) { + log.info("Module ${item.name} is not enabled because dependency ${ref.id} is not available") + } + continue@m + } + } + enabledModuleV2Ids.put(item.name, descriptor) + } + } + + // use only for init plugins + internal fun initEnableState(descriptor: IdeaPluginDescriptorImpl, + idMap: Map, + disabledRequired: MutableSet, + disabledPlugins: Set, + errors: MutableMap): Boolean { + val notifyUser = !descriptor.isImplementationDetail + for (incompatibleId in descriptor.incompatibilities) { + if (!enabledPluginIds.containsKey(incompatibleId) || disabledPlugins.contains(incompatibleId)) { + continue + } + + val presentableName = incompatibleId.idString + errors.put(descriptor.pluginId, PluginLoadingError( + plugin = descriptor, + detailedMessageSupplier = message("plugin.loading.error.long.ide.contains.conflicting.module", descriptor.name, presentableName), + shortMessageSupplier = message("plugin.loading.error.short.ide.contains.conflicting.module", presentableName), + isNotifyUser = notifyUser, + )) + return false + } + + for (dependency in descriptor.pluginDependencies) { + val depId = dependency.pluginId + if (dependency.isOptional || enabledPluginIds.containsKey(depId)) { + continue + } + + val dep = idMap.get(depId) + if (dep != null && disabledPlugins.contains(depId)) { + // broken/incompatible plugins can be updated, add them anyway + disabledRequired.add(dep) + } + addCannotLoadError(descriptor, errors, notifyUser, depId, dep) + return false + } + + for (item in descriptor.dependencies.plugins) { + if (enabledPluginIds.containsKey(item.id)) { + continue + } + + val dep = idMap.get(item.id) + if (dep != null && disabledPlugins.contains(item.id)) { + // broken/incompatible plugins can be updated, add them anyway + disabledRequired.add(dep) + } + addCannotLoadError(descriptor, errors, notifyUser, item.id, dep) + return false + } + + for (item in descriptor.dependencies.modules) { + if (enabledModuleV2Ids.containsKey(item.name)) { + continue + } + + errors.put(descriptor.pluginId, PluginLoadingError( + plugin = descriptor, + detailedMessageSupplier = message("plugin.loading.error.long.depends.on.not.installed.plugin", descriptor.name, item.name), + shortMessageSupplier = message("plugin.loading.error.short.depends.on.not.installed.plugin", item.name), + isNotifyUser = notifyUser, + )) + return false + } + return true + } +} + +private fun addCannotLoadError(descriptor: IdeaPluginDescriptorImpl, + errors: MutableMap, + notifyUser: Boolean, + depId: PluginId, + dep: IdeaPluginDescriptor?) { + val depName = dep?.name + if (depName == null) { + val depPresentableId = depId.idString + if (errors.containsKey(depId)) { + val depError = errors.get(depId)!! + val depNameFromError = depError.plugin.name + errors.put(descriptor.pluginId, PluginLoadingError( + plugin = descriptor, + detailedMessageSupplier = message("plugin.loading.error.long.depends.on.failed.to.load.plugin", + descriptor.name, depNameFromError ?: depPresentableId), + shortMessageSupplier = message("plugin.loading.error.short.depends.on.failed.to.load.plugin", depPresentableId), + isNotifyUser = notifyUser, + disabledDependency = null + )) + } + else { + errors.put(descriptor.pluginId, PluginLoadingError( + plugin = descriptor, + detailedMessageSupplier = message("plugin.loading.error.long.depends.on.not.installed.plugin", descriptor.name, depPresentableId), + shortMessageSupplier = message("plugin.loading.error.short.depends.on.not.installed.plugin", depPresentableId), + isNotifyUser = notifyUser, disabledDependency = null + )) + } + } + else { + errors.put(descriptor.pluginId, PluginLoadingError( + plugin = descriptor, + detailedMessageSupplier = message("plugin.loading.error.long.depends.on.disabled.plugin", descriptor.name, depName), + shortMessageSupplier = message("plugin.loading.error.short.depends.on.disabled.plugin", depName), + isNotifyUser = notifyUser, + disabledDependency = dep.pluginId + )) + } +} + +private fun message(key: @PropertyKey(resourceBundle = CoreBundle.BUNDLE) String, vararg params: Any): @Nls Supplier { + return Supplier { CoreBundle.message(key, *params) } +} \ No newline at end of file diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PluginXmlPathResolver.kt b/platform/core-impl/src/com/intellij/ide/plugins/PluginXmlPathResolver.kt index 4772ad7f9e0c..39eb74aad6e8 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PluginXmlPathResolver.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginXmlPathResolver.kt @@ -25,7 +25,7 @@ class PluginXmlPathResolver(private val pluginJarFiles: List) : PathResolv try { // do not use kotlin stdlib here val entry = zipFile.getEntry(if (relativePath.startsWith("/")) relativePath.substring(1) else relativePath) ?: return false - readModuleDescriptor(inputStream = zipFile.getInputStream(entry), + readModuleDescriptor(input = zipFile.getInputStream(entry), readContext = readContext, pathResolver = pathResolver, dataLoader = dataLoader, @@ -96,7 +96,7 @@ class PluginXmlPathResolver(private val pluginJarFiles: List) : PathResolv // it is allowed to reference any platform XML file using href="/META-INF/EnforcedPlainText.xml" if (path.startsWith("META-INF/")) { PluginXmlPathResolver::class.java.classLoader.getResourceAsStream(path)?.let { - readModuleDescriptor(inputStream = it, + readModuleDescriptor(input = it, readContext = readContext, pathResolver = this, dataLoader = dataLoader, diff --git a/platform/core-impl/src/com/intellij/ide/plugins/XmlReader.kt b/platform/core-impl/src/com/intellij/ide/plugins/XmlReader.kt index 7f0f433b592e..db45489c2b41 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/XmlReader.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/XmlReader.kt @@ -34,14 +34,17 @@ import javax.xml.stream.events.XMLEvent private const val defaultXPointerValue = "xpointer(/idea-plugin/*)" -fun readModuleDescriptor(inputStream: InputStream, +/** + * Do not use [java.io.BufferedInputStream] - buffer is used internally already. + */ +fun readModuleDescriptor(input: InputStream, readContext: ReadModuleContext, pathResolver: PathResolver, dataLoader: DataLoader, includeBase: String?, readInto: RawPluginDescriptor?, locationSource: String?): RawPluginDescriptor { - return readModuleDescriptor(reader = createNonCoalescingXmlStreamReader(inputStream, locationSource), + return readModuleDescriptor(reader = createNonCoalescingXmlStreamReader(input, locationSource), readContext = readContext, pathResolver = pathResolver, dataLoader = dataLoader, @@ -691,7 +694,7 @@ private fun readContent(reader: XMLStreamReader2, } reader.skipElement() } - descriptor.content = PluginContentDescriptor(Java11Shim.INSTANCE.copyOf(items)) + descriptor.content = PluginContentDescriptor(Java11Shim.INSTANCE.copyOfCollection(items)) assert(reader.isEndElement) } diff --git a/platform/core-impl/src/com/intellij/ide/plugins/cl/PluginClassLoader.java b/platform/core-impl/src/com/intellij/ide/plugins/cl/PluginClassLoader.java index 9a6a9682500d..d7986bc89b0e 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/cl/PluginClassLoader.java +++ b/platform/core-impl/src/com/intellij/ide/plugins/cl/PluginClassLoader.java @@ -113,7 +113,7 @@ public final class PluginClassLoader extends UrlClassLoader implements PluginAwa logStream = logStreamCandidate; } - private IdeaPluginDescriptorImpl[] parents; + private final IdeaPluginDescriptorImpl[] parents; // cache of computed list of all parents (not only direct) private volatile ClassLoader[] allParents; @@ -553,10 +553,10 @@ public final class PluginClassLoader extends UrlClassLoader implements PluginAwa } private static final class DeepEnumeration implements Enumeration { - private final @NotNull List> list; + private final @NotNull List> list; private int myIndex; - DeepEnumeration(@NotNull List> enumerations) { + DeepEnumeration(@NotNull List> enumerations) { list = enumerations; } @@ -582,50 +582,11 @@ public final class PluginClassLoader extends UrlClassLoader implements PluginAwa } @TestOnly - @ApiStatus.Internal public @NotNull List _getParents() { //noinspection SSBasedInspection return Collections.unmodifiableList(Arrays.asList(parents)); } - @ApiStatus.Internal - public void attachParent(@NotNull IdeaPluginDescriptorImpl parent) { - //noinspection SSBasedInspection - if (Arrays.stream(parents).anyMatch(it -> it == parent)) { - return; - } - - int length = parents.length; - IdeaPluginDescriptorImpl[] result = new IdeaPluginDescriptorImpl[length + 1]; - System.arraycopy(parents, 0, result, 0, length); - result[length] = parent; - parents = result; - allParents = null; - parentListCacheIdCounter.incrementAndGet(); - } - - /** - * You must clear allParents cache for all loaded plugins. - */ - @ApiStatus.Internal - public boolean detachParent(@NotNull IdeaPluginDescriptorImpl parent) { - for (int i = 0; i < parents.length; i++) { - if (parent != parents[i]) { - continue; - } - - int length = parents.length; - IdeaPluginDescriptorImpl[] result = new IdeaPluginDescriptorImpl[length - 1]; - System.arraycopy(parents, 0, result, 0, i); - System.arraycopy(parents, i + 1, result, i, length - i - 1); - parents = result; - allParents = null; - parentListCacheIdCounter.incrementAndGet(); - return true; - } - return false; - } - @Override protected ProtectionDomain getProtectionDomain() { return PROTECTION_DOMAIN; diff --git a/platform/core-impl/src/com/intellij/serviceContainer/PrecomputedExtensionModel.kt b/platform/core-impl/src/com/intellij/serviceContainer/PrecomputedExtensionModel.kt index 136a143e7782..448eb39c4779 100644 --- a/platform/core-impl/src/com/intellij/serviceContainer/PrecomputedExtensionModel.kt +++ b/platform/core-impl/src/com/intellij/serviceContainer/PrecomputedExtensionModel.kt @@ -1,11 +1,15 @@ // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +@file:Suppress("ReplaceGetOrSet") package com.intellij.serviceContainer import com.intellij.ide.plugins.IdeaPluginDescriptor import com.intellij.ide.plugins.IdeaPluginDescriptorImpl +import com.intellij.ide.plugins.PluginManagerCore import com.intellij.openapi.extensions.ExtensionDescriptor import com.intellij.openapi.extensions.ExtensionPointDescriptor +import org.jetbrains.annotations.ApiStatus +@ApiStatus.Internal class PrecomputedExtensionModel( @JvmField val extensionPoints: List>, @JvmField val pluginDescriptors: List, @@ -14,14 +18,15 @@ class PrecomputedExtensionModel( @JvmField val nameToExtensions: Map>>> ) -fun precomputeExtensionModel(plugins: List): PrecomputedExtensionModel { +fun precomputeExtensionModel(): PrecomputedExtensionModel { val extensionPointDescriptors = ArrayList>() val pluginDescriptors = ArrayList() var extensionPointTotalCount = 0 val nameToExtensions = HashMap>>>() // step 1 - collect container level extension points - executeRegisterTask(plugins) { pluginDescriptor -> + val modules = PluginManagerCore.getPluginSet().getEnabledModules() + executeRegisterTask(modules) { pluginDescriptor -> pluginDescriptor.moduleContainerDescriptor.extensionPoints?.let { extensionPointDescriptors.add(it) pluginDescriptors.add(pluginDescriptor) @@ -34,7 +39,7 @@ fun precomputeExtensionModel(plugins: List): Precomput } // step 2 - collect container level extensions - executeRegisterTask(plugins) { pluginDescriptor -> + executeRegisterTask(modules) { pluginDescriptor -> val unsortedMap = pluginDescriptor.epNameToExtensions ?: return@executeRegisterTask for ((name, list) in unsortedMap.entries) { nameToExtensions.get(name)?.add(pluginDescriptor to list) @@ -50,15 +55,15 @@ fun precomputeExtensionModel(plugins: List): Precomput ) } -inline fun executeRegisterTask(plugins: List, crossinline task: (IdeaPluginDescriptorImpl) -> Unit) { - for (plugin in plugins) { - task(plugin) - executeRegisterTaskForContent(mainPluginDescriptor = plugin, task = task) +private inline fun executeRegisterTask(modules: Sequence, crossinline task: (IdeaPluginDescriptorImpl) -> Unit) { + for (module in modules) { + task(module) + executeRegisterTaskForOldContent(mainPluginDescriptor = module, task = task) } } -inline fun executeRegisterTaskForContent(mainPluginDescriptor: IdeaPluginDescriptorImpl, - crossinline task: (IdeaPluginDescriptorImpl) -> Unit) { +inline fun executeRegisterTaskForOldContent(mainPluginDescriptor: IdeaPluginDescriptorImpl, + crossinline task: (IdeaPluginDescriptorImpl) -> Unit) { for (dep in mainPluginDescriptor.pluginDependencies) { val subDescriptor = dep.subDescriptor if (subDescriptor?.classLoader == null) { @@ -75,11 +80,4 @@ inline fun executeRegisterTaskForContent(mainPluginDescriptor: IdeaPluginDescrip } } } - - for (item in mainPluginDescriptor.content.modules) { - val module = item.requireDescriptor() - if (module.classLoader != null) { - task(module) - } - } } \ No newline at end of file diff --git a/platform/diagnostic/src/startUpPerformanceReporter/IdeIdeaFormatWriter.kt b/platform/diagnostic/src/startUpPerformanceReporter/IdeIdeaFormatWriter.kt index 1ad2d33274a5..f30853eee252 100644 --- a/platform/diagnostic/src/startUpPerformanceReporter/IdeIdeaFormatWriter.kt +++ b/platform/diagnostic/src/startUpPerformanceReporter/IdeIdeaFormatWriter.kt @@ -1,11 +1,11 @@ -// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +@file:Suppress("ReplaceGetOrSet") package com.intellij.diagnostic.startUpPerformanceReporter import com.fasterxml.jackson.core.JsonGenerator import com.intellij.diagnostic.ActivityImpl import com.intellij.diagnostic.StartUpMeasurer import com.intellij.diagnostic.ThreadNameManager -import com.intellij.ide.plugins.IdeaPluginDescriptorImpl import com.intellij.ide.plugins.PluginManagerCore import com.intellij.ide.plugins.cl.PluginAwareClassLoader import com.intellij.openapi.application.ApplicationInfo @@ -155,11 +155,11 @@ private fun writeServiceStats(writer: JsonGenerator) { val component = StatItem("component") val service = StatItem("service") - val plugins = PluginManagerCore.getLoadedPlugins(null).sortedBy { it.pluginId } - for (plugin in plugins) { - service.app += (plugin as IdeaPluginDescriptorImpl).appContainerDescriptor.services?.size ?: 0 - service.project += plugin.projectContainerDescriptor.services?.size ?: 0 - service.module += plugin.moduleContainerDescriptor.services?.size ?: 0 + val pluginSet = PluginManagerCore.getPluginSet() + for (plugin in pluginSet.getEnabledModules()) { + service.app += plugin.appContainerDescriptor.services.size + service.project += plugin.projectContainerDescriptor.services.size + service.module += plugin.moduleContainerDescriptor.services.size component.app += plugin.appContainerDescriptor.components?.size ?: 0 component.project += plugin.projectContainerDescriptor.components?.size ?: 0 @@ -167,7 +167,7 @@ private fun writeServiceStats(writer: JsonGenerator) { } writer.obj("stats") { - writer.writeNumberField("plugin", plugins.size) + writer.writeNumberField("plugin", pluginSet.enabledPlugins.size) for (statItem in listOf(component, service)) { writer.obj(statItem.name) { writer.writeNumberField("app", statItem.app) @@ -178,7 +178,7 @@ private fun writeServiceStats(writer: JsonGenerator) { } writer.array("plugins") { - for (plugin in plugins) { + for (plugin in pluginSet.enabledPlugins) { val classLoader = plugin.pluginClassLoader as? PluginAwareClassLoader ?: continue writer.obj { writer.writeStringField("id", plugin.pluginId.idString) diff --git a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/IntentionHintComponent.java b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/IntentionHintComponent.java index 29c01671e59f..aa76161fff42 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/IntentionHintComponent.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/IntentionHintComponent.java @@ -1,5 +1,4 @@ -// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. - +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.codeInsight.intention.impl; import com.intellij.codeInsight.CodeInsightBundle; @@ -442,7 +441,7 @@ public final class IntentionHintComponent implements Disposable, ScrollAwareHint myComponentHint = new MyComponentHint(myPanel); EditorUtil.disposeWithEditor(myEditor, this); - DynamicPlugins.onPluginUnload(this, () -> Disposer.dispose(this)); + DynamicPlugins.INSTANCE.onPluginUnload(this, () -> Disposer.dispose(this)); } public void hide() { diff --git a/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplatesLoader.java b/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplatesLoader.java index 8ca460aa8c89..89582bea79b5 100644 --- a/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplatesLoader.java +++ b/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplatesLoader.java @@ -1,4 +1,4 @@ -// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ide.fileTemplates.impl; import com.intellij.ide.fileTemplates.FileTemplateManager; @@ -177,13 +177,14 @@ class FileTemplatesLoader implements Disposable { FileTemplateLoadResult result = new FileTemplateLoadResult(new MultiMap<>()); Set processedUrls = new HashSet<>(); Set processedLoaders = new HashSet<>(); - for (IdeaPluginDescriptorImpl plugin : PluginManagerCore.getLoadedPlugins(null)) { + for (IdeaPluginDescriptorImpl plugin : PluginManagerCore.getPluginSet().enabledPlugins) { ClassLoader loader = plugin.getPluginClassLoader(); if (loader instanceof PluginAwareClassLoader && ((PluginAwareClassLoader)loader).getFiles().isEmpty() || !processedLoaders.add(loader)) { // test or development mode, when IDEA_CORE's loader contains all the classpath continue; } + try { Enumeration systemResources = loader.getResources(DEFAULT_TEMPLATES_ROOT); while (systemResources.hasMoreElements()) { diff --git a/platform/lang-impl/src/com/intellij/openapi/module/impl/ModuleImpl.java b/platform/lang-impl/src/com/intellij/openapi/module/impl/ModuleImpl.java index 341209256d08..1239e424fb9c 100644 --- a/platform/lang-impl/src/com/intellij/openapi/module/impl/ModuleImpl.java +++ b/platform/lang-impl/src/com/intellij/openapi/module/impl/ModuleImpl.java @@ -1,11 +1,10 @@ -// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.openapi.module.impl; import com.intellij.configurationStore.RenameableStateStorageManager; import com.intellij.ide.highlighter.ModuleFileType; import com.intellij.ide.plugins.ContainerDescriptor; import com.intellij.ide.plugins.IdeaPluginDescriptorImpl; -import com.intellij.ide.plugins.PluginManagerCore; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.*; import com.intellij.openapi.components.impl.stores.IComponentStore; @@ -90,7 +89,7 @@ public class ModuleImpl extends ComponentManagerImpl implements ModuleEx { public void init(@Nullable Runnable beforeComponentCreation) { // do not measure (activityNamePrefix method not overridden by this class) // because there are a lot of modules and no need to measure each one - registerComponents(PluginManagerCore.getLoadedPlugins(null), ApplicationManager.getApplication(), null, null); + registerComponents(); if (!isPersistent()) { registerService(IComponentStore.class, NonPersistentModuleStore.class, diff --git a/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/module/ModuleBridgeImpl.kt b/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/module/ModuleBridgeImpl.kt index 59bcc630299c..d4a831127714 100644 --- a/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/module/ModuleBridgeImpl.kt +++ b/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/module/ModuleBridgeImpl.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.workspaceModel.ide.impl.legacyBridge.module import com.intellij.ide.plugins.IdeaPluginDescriptor @@ -66,12 +66,12 @@ internal class ModuleBridgeImpl( super.rename(newName, notifyStorage) } - override fun registerComponents(plugins: List, + override fun registerComponents(modules: Sequence, app: Application?, precomputedExtensionModel: PrecomputedExtensionModel?, listenerCallbacks: List?) { - registerComponents(corePlugin = plugins.find { it.pluginId == PluginManagerCore.CORE_ID }, - plugins = plugins, + registerComponents(corePlugin = modules.find { it.pluginId == PluginManagerCore.CORE_ID }, + modules = modules, precomputedExtensionModel = precomputedExtensionModel, app = app, listenerCallbacks = listenerCallbacks) @@ -82,15 +82,17 @@ internal class ModuleBridgeImpl( } override fun registerComponents(corePlugin: IdeaPluginDescriptor?, - plugins: List, - precomputedExtensionModel: PrecomputedExtensionModel?, - app: Application?, - listenerCallbacks: List?) { - super.registerComponents(plugins = plugins, + modules: Sequence, + precomputedExtensionModel: PrecomputedExtensionModel?, + app: Application?, + listenerCallbacks: List?) { + super.registerComponents(modules = modules, app = app, precomputedExtensionModel = precomputedExtensionModel, listenerCallbacks = listenerCallbacks) - if (corePlugin == null) return + if (corePlugin == null) { + return + } unregisterComponent(DeprecatedModuleOptionManager::class.java) try { diff --git a/platform/platform-impl/src/com/intellij/externalDependencies/impl/CheckRequiredPluginsActivity.java b/platform/platform-impl/src/com/intellij/externalDependencies/impl/CheckRequiredPluginsActivity.java index bd5e7b874e45..edf1ab6d327d 100644 --- a/platform/platform-impl/src/com/intellij/externalDependencies/impl/CheckRequiredPluginsActivity.java +++ b/platform/platform-impl/src/com/intellij/externalDependencies/impl/CheckRequiredPluginsActivity.java @@ -75,7 +75,7 @@ final class CheckRequiredPluginsActivity implements StartupActivity.RequiredForS if (!plugin.isEnabled() || pluginTracker.isDisabled(pluginId)) { boolean canEnableWithoutRestart = false; if (Registry.is("ide.plugins.load.automatically")) { - String message = DynamicPlugins.checkCanUnloadWithoutRestart((IdeaPluginDescriptorImpl)plugin); + String message = DynamicPlugins.INSTANCE.checkCanUnloadWithoutRestart((IdeaPluginDescriptorImpl)plugin); if (message == null) { canEnableWithoutRestart = true; pluginsToEnableWithoutRestart.add(plugin); diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/ClassLoaderTreeChecker.kt b/platform/platform-impl/src/com/intellij/ide/plugins/ClassLoaderTreeChecker.kt index fcf1a8b9458d..9569f3629bed 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/ClassLoaderTreeChecker.kt +++ b/platform/platform-impl/src/com/intellij/ide/plugins/ClassLoaderTreeChecker.kt @@ -20,7 +20,7 @@ internal class ClassLoaderTreeChecker(private val unloadedMainDescriptor: IdeaPl } @Suppress("TestOnlyProblems") for (it in pluginSet.getUnsortedEnabledModules()) { - checkThatClassloaderNotReferenced(it.requireDescriptor()) + checkThatClassloaderNotReferenced(it) } } diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPlugins.kt b/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPlugins.kt index 084e3ce2c5db..e9c9c31465f0 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPlugins.kt +++ b/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPlugins.kt @@ -1,4 +1,4 @@ - // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ide.plugins import com.fasterxml.jackson.databind.type.TypeFactory @@ -80,7 +80,6 @@ import com.intellij.util.containers.WeakList import com.intellij.util.messages.impl.MessageBusEx import com.intellij.util.ref.GCWatcher import net.sf.cglib.core.ClassNameReader -import org.jetbrains.annotations.NonNls import java.awt.KeyboardFocusManager import java.awt.Window import java.nio.channels.FileChannel @@ -94,18 +93,17 @@ import javax.swing.ToolTipManager import kotlin.collections.component1 import kotlin.collections.component2 - private val LOG = logger() +private val LOG = logger() private val classloadersFromUnloadedPlugins = mutableMapOf>() +private const val GROUP_ID = "Dynamic plugin installation" object DynamicPlugins { - private const val GROUP_ID = "Dynamic plugin installation" - @JvmStatic @JvmOverloads fun allowLoadUnloadWithoutRestart(descriptor: IdeaPluginDescriptorImpl, baseDescriptor: IdeaPluginDescriptorImpl? = null, context: List = emptyList()): Boolean { - val reason = checkCanUnloadWithoutRestart(descriptor, baseDescriptor, context = context) + val reason = checkCanUnloadWithoutRestart(module = descriptor, parentModule = baseDescriptor, context = context) if (reason != null) { LOG.info(reason) } @@ -145,11 +143,8 @@ object DynamicPlugins { } val pluginSet = PluginManagerCore.getPluginSet() - val descriptors = plugins - .asSequence() - .filterIsInstance() - .filterNot { pluginSet.isPluginEnabled(it.pluginId) == load } - .toList() + @Suppress("UNCHECKED_CAST") + val descriptors = (plugins as Collection).filter { pluginSet.isPluginEnabled(it.pluginId) != load } val operationText = if (load) "load" else "unload" val message = descriptors.joinToString(prefix = "Plugins to $operationText: [", postfix = "]") @@ -159,7 +154,19 @@ object DynamicPlugins { return false } - for (descriptor in pluginsSortedByDependency(descriptors, load)) { + // todo plugin installation should be done not in this method + var allPlugins = pluginSet.allPlugins + for (descriptor in descriptors) { + if (!allPlugins.contains(descriptor)) { + allPlugins = allPlugins + descriptor + } + } + + var comparator = PluginSet.getTopologicalComparator(allPlugins) + if (!load) { + comparator = comparator.reversed() + } + for (descriptor in descriptors.sortedWith(comparator)) { descriptor.isEnabled = load if (!executor.invoke(descriptor)) { LOG.info("Failed to $operationText: $descriptor, restart required") @@ -171,81 +178,73 @@ object DynamicPlugins { return true } - private fun pluginsSortedByDependency(descriptors: List, load: Boolean): List { - val plugins = PluginManagerCore.getPluginSet().sortTopologically(descriptors) - return if (load) plugins else plugins.asReversed() + fun checkCanUnloadWithoutRestart(module: IdeaPluginDescriptorImpl): String? { + return checkCanUnloadWithoutRestart(module, parentModule = null) } /** - * @param context Plugins which are being loaded at the same time as [descriptor] + * @param context Plugins which are being loaded at the same time as [module] */ - @JvmStatic - @JvmOverloads - @NonNls - fun checkCanUnloadWithoutRestart(descriptor: IdeaPluginDescriptorImpl, - baseDescriptor: IdeaPluginDescriptorImpl? = null, - optionalDependencyPluginId: PluginId? = null, - context: List = emptyList(), - checkImplementationDetailDependencies: Boolean = true): String? { - if (descriptor.isRequireRestart) { - return "Plugin ${descriptor.pluginId} is explicitly marked as requiring restart" - } - if (descriptor.productCode != null && !descriptor.isBundled && !PluginManagerCore.isDevelopedByJetBrains(descriptor)) { - return "Plugin ${descriptor.pluginId} is a paid plugin" - } - - if (InstalledPluginsState.getInstance().isRestartRequired) { - return InstalledPluginsState.RESTART_REQUIRED_MESSAGE - } - if (classloadersFromUnloadedPlugins[descriptor.pluginId]?.isEmpty() == false) { - return "Not allowing load/unload of ${descriptor.pluginId} because of incomplete previous unload operation for that plugin" - } - findMissingRequiredDependency(descriptor, context)?.let { pluginDependency -> - return "Required dependency ${pluginDependency} of plugin ${descriptor.pluginId} is not currently loaded" - } - - if (!RegistryManager.getInstance().`is`("ide.plugins.allow.unload")) { - val canLoadSynchronously = allowLoadUnloadSynchronously(descriptor) - if (!canLoadSynchronously) { - return "ide.plugins.allow.unload is disabled and synchronous load/unload is not possible for ${descriptor.pluginId}" + private fun checkCanUnloadWithoutRestart(module: IdeaPluginDescriptorImpl, + parentModule: IdeaPluginDescriptorImpl?, + optionalDependencyPluginId: PluginId? = null, + context: List = emptyList(), + checkImplementationDetailDependencies: Boolean = true): String? { + if (parentModule == null) { + if (module.isRequireRestart) { + return "Plugin ${module.pluginId} is explicitly marked as requiring restart" + } + if (module.productCode != null && !module.isBundled && !PluginManagerCore.isDevelopedByJetBrains(module)) { + return "Plugin ${module.pluginId} is a paid plugin" + } + if (InstalledPluginsState.getInstance().isRestartRequired) { + return InstalledPluginsState.RESTART_REQUIRED_MESSAGE } - return null - } - - val app = ApplicationManager.getApplication() - try { - app.messageBus.syncPublisher(DynamicPluginListener.TOPIC).checkUnloadPlugin(descriptor) - } - catch (e: CannotUnloadPluginException) { - return e.cause?.localizedMessage ?: "checkUnloadPlugin listener blocked plugin unload" } val pluginSet = PluginManagerCore.getPluginSet() - if (!Registry.`is`("ide.plugins.allow.unload.from.sources")) { - val loadedPluginDescriptor = if (descriptor === baseDescriptor) { - pluginSet.findEnabledPlugin(descriptor.pluginId) - } - else { - null + if (classloadersFromUnloadedPlugins[module.pluginId]?.isEmpty() == false) { + return "Not allowing load/unload of ${module.pluginId} because of incomplete previous unload operation for that plugin" + } + findMissingRequiredDependency(module, context, pluginSet)?.let { pluginDependency -> + return "Required dependency ${pluginDependency} of plugin ${module.pluginId} is not currently loaded" + } + + val app = ApplicationManager.getApplication() + + if (parentModule == null) { + if (!RegistryManager.getInstance().`is`("ide.plugins.allow.unload")) { + if (!allowLoadUnloadSynchronously(module)) { + return "ide.plugins.allow.unload is disabled and synchronous load/unload is not possible for ${module.pluginId}" + } + return null } - if (loadedPluginDescriptor != null && !descriptor.isUseIdeaClassLoader && - pluginSet.isPluginEnabled(loadedPluginDescriptor.pluginId)) { - val pluginClassLoader = loadedPluginDescriptor.classLoader + try { + app.messageBus.syncPublisher(DynamicPluginListener.TOPIC).checkUnloadPlugin(module) + } + catch (e: CannotUnloadPluginException) { + return e.cause?.localizedMessage ?: "checkUnloadPlugin listener blocked plugin unload" + } + } + + if (!Registry.`is`("ide.plugins.allow.unload.from.sources")) { + if (pluginSet.findEnabledPlugin(module.pluginId) != null && module === parentModule && !module.isUseIdeaClassLoader) { + val pluginClassLoader = module.classLoader if (pluginClassLoader != null && pluginClassLoader !is PluginClassLoader && !app.isUnitTestMode) { - return "Plugin ${descriptor.pluginId} is not unload-safe because of use of ${pluginClassLoader.javaClass.name} as the default class loader. " + + return "Plugin ${module.pluginId} is not unload-safe because of use of ${pluginClassLoader.javaClass.name} as the default class loader. " + "For example, the IDE is started from the sources with the plugin." } } } - val epNameToExtensions = descriptor.epNameToExtensions + val epNameToExtensions = module.epNameToExtensions if (epNameToExtensions != null) { doCheckExtensionsCanUnloadWithoutRestart( extensions = epNameToExtensions, - descriptor = descriptor, - baseDescriptor = baseDescriptor, + descriptor = module, + baseDescriptor = parentModule, app = app, optionalDependencyPluginId = optionalDependencyPluginId, context = context, @@ -253,28 +252,39 @@ object DynamicPlugins { )?.let { return it } } - val pluginId = descriptor.pluginId - checkNoComponentsOrServiceOverrides(pluginId, descriptor)?.let { return it } - ActionManagerImpl.checkUnloadActions(pluginId, descriptor)?.let { return it } + checkNoComponentsOrServiceOverrides(module)?.let { return it } + ActionManagerImpl.checkUnloadActions(module)?.let { return it } - for (dependency in descriptor.pluginDependencies) { + for (moduleRef in module.content.modules) { + if (pluginSet.isModuleEnabled(moduleRef.name)) { + val subModule = moduleRef.requireDescriptor() + checkCanUnloadWithoutRestart(module = subModule, + parentModule = module, + optionalDependencyPluginId = null, + context = context)?.let { + return "$it in optional dependency on ${subModule.pluginId}" + } + } + } + + for (dependency in module.pluginDependencies) { if (pluginSet.isPluginEnabled(dependency.pluginId)) { - checkCanUnloadWithoutRestart(dependency.subDescriptor ?: continue, baseDescriptor ?: descriptor, null, context)?.let { + checkCanUnloadWithoutRestart(dependency.subDescriptor ?: continue, parentModule ?: module, null, context)?.let { return "$it in optional dependency on ${dependency.pluginId}" } } } // if not a sub plugin descriptor, then check that any dependent plugin also reloadable - if (baseDescriptor != null && descriptor !== baseDescriptor) { + if (parentModule != null && module !== parentModule) { return null } var dependencyMessage: String? = null - processOptionalDependenciesOnPlugin(descriptor, pluginSet, isLoaded = true) { mainDescriptor, subDescriptor -> + processOptionalDependenciesOnPlugin(module, pluginSet, isLoaded = true) { mainDescriptor, subDescriptor -> if (subDescriptor.packagePrefix == null || mainDescriptor.pluginId.idString == "org.jetbrains.kotlin" || mainDescriptor.pluginId == PluginManagerCore.JAVA_PLUGIN_ID) { - dependencyMessage = "Plugin ${subDescriptor.pluginId} that optionally depends on ${descriptor.pluginId}" + + dependencyMessage = "Plugin ${subDescriptor.pluginId} that optionally depends on ${module.pluginId}" + " does not have a separate classloader for the dependency" return@processOptionalDependenciesOnPlugin false } @@ -284,25 +294,26 @@ object DynamicPlugins { true } else { - dependencyMessage = "Plugin ${subDescriptor.pluginId} that optionally depends on ${descriptor.pluginId} requires restart: $dependencyMessage" + dependencyMessage = "Plugin ${subDescriptor.pluginId} that optionally depends on ${module.pluginId} requires restart: $dependencyMessage" false } } if (dependencyMessage == null && checkImplementationDetailDependencies) { val contextWithImplementationDetails = context.toMutableList() - contextWithImplementationDetails.add(descriptor) - processImplementationDetailDependenciesOnPlugin(descriptor, pluginSet, contextWithImplementationDetails::add) + contextWithImplementationDetails.add(module) + processImplementationDetailDependenciesOnPlugin(module, pluginSet, contextWithImplementationDetails::add) - processImplementationDetailDependenciesOnPlugin(descriptor, pluginSet) { dependentDescriptor -> + processImplementationDetailDependenciesOnPlugin(module, pluginSet) { dependentDescriptor -> // don't check a plugin that is an implementation-detail dependency on the current plugin if it has other disabled dependencies // and won't be loaded anyway - if (findMissingRequiredDependency(dependentDescriptor, contextWithImplementationDetails) == null) { - dependencyMessage = checkCanUnloadWithoutRestart(descriptor = dependentDescriptor, + if (findMissingRequiredDependency(dependentDescriptor, contextWithImplementationDetails, pluginSet) == null) { + dependencyMessage = checkCanUnloadWithoutRestart(module = dependentDescriptor, + parentModule = null, context = contextWithImplementationDetails, checkImplementationDetailDependencies = false) if (dependencyMessage != null) { - dependencyMessage = "implementation-detail plugin ${dependentDescriptor.pluginId} which depends on ${descriptor.pluginId}" + + dependencyMessage = "implementation-detail plugin ${dependentDescriptor.pluginId} which depends on ${module.pluginId}" + " requires restart: $dependencyMessage" } } @@ -313,13 +324,13 @@ object DynamicPlugins { } private fun findMissingRequiredDependency(descriptor: IdeaPluginDescriptorImpl, - context: List): PluginId? { + context: List, + pluginSet: PluginSet): PluginId? { for (dependency in descriptor.pluginDependencies) { if (!dependency.isOptional && !PluginManagerCore.isModuleDependency(dependency.pluginId) && - PluginManagerCore.getLoadedPlugins(null).none { it.pluginId == dependency.pluginId } && - context.none { it.pluginId == dependency.pluginId } - ) { + !pluginSet.isPluginEnabled(dependency.pluginId) && + context.none { it.pluginId == dependency.pluginId }) { return dependency.pluginId } } @@ -331,22 +342,22 @@ object DynamicPlugins { * plugins settings, without pressing the Apply button. */ @JvmStatic - fun allowLoadUnloadSynchronously(pluginDescriptor: IdeaPluginDescriptorImpl): Boolean { - val extensions = (pluginDescriptor.unsortedEpNameToExtensionElements.takeIf { it.isNotEmpty() } ?: pluginDescriptor.appContainerDescriptor.extensions) + fun allowLoadUnloadSynchronously(module: IdeaPluginDescriptorImpl): Boolean { + val extensions = (module.unsortedEpNameToExtensionElements.takeIf { it.isNotEmpty() } ?: module.appContainerDescriptor.extensions) if (extensions != null && !extensions.all { it.key == UIThemeProvider.EP_NAME.name || it.key == BundledKeymapBean.EP_NAME.name }) { return false } - return checkNoComponentsOrServiceOverrides(pluginDescriptor.pluginId, - pluginDescriptor) == null && pluginDescriptor.actions.isNullOrEmpty() + return checkNoComponentsOrServiceOverrides(module) == null && module.actions.isEmpty() } - private fun checkNoComponentsOrServiceOverrides(pluginId: PluginId?, pluginDescriptor: IdeaPluginDescriptorImpl): String? { - return checkNoComponentsOrServiceOverrides(pluginId, pluginDescriptor.appContainerDescriptor) - ?: checkNoComponentsOrServiceOverrides(pluginId, pluginDescriptor.projectContainerDescriptor) - ?: checkNoComponentsOrServiceOverrides(pluginId, pluginDescriptor.moduleContainerDescriptor) + private fun checkNoComponentsOrServiceOverrides(module: IdeaPluginDescriptorImpl): String? { + val id = module.pluginId + return checkNoComponentsOrServiceOverrides(id, module.appContainerDescriptor) + ?: checkNoComponentsOrServiceOverrides(id, module.projectContainerDescriptor) + ?: checkNoComponentsOrServiceOverrides(id, module.moduleContainerDescriptor) } private fun checkNoComponentsOrServiceOverrides(pluginId: PluginId?, containerDescriptor: ContainerDescriptor): String? { @@ -359,8 +370,6 @@ object DynamicPlugins { return null } - @JvmStatic - @JvmOverloads fun unloadPluginWithProgress(project: Project? = null, parentComponent: JComponent?, pluginDescriptor: IdeaPluginDescriptorImpl, @@ -468,7 +477,7 @@ object DynamicPlugins { unloadLoadedOptionalDependenciesOnPlugin(pluginDescriptor, pluginSet = pluginSet, classLoaders = classLoaders) unloadDependencyDescriptors(pluginDescriptor, pluginSet, classLoaders) - unloadPluginDescriptorNotRecursively(pluginDescriptor) + unloadModuleDescriptorNotRecursively(pluginDescriptor) clearPluginClassLoaderParentListCache(pluginSet) @@ -613,7 +622,7 @@ object DynamicPlugins { val dependencyClassloader = dependencyPlugin.classLoader processOptionalDependenciesOnPlugin(dependencyPlugin, pluginSet, isLoaded = true) { mainDescriptor, subDescriptor -> val classLoader = subDescriptor.classLoader - unloadPluginDescriptorNotRecursively(subDescriptor) + unloadModuleDescriptorNotRecursively(subDescriptor) // this additional code is required because in unit tests PluginClassLoader is not used if (mainDescriptor !== subDescriptor) { @@ -626,9 +635,6 @@ object DynamicPlugins { classLoaders.add(classLoader) classLoader.state = PluginClassLoader.UNLOAD_IN_PROGRESS } - else if (!classLoader.detachParent(dependencyPlugin)) { - LOG.warn("Classloader $dependencyClassloader doesn't have $classLoader as parent") - } } true } @@ -651,7 +657,7 @@ object DynamicPlugins { } unloadDependencyDescriptors(subDescriptor, pluginSet, classLoaders) - unloadPluginDescriptorNotRecursively(subDescriptor) + unloadModuleDescriptorNotRecursively(subDescriptor) subDescriptor.classLoader = null } @@ -663,7 +669,7 @@ object DynamicPlugins { classLoaders.add(classLoader) } - unloadPluginDescriptorNotRecursively(subDescriptor) + unloadModuleDescriptorNotRecursively(subDescriptor) subDescriptor.classLoader = null } } @@ -678,28 +684,28 @@ object DynamicPlugins { // PluginId cannot be used to unload related resources because one plugin descriptor may consist of several sub descriptors, // each of them depends on presense of another plugin, here not the whole plugin is unloaded, but only one part. - private fun unloadPluginDescriptorNotRecursively(pluginDescriptor: IdeaPluginDescriptorImpl) { + private fun unloadModuleDescriptorNotRecursively(module: IdeaPluginDescriptorImpl) { val app = ApplicationManager.getApplication() as ApplicationImpl - (ActionManager.getInstance() as ActionManagerImpl).unloadActions(pluginDescriptor) + (ActionManager.getInstance() as ActionManagerImpl).unloadActions(module) val openedProjects = ProjectUtil.getOpenProjects().asList() val appExtensionArea = app.extensionArea val priorityUnloadListeners = mutableListOf() val unloadListeners = mutableListOf() - unregisterUnknownLevelExtensions(pluginDescriptor.unsortedEpNameToExtensionElements, pluginDescriptor, appExtensionArea, openedProjects, + unregisterUnknownLevelExtensions(module.unsortedEpNameToExtensionElements, module, appExtensionArea, openedProjects, priorityUnloadListeners, unloadListeners) - for ((epName, epExtensions) in (pluginDescriptor.appContainerDescriptor.extensions ?: emptyMap())) { - appExtensionArea.unregisterExtensions(epName, pluginDescriptor, epExtensions, priorityUnloadListeners, unloadListeners) + for ((epName, epExtensions) in (module.appContainerDescriptor.extensions ?: emptyMap())) { + appExtensionArea.unregisterExtensions(epName, module, epExtensions, priorityUnloadListeners, unloadListeners) } - for ((epName, epExtensions) in (pluginDescriptor.projectContainerDescriptor.extensions ?: emptyMap())) { + for ((epName, epExtensions) in (module.projectContainerDescriptor.extensions ?: emptyMap())) { for (project in openedProjects) { - (project.extensionArea as ExtensionsAreaImpl).unregisterExtensions(epName, pluginDescriptor, epExtensions, priorityUnloadListeners, + (project.extensionArea as ExtensionsAreaImpl).unregisterExtensions(epName, module, epExtensions, priorityUnloadListeners, unloadListeners) } } // not an error - unsorted goes to module level, see registerExtensions - unregisterUnknownLevelExtensions(pluginDescriptor.moduleContainerDescriptor.extensions, pluginDescriptor, appExtensionArea, openedProjects, + unregisterUnknownLevelExtensions(module.moduleContainerDescriptor.extensions, module, appExtensionArea, openedProjects, priorityUnloadListeners, unloadListeners) for (priorityUnloadListener in priorityUnloadListeners) { @@ -710,39 +716,39 @@ object DynamicPlugins { } // first, reset all plugin extension points before unregistering, so that listeners don't see plugin in semi-torn-down state - processExtensionPoints(pluginDescriptor, openedProjects) { points, area -> - area.resetExtensionPoints(points, pluginDescriptor) + processExtensionPoints(module, openedProjects) { points, area -> + area.resetExtensionPoints(points, module) } // unregister plugin extension points - processExtensionPoints(pluginDescriptor, openedProjects) { points, area -> - area.unregisterExtensionPoints(points, pluginDescriptor) + processExtensionPoints(module, openedProjects) { points, area -> + area.unregisterExtensionPoints(points, module) } - val pluginId = pluginDescriptor.pluginId - app.unloadServices(pluginDescriptor.appContainerDescriptor.services, pluginId) + val pluginId = module.pluginId + app.unloadServices(module.appContainerDescriptor.services, pluginId) val appMessageBus = app.messageBus as MessageBusEx - pluginDescriptor.appContainerDescriptor.listeners?.let { appMessageBus.unsubscribeLazyListeners(pluginDescriptor, it) } + module.appContainerDescriptor.listeners?.let { appMessageBus.unsubscribeLazyListeners(module, it) } for (project in openedProjects) { - (project as ComponentManagerImpl).unloadServices(pluginDescriptor.projectContainerDescriptor.services, pluginId) - pluginDescriptor.projectContainerDescriptor.listeners?.let { - ((project as ComponentManagerImpl).messageBus as MessageBusEx).unsubscribeLazyListeners(pluginDescriptor, it) + (project as ComponentManagerImpl).unloadServices(module.projectContainerDescriptor.services, pluginId) + module.projectContainerDescriptor.listeners?.let { + ((project as ComponentManagerImpl).messageBus as MessageBusEx).unsubscribeLazyListeners(module, it) } - val moduleServices = pluginDescriptor.moduleContainerDescriptor.services - for (module in ModuleManager.getInstance(project).modules) { - (module as ComponentManagerImpl).unloadServices(moduleServices, pluginId) - createDisposeTreePredicate(pluginDescriptor)?.let { Disposer.disposeChildren(module, it) } + val moduleServices = module.moduleContainerDescriptor.services + for (ideaModule in ModuleManager.getInstance(project).modules) { + (ideaModule as ComponentManagerImpl).unloadServices(moduleServices, pluginId) + createDisposeTreePredicate(module)?.let { Disposer.disposeChildren(ideaModule, it) } } - createDisposeTreePredicate(pluginDescriptor)?.let { Disposer.disposeChildren(project, it) } + createDisposeTreePredicate(module)?.let { Disposer.disposeChildren(project, it) } } appMessageBus.disconnectPluginConnections(Predicate { aClass -> - (aClass.classLoader as? PluginClassLoader)?.pluginDescriptor == pluginDescriptor + (aClass.classLoader as? PluginClassLoader)?.pluginDescriptor === module }) - createDisposeTreePredicate(pluginDescriptor)?.let { Disposer.disposeChildren(ApplicationManager.getApplication(), it) } + createDisposeTreePredicate(module)?.let { Disposer.disposeChildren(ApplicationManager.getApplication(), it) } } private fun unregisterUnknownLevelExtensions(extensionMap: Map>?, @@ -803,18 +809,20 @@ object DynamicPlugins { } val loadStartTime = System.currentTimeMillis() - val app = ApplicationManager.getApplication() as ApplicationImpl val pluginSet = PluginManagerCore.getPluginSet().enablePlugin(pluginDescriptor) val classLoaderConfigurator = ClassLoaderConfigurator(pluginSet) - classLoaderConfigurator.configure(pluginDescriptor) + // todo loadPlugin should be called per each module, getPluginWithContentModules is a temporary solution + val pluginWithContentModules = getPluginWithContentModules(pluginDescriptor, pluginSet) + pluginWithContentModules.forEach(classLoaderConfigurator::configureModule) + val app = ApplicationManager.getApplication() as ApplicationImpl app.messageBus.syncPublisher(DynamicPluginListener.TOPIC).beforePluginLoaded(pluginDescriptor) app.runWriteAction { try { PluginManagerCore.setPluginSet(pluginSet) val listenerCallbacks = mutableListOf() - loadPluginDescriptor(pluginDescriptor, app, listenerCallbacks) + loadModule(pluginWithContentModules, app, listenerCallbacks) loadOptionalDependenciesOnPlugin(pluginDescriptor, classLoaderConfigurator, pluginSet, listenerCallbacks) clearPluginClassLoaderParentListCache(pluginSet) @@ -850,7 +858,6 @@ object DynamicPlugins { return true } - @JvmStatic fun onPluginUnload(parentDisposable: Disposable, callback: Runnable) { ApplicationManager.getApplication().messageBus.connect(parentDisposable) .subscribe(DynamicPluginListener.TOPIC, object : DynamicPluginListener { @@ -859,109 +866,108 @@ object DynamicPlugins { } }) } +} - private fun clearTemporaryLostComponent() { +private fun clearTemporaryLostComponent() { + try { + val clearMethod = Window::class.java.declaredMethods.find { it.name == "setTemporaryLostComponent" } + if (clearMethod == null) { + LOG.info("setTemporaryLostComponent method not found") + return + } + clearMethod.isAccessible = true + loop@ for (frame in WindowManager.getInstance().allProjectFrames) { + val window = when (frame) { + is ProjectFrameHelper -> frame.frame + is Window -> frame + else -> continue@loop + } + clearMethod.invoke(window, null) + } + } + catch (e: Throwable) { + LOG.info("Failed to clear Window.temporaryLostComponent", e) + } +} + +private fun hideTooltip() { + try { + val showMethod = ToolTipManager::class.java.declaredMethods.find { it.name == "show" } + if (showMethod == null) { + LOG.info("ToolTipManager.show method not found") + return + } + showMethod.isAccessible = true + showMethod.invoke(ToolTipManager.sharedInstance(), null) + } + catch (e: Throwable) { + LOG.info("Failed to hide tooltip", e) + } +} + +private fun clearCglibStopBacktrace() { + val field = ReflectionUtil.getDeclaredField(ClassNameReader::class.java, "EARLY_EXIT") + if (field != null) { try { - val clearMethod = Window::class.java.declaredMethods.find { it.name == "setTemporaryLostComponent" } - if (clearMethod == null) { - LOG.info("setTemporaryLostComponent method not found") - return - } - clearMethod.isAccessible = true - loop@ for (frame in WindowManager.getInstance().allProjectFrames) { - val window = when (frame) { - is ProjectFrameHelper -> frame.frame - is Window -> frame - else -> continue@loop - } - clearMethod.invoke(window, null) - } + ThrowableInterner.clearBacktrace((field[null] as Throwable)) } catch (e: Throwable) { - LOG.info("Failed to clear Window.temporaryLostComponent", e) + LOG.info(e) } } +} - private fun hideTooltip() { +private fun clearNewFocusOwner() { + val field = ReflectionUtil.getDeclaredField(KeyboardFocusManager::class.java, "newFocusOwner") + if (field != null) { try { - val showMethod = ToolTipManager::class.java.declaredMethods.find { it.name == "show" } - if (showMethod == null) { - LOG.info("ToolTipManager.show method not found") - return - } - showMethod.isAccessible = true - showMethod.invoke(ToolTipManager.sharedInstance(), null) + field.set(null, null) } catch (e: Throwable) { - LOG.info("Failed to hide tooltip", e) + LOG.info(e) } } +} +private fun saveMemorySnapshot(pluginId: PluginId): Boolean { + val snapshotDate = SimpleDateFormat("dd.MM.yyyy_HH.mm.ss").format(Date()) + val snapshotFileName = "unload-$pluginId-$snapshotDate.hprof" + val snapshotPath = System.getProperty("memory.snapshots.path", SystemProperties.getUserHome()) + "/" + snapshotFileName - private fun clearCglibStopBacktrace() { - val field = ReflectionUtil.getDeclaredField(ClassNameReader::class.java, "EARLY_EXIT") - if (field != null) { - try { - ThrowableInterner.clearBacktrace((field[null] as Throwable)) - } - catch (e: Throwable) { - LOG.info(e) - } - } + MemoryDumpHelper.captureMemoryDump(snapshotPath) + + if (classloadersFromUnloadedPlugins[pluginId]?.isEmpty() != false) { + LOG.info("Successfully unloaded plugin $pluginId (classloader collected during memory snapshot generation)") + return true } - private fun clearNewFocusOwner() { - val field = ReflectionUtil.getDeclaredField(KeyboardFocusManager::class.java, "newFocusOwner") - if (field != null) { - try { - field.set(null, null) - } - catch (e: Throwable) { - LOG.info(e) - } - } - } - - private fun saveMemorySnapshot(pluginId: PluginId): Boolean { - val snapshotDate = SimpleDateFormat("dd.MM.yyyy_HH.mm.ss").format(Date()) - val snapshotFileName = "unload-$pluginId-$snapshotDate.hprof" - val snapshotPath = System.getProperty("memory.snapshots.path", SystemProperties.getUserHome()) + "/" + snapshotFileName - - MemoryDumpHelper.captureMemoryDump(snapshotPath) - - if (classloadersFromUnloadedPlugins[pluginId]?.isEmpty() != false) { - LOG.info("Successfully unloaded plugin $pluginId (classloader collected during memory snapshot generation)") + if (Registry.`is`("ide.plugins.analyze.snapshot")) { + val analysisResult = analyzeSnapshot(snapshotPath, pluginId) + @Suppress("ReplaceSizeZeroCheckWithIsEmpty") + if (analysisResult.length == 0) { + LOG.info("Successfully unloaded plugin $pluginId (no strong references to classloader in .hprof file)") + classloadersFromUnloadedPlugins.remove(pluginId) return true } - - if (Registry.`is`("ide.plugins.analyze.snapshot")) { - val analysisResult = analyzeSnapshot(snapshotPath, pluginId) - @Suppress("ReplaceSizeZeroCheckWithIsEmpty") - if (analysisResult.length == 0) { - LOG.info("Successfully unloaded plugin $pluginId (no strong references to classloader in .hprof file)") - classloadersFromUnloadedPlugins.remove(pluginId) - return true - } - else { - LOG.info("Snapshot analysis result: $analysisResult") - } + else { + LOG.info("Snapshot analysis result: $analysisResult") } - - notify( - IdeBundle.message("memory.snapshot.captured.text", snapshotPath, snapshotFileName), - NotificationType.WARNING, - object : AnAction(IdeBundle.message("ide.restart.action")), DumbAware { - override fun actionPerformed(e: AnActionEvent) = ApplicationManager.getApplication().restart() - }, - object : AnAction( - IdeBundle.message("memory.snapshot.captured.action.text", snapshotFileName, RevealFileAction.getFileManagerName())), DumbAware { - override fun actionPerformed(e: AnActionEvent) = RevealFileAction.openFile(Paths.get(snapshotPath)) - } - ) - - LOG.info("Plugin $pluginId is not unload-safe because class loader cannot be unloaded. Memory snapshot created at $snapshotPath") - return false } + + DynamicPlugins.notify( + IdeBundle.message("memory.snapshot.captured.text", snapshotPath, snapshotFileName), + NotificationType.WARNING, + object : AnAction(IdeBundle.message("ide.restart.action")), DumbAware { + override fun actionPerformed(e: AnActionEvent) = ApplicationManager.getApplication().restart() + }, + object : AnAction( + IdeBundle.message("memory.snapshot.captured.action.text", snapshotFileName, RevealFileAction.getFileManagerName())), DumbAware { + override fun actionPerformed(e: AnActionEvent) = RevealFileAction.openFile(Paths.get(snapshotPath)) + } + ) + + LOG.info("Plugin $pluginId is not unload-safe because class loader cannot be unloaded. Memory snapshot created at $snapshotPath") + return false } private fun processImplementationDetailDependenciesOnPlugin(pluginDescriptor: IdeaPluginDescriptorImpl, @@ -1003,13 +1009,13 @@ private fun loadOptionalDependenciesOnPlugin(dependencyPlugin: IdeaPluginDescrip } // 2. setup classloaders - classLoaderConfigurator.configureDependenciesIfNeeded(mainToModule, dependencyPlugin) + classLoaderConfigurator.configureDependenciesIfNeeded(mainToModule) - val app = ApplicationManager.getApplication() as ComponentManagerImpl + val app = ApplicationManager.getApplication() as ApplicationImpl // 3. load into service container for (entry in mainToModule.entries) { for (subDescriptor in entry.value) { - loadPluginDescriptor(subDescriptor, app, listenerCallbacks) + loadModule(sequenceOf(subDescriptor), app, listenerCallbacks) } } } @@ -1021,25 +1027,35 @@ private fun clearPluginClassLoaderParentListCache(pluginSet: PluginSet) { } } -private fun loadPluginDescriptor(pluginDescriptor: IdeaPluginDescriptorImpl, - app: ComponentManagerImpl, - listenerCallbacks: MutableList) { - val list = listOf(pluginDescriptor) - app.registerComponents(plugins = list, - app = ApplicationManager.getApplication(), - precomputedExtensionModel = null, - listenerCallbacks = listenerCallbacks) +private fun loadModule(modules: Sequence, + app: ApplicationImpl, + listenerCallbacks: MutableList) { + app.registerComponents(modules = modules, + app = app, + precomputedExtensionModel = null, + listenerCallbacks = listenerCallbacks) for (openProject in ProjectUtil.getOpenProjects()) { - (openProject as ComponentManagerImpl).registerComponents(list, ApplicationManager.getApplication(), null, listenerCallbacks) + (openProject as ComponentManagerImpl).registerComponents(modules = modules, + app = app, + precomputedExtensionModel = null, + listenerCallbacks = listenerCallbacks) for (module in ModuleManager.getInstance(openProject).modules) { - (module as ComponentManagerImpl).registerComponents(list, ApplicationManager.getApplication(), null, listenerCallbacks) + (module as ComponentManagerImpl).registerComponents(modules = modules, + app = app, + precomputedExtensionModel = null, + listenerCallbacks = listenerCallbacks) } } - (ActionManager.getInstance() as ActionManagerImpl).registerActions(list) + (ActionManager.getInstance() as ActionManagerImpl).registerActions(modules) } -private fun analyzeSnapshot(hprofPath: String, pluginId: PluginId): String { +private fun getPluginWithContentModules(pluginDescriptor: IdeaPluginDescriptorImpl, + pluginSet: PluginSet): Sequence { + return pluginSet.getEnabledModules().filter { it.pluginId == pluginDescriptor.pluginId } +} + + private fun analyzeSnapshot(hprofPath: String, pluginId: PluginId): String { FileChannel.open(Paths.get(hprofPath), StandardOpenOption.READ).use { channel -> val analysis = HProfAnalysis(channel, SystemTempFilenameSupplier()) { analysisContext, progressIndicator -> AnalyzeClassloaderReferencesGraph(analysisContext, pluginId.idString).analyze(progressIndicator) @@ -1058,7 +1074,7 @@ private fun createDisposeTreePredicate(pluginDescriptor: IdeaPluginDescriptorImp it.classLoaderId == classLoader.instanceId } else { - it::class.java.classLoader == classLoader + it::class.java.classLoader === classLoader } } } @@ -1086,7 +1102,7 @@ private fun processDependenciesOnPlugin( processor: (pluginDescriptor: IdeaPluginDescriptorImpl, moduleDescriptor: IdeaPluginDescriptorImpl) -> Boolean, ) { val wantedIds = HashSet(1 + dependencyPlugin.content.modules.size) - wantedIds.add(dependencyPlugin.id.idString) + wantedIds.add(dependencyPlugin.pluginId.idString) for (module in dependencyPlugin.content.modules) { wantedIds.add(module.name) } @@ -1096,7 +1112,7 @@ private fun processDependenciesOnPlugin( continue } - if (!processOptionalDependenciesInOldFormatOnPlugin(dependencyPluginId = dependencyPlugin.id, + if (!processOptionalDependenciesInOldFormatOnPlugin(dependencyPluginId = dependencyPlugin.pluginId, mainDescriptor = plugin, loadStateFilter = loadStateFilter, onlyOptional = onlyOptional, @@ -1120,7 +1136,7 @@ private fun processDependenciesOnPlugin( } } for (item in module.dependencies.plugins) { - if (dependencyPlugin.id == item.id && !processor(plugin, module)) { + if (dependencyPlugin.pluginId == item.id && !processor(plugin, module)) { return } } @@ -1171,54 +1187,51 @@ private fun processOptionalDependenciesInOldFormatOnPlugin( return true } - private fun doCheckExtensionsCanUnloadWithoutRestart( - extensions: Map>, - descriptor: IdeaPluginDescriptorImpl, - baseDescriptor: IdeaPluginDescriptorImpl?, - app: Application, - optionalDependencyPluginId: PluginId?, - context: List, - pluginSet: PluginSet, - ): String? { - val firstProject = ProjectUtil.getOpenProjects().firstOrNull() - val anyProject = firstProject ?: ProjectManager.getInstance().defaultProject - val anyModule = firstProject?.let { ModuleManager.getInstance(it).modules.firstOrNull() } +private fun doCheckExtensionsCanUnloadWithoutRestart( + extensions: Map>, + descriptor: IdeaPluginDescriptorImpl, + baseDescriptor: IdeaPluginDescriptorImpl?, + app: Application, + optionalDependencyPluginId: PluginId?, + context: List, + pluginSet: PluginSet, +): String? { + val firstProject = ProjectUtil.getOpenProjects().firstOrNull() + val anyProject = firstProject ?: ProjectManager.getInstance().defaultProject + val anyModule = firstProject?.let { ModuleManager.getInstance(it).modules.firstOrNull() } - val seenPlugins: MutableSet = Collections.newSetFromMap(IdentityHashMap()) - epLoop@ for (epName in extensions.keys) { - seenPlugins.clear() + val seenPlugins: MutableSet = Collections.newSetFromMap(IdentityHashMap()) + epLoop@ for (epName in extensions.keys) { + seenPlugins.clear() - fun getNonDynamicUnloadError(optionalDependencyPluginId: PluginId?): String = - optionalDependencyPluginId?.let { - "Plugin ${baseDescriptor?.pluginId} is not unload-safe because of use of non-dynamic EP $epName in plugin $it that optionally depends on it" - } ?: "Plugin ${descriptor.id} is not unload-safe because of extension to non-dynamic EP $epName" + fun getNonDynamicUnloadError(optionalDependencyPluginId: PluginId?): String = optionalDependencyPluginId?.let { + "Plugin ${baseDescriptor?.pluginId} is not unload-safe because of use of non-dynamic EP $epName in plugin $it that optionally depends on it" + } ?: "Plugin ${descriptor.pluginId} is not unload-safe because of extension to non-dynamic EP $epName" - val result = findLoadedPluginExtensionPointRecursive( - pluginDescriptor = baseDescriptor ?: descriptor, - epName = epName, - pluginSet = pluginSet, - context = context, - seenPlugins = seenPlugins, - ) - if (result != null) { - val (pluginExtensionPoint, foundInDependencies) = result - // descriptor.pluginId is null when we check the optional dependencies of the plugin which is being loaded - // if an optional dependency of a plugin extends a non-dynamic EP of that plugin, it shouldn't prevent plugin loading - if (!pluginExtensionPoint.isDynamic) { - if (baseDescriptor == null || foundInDependencies) { - return getNonDynamicUnloadError(null) - } - else if (descriptor === baseDescriptor) { - return getNonDynamicUnloadError(descriptor.pluginId) - } - } - continue - } + val result = findLoadedPluginExtensionPointRecursive( + pluginDescriptor = baseDescriptor ?: descriptor, + epName = epName, + pluginSet = pluginSet, + context = context, + seenPlugins = seenPlugins, + ) + if (result != null) { + val (pluginExtensionPoint, foundInDependencies) = result // descriptor.pluginId is null when we check the optional dependencies of the plugin which is being loaded + // if an optional dependency of a plugin extends a non-dynamic EP of that plugin, it shouldn't prevent plugin loading + if (!pluginExtensionPoint.isDynamic) { + if (baseDescriptor == null || foundInDependencies) { + return getNonDynamicUnloadError(null) + } + else if (descriptor === baseDescriptor) { + return getNonDynamicUnloadError(descriptor.pluginId) + } + } + continue + } - @Suppress("RemoveExplicitTypeArguments") - val ep = app.extensionArea.getExtensionPointIfRegistered(epName) - ?: anyProject.extensionArea.getExtensionPointIfRegistered(epName) - ?: anyModule?.extensionArea?.getExtensionPointIfRegistered(epName) + @Suppress("RemoveExplicitTypeArguments") val ep = app.extensionArea.getExtensionPointIfRegistered(epName) + ?: anyProject.extensionArea.getExtensionPointIfRegistered(epName) + ?: anyModule?.extensionArea?.getExtensionPointIfRegistered(epName) if (ep != null) { if (!ep.isDynamic) { return getNonDynamicUnloadError(optionalDependencyPluginId) @@ -1248,11 +1261,11 @@ private fun processOptionalDependenciesInOldFormatOnPlugin( } // special case Kotlin EPs registered via code in Kotlin compiler - if (epName.startsWith("org.jetbrains.kotlin") && descriptor.id.idString == "org.jetbrains.kotlin") { + if (epName.startsWith("org.jetbrains.kotlin") && descriptor.pluginId.idString == "org.jetbrains.kotlin") { continue } - return "Plugin ${descriptor.id} is not unload-safe because of unresolved extension $epName" + return "Plugin ${descriptor.pluginId} is not unload-safe because of unresolved extension $epName" } return null } @@ -1278,7 +1291,7 @@ private fun findLoadedPluginExtensionPointRecursive(pluginDescriptor: IdeaPlugin findPluginExtensionPoint(pluginDescriptor, epName)?.let { return it to false } for (dependency in pluginDescriptor.pluginDependencies) { - if (pluginSet.isPluginEnabled(dependency.pluginId) || context.any { it.id == dependency.pluginId }) { + if (pluginSet.isPluginEnabled(dependency.pluginId) || context.any { it.pluginId == dependency.pluginId }) { dependency.subDescriptor?.let { subDescriptor -> findLoadedPluginExtensionPointRecursive(subDescriptor, epName, pluginSet, context, seenPlugins)?.let { return it } } @@ -1294,6 +1307,23 @@ private fun findLoadedPluginExtensionPointRecursive(pluginDescriptor: IdeaPlugin return null } +private inline fun processDirectDependencies(module: IdeaPluginDescriptorImpl, + pluginSet: PluginSet, + processor: (IdeaPluginDescriptorImpl) -> Unit) { + for (item in module.dependencies.modules) { + val descriptor = pluginSet.findEnabledModule(item.name) + if (descriptor != null) { + processor(descriptor) + } + } + for (item in module.dependencies.plugins) { + val descriptor = pluginSet.findEnabledPlugin(item.id) + if (descriptor != null) { + processor(descriptor) + } + } +} + private fun unloadClassLoader(pluginDescriptor: IdeaPluginDescriptorImpl, timeoutMs: Int): Boolean { if (timeoutMs == 0) { pluginDescriptor.classLoader = null diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/PluginInstaller.java b/platform/platform-impl/src/com/intellij/ide/plugins/PluginInstaller.java index 0ecdcd635207..dfd5a997e601 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/PluginInstaller.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/PluginInstaller.java @@ -102,7 +102,7 @@ public final class PluginInstaller { .withWaitForClassloaderUnload(true); uninstalledWithoutRestart = parentComponent != null ? - DynamicPlugins.unloadPluginWithProgress(null, parentComponent, pluginDescriptor, options) : + DynamicPlugins.INSTANCE.unloadPluginWithProgress(null, parentComponent, pluginDescriptor, options) : DynamicPlugins.INSTANCE.unloadPlugin(pluginDescriptor, options); } diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/newui/MyPluginModel.java b/platform/platform-impl/src/com/intellij/ide/plugins/newui/MyPluginModel.java index 3c031fb6344b..e8197161c2d0 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/newui/MyPluginModel.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/newui/MyPluginModel.java @@ -840,7 +840,7 @@ public class MyPluginModel extends InstalledPluginsTableModel implements PluginE boolean requiresRestart(@NotNull IdeaPluginDescriptor descriptor) { return myRequiresRestart .computeIfAbsent(descriptor instanceof IdeaPluginDescriptorImpl ? (IdeaPluginDescriptorImpl)descriptor : null, - it -> it == null || DynamicPlugins.checkCanUnloadWithoutRestart(it) != null); + it -> it == null || DynamicPlugins.INSTANCE.checkCanUnloadWithoutRestart(it) != null); } boolean isUninstalled(@NotNull IdeaPluginDescriptor descriptor) { diff --git a/platform/platform-impl/src/com/intellij/idea/ApplicationLoader.kt b/platform/platform-impl/src/com/intellij/idea/ApplicationLoader.kt index 031e4163b1e3..de8f9f44d32e 100644 --- a/platform/platform-impl/src/com/intellij/idea/ApplicationLoader.kt +++ b/platform/platform-impl/src/com/intellij/idea/ApplicationLoader.kt @@ -10,6 +10,7 @@ import com.intellij.icons.AllIcons import com.intellij.ide.* import com.intellij.ide.plugins.IdeaPluginDescriptorImpl import com.intellij.ide.plugins.PluginManagerCore +import com.intellij.ide.plugins.PluginSet import com.intellij.ide.plugins.StartupAbortedException import com.intellij.ide.ui.laf.darcula.DarculaLaf import com.intellij.openapi.application.* @@ -72,17 +73,20 @@ fun initApplication(rawArgs: List, prepareUiFuture: CompletionStage<*>) } loadAndInitPluginFuture - .thenAccept { plugins -> + .thenAccept { pluginSet -> runActivity("app component registration") { - app.registerComponents(plugins, app, null, null) + app.registerComponents(modules = pluginSet.getEnabledModules(), + app = app, + precomputedExtensionModel = null, + listenerCallbacks = null) } if (args.isEmpty()) { - startApp(app, IdeStarter(), initAppActivity, plugins, args) + startApp(app, IdeStarter(), initAppActivity, pluginSet, args) } else { // `ApplicationStarter` is an extension, so to find a starter, extensions must be registered first - findCustomAppStarterAndStart(plugins, args, app, initAppActivity) + findCustomAppStarterAndStart(pluginSet, args, app, initAppActivity) } if (!Main.isHeadless()) { @@ -114,12 +118,12 @@ fun initApplication(rawArgs: List, prepareUiFuture: CompletionStage<*>) private fun startApp(app: ApplicationImpl, starter: ApplicationStarter, initAppActivity: Activity, - plugins: List, + pluginSet: PluginSet, args: List) { // initSystemProperties or RegistryKeyBean.addKeysFromPlugins maybe not yet performed, // but it is OK, because registry is not and should not be used. initConfigurationStore(app) - val preloadSyncServiceFuture = preloadServices(plugins, app, activityPrefix = "") + val preloadSyncServiceFuture = preloadServices(pluginSet.getEnabledModules(), app, activityPrefix = "") val placeOnEventQueueActivity = initAppActivity.startChild(Activities.PLACE_ON_EVENT_QUEUE) val loadComponentInEdtFuture = CompletableFuture.runAsync({ @@ -201,7 +205,7 @@ private fun startApp(app: ApplicationImpl, } } -private fun findCustomAppStarterAndStart(plugins: List, +private fun findCustomAppStarterAndStart(pluginSet: PluginSet, args: List, app: ApplicationImpl, initAppActivity: Activity) { @@ -226,7 +230,7 @@ private fun findCustomAppStarterAndStart(plugins: List } starter.premain(args) - startApp(app, starter, initAppActivity, plugins, args) + startApp(app, starter, initAppActivity, pluginSet, args) } @VisibleForTesting @@ -241,12 +245,11 @@ internal fun createAppLocatorFile() { } } -@JvmOverloads -fun preloadServices(plugins: List, +fun preloadServices(modules: Sequence, container: ComponentManagerImpl, activityPrefix: String, onlyIfAwait: Boolean = false): CompletableFuture { - val result = container.preloadServices(plugins, activityPrefix, onlyIfAwait) + val result = container.preloadServices(modules, activityPrefix, onlyIfAwait) fun logError(future: CompletableFuture): CompletableFuture { return future diff --git a/platform/platform-impl/src/com/intellij/idea/CommandLineArgs.java b/platform/platform-impl/src/com/intellij/idea/CommandLineArgs.java index 15138003c953..38409f48f30b 100644 --- a/platform/platform-impl/src/com/intellij/idea/CommandLineArgs.java +++ b/platform/platform-impl/src/com/intellij/idea/CommandLineArgs.java @@ -1,8 +1,8 @@ -// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.idea; import com.intellij.ide.RecentProjectsManagerBase; -import com.intellij.ide.plugins.PluginManagerCore; +import com.intellij.ide.plugins.IdeaPluginDescriptorImpl; import org.jetbrains.annotations.NotNull; public final class CommandLineArgs { @@ -15,7 +15,7 @@ public final class CommandLineArgs { public static void parse(String[] args) { for (String arg : args) { if (arg.equalsIgnoreCase(DISABLE_NON_BUNDLED_PLUGINS)) { - PluginManagerCore.ourDisableNonBundledPlugins = true; + IdeaPluginDescriptorImpl.disableNonBundledPlugins = true; break; } else if (arg.equalsIgnoreCase(DONT_REOPEN_PROJECTS)) { diff --git a/platform/platform-impl/src/com/intellij/idea/StartupUtil.java b/platform/platform-impl/src/com/intellij/idea/StartupUtil.java index 3481ea41d082..8a2bff6e1bb0 100644 --- a/platform/platform-impl/src/com/intellij/idea/StartupUtil.java +++ b/platform/platform-impl/src/com/intellij/idea/StartupUtil.java @@ -980,13 +980,8 @@ public final class StartupUtil { } @Override - public @NotNull List copyOf(List collection) { + public @NotNull List copyOfCollection(Collection collection) { return List.copyOf(collection); } - - @Override - public @NotNull List listOf(E[] collection) { - return List.of(collection); - } } } diff --git a/platform/platform-impl/src/com/intellij/idea/ZipFilePoolImpl.java b/platform/platform-impl/src/com/intellij/idea/ZipFilePoolImpl.java index 9a6251e41297..ad9002619cc3 100644 --- a/platform/platform-impl/src/com/intellij/idea/ZipFilePoolImpl.java +++ b/platform/platform-impl/src/com/intellij/idea/ZipFilePoolImpl.java @@ -1,4 +1,4 @@ -// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.idea; import com.intellij.util.lang.ClassLoadingLocks; @@ -10,6 +10,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Path; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -55,9 +56,9 @@ public final class ZipFilePoolImpl extends ZipFilePool { } @Override - public byte @Nullable [] loadZipEntry(@NotNull String path) throws IOException { + public @Nullable InputStream loadZipEntry(@NotNull String path) throws IOException { ImmutableZipEntry entry = zipFile.getEntry(path.charAt(0) == '/' ? path.substring(1) : path); - return entry == null ? null : entry.getData(zipFile); + return entry == null ? null : entry.getInputStream(zipFile); } @Override diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionManagerImpl.java index b078da54a7dc..72033a45a214 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionManagerImpl.java @@ -64,6 +64,7 @@ import com.intellij.util.ui.UIUtil; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import kotlin.Unit; +import kotlin.sequences.Sequence; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -140,7 +141,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { } } - registerActions(PluginManagerCore.getLoadedPlugins(null)); + registerActions(PluginManagerCore.getPluginSet().getEnabledModules()); EP.forEachExtensionSafe(customizer -> customizer.customize(this)); DYNAMIC_EP_NAME.forEachExtensionSafe(customizer -> customizer.registerActions(this)); @@ -164,12 +165,16 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { } @ApiStatus.Internal - public void registerActions(@NotNull List plugins) { + public void registerActions(@NotNull Sequence modules) { KeymapManagerEx keymapManager = Objects.requireNonNull(KeymapManagerEx.getInstanceEx()); - PrecomputedExtensionModelKt.executeRegisterTask(plugins, it -> { - registerPluginActions(it, keymapManager); - return Unit.INSTANCE; - }); + for (Iterator iter = modules.iterator(); iter.hasNext(); ) { + IdeaPluginDescriptorImpl module = iter.next(); + registerPluginActions(module, keymapManager); + PrecomputedExtensionModelKt.executeRegisterTaskForOldContent(module, it -> { + registerPluginActions(it, keymapManager); + return Unit.INSTANCE; + }); + } } private static @NotNull AnActionListener publisher() { @@ -244,13 +249,13 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { } private static void setIconFromClass(@Nullable Class actionClass, - @NotNull PluginDescriptor pluginDescriptor, + @NotNull PluginDescriptor module, @NotNull String iconPath, @NotNull Presentation presentation) { long start = StartUpMeasurer.getCurrentTimeIfEnabled(); - Icon icon = IconLoader.findIcon(iconPath, actionClass, pluginDescriptor.getPluginClassLoader(), null, true); + Icon icon = IconLoader.findIcon(iconPath, actionClass, module.getPluginClassLoader(), null, true); if (icon == null) { - reportActionError(pluginDescriptor.getPluginId(), "Icon cannot be found in '" + iconPath + "', action '" + actionClass + "'"); + reportActionError(module, "Icon cannot be found in '" + iconPath + "', action '" + actionClass + "'"); icon = AllIcons.Nodes.Unknown; } IconLoadMeasurer.actionIcon.end(start); @@ -282,19 +287,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { return bundle == null ? defaultValue : AbstractBundle.messageOrDefault(bundle, elementType + "." + id + "." + TEXT_ATTR_NAME, defaultValue); } - private static boolean checkRelativeToAction(String relativeToActionId, - @NotNull Anchor anchor, - @NotNull String actionName, - @Nullable PluginId pluginId) { - if ((Anchor.BEFORE == anchor || Anchor.AFTER == anchor) && relativeToActionId == null) { - reportActionError(pluginId, actionName + ": \"relative-to-action\" cannot be null if anchor is \"after\" or \"before\""); - return false; - } - return true; - } - - @Nullable - private static Anchor parseAnchor(String anchorStr, @Nullable String actionName, @Nullable PluginId pluginId) { + private static @Nullable Anchor parseAnchor(String anchorStr, @Nullable String actionName, @NotNull IdeaPluginDescriptor module) { if (anchorStr == null) { return Anchor.LAST; } @@ -312,15 +305,19 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { return Anchor.AFTER; } else { - reportActionError(pluginId, actionName + ": anchor should be one of the following constants: \"first\", \"last\", \"before\" or \"after\""); + reportActionError(module, + actionName + ": anchor should be one of the following constants: \"first\", \"last\", \"before\" or \"after\""); return null; } } - private static void processMouseShortcutNode(XmlElement element, String actionId, PluginId pluginId, @NotNull KeymapManager keymapManager) { + private static void processMouseShortcutNode(@NotNull XmlElement element, + String actionId, + @NotNull IdeaPluginDescriptor module, + @NotNull KeymapManager keymapManager) { String keystrokeString = element.attributes.get("keystroke"); if (keystrokeString == null || keystrokeString.trim().isEmpty()) { - reportActionError(pluginId, "\"keystroke\" attribute must be specified for action with id=" + actionId); + reportActionError(module, "\"keystroke\" attribute must be specified for action with id=" + actionId); return; } MouseShortcut shortcut; @@ -328,45 +325,35 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { shortcut = KeymapUtil.parseMouseShortcut(keystrokeString); } catch (Exception ex) { - reportActionError(pluginId, "\"keystroke\" attribute has invalid value for action with id=" + actionId); + reportActionError(module, "\"keystroke\" attribute has invalid value for action with id=" + actionId); return; } String keymapName = element.attributes.get(KEYMAP_ATTR_NAME); if (keymapName == null || keymapName.isEmpty()) { - reportActionError(pluginId, "attribute \"keymap\" should be defined"); + reportActionError(module, "attribute \"keymap\" should be defined"); return; } Keymap keymap = keymapManager.getKeymap(keymapName); if (keymap == null) { - reportKeymapNotFoundWarning(pluginId, keymapName); + reportKeymapNotFoundWarning(module, keymapName); return; } processRemoveAndReplace(element, actionId, keymap, shortcut); } - private static void reportActionError(@Nullable PluginId pluginId, @NotNull String message) { - reportActionError(pluginId, message, null); + private static void reportActionError(@NotNull PluginDescriptor module, @NotNull String message) { + reportActionError(module, message, null); } - private static void reportActionError(@Nullable PluginId pluginId, @NotNull String message, @Nullable Throwable cause) { - if (pluginId != null) { - LOG.error(new PluginException(message, cause, pluginId)); - } - else if (cause != null) { - LOG.error(message, cause); - } - else { - LOG.error(message); - } + private static void reportActionError(@NotNull PluginDescriptor module, @NotNull String message, @Nullable Throwable cause) { + LOG.error(new PluginException(message + " (module=" + module + ")", cause, module.getPluginId())); } - private static void reportKeymapNotFoundWarning(@Nullable PluginId pluginId, @NotNull String keymapName) { - if (DefaultKeymap.Companion.isBundledKeymapHidden(keymapName)) { - return; + private static void reportKeymapNotFoundWarning(@NotNull PluginDescriptor module, @NotNull String keymapName) { + if (!DefaultKeymap.Companion.isBundledKeymapHidden(keymapName)) { + LOG.warn("keymap \"" + keymapName + "\" not found" + " " + module); } - String message = "keymap \"" + keymapName + "\" not found"; - LOG.warn(pluginId == null ? message : new PluginException(message, null, pluginId).getMessage()); } private static String getPluginInfo(@Nullable PluginId id) { @@ -397,7 +384,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { } @Override - public void addTimerListener(@NotNull final TimerListener listener) { + public void addTimerListener(final @NotNull TimerListener listener) { if (ApplicationManager.getApplication().isUnitTestMode()) return; if (myTimer == null) { myTimer = new MyTimer(); @@ -415,32 +402,28 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { } } - @NotNull - public ActionPopupMenu createActionPopupMenu(@NotNull String place, @NotNull ActionGroup group, @Nullable PresentationFactory presentationFactory) { + public @NotNull ActionPopupMenu createActionPopupMenu(@NotNull String place, @NotNull ActionGroup group, @Nullable PresentationFactory presentationFactory) { return new ActionPopupMenuImpl(place, group, this, presentationFactory); } - @NotNull @Override - public ActionPopupMenu createActionPopupMenu(@NotNull String place, @NotNull ActionGroup group) { + public @NotNull ActionPopupMenu createActionPopupMenu(@NotNull String place, @NotNull ActionGroup group) { return new ActionPopupMenuImpl(place, group, this, null); } - @NotNull @Override - public ActionToolbar createActionToolbar(@NotNull final String place, @NotNull final ActionGroup group, final boolean horizontal) { + public @NotNull ActionToolbar createActionToolbar(final @NotNull String place, final @NotNull ActionGroup group, final boolean horizontal) { return createActionToolbar(place, group, horizontal, false); } - @NotNull @Override - public ActionToolbar createActionToolbar(@NotNull String place, @NotNull ActionGroup group, boolean horizontal, boolean decorateButtons) { + public @NotNull ActionToolbar createActionToolbar(@NotNull String place, @NotNull ActionGroup group, boolean horizontal, boolean decorateButtons) { return new ActionToolbarImpl(place, group, horizontal, decorateButtons); } - private void registerPluginActions(@NotNull IdeaPluginDescriptorImpl pluginDescriptor, @NotNull KeymapManagerEx keymapManager) { - List elements = pluginDescriptor.actions; - if (elements == null) { + private void registerPluginActions(@NotNull IdeaPluginDescriptorImpl module, @NotNull KeymapManagerEx keymapManager) { + List elements = module.actions; + if (elements.isEmpty()) { return; } @@ -451,7 +434,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { for (RawPluginDescriptor.ActionDescriptor descriptor : elements) { String bundleName = descriptor.resourceBundle; if (bundleName == null) { - bundleName = PluginManagerCore.CORE_ID.equals(pluginDescriptor.getPluginId()) ? "messages.ActionsBundle" : pluginDescriptor.getResourceBundleBaseName(); + bundleName = PluginManagerCore.CORE_ID.equals(module.getPluginId()) ? "messages.ActionsBundle" : module.getResourceBundleBaseName(); } XmlElement element = descriptor.element; @@ -465,41 +448,41 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { } else { try { - bundle = DynamicBundle.INSTANCE.getResourceBundle(bundleName, pluginDescriptor.getPluginClassLoader()); + bundle = DynamicBundle.INSTANCE.getResourceBundle(bundleName, module.getPluginClassLoader()); lastBundle = bundle; lastBundleName = bundleName; } catch (MissingResourceException e) { - LOG.error(new PluginException("Cannot resolve resource bundle " + bundleName + " for action " + element, e, pluginDescriptor.getPluginId())); + LOG.error(new PluginException("Cannot resolve resource bundle " + bundleName + " for action " + element, e, module.getPluginId())); bundle = null; } } switch (descriptor.name) { case ACTION_ELEMENT_NAME: - processActionElement(element, pluginDescriptor, bundle, keymapManager, pluginDescriptor.getPluginClassLoader()); + processActionElement(element, module, bundle, keymapManager, module.getPluginClassLoader()); break; case GROUP_ELEMENT_NAME: - processGroupElement(element, pluginDescriptor, bundle, keymapManager, pluginDescriptor.getPluginClassLoader()); + processGroupElement(element, module, bundle, keymapManager, module.getPluginClassLoader()); break; case SEPARATOR_ELEMENT_NAME: - processSeparatorNode(null, element, pluginDescriptor.getPluginId(), bundle); + processSeparatorNode(null, element, module, bundle); break; case REFERENCE_ELEMENT_NAME: - processReferenceNode(element, pluginDescriptor.getPluginId(), bundle); + processReferenceNode(element, module, bundle); break; case "unregister": - processUnregisterNode(element, pluginDescriptor.getPluginId()); + processUnregisterNode(element, module); break; case "prohibit": - processProhibitNode(element, pluginDescriptor.getPluginId()); + processProhibitNode(element, module); break; default: - LOG.error(new PluginException("Unexpected name of element" + descriptor.name, pluginDescriptor.getPluginId())); + LOG.error(new PluginException("Unexpected name of element" + descriptor.name, module.getPluginId())); break; } } - StartUpMeasurer.addPluginCost(pluginDescriptor.getPluginId().getIdString(), "Actions", StartUpMeasurer.getCurrentTime() - startTime); + StartUpMeasurer.addPluginCost(module.getPluginId().getIdString(), "Actions", StartUpMeasurer.getCurrentTime() - startTime); } @Override @@ -585,9 +568,8 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { return getActionImpl(actionId, true) instanceof ActionGroup; } - @NotNull @Override - public JComponent createButtonToolbar(@NotNull final String actionPlace, @NotNull final ActionGroup messageActionGroup) { + public @NotNull JComponent createButtonToolbar(final @NotNull String actionPlace, final @NotNull ActionGroup messageActionGroup) { //noinspection deprecation return new ButtonToolbarImpl(actionPlace, messageActionGroup); } @@ -601,13 +583,13 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { * @return instance of ActionGroup or ActionStub. The method never returns real subclasses of {@code AnAction}. */ private @Nullable AnAction processActionElement(@NotNull XmlElement element, - @NotNull IdeaPluginDescriptorImpl plugin, + @NotNull IdeaPluginDescriptorImpl module, @Nullable ResourceBundle bundle, @NotNull KeymapManager keymapManager, @NotNull ClassLoader classLoader) { String className = element.attributes.get(CLASS_ATTR_NAME); if (className == null || className.isEmpty()) { - reportActionError(plugin.getPluginId(), "action element should have specified \"class\" attribute"); + reportActionError(module, "action element should have specified \"class\" attribute"); return null; } @@ -631,11 +613,11 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { //noinspection HardCodedStringLiteral String descriptionValue = element.attributes.get(DESCRIPTION); - ActionStub stub = new ActionStub(className, id, plugin, iconPath, ProjectType.create(projectType), () -> { + ActionStub stub = new ActionStub(className, id, module, iconPath, ProjectType.create(projectType), () -> { Supplier text = () -> computeActionText(bundle, id, ACTION_ELEMENT_NAME, textValue, classLoader); if (text.get() == null) { - reportActionError(plugin.getPluginId(), "'text' attribute is mandatory (actionId=" + id + - ", plugin=" + plugin + ")"); + LOG.error(new PluginException("'text' attribute is mandatory (actionId=" + id + ", module=" + " " + module + ")", + module.getPluginId())); } Presentation presentation = new Presentation(); @@ -653,25 +635,25 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { for (XmlElement e : element.children) { switch (e.name) { case ADD_TO_GROUP_ELEMENT_NAME: - processAddToGroupNode(stub, e, plugin.getPluginId(), isSecondary(e)); + processAddToGroupNode(stub, e, module, isSecondary(e)); break; case "keyboard-shortcut": - processKeyboardShortcutNode(e, id, plugin.getPluginId(), keymapManager); + processKeyboardShortcutNode(e, id, module, keymapManager); break; case "mouse-shortcut": - processMouseShortcutNode(e, id, plugin.getPluginId(), keymapManager); + processMouseShortcutNode(e, id, module, keymapManager); break; case "abbreviation": processAbbreviationNode(e, id); break; case OVERRIDE_TEXT_ELEMENT_NAME: - processOverrideTextNode(stub, stub.getId(), e, plugin.getPluginId(), bundle); + processOverrideTextNode(stub, stub.getId(), e, module, bundle); break; case SYNONYM_ELEMENT_NAME: - processSynonymNode(stub, e, plugin.getPluginId(), bundle); + processSynonymNode(stub, e, module, bundle); break; default: - reportActionError(plugin.getPluginId(), "unexpected name of element \"" + e.name + "\""); + reportActionError(module, "unexpected name of element \"" + e.name + "\""); return null; } } @@ -681,7 +663,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { keymapManager.bindShortcuts(shortcutOfActionId, id); } - registerOrReplaceActionInner(element, id, stub, plugin); + registerOrReplaceActionInner(element, id, stub, module); return stub; } @@ -718,7 +700,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { } private AnAction processGroupElement(@NotNull XmlElement element, - @NotNull IdeaPluginDescriptorImpl plugin, + @NotNull IdeaPluginDescriptorImpl module, @Nullable ResourceBundle bundle, @NotNull KeymapManagerEx keymapManager, @NotNull ClassLoader classLoader) { @@ -733,7 +715,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { try { String id = element.attributes.get(ID_ATTR_NAME); if (id != null && id.isEmpty()) { - reportActionError(plugin.getPluginId(), "ID of the group cannot be an empty string"); + reportActionError(module, "ID of the group cannot be an empty string"); return null; } synchronized (myLock) { @@ -751,14 +733,14 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { group = new DefaultCompactActionGroup(); } else if (id == null) { - Object obj = ApplicationManager.getApplication().instantiateClass(className, plugin); + Object obj = ApplicationManager.getApplication().instantiateClass(className, module); if (!(obj instanceof ActionGroup)) { - reportActionError(plugin.getPluginId(), "class with name \"" + className + "\" should be instance of " + ActionGroup.class.getName()); + reportActionError(module, "class with name \"" + className + "\" should be instance of " + ActionGroup.class.getName()); return null; } if (element.children.size() != element.count(ADD_TO_GROUP_ELEMENT_NAME)) { // if (!(obj instanceof DefaultActionGroup)) { - reportActionError(plugin.getPluginId(), "class with name \"" + className + "\" should be instance of " + DefaultActionGroup.class.getName() + + reportActionError(module, "class with name \"" + className + "\" should be instance of " + DefaultActionGroup.class.getName() + " because there are children specified"); return null; } @@ -767,7 +749,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { group = (ActionGroup)obj; } else { - group = new ActionGroupStub(id, className, plugin); + group = new ActionGroupStub(id, className, module); customClass = true; } // read ID and register loaded group @@ -780,7 +762,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { id = ""; } - registerOrReplaceActionInner(element, id, group, plugin); + registerOrReplaceActionInner(element, id, group, module); Presentation presentation = group.getTemplatePresentation(); String finalId = id; @@ -813,7 +795,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { ((ActionGroupStub)group).setIconPath(iconPath); } else if (iconPath != null) { - setIconFromClass(null, plugin, iconPath, presentation); + setIconFromClass(null, module, iconPath, presentation); } // popup @@ -839,37 +821,37 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { for (XmlElement child : element.children) { switch (child.name) { case ACTION_ELEMENT_NAME: { - AnAction action = processActionElement(child, plugin, bundle, keymapManager, classLoader); + AnAction action = processActionElement(child, module, bundle, keymapManager, classLoader); if (action != null) { addToGroupInner(group, action, Constraints.LAST, isSecondary(child)); } break; } case SEPARATOR_ELEMENT_NAME: - processSeparatorNode((DefaultActionGroup)group, child, plugin.getPluginId(), bundle); + processSeparatorNode((DefaultActionGroup)group, child, module, bundle); break; case GROUP_ELEMENT_NAME: { - AnAction action = processGroupElement(child, plugin, bundle, keymapManager, classLoader); + AnAction action = processGroupElement(child, module, bundle, keymapManager, classLoader); if (action != null) { addToGroupInner(group, action, Constraints.LAST, false); } break; } case ADD_TO_GROUP_ELEMENT_NAME: - processAddToGroupNode(group, child, plugin.getPluginId(), isSecondary(child)); + processAddToGroupNode(group, child, module, isSecondary(child)); break; case REFERENCE_ELEMENT_NAME: { - AnAction action = processReferenceElement(child, plugin.getPluginId()); + AnAction action = processReferenceElement(child, module); if (action != null) { addToGroupInner(group, action, Constraints.LAST, isSecondary(child)); } break; } case OVERRIDE_TEXT_ELEMENT_NAME: - processOverrideTextNode(group, id, child, plugin.getPluginId(), bundle); + processOverrideTextNode(group, id, child, module, bundle); break; default: - reportActionError(plugin.getPluginId(), "unexpected name of element \"" + child.name + "\n"); + reportActionError(module, "unexpected name of element \"" + child.name + "\n"); return null; } } @@ -877,23 +859,23 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { } catch (Exception e) { String message = "cannot create class \"" + className + "\""; - reportActionError(plugin.getPluginId(), message, e); + reportActionError(module, message, e); return null; } } - private void processReferenceNode(@NotNull XmlElement element, @Nullable PluginId pluginId, @Nullable ResourceBundle bundle) { - AnAction action = processReferenceElement(element, pluginId); + private void processReferenceNode(@NotNull XmlElement element, @NotNull IdeaPluginDescriptor module, @Nullable ResourceBundle bundle) { + AnAction action = processReferenceElement(element, module); if (action == null) { return; } for (XmlElement child : element.children) { if (ADD_TO_GROUP_ELEMENT_NAME.equals(child.name)) { - processAddToGroupNode(action, child, pluginId, isSecondary(child)); + processAddToGroupNode(action, child, module, isSecondary(child)); } else if (SYNONYM_ELEMENT_NAME.equals(child.name)) { - processSynonymNode(action, child, pluginId, bundle); + processSynonymNode(action, child, module, bundle); } } } @@ -901,25 +883,26 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { /** * @param element description of link */ - private void processAddToGroupNode(AnAction action, XmlElement element, PluginId pluginId, boolean secondary) { + private void processAddToGroupNode(AnAction action, XmlElement element, @NotNull IdeaPluginDescriptor module, boolean secondary) { String name = action instanceof ActionStub ? ((ActionStub)action).getClassName() : action.getClass().getName(); String id = action instanceof ActionStub ? ((ActionStub)action).getId() : actionToId.get(action); String actionName = name + " (" + id + ")"; // parent group - final AnAction parentGroup = getParentGroup(element.attributes.get(GROUP_ID_ATTR_NAME), actionName, pluginId); + final AnAction parentGroup = getParentGroup(element.attributes.get(GROUP_ID_ATTR_NAME), actionName, module); if (parentGroup == null) { return; } // anchor attribute - final Anchor anchor = parseAnchor(element.attributes.get("anchor"), actionName, pluginId); + final Anchor anchor = parseAnchor(element.attributes.get("anchor"), actionName, module); if (anchor == null) { return; } final String relativeToActionId = element.attributes.get("relative-to-action"); - if (!checkRelativeToAction(relativeToActionId, anchor, actionName, pluginId)) { + if ((Anchor.BEFORE == anchor || Anchor.AFTER == anchor) && relativeToActionId == null) { + reportActionError(module, actionName + ": \"relative-to-action\" cannot be null if anchor is \"after\" or \"before\""); return; } addToGroupInner(parentGroup, action, new Constraints(anchor, relativeToActionId), secondary); @@ -931,32 +914,34 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { idToGroupId.putValue(actionId, actionToId.get(group)); } - @Nullable - public DefaultActionGroup getParentGroup(final String groupId, - @Nullable final String actionName, - @Nullable final PluginId pluginId) { + public @Nullable DefaultActionGroup getParentGroup(String groupId, + @Nullable String actionName, + @NotNull IdeaPluginDescriptor module) { if (groupId == null || groupId.isEmpty()) { - reportActionError(pluginId, actionName + ": attribute \"group-id\" should be defined"); + reportActionError(module, actionName + ": attribute \"group-id\" should be defined"); return null; } AnAction parentGroup = getActionImpl(groupId, true); if (parentGroup == null) { - reportActionError(pluginId, actionName + ": group with id \"" + groupId + "\" isn't registered; action will be added to the \"Other\" group", null); + reportActionError(module, actionName + ": group with id \"" + groupId + "\" isn't registered; action will be added to the \"Other\" group", null); parentGroup = getActionImpl(IdeActions.GROUP_OTHER_MENU, true); } if (!(parentGroup instanceof DefaultActionGroup)) { - reportActionError(pluginId, actionName + ": group with id \"" + groupId + "\" should be instance of " + DefaultActionGroup.class.getName() + + reportActionError(module, actionName + ": group with id \"" + groupId + "\" should be instance of " + DefaultActionGroup.class.getName() + " but was " + (parentGroup != null ? parentGroup.getClass() : "[null]")); return null; } return (DefaultActionGroup)parentGroup; } - private static void processOverrideTextNode(AnAction action, String id, XmlElement element, PluginId pluginId, + private static void processOverrideTextNode(AnAction action, + String id, + XmlElement element, + @NotNull IdeaPluginDescriptor module, @Nullable ResourceBundle bundle) { String place = element.attributes.get("place"); if (place == null) { - reportActionError(pluginId, id + ": override-text specified without place"); + reportActionError(module, id + ": override-text specified without place"); return; } String useTextOfPlace = element.attributes.get("use-text-of-place"); @@ -976,7 +961,10 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { } } - private static void processSynonymNode(AnAction action, XmlElement element, PluginId pluginId, @Nullable ResourceBundle bundle) { + private static void processSynonymNode(AnAction action, + XmlElement element, + @NotNull IdeaPluginDescriptor module, + @Nullable ResourceBundle bundle) { @SuppressWarnings("HardCodedStringLiteral") String text = element.attributes.get(TEXT_ATTR_NAME); if (text != null && !text.isEmpty()) { @@ -988,7 +976,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { action.addSynonym(() -> BundleBase.message(bundle, key)); } else { - reportActionError(pluginId, "Can't process synonym: neither text nor resource bundle key is specified"); + reportActionError(module, "Can't process synonym: neither text nor resource bundle key is specified"); } } } @@ -998,7 +986,10 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { * case separator will be added to group described in the sub element. * @param element XML element which represent separator. */ - private void processSeparatorNode(@Nullable DefaultActionGroup parentGroup, @NotNull XmlElement element, PluginId pluginId, @Nullable ResourceBundle bundle) { + private void processSeparatorNode(@Nullable DefaultActionGroup parentGroup, + @NotNull XmlElement element, + @NotNull IdeaPluginDescriptor module, + @Nullable ResourceBundle bundle) { //noinspection HardCodedStringLiteral String text = element.attributes.get(TEXT_ATTR_NAME); String key = element.attributes.get(KEY_ATTR_NAME); @@ -1010,36 +1001,35 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { // try to find inner tag for (XmlElement child : element.children) { if (ADD_TO_GROUP_ELEMENT_NAME.equals(child.name)) { - processAddToGroupNode(separator, child, pluginId, isSecondary(child)); + processAddToGroupNode(separator, child, module, isSecondary(child)); } } } - @NotNull - private static Separator createSeparator(@Nullable ResourceBundle bundle, @NotNull String key) { + private static @NotNull Separator createSeparator(@Nullable ResourceBundle bundle, @NotNull String key) { String text = bundle != null ? AbstractBundle.messageOrNull(bundle, key) : null; return text != null ? new Separator(text) : Separator.getInstance(); } - private void processProhibitNode(XmlElement element, PluginId pluginId) { + private void processProhibitNode(XmlElement element, @NotNull IdeaPluginDescriptor module) { String id = element.attributes.get(ID_ATTR_NAME); if (id == null) { - reportActionError(pluginId, "'id' attribute is required for 'unregister' elements"); + reportActionError(module, "'id' attribute is required for 'unregister' elements"); return; } prohibitAction(id); } - private void processUnregisterNode(XmlElement element, PluginId pluginId) { + private void processUnregisterNode(XmlElement element, @NotNull IdeaPluginDescriptor module) { String id = element.attributes.get(ID_ATTR_NAME); if (id == null) { - reportActionError(pluginId, "'id' attribute is required for 'unregister' elements"); + reportActionError(module, "'id' attribute is required for 'unregister' elements"); return; } AnAction action = getAction(id); if (action == null) { - reportActionError(pluginId, "Trying to unregister non-existing action " + id); + reportActionError(module, "Trying to unregister non-existing action " + id); return; } @@ -1049,16 +1039,16 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { private static void processKeyboardShortcutNode(XmlElement element, String actionId, - PluginId pluginId, + @NotNull PluginDescriptor module, @NotNull KeymapManager keymapManager) { String firstStrokeString = element.attributes.get("first-keystroke"); if (firstStrokeString == null) { - reportActionError(pluginId, "\"first-keystroke\" attribute must be specified for action with id=" + actionId); + reportActionError(module, "\"first-keystroke\" attribute must be specified for action with id=" + actionId); return; } KeyStroke firstKeyStroke = getKeyStroke(firstStrokeString); if (firstKeyStroke == null) { - reportActionError(pluginId, "\"first-keystroke\" attribute has invalid value for action with id=" + actionId); + reportActionError(module, "\"first-keystroke\" attribute has invalid value for action with id=" + actionId); return; } @@ -1067,19 +1057,19 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { if (secondStrokeString != null) { secondKeyStroke = getKeyStroke(secondStrokeString); if (secondKeyStroke == null) { - reportActionError(pluginId, "\"second-keystroke\" attribute has invalid value for action with id=" + actionId); + reportActionError(module, "\"second-keystroke\" attribute has invalid value for action with id=" + actionId); return; } } String keymapName = element.attributes.get(KEYMAP_ATTR_NAME); if (keymapName == null || keymapName.trim().isEmpty()) { - reportActionError(pluginId, "attribute \"keymap\" should be defined"); + reportActionError(module, "attribute \"keymap\" should be defined"); return; } Keymap keymap = keymapManager.getKeymap(keymapName); if (keymap == null) { - reportKeymapNotFoundWarning(pluginId, keymapName); + reportKeymapNotFoundWarning(module, keymapName); return; } processRemoveAndReplace(element, actionId, keymap, new KeyboardShortcut(firstKeyStroke, secondKeyStroke)); @@ -1099,10 +1089,10 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { } } - private @Nullable AnAction processReferenceElement(XmlElement element, PluginId pluginId) { + private @Nullable AnAction processReferenceElement(@NotNull XmlElement element, @NotNull IdeaPluginDescriptor module) { String ref = getReferenceActionId(element); if (ref == null || ref.isEmpty()) { - reportActionError(pluginId, "ID of reference element should be defined", null); + reportActionError(module, "ID of reference element should be defined", null); return null; } @@ -1115,7 +1105,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { AnAction action = getActionImpl(ref, true); if (action == null) { if (!myNotRegisteredInternalActionIds.contains(ref)) { - reportActionError(pluginId, "action specified by reference isn't registered (ID=" + ref + ")", null); + reportActionError(module, "action specified by reference isn't registered (ID=" + ref + ")", null); } return null; } @@ -1132,19 +1122,15 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { } @ApiStatus.Internal - public static @Nullable String checkUnloadActions(PluginId pluginId, @NotNull IdeaPluginDescriptorImpl pluginDescriptor) { - List descriptors = pluginDescriptor.actions; - if (descriptors == null) { - return null; - } - + public static @Nullable String checkUnloadActions(@NotNull IdeaPluginDescriptorImpl module) { + List descriptors = module.actions; for (RawPluginDescriptor.ActionDescriptor descriptor : descriptors) { XmlElement element = descriptor.element; String elementName = descriptor.name; if (!elementName.equals(ACTION_ELEMENT_NAME) && !(elementName.equals(GROUP_ELEMENT_NAME) && canUnloadGroup(element)) && !elementName.equals(REFERENCE_ELEMENT_NAME)) { - return "Plugin " + pluginId + " is not unload-safe because of action element " + elementName; + return "Plugin " + module + " is not unload-safe because of action element " + elementName; } } return null; @@ -1162,12 +1148,8 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { return true; } - public void unloadActions(@NotNull IdeaPluginDescriptorImpl pluginDescriptor) { - List descriptors = pluginDescriptor.actions; - if (descriptors == null) { - return; - } - + public void unloadActions(@NotNull IdeaPluginDescriptorImpl module) { + List descriptors = module.actions; for (int i = descriptors.size() - 1; i >= 0; i--) { RawPluginDescriptor.ActionDescriptor descriptor = descriptors.get(i); XmlElement element = descriptor.element; @@ -1179,9 +1161,11 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { unloadGroupElement(element); break; case REFERENCE_ELEMENT_NAME: - PluginId pluginId = pluginDescriptor.getPluginId(); - AnAction action = processReferenceElement(element, pluginId); - if (action == null) return; + AnAction action = processReferenceElement(element, module); + if (action == null) { + return; + } + String actionId = getReferenceActionId(element); for (XmlElement child : element.children) { @@ -1190,8 +1174,10 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { } String groupId = child.attributes.get(GROUP_ID_ATTR_NAME); - final DefaultActionGroup parentGroup = getParentGroup(groupId, actionId, pluginId); - if (parentGroup == null) return; + DefaultActionGroup parentGroup = getParentGroup(groupId, actionId, module); + if (parentGroup == null) { + return; + } parentGroup.remove(action); idToGroupId.remove(actionId, groupId); } @@ -1235,16 +1221,25 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { if (myProhibitedActionIds.contains(actionId)) { return; } + if (addToMap(actionId, action, ProjectType.create(projectType)) == null) { reportActionIdCollision(actionId, action, pluginId); return; } + if (actionToId.containsKey(action)) { - reportActionError(pluginId, - "ID \"" + actionToId.get(action) + "\" is already taken by action \"" + action + "\"" + getPluginInfo(pluginId) + - ". ID \"" + actionId + "\" cannot be registered for the same action"); + IdeaPluginDescriptorImpl module = pluginId == null ? null : PluginManagerCore.getPluginSet().findEnabledPlugin(pluginId); + String message = "ID \"" + actionToId.get(action) + "\" is already taken by action \"" + action + "\". " + + "ID \"" + actionId + "\" cannot be registered for the same action"; + if (module == null) { + LOG.error(new PluginException(message + " " + pluginId, null, pluginId)); + } + else { + reportActionError(module, message); + } return; } + action.registerCustomShortcutSet(new ProxyShortcutSet(actionId), null); idToIndex.put(actionId, myRegisteredActionsCount++); actionToId.put(action, actionId); @@ -1267,9 +1262,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { } } - private @Nullable AnAction addToMap(@NotNull String actionId, - @NotNull AnAction action, - @Nullable ProjectType projectType) { + private @Nullable AnAction addToMap(@NotNull String actionId, @NotNull AnAction action, @Nullable ProjectType projectType) { AnAction chameleonAction = idToAction.computeIfPresent(actionId, (__, old) -> { return old instanceof ChameleonAction ? old : new ChameleonAction(old, projectType); }); @@ -1283,9 +1276,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { } } - private void reportActionIdCollision(@NotNull String actionId, - @NotNull AnAction action, - @Nullable PluginId pluginId) { + private void reportActionIdCollision(@NotNull String actionId, @NotNull AnAction action, @Nullable PluginId pluginId) { String oldPluginInfo = pluginToId.entrySet() .stream() .filter(entry -> entry.getValue().contains(actionId)) @@ -1293,9 +1284,14 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { .map(ActionManagerImpl::getPluginInfo) .collect(Collectors.joining(",")); - reportActionError(pluginId, - "ID \"" + actionId + "\" is already taken by action \"" + idToAction.get(actionId) + "\"" + oldPluginInfo + - ". Action \"" + action + "\"" + getPluginInfo(pluginId) + " cannot use the same ID"); + String message = ("ID \"" + actionId + "\" is already taken by action \"" + idToAction.get(actionId) + "\"" + oldPluginInfo + + ". Action \"" + action + "\"" + " cannot use the same ID") + " " + pluginId; + if (pluginId == null) { + LOG.error(message); + } + else { + LOG.error(new PluginException(message, null, pluginId)); + } } @Override @@ -1385,9 +1381,8 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { } } - @NotNull @Override - public Comparator getRegistrationOrderComparator() { + public @NotNull Comparator getRegistrationOrderComparator() { return Comparator.comparingInt(idToIndex::getInt); } @@ -1592,13 +1587,12 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable { } } - @NotNull @Override - public ActionCallback tryToExecute(@NotNull AnAction action, - @Nullable InputEvent inputEvent, - @Nullable Component contextComponent, - @Nullable String place, - boolean now) { + public @NotNull ActionCallback tryToExecute(@NotNull AnAction action, + @Nullable InputEvent inputEvent, + @Nullable Component contextComponent, + @Nullable String place, + boolean now) { ApplicationManager.getApplication().assertIsDispatchThread(); ActionCallback result = new ActionCallback(); diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java index 037982f8a0c3..8fb5aa34fdb7 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java @@ -48,13 +48,13 @@ import com.intellij.util.containers.Stack; import com.intellij.util.messages.Topic; import com.intellij.util.ui.EDT; import com.intellij.util.ui.EdtInvocationManager; +import kotlin.sequences.Sequence; import org.jetbrains.annotations.*; import sun.awt.AWTAccessor; import sun.awt.AWTAutoShutdown; import javax.swing.*; import java.awt.*; -import java.util.List; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -352,10 +352,13 @@ public class ApplicationImpl extends ClientAwareComponentManager implements Appl @Override public final void load() { - List plugins = PluginManagerCore.getLoadedPlugins(null); - registerComponents(plugins, this, null, null); + PluginManagerCore.scheduleDescriptorLoading(); + Sequence modules = + PluginManagerCore.initPlugins(ApplicationImpl.class.getClassLoader()).join().getEnabledModules(); + + registerComponents(modules, this, null, null); ApplicationLoader.initConfigurationStore(this); - preloadServices(plugins, "", false).getSecond().join(); + preloadServices(modules, "", false).getSecond().join(); loadComponents(null); ForkJoinTask.invokeAll(ApplicationLoader.callAppInitialized(this)); } @@ -1022,6 +1025,7 @@ public class ApplicationImpl extends ClientAwareComponentManager implements Appl new Attachment("threadDump.txt", ThreadDumper.dumpThreadsToString())); } + @Override public void assertIsWriteThread() { if (isWriteThread()) return; if (ShutDownTracker.isShutdownHookRunning()) return; diff --git a/platform/platform-impl/src/com/intellij/openapi/client/ClientAwareComponentManager.kt b/platform/platform-impl/src/com/intellij/openapi/client/ClientAwareComponentManager.kt index 241ccd43023a..96411b6853b2 100644 --- a/platform/platform-impl/src/com/intellij/openapi/client/ClientAwareComponentManager.kt +++ b/platform/platform-impl/src/com/intellij/openapi/client/ClientAwareComponentManager.kt @@ -1,12 +1,12 @@ -// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.openapi.client import com.intellij.codeWithMe.ClientId -import com.intellij.openapi.progress.ProgressIndicatorProvider import com.intellij.ide.plugins.IdeaPluginDescriptorImpl import com.intellij.openapi.application.Application import com.intellij.openapi.components.ServiceDescriptor import com.intellij.openapi.extensions.PluginId +import com.intellij.openapi.progress.ProgressIndicatorProvider import com.intellij.serviceContainer.ComponentManagerImpl import com.intellij.serviceContainer.PrecomputedExtensionModel import com.intellij.serviceContainer.throwAlreadyDisposedError @@ -56,15 +56,15 @@ abstract class ClientAwareComponentManager @JvmOverloads constructor( return session?.doGetService(serviceClass, createIfNeeded, false) } - override fun registerComponents(plugins: List, + override fun registerComponents(modules: Sequence, app: Application?, precomputedExtensionModel: PrecomputedExtensionModel?, listenerCallbacks: List?) { - super.registerComponents(plugins, app, precomputedExtensionModel, listenerCallbacks) + super.registerComponents(modules, app, precomputedExtensionModel, listenerCallbacks) val sessionsManager = super.getService(ClientSessionsManager::class.java)!! for (session in sessionsManager.getSessions(true)) { - (session as? ClientSessionImpl)?.registerComponents(plugins, app, precomputedExtensionModel, listenerCallbacks) + (session as? ClientSessionImpl)?.registerComponents(modules, app, precomputedExtensionModel, listenerCallbacks) } } @@ -77,17 +77,17 @@ abstract class ClientAwareComponentManager @JvmOverloads constructor( } } - override fun preloadServices(plugins: List, + override fun preloadServices(modules: Sequence, activityPrefix: String, onlyIfAwait: Boolean): Pair, CompletableFuture> { - val (asyncPreloadFuture, syncPreloadFuture) = super.preloadServices(plugins, activityPrefix, onlyIfAwait) + val (asyncPreloadFuture, syncPreloadFuture) = super.preloadServices(modules, activityPrefix, onlyIfAwait) val sessionsManager = super.getService(ClientSessionsManager::class.java)!! val asyncPreloadFutures = mutableListOf>() val syncPreloadFutures = mutableListOf>() for (session in sessionsManager.getSessions(true)) { session as? ClientSessionImpl ?: continue - val (sessionAsyncPreloadFuture, sessionSyncPreloadFuture) = session.preloadServices(plugins, activityPrefix, onlyIfAwait) + val (sessionAsyncPreloadFuture, sessionSyncPreloadFuture) = session.preloadServices(modules, activityPrefix, onlyIfAwait) asyncPreloadFutures.add(sessionAsyncPreloadFuture) syncPreloadFutures.add(sessionSyncPreloadFuture) } diff --git a/platform/platform-impl/src/com/intellij/openapi/client/ClientSessionImpl.kt b/platform/platform-impl/src/com/intellij/openapi/client/ClientSessionImpl.kt index 75d2ba1b75c3..0e9cbf5393e9 100644 --- a/platform/platform-impl/src/com/intellij/openapi/client/ClientSessionImpl.kt +++ b/platform/platform-impl/src/com/intellij/openapi/client/ClientSessionImpl.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.openapi.client import com.intellij.codeWithMe.ClientId @@ -9,7 +9,6 @@ import com.intellij.ide.plugins.IdeaPluginDescriptorImpl import com.intellij.ide.plugins.PluginManagerCore import com.intellij.idea.preloadServices import com.intellij.openapi.application.Application -import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.impl.ApplicationImpl import com.intellij.openapi.components.ComponentConfig import com.intellij.openapi.components.PersistentStateComponent @@ -21,7 +20,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.project.impl.ProjectExImpl import com.intellij.serviceContainer.ComponentManagerImpl import com.intellij.serviceContainer.PrecomputedExtensionModel -import com.intellij.serviceContainer.executeRegisterTask +import com.intellij.serviceContainer.executeRegisterTaskForOldContent import com.intellij.util.messages.MessageBus import org.jetbrains.annotations.ApiStatus import java.nio.file.Path @@ -34,7 +33,6 @@ abstract class ClientSessionImpl( final override val clientId: ClientId, protected val sharedComponentManager: ClientAwareComponentManager ) : ComponentManagerImpl(null, false), ClientSession { - override val isLocal = clientId.isLocal override val isLightServiceSupported = false @@ -45,18 +43,13 @@ abstract class ClientSessionImpl( } fun registerServices() { - registerComponents( - PluginManagerCore.getLoadedPlugins(null), - ApplicationManager.getApplication(), - null, - null - ) + registerComponents() } fun preloadServices() { assert(containerState.get() == ContainerState.PRE_INIT) preloadServices( - PluginManagerCore.getLoadedPlugins(null), + PluginManagerCore.getPluginSet().getEnabledModules(), container = this, activityPrefix = "client ", onlyIfAwait = false @@ -79,14 +72,14 @@ abstract class ClientSessionImpl( /** * only per-client services are supported (no components, extensions, listeners) */ - override fun registerComponents(plugins: List, + override fun registerComponents(modules: Sequence, app: Application?, precomputedExtensionModel: PrecomputedExtensionModel?, listenerCallbacks: List?) { - for (mainPlugin in plugins) { - executeRegisterTask(mainPlugin) { pluginDescriptor -> - val containerDescriptor = getContainerDescriptor(pluginDescriptor) - registerServices(containerDescriptor.services, pluginDescriptor) + for (rootModule in modules) { + registerServices(getContainerDescriptor(rootModule).services, rootModule) + executeRegisterTaskForOldContent(rootModule) { module -> + registerServices(getContainerDescriptor(module).services, module) } } } @@ -108,7 +101,7 @@ abstract class ClientSessionImpl( val sessionsManager = sharedComponentManager.getService(ClientSessionsManager::class.java) val localSession = sessionsManager?.getSession(ClientId.localId) as? ClientSessionImpl - if (localSession?.doGetService(serviceClass, createIfNeeded, fallbackToShared = false) != null) { + if (localSession?.doGetService(serviceClass, createIfNeeded = true, fallbackToShared = false) != null) { LOG.error("$serviceClass is registered only for client=\"local\", " + "please provide a guest-specific implementation, or change to client=\"all\"") return null @@ -200,7 +193,6 @@ open class ClientProjectSessionImpl( clientId: ClientId, final override val project: ProjectExImpl, ) : ClientSessionImpl(clientId, project), ClientProjectSession { - override fun getContainerDescriptor(pluginDescriptor: IdeaPluginDescriptorImpl): ContainerDescriptor { return pluginDescriptor.projectContainerDescriptor } diff --git a/platform/platform-impl/src/com/intellij/openapi/project/impl/DefaultProject.java b/platform/platform-impl/src/com/intellij/openapi/project/impl/DefaultProject.java index b4f347e1fba8..1ec6e6e8eeab 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/impl/DefaultProject.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/impl/DefaultProject.java @@ -5,7 +5,6 @@ import com.intellij.configurationStore.StoreUtil; import com.intellij.diagnostic.ActivityCategory; import com.intellij.ide.plugins.ContainerDescriptor; import com.intellij.ide.plugins.IdeaPluginDescriptorImpl; -import com.intellij.ide.plugins.PluginManagerCore; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ex.ApplicationManagerEx; import com.intellij.openapi.components.BaseComponent; @@ -27,7 +26,6 @@ import com.intellij.util.messages.MessageBus; import org.jetbrains.annotations.*; import org.picocontainer.PicoContainer; -import java.util.List; import java.util.Map; /** @@ -312,9 +310,7 @@ final class DefaultProjectImpl extends ComponentManagerImpl implements Project { public void init() { // do not leak internal delegate, use DefaultProject everywhere instead registerServiceInstance(Project.class, actualContainerInstance, ComponentManagerImpl.fakeCorePluginDescriptor); - - //noinspection unchecked - registerComponents((List)PluginManagerCore.getLoadedPlugins(), ApplicationManager.getApplication(), null, null); + registerComponents(); createComponents(null); Disposer.register(actualContainerInstance, this); } diff --git a/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectExImpl.kt b/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectExImpl.kt index 491ef1576140..c1a5ebdbefd0 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectExImpl.kt +++ b/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectExImpl.kt @@ -172,7 +172,7 @@ open class ProjectExImpl(filePath: Path, projectName: String?) : ProjectImpl(App // for light projects, preload only services that are essential // ("await" means "project component loading activity is completed only when all such services are completed") val servicePreloadingFuture = if (preloadServices) { - preloadServices(PluginManagerCore.getLoadedPlugins(null), container = this, activityPrefix = "project ", onlyIfAwait = isLight) + preloadServices(PluginManagerCore.getPluginSet().getEnabledModules(), container = this, activityPrefix = "project ", onlyIfAwait = isLight) } else { null diff --git a/platform/platform-impl/src/com/intellij/openapi/project/impl/projectLoader.kt b/platform/platform-impl/src/com/intellij/openapi/project/impl/projectLoader.kt index 00fcad8170fe..57ed0eec20b8 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/impl/projectLoader.kt +++ b/platform/platform-impl/src/com/intellij/openapi/project/impl/projectLoader.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. @file:JvmName("ProjectLoadHelper") @file:ApiStatus.Internal package com.intellij.openapi.project.impl @@ -22,10 +22,7 @@ import org.jetbrains.annotations.ApiStatus internal fun registerComponents(project: ProjectImpl) { var activity = createActivity(project) { "project ${Activities.REGISTER_COMPONENTS_SUFFIX}" } // at this point of time plugins are already loaded by application - no need to pass indicator to getLoadedPlugins call - project.registerComponents(plugins = PluginManagerCore.getLoadedPlugins(null), - app = ApplicationManager.getApplication(), - precomputedExtensionModel = null, - listenerCallbacks = null) + project.registerComponents() activity = activity?.endAndStart("projectComponentRegistered") runOnlyCorePluginExtensions( diff --git a/platform/platform-tests/testData/plugins/sort/moduleSort.txt b/platform/platform-tests/testData/plugins/sort/moduleSort.txt new file mode 100644 index 000000000000..40eeccae2dc0 --- /dev/null +++ b/platform/platform-tests/testData/plugins/sort/moduleSort.txt @@ -0,0 +1,4 @@ ++ com.intellij ++ com.jetbrains.restClient ++ com.intellij.microservices.ui ++ com.jetbrains.restClient | intellij.restClient/microservicesUI \ No newline at end of file diff --git a/platform/platform-tests/testData/plugins/sort/moduleSort.xml b/platform/platform-tests/testData/plugins/sort/moduleSort.xml new file mode 100644 index 000000000000..df1baa4bed48 --- /dev/null +++ b/platform/platform-tests/testData/plugins/sort/moduleSort.xml @@ -0,0 +1,35 @@ + + + com.intellij + + + + com.intellij.microservices.ui + Endpoints + JetBrains + Microservices + + + + + + + com.jetbrains.restClient + HTTP Client + Other Tools + JetBrains + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platform/platform-tests/testData/plugins/sort/simplePluginSort.txt b/platform/platform-tests/testData/plugins/sort/simplePluginSort.txt index 3cad0bdc17f0..df71ce900c3c 100644 --- a/platform/platform-tests/testData/plugins/sort/simplePluginSort.txt +++ b/platform/platform-tests/testData/plugins/sort/simplePluginSort.txt @@ -1,40 +1,30 @@ + com.intellij - cycle3 - cycle1 - cycle2 - cycle0 - cycle4 - cycle5 -+ duplicate -+ duplicate0 -+ incompatible_with_unknown_module - missing1 - missing2 -+ incompatible_with_disabled_module - missing3 -+ optional_config_cycle0 -+ optional_config_cycle1 -+ optional_config_cycle2 - optional_cycle0 - optional_cycle1 - optional_cycle2 + plugin3 ++ optional_config_cycle1 ++ missing0 ++ incompatible_with_unknown_module ++ incompatible_with_disabled_module ++ duplicate0 ++ duplicate + plugin0 - incompatible_with_enabled_module -+ plugin1 + plugin2 ++ plugin1 + Module com.intellij.modules.duplicate is declared by multiple plugins: - PluginDescriptor(name=duplicate0, id=duplicate0, path=file:/duplicate0.xml, version=2042.42) - PluginDescriptor(name=duplicate, id=duplicate, path=file:/duplicate1.xml, version=2042.42) -Plugins 'optional_cycle1', 'optional_cycle2', 'optional_cycle0' cannot be loaded because they form a dependency cycle -Plugins 'cycle5', 'cycle4' cannot be loaded because they form a dependency cycle -Plugins 'cycle2', 'cycle3', 'cycle1' cannot be loaded because they form a dependency cycle -Plugin 'cycle0' requires plugin 'cycle1' to be enabled -Plugin 'incompatible0' (version 'null') is not compatible with the current version of the IDE, because it requires build 1.111111 or older but the current build is 2042.42 -Plugin 'incompatible0-alt' (version 'null') is not compatible with the current version of the IDE, because it requires build 1.111 or older but the current build is 2042.42 -Plugin 'incompatible1' (version 'null') is not compatible with the current version of the IDE, because it requires build 9999.0 or newer but the current build is 2042.42 -Plugin 'incompatible2' (version 'null') is not compatible with the current version of the IDE, because it requires build 1.111111 or older but the current build is 2042.42 + PluginDescriptor(name=duplicate0, id=duplicate0, descriptorPath=plugin.xml, path=duplicate0.xml, version=2042.42, package=null, isBundled=false) + PluginDescriptor(name=duplicate, id=duplicate, descriptorPath=plugin.xml, path=duplicate1.xml, version=2042.42, package=null, isBundled=false) +Plugins 'optional_cycle0', 'optional_cycle2', 'optional_cycle1' cannot be loaded because they form a dependency cycle +Plugins 'optional_config_cycle0', 'optional_config_cycle2' cannot be loaded because they form a dependency cycle +Plugins 'cycle4', 'cycle5' cannot be loaded because they form a dependency cycle +Plugins 'cycle1', 'cycle3', 'cycle2' cannot be loaded because they form a dependency cycle +Plugin 'cycle0' requires plugin ' + cycle1 + ' to be installed +Plugin 'incompatible0' (version '2042.42') is not compatible with the current version of the IDE, because it requires build 1.111111 or older but the current build is 2042.42 +Plugin 'incompatible0-alt' (version '2042.42') is not compatible with the current version of the IDE, because it requires build 1.111 or older but the current build is 2042.42 +Plugin 'incompatible1' (version '2042.42') is not compatible with the current version of the IDE, because it requires build 9999.0 or newer but the current build is 2042.42 +Plugin 'incompatible2' (version '2042.42') is not compatible with the current version of the IDE, because it requires build 1.111111 or older but the current build is 2042.42 Plugin 'incompatible_with_enabled_module' is not compatible with the IDE because the IDE contains module 'com.intellij.modules.plugin0' which conflicts with the plugin Plugin 'missing1' requires plugin 'com.intellij.modules.missing' to be installed Plugin 'missing2' requires plugin 'com.intellij.missing' to be installed diff --git a/platform/platform-tests/testData/plugins/sort/ultimatePlugins.txt b/platform/platform-tests/testData/plugins/sort/ultimatePlugins.txt index d6047db3ca24..7a949a47c4ce 100644 --- a/platform/platform-tests/testData/plugins/sort/ultimatePlugins.txt +++ b/platform/platform-tests/testData/plugins/sort/ultimatePlugins.txt @@ -173,11 +173,11 @@ Plugin 'Jupyter Notebook' requires plugin 'com.intellij.modules.pycharm' to be installed -Plugin 'Python pytest-bdd support' requires plugin 'Python BDD support for engines, based on Gherkin (cucumber, behave)' to be enabled +Plugin 'Python pytest-bdd support' requires plugin 'com.intellij.modules.pycharm.professional' to be installed Plugin 'Python Docker' requires plugin 'com.intellij.modules.pycharm.professional' to be installed Plugin 'Python BDD support for engines, based on Gherkin (cucumber, behave)' requires plugin 'com.intellij.modules.pycharm.professional' to be installed Plugin 'Python SSH Interpreter' requires plugin 'com.intellij.modules.pycharm.professional' to be installed -Plugin 'Python WSL Support' requires plugin 'Python SSH Interpreter' to be enabled +Plugin 'Python WSL Support' requires plugin 'com.intellij.modules.pycharm.professional' to be installed Plugin 'Concurrency Visualiser' requires plugin 'com.intellij.modules.pycharm.professional' to be installed Disable not loaded plugins Open plugin manager \ No newline at end of file diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/ClassLoaderConfiguratorTest.kt b/platform/platform-tests/testSrc/com/intellij/ide/plugins/ClassLoaderConfiguratorTest.kt index ccf7dab8a058..c10d98b77a0d 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/ClassLoaderConfiguratorTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/ClassLoaderConfiguratorTest.kt @@ -1,4 +1,5 @@ // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +@file:Suppress("ReplaceGetOrSet") package com.intellij.ide.plugins import com.intellij.ide.plugins.cl.PluginAwareClassLoader @@ -80,14 +81,14 @@ internal class ClassLoaderConfiguratorTest { val plugins = loadDescriptors(rootDir).getEnabledPlugins() assertThat(plugins).hasSize(2) - val barPlugin = plugins[1] - assertThat(barPlugin.id.idString).isEqualTo("2-bar") + val barPlugin = plugins.get(1) + assertThat(barPlugin.pluginId.idString).isEqualTo("2-bar") - val classLoaderConfigurator = ClassLoaderConfigurator(PluginSet.createPluginSet(plugins, plugins)) - plugins.forEach(classLoaderConfigurator::configure) + val classLoaderConfigurator = ClassLoaderConfigurator(PluginSetBuilder(plugins).computeEnabledModuleMap().createPluginSet()) + classLoaderConfigurator.configure() assertThat((barPlugin.classLoader as PluginClassLoader)._getParents().map { it.descriptorPath }) - .containsExactly("com.example.sub.xml", null) + .containsExactly(null, "com.example.sub.xml") } @Suppress("PluginXmlValidity") @@ -128,8 +129,8 @@ internal class ClassLoaderConfiguratorTest { val plugins = loadResult.getEnabledPlugins() assertThat(plugins).hasSize(2) - val classLoaderConfigurator = ClassLoaderConfigurator(PluginSet.createPluginSet(plugins, plugins)) - plugins.forEach(classLoaderConfigurator::configure) + val classLoaderConfigurator = ClassLoaderConfigurator(PluginSetBuilder(plugins).computeEnabledModuleMap().createPluginSet()) + classLoaderConfigurator.configure() return loadResult } } @@ -142,7 +143,7 @@ private fun loadDescriptors(dir: Path): PluginLoadingResult { val paths = dir.directoryStreamIfExists { it.sorted() }!! context.use { for (file in paths) { - result.add(loadDescriptor(file, false, context) ?: continue, false) + result.add(loadDescriptor(file, context) ?: continue, false) } } result.finishLoading() diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTest.kt b/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTest.kt index 3437b032339d..e4b635b3e60a 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTest.kt @@ -111,7 +111,7 @@ class DynamicPluginsTest { DisabledPluginsState.saveDisabledPlugins(PathManager.getConfigDir()) val newDescriptor = loadDescriptorInTest(path) - ClassLoaderConfigurator(PluginManagerCore.getPluginSet().enablePlugin(newDescriptor)).configure(newDescriptor) + ClassLoaderConfigurator(PluginManagerCore.getPluginSet().enablePlugin(newDescriptor)).configureModule(newDescriptor) DynamicPlugins.loadPlugin(newDescriptor) try { assertThat(PluginManagerCore.getPlugin(descriptor.pluginId)?.pluginClassLoader as? PluginClassLoader).isNotNull() @@ -201,7 +201,7 @@ class DynamicPluginsTest { Files.createTempDirectory(inMemoryFs.fs.getPath("/"), null), ) assertThat(DynamicPlugins.checkCanUnloadWithoutRestart(descriptor)) - .isEqualTo("Plugin ${descriptor.id} is not unload-safe because of extension to non-dynamic EP $epName") + .isEqualTo("Plugin ${descriptor.pluginId} is not unload-safe because of extension to non-dynamic EP $epName") } @Test @@ -298,9 +298,9 @@ class DynamicPluginsTest { """""") .module("intellij.foo.sub", PluginBuilder() - .extensions("""""", "foo") - .packagePrefix("foo1") - .pluginDependency(barBuilder.id) + .extensions("""""", "foo") + .packagePrefix("foo1") + .pluginDependency(barBuilder.id) ) loadPluginWithText(fooBuilder).use { val ep = ApplicationManager.getApplication().extensionArea.getExtensionPointIfRegistered>("foo.barExtension") diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTestUtil.kt b/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTestUtil.kt index 3d2dc3fcd60c..2964622515fe 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTestUtil.kt +++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTestUtil.kt @@ -15,19 +15,21 @@ import java.nio.file.Files import java.nio.file.Path import java.util.function.Supplier -internal fun loadDescriptorInTest(dir: Path, disabledPlugins: Set = emptySet(), isBundled: Boolean = false): IdeaPluginDescriptorImpl { +internal fun loadDescriptorInTest(dir: Path, + disabledPlugins: Set = emptySet(), + isBundled: Boolean = false): IdeaPluginDescriptorImpl { assertThat(dir).exists() PluginManagerCore.getAndClearPluginLoadingErrors() val buildNumber = BuildNumber.fromString("2042.42")!! val parentContext = DescriptorListLoadingContext(disabledPlugins = disabledPlugins, result = PluginLoadingResult(emptyMap(), Supplier { buildNumber })) val result = loadDescriptorFromFileOrDir(file = dir, - pathName = PluginManagerCore.PLUGIN_XML, context = parentContext, pathResolver = PluginXmlPathResolver.DEFAULT_PATH_RESOLVER, isBundled = isBundled, isEssential = true, - isDirectory = Files.isDirectory(dir)) + isDirectory = Files.isDirectory(dir), + useCoreClassLoader = false) if (result == null) { @Suppress("USELESS_CAST") assertThat(PluginManagerCore.getAndClearPluginLoadingErrors()).isNotEmpty() diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginDescriptorTest.kt b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginDescriptorTest.kt index fd442574fefd..7bee58bc19b5 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginDescriptorTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginDescriptorTest.kt @@ -1,5 +1,5 @@ // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -@file:Suppress("UsePropertyAccessSyntax") +@file:Suppress("UsePropertyAccessSyntax", "ReplaceGetOrSet") package com.intellij.ide.plugins import com.intellij.ide.plugins.cl.PluginClassLoader @@ -41,7 +41,7 @@ private fun loadDescriptors(dir: Path, buildNumber: BuildNumber, disabledPlugins val paths: List = dir.directoryStreamIfExists { it.sorted() }!! context.use { for (file in paths) { - val descriptor = loadDescriptor(file, false, context) ?: continue + val descriptor = loadDescriptor(file, context) ?: continue context.result.add(descriptor, false) } } @@ -201,7 +201,7 @@ class PluginDescriptorTest { assertThat(foo.version).isEqualTo("2.0") assertThat(foo.pluginId.idString).isEqualTo("foo") - assertThat(pluginSet.allPlugins.toList()).map(Function { it.id }).containsOnly(foo.pluginId) + assertThat(pluginSet.allPlugins.toList()).map(Function { it.pluginId }).containsOnly(foo.pluginId) assertThat(pluginSet.findEnabledPlugin(foo.pluginId)).isSameAs(foo) } @@ -291,7 +291,7 @@ class PluginDescriptorTest { assertThat(foo.version).isEqualTo("2.0") assertThat(foo.pluginId.idString).isEqualTo("foo") - assertThat(pluginSet.allPlugins.toList()).map(Function { it.id }).containsOnly(foo.pluginId) + assertThat(pluginSet.allPlugins.toList()).map(Function { it.pluginId }).containsOnly(foo.pluginId) assertThat(pluginSet.findEnabledPlugin(foo.pluginId)).isSameAs(foo) } @@ -309,7 +309,7 @@ class PluginDescriptorTest { assertThat(foo.version).isEqualTo("1.0") assertThat(foo.pluginId.idString).isEqualTo("foo") - assertThat(pluginSet.allPlugins.toList()).map(Function { it.id }).containsOnly(foo.pluginId) + assertThat(pluginSet.allPlugins.toList()).map(Function { it.pluginId }).containsOnly(foo.pluginId) assertThat(pluginSet.findEnabledPlugin(foo.pluginId)).isSameAs(foo) } @@ -519,7 +519,7 @@ fun readDescriptorForTest(path: Path, isBundled: Boolean, input: ByteArray, id: if (id != null) { raw.id = id.idString } - val result = IdeaPluginDescriptorImpl(raw = raw, path = path, isBundled = isBundled, id = id) + val result = IdeaPluginDescriptorImpl(raw = raw, path = path, isBundled = isBundled, id = id, moduleName = null) result.readExternal( raw = raw, isSub = false, @@ -543,7 +543,7 @@ fun createFromDescriptor(path: Path, includeBase = null, readInto = null, locationSource = path.toString()) - val result = IdeaPluginDescriptorImpl(raw = raw, path = path, isBundled = isBundled, id = null) + val result = IdeaPluginDescriptorImpl(raw = raw, path = path, isBundled = isBundled, id = null, moduleName = null) result.readExternal(raw = raw, pathResolver = pathResolver, context = context, diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginManagerTest.java b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginManagerTest.java index 92d9c50012ae..de26c910264c 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginManagerTest.java +++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginManagerTest.java @@ -1,12 +1,13 @@ // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ide.plugins; -import com.intellij.idea.Bombed; import com.intellij.openapi.extensions.PluginId; import com.intellij.openapi.util.BuildNumber; +import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.io.IoTestUtil; import com.intellij.openapi.util.text.HtmlChunk; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.util.text.Strings; import com.intellij.platform.util.plugins.DataLoader; import com.intellij.platform.util.plugins.LocalFsDataLoader; import com.intellij.testFramework.PlatformTestUtil; @@ -26,17 +27,18 @@ import javax.xml.stream.XMLStreamWriter; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Calendar; import java.util.Collections; +import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.function.Function; import static com.intellij.ide.plugins.DynamicPluginsTestUtil.loadDescriptorInTest; import static com.intellij.openapi.util.io.IoTestUtil.assumeSymLinkCreationIsSupported; import static com.intellij.testFramework.assertions.Assertions.assertThat; -import static java.util.Objects.requireNonNull; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.replay; import static org.junit.Assert.*; @@ -129,11 +131,34 @@ public class PluginManagerTest { } @Test - @Bombed(user = "Vladimir Krivosheev / Andrew Kozlow", day = 1, month = Calendar.SEPTEMBER, year = 2021) public void testSimplePluginSort() throws Exception { doPluginSortTest("simplePluginSort", false); } + /** + + Actual result: + + HTTP Client (main) + Endpoints (main) + HTTP Client (intellij.restClient.microservicesUI, depends on Endpoints) + + Expected: + + Endpoints (main) + HTTP Client (main) + HTTP Client (intellij.restClient.microservicesUI, depends on Endpoints) + + But graph is correct - HTTP Client (main) it is node that doesn't depend on Endpoints (main), + so, no reason for DFSTBuilder to put it after. + + See CachingSemiGraph.getSortedPlugins for solution + */ + @Test + public void moduleSort() throws Exception { + doPluginSortTest("moduleSort", true); + } + @Test public void testUltimatePlugins() throws Exception { doPluginSortTest("ultimatePlugins", true); @@ -143,10 +168,12 @@ public class PluginManagerTest { public void testModulePluginIdContract() { Path pluginsPath = Path.of(PlatformTestUtil.getPlatformTestDataPath(), "plugins", "withModules"); IdeaPluginDescriptorImpl descriptorBundled = loadDescriptorInTest(pluginsPath, Collections.emptySet(), true); - Map idMap = PluginManagerCore.buildPluginIdMap(Collections.singletonList(descriptorBundled)); + PluginSet pluginSet = new PluginSetBuilder(Collections.singletonList(descriptorBundled)) + .computeEnabledModuleMap(null) + .createPluginSet(Collections.emptyList()); PluginId moduleId = PluginId.getId("foo.bar"); PluginId corePlugin = PluginId.getId("my.plugin"); - assertEquals(corePlugin, idMap.get(moduleId).getPluginId()); + assertThat(pluginSet.findEnabledPlugin(moduleId).getPluginId()).isEqualTo(corePlugin); } @Test @@ -197,8 +224,12 @@ public class PluginManagerTest { PluginManagerCore.getAndClearPluginLoadingErrors(); PluginManagerState loadPluginResult = loadAndInitializeDescriptors(testDataName + ".xml", isBundled); StringBuilder text = new StringBuilder(); - for (IdeaPluginDescriptorImpl descriptor : loadPluginResult.pluginSet.enabledPlugins) { - text.append(descriptor.isEnabled() ? "+ " : " ").append(descriptor.getPluginId().getIdString()).append('\n'); + for (IdeaPluginDescriptorImpl descriptor : loadPluginResult.pluginSet.getRawListOfEnabledModules()) { + text.append(descriptor.isEnabled() ? "+ " : " ").append(descriptor.getPluginId().getIdString()); + if (descriptor.moduleName != null) { + text.append(" | ").append(descriptor.moduleName); + } + text.append('\n'); } text.append("\n\n"); for (HtmlChunk html : PluginManagerCore.getAndClearPluginLoadingErrors()) { @@ -221,7 +252,7 @@ public class PluginManagerTest { expect(mock.getUntilBuild()).andReturn(untilBuild).anyTimes(); replay(mock); PluginLoadingError error = - PluginManagerCore.checkBuildNumberCompatibility(mock, requireNonNull(BuildNumber.fromString(ideVersion))); + PluginManagerCore.checkBuildNumberCompatibility(mock, Objects.requireNonNull(BuildNumber.fromString(ideVersion))); return error != null ? error.getDetailedMessage() : null; } @@ -235,6 +266,8 @@ public class PluginManagerTest { new DescriptorListLoadingContext(Collections.emptySet(), createPluginLoadingResult(true), false, false, false, false); XmlElement root = XmlDomReader.readXmlAsModel(Files.newInputStream(file)); + Ref autoGenerateModuleDescriptor = new Ref<>(false); + Map moduleMap = new HashMap<>(); PathResolver pathResolver = new PathResolver() { @Override public boolean isFlat() { @@ -257,7 +290,7 @@ public class PluginManagerTest { @Nullable RawPluginDescriptor readInto) { for (XmlElement child : root.children) { if (child.name.equals("config-file-idea-plugin")) { - String url = requireNonNull(child.getAttributeValue("url")); + String url = Objects.requireNonNull(child.getAttributeValue("url")); if (url.endsWith("/" + relativePath)) { try { return XmlReader.readModuleDescriptor(elementAsBytes(child), readContext, this, dataLoader, null, readInto, null); @@ -276,16 +309,53 @@ public class PluginManagerTest { @NotNull DataLoader dataLoader, @NotNull String path, @Nullable RawPluginDescriptor readInto) { + if (autoGenerateModuleDescriptor.get() && path.startsWith("intellij.")) { + XmlElement element = moduleMap.get(path); + if (element != null) { + try { + return XmlReader.readModuleDescriptorForTest(elementAsBytes(element)); + } + catch (XMLStreamException e) { + throw new RuntimeException(e); + } + } + + assert readInto == null; + // auto-generate empty descriptor + return XmlReader.readModuleDescriptorForTest(("") + .getBytes(StandardCharsets.UTF_8)); + } return resolvePath(readContext, dataLoader, path, readInto); } }; + for (XmlElement element : root.children) { + String moduleFile = element.attributes.get("moduleFile"); + if (moduleFile != null) { + moduleMap.put(moduleFile, element); + } + } + for (XmlElement element : root.children) { if (!element.name.equals("idea-plugin")) { continue; } - Path pluginPath = Path.of(StringUtil.trimStart(requireNonNull(element.getAttributeValue("url")), "file://")); + String url = element.getAttributeValue("url"); + Path pluginPath; + if (url == null) { + XmlElement id = element.getChild("id"); + if (id == null) { + assert element.attributes.containsKey("moduleFile"); + continue; + } + + pluginPath = Path.of(id.content.replace('.', '_') + ".xml"); + autoGenerateModuleDescriptor.set(true); + } + else { + pluginPath = Path.of(Strings.trimStart(Objects.requireNonNull(url), "file://")); + } IdeaPluginDescriptorImpl descriptor = PluginDescriptorTestKt.createFromDescriptor( pluginPath, isBundled, elementAsBytes(element), parentContext, pathResolver, new LocalFsDataLoader(pluginPath)); parentContext.result.add(descriptor, /* overrideUseIfCompatible = */ false); diff --git a/platform/platform-tests/testSrc/com/intellij/util/graph/ChunkGraphTest.java b/platform/platform-tests/testSrc/com/intellij/util/graph/ChunkGraphTest.java index 92fc4c3b0f1b..371b1efd536c 100644 --- a/platform/platform-tests/testSrc/com/intellij/util/graph/ChunkGraphTest.java +++ b/platform/platform-tests/testSrc/com/intellij/util/graph/ChunkGraphTest.java @@ -1,4 +1,4 @@ -// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.util.graph; import com.intellij.util.Chunk; @@ -7,7 +7,7 @@ import org.junit.Test; import java.util.*; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; /** * @author Eugene Zhuravlev @@ -27,17 +27,17 @@ public class ChunkGraphTest extends GraphTestCase { final List> expectedNodes = new ArrayList<>(); Chunk A = new Chunk<>("a"); expectedNodes.add(A); - Chunk BC = new Chunk<>(ContainerUtil.newHashSet("b", "c")); + Chunk BC = new Chunk<>(Set.of("b", "c")); expectedNodes.add(BC); - Chunk DE = new Chunk<>(ContainerUtil.newHashSet("d", "e")); + Chunk DE = new Chunk<>(Set.of("d", "e")); expectedNodes.add(DE); checkVertices(expectedNodes, graph.getNodes()); final Map, Set>> expectedArcs = new HashMap<>(); expectedArcs.put(A, new HashSet<>()); - expectedArcs.put(BC, ContainerUtil.newHashSet(A)); - expectedArcs.put(DE, ContainerUtil.newHashSet(BC)); + expectedArcs.put(BC, Set.of(A)); + expectedArcs.put(DE, Set.of(BC)); checkArcs(expectedArcs, graph); } @@ -53,7 +53,7 @@ public class ChunkGraphTest extends GraphTestCase { final Graph> graph = getAlgorithmsInstance().computeSCCGraph(initGraph(arcs)); final List> expectedNodes = new ArrayList<>(); - Chunk ABC = new Chunk<>(ContainerUtil.newHashSet("a", "b", "c")); + Chunk ABC = new Chunk<>(Set.of("a", "b", "c")); expectedNodes.add(ABC); Chunk D = new Chunk<>("d"); expectedNodes.add(D); @@ -62,7 +62,7 @@ public class ChunkGraphTest extends GraphTestCase { final Map, Set>> expectedArcs = new HashMap<>(); expectedArcs.put(ABC, new HashSet<>()); - expectedArcs.put(D, ContainerUtil.newHashSet(ABC)); + expectedArcs.put(D, Set.of(ABC)); checkArcs(expectedArcs, graph); } @@ -71,14 +71,14 @@ public class ChunkGraphTest extends GraphTestCase { for (Chunk chunk : graph.getNodes()) { List> ins = ContainerUtil.newArrayList(() -> graph.getIn(chunk)); Set> expectedIns = expectedArcs.get(chunk); - assertTrue(expectedIns.size() == ins.size()); - assertTrue(expectedIns.equals(new HashSet<>(ins))); + assertEquals(expectedIns.size(), ins.size()); + assertEquals(expectedIns, new HashSet<>(ins)); } } private static void checkVertices(List> expected, Iterable> nodes) { List> realNodes = ContainerUtil.newArrayList(nodes); - assertTrue(expected.size() == realNodes.size()); - assertTrue(new HashSet<>(expected).equals(new HashSet<>(realNodes))); + assertEquals(expected.size(), realNodes.size()); + assertEquals(new HashSet<>(expected), new HashSet<>(realNodes)); } } \ No newline at end of file diff --git a/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/module/ModuleManagerBridgeImpl.kt b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/module/ModuleManagerBridgeImpl.kt index 6c8d47d6f7bc..7db48ea52e98 100644 --- a/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/module/ModuleManagerBridgeImpl.kt +++ b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/module/ModuleManagerBridgeImpl.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.workspaceModel.ide.impl.legacyBridge.module import com.intellij.ProjectTopics @@ -85,8 +85,7 @@ abstract class ModuleManagerBridgeImpl(private val project: Project) : ModuleMan val (unloadedEntities, loadedEntities) = entities.partition { it.name in unloadedModuleNames } LOG.debug { "Loading modules for ${loadedEntities.size} entities" } - val plugins = PluginManagerCore.getLoadedPlugins(null) - val precomputedExtensionModel = precomputeExtensionModel(plugins) + val precomputedExtensionModel = precomputeExtensionModel() val tasks = loadedEntities.map { moduleEntity -> ForkJoinTask.adapt(Callable { @@ -278,7 +277,7 @@ abstract class ModuleManagerBridgeImpl(private val project: Project) : ModuleMan fun createModuleInstance(moduleEntity: ModuleEntity, versionedStorage: VersionedEntityStorage, diff: WorkspaceEntityStorageDiffBuilder?, isNew: Boolean, precomputedExtensionModel: PrecomputedExtensionModel?): ModuleBridge { - val plugins = PluginManagerCore.getLoadedPlugins(null) + val plugins = PluginManagerCore.getPluginSet().getEnabledModules() val corePlugin = plugins.find { it.pluginId == PluginManagerCore.CORE_ID } val moduleFileUrl = getModuleVirtualFileUrl(moduleEntity) @@ -289,7 +288,7 @@ abstract class ModuleManagerBridgeImpl(private val project: Project) : ModuleMan diff = diff) module.registerComponents(corePlugin = corePlugin, - plugins = plugins, + modules = plugins, app = ApplicationManager.getApplication(), precomputedExtensionModel = precomputedExtensionModel, listenerCallbacks = null) diff --git a/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/legacyBridge/ModuleBridge.kt b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/legacyBridge/ModuleBridge.kt index 0a03cb9738fc..59cae805bafb 100644 --- a/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/legacyBridge/ModuleBridge.kt +++ b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/legacyBridge/ModuleBridge.kt @@ -1,3 +1,4 @@ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.workspaceModel.ide.legacyBridge import com.intellij.ide.plugins.IdeaPluginDescriptor @@ -9,7 +10,9 @@ import com.intellij.workspaceModel.storage.VersionedEntityStorage import com.intellij.workspaceModel.storage.WorkspaceEntityStorageDiffBuilder import com.intellij.workspaceModel.storage.bridgeEntities.ModuleId import com.intellij.workspaceModel.storage.url.VirtualFileUrl +import org.jetbrains.annotations.ApiStatus +@ApiStatus.Internal interface ModuleBridge : ModuleEx { val moduleEntityId: ModuleId @@ -28,8 +31,11 @@ interface ModuleBridge : ModuleEx { fun rename(newName: String, newModuleFileUrl: VirtualFileUrl?, notifyStorage: Boolean) - fun registerComponents(corePlugin: IdeaPluginDescriptor?, plugins: List, - precomputedExtensionModel: PrecomputedExtensionModel?, app: Application?, listenerCallbacks: List?) + fun registerComponents(corePlugin: IdeaPluginDescriptor?, + modules: Sequence, + precomputedExtensionModel: PrecomputedExtensionModel?, + app: Application?, + listenerCallbacks: List?) fun callCreateComponents() } diff --git a/platform/service-container/src/com/intellij/serviceContainer/ComponentManagerImpl.kt b/platform/service-container/src/com/intellij/serviceContainer/ComponentManagerImpl.kt index 3ebedf8d9850..f1d761d1f73e 100644 --- a/platform/service-container/src/com/intellij/serviceContainer/ComponentManagerImpl.kt +++ b/platform/service-container/src/com/intellij/serviceContainer/ComponentManagerImpl.kt @@ -213,20 +213,27 @@ abstract class ComponentManagerImpl @JvmOverloads constructor( indicator.fraction = getPercentageOfComponentsLoaded() } - @Internal fun getPercentageOfComponentsLoaded(): Double { return instantiatedComponentCount.toDouble() / componentConfigCount } - override fun getExtensionArea(): ExtensionsAreaImpl { + final override fun getExtensionArea(): ExtensionsAreaImpl { if (!isExtensionSupported) { error("Extensions aren't supported") } return _extensionArea } + // convenient method - use only in a not important code + fun registerComponents() { + registerComponents(modules = PluginManagerCore.getPluginSet().getEnabledModules(), + app = getApplication(), + precomputedExtensionModel = null, + listenerCallbacks = null) + } + @Internal - open fun registerComponents(plugins: List, + open fun registerComponents(modules: Sequence, app: Application?, precomputedExtensionModel: PrecomputedExtensionModel?, listenerCallbacks: List?) { @@ -242,20 +249,12 @@ abstract class ComponentManagerImpl @JvmOverloads constructor( // register services before registering extensions because plugins can access services in their // extensions which can be invoked right away if the plugin is loaded dynamically val extensionPoints = if (precomputedExtensionModel == null) HashMap(extensionArea.extensionPoints) else null - for (mainPlugin in plugins) { + for (rootModule in modules) { + executeRegisterTask(rootModule) { module -> + val containerDescriptor = getContainerDescriptor(module) + registerServices(containerDescriptor.services, module) + newComponentConfigCount += registerComponents(module, containerDescriptor, isHeadless) - executeRegisterTask(mainPlugin) { pluginDescriptor -> - val containerDescriptor = getContainerDescriptor(pluginDescriptor) - registerServices(containerDescriptor.services, pluginDescriptor) - } - - executeRegisterTask(mainPlugin) { pluginDescriptor -> - val containerDescriptor = getContainerDescriptor(pluginDescriptor) - newComponentConfigCount += registerComponents(pluginDescriptor, containerDescriptor, isHeadless) - } - - executeRegisterTask(mainPlugin) { pluginDescriptor -> - val containerDescriptor = getContainerDescriptor(pluginDescriptor) containerDescriptor.listeners?.let { listeners -> var m = map if (m == null) { @@ -271,17 +270,14 @@ abstract class ComponentManagerImpl @JvmOverloads constructor( continue } - listener.pluginDescriptor = pluginDescriptor + listener.pluginDescriptor = module m.computeIfAbsent(listener.topicClassName) { ArrayList() }.add(listener) } } - } - if (extensionPoints != null) { - executeRegisterTask(mainPlugin) { pluginDescriptor -> - val containerDescriptor = getContainerDescriptor(pluginDescriptor) + if (extensionPoints != null) { containerDescriptor.extensionPoints?.let { - ExtensionsAreaImpl.createExtensionPoints(it, this, extensionPoints, pluginDescriptor) + ExtensionsAreaImpl.createExtensionPoints(it, this, extensionPoints, module) } } } @@ -295,10 +291,9 @@ abstract class ComponentManagerImpl @JvmOverloads constructor( val immutableExtensionPoints = if (extensionPoints!!.isEmpty()) Collections.emptyMap() else java.util.Map.copyOf(extensionPoints) extensionArea.setPoints(immutableExtensionPoints) - for (mainPlugin in plugins) { - executeRegisterTask(mainPlugin) { pluginDescriptor -> - val containerDescriptor = getContainerDescriptor(pluginDescriptor) - pluginDescriptor.registerExtensions(immutableExtensionPoints, containerDescriptor, listenerCallbacks) + for (rootModule in modules) { + executeRegisterTask(rootModule) { module -> + module.registerExtensions(immutableExtensionPoints, getContainerDescriptor(module), listenerCallbacks) } } } @@ -979,12 +974,12 @@ abstract class ComponentManagerImpl @JvmOverloads constructor( open fun activityNamePrefix(): String? = null @ApiStatus.Internal - open fun preloadServices(plugins: List, - activityPrefix: String, - onlyIfAwait: Boolean = false): Pair, CompletableFuture> { + open fun preloadServices(modules: Sequence, + activityPrefix: String, + onlyIfAwait: Boolean = false): Pair, CompletableFuture> { val asyncPreloadedServices = mutableListOf>() val syncPreloadedServices = mutableListOf>() - for (plugin in plugins) { + for (plugin in modules) { serviceLoop@ for (service in getContainerDescriptor(plugin).services) { if (!isServiceSuitable(service) || service.os != null && !isSuitableForOs(service.os)) { continue@serviceLoop @@ -1469,4 +1464,10 @@ private class LightServiceComponentAdapter(private val initializedInstance: Any) override fun getComponentInstance(container: PicoContainer?) = initializedInstance override fun toString() = componentKey +} + +private inline fun executeRegisterTask(mainPluginDescriptor: IdeaPluginDescriptorImpl, + crossinline task: (IdeaPluginDescriptorImpl) -> Unit) { + task(mainPluginDescriptor) + executeRegisterTaskForOldContent(mainPluginDescriptor, task) } \ No newline at end of file diff --git a/platform/service-container/src/com/intellij/serviceContainer/containerUtil.kt b/platform/service-container/src/com/intellij/serviceContainer/containerUtil.kt index 7254dcfead25..1051fbb91941 100644 --- a/platform/service-container/src/com/intellij/serviceContainer/containerUtil.kt +++ b/platform/service-container/src/com/intellij/serviceContainer/containerUtil.kt @@ -2,7 +2,6 @@ @file:ApiStatus.Internal package com.intellij.serviceContainer -import com.intellij.ide.plugins.IdeaPluginDescriptorImpl import com.intellij.ide.plugins.PluginManagerCore import com.intellij.openapi.components.Service import com.intellij.openapi.extensions.PluginDescriptor @@ -25,14 +24,6 @@ internal fun checkCanceledIfNotInClassInit() { } } -inline fun executeRegisterTask(mainPluginDescriptor: IdeaPluginDescriptorImpl, - crossinline task: (IdeaPluginDescriptorImpl) -> Unit) { - task(mainPluginDescriptor) - executeRegisterTaskForContent(mainPluginDescriptor) { - task(it) - } -} - internal fun isGettingServiceAllowedDuringPluginUnloading(descriptor: PluginDescriptor): Boolean { return descriptor.isRequireRestart || descriptor.pluginId == PluginManagerCore.CORE_ID || descriptor.pluginId == PluginManagerCore.JAVA_PLUGIN_ID diff --git a/platform/testFramework/src/com/intellij/simpleApplicationBootstrapper.kt b/platform/testFramework/src/com/intellij/simpleApplicationBootstrapper.kt index 256fab82a474..7bca129693f2 100644 --- a/platform/testFramework/src/com/intellij/simpleApplicationBootstrapper.kt +++ b/platform/testFramework/src/com/intellij/simpleApplicationBootstrapper.kt @@ -4,8 +4,8 @@ package com.intellij import com.intellij.concurrency.IdeaForkJoinWorkerThreadFactory import com.intellij.diagnostic.ThreadDumper import com.intellij.ide.IdeEventQueue -import com.intellij.ide.plugins.IdeaPluginDescriptorImpl import com.intellij.ide.plugins.PluginManagerCore +import com.intellij.ide.plugins.PluginSet import com.intellij.idea.Main import com.intellij.idea.callAppInitialized import com.intellij.idea.initConfigurationStore @@ -19,8 +19,10 @@ import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS import com.intellij.openapi.vfs.newvfs.persistent.PersistentFSImpl import com.intellij.util.SystemProperties import java.awt.EventQueue -import java.util.concurrent.* -import java.util.function.Supplier +import java.util.concurrent.ExecutionException +import java.util.concurrent.ForkJoinTask +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException fun loadHeadlessAppInUnitTestMode() { doLoadApp { @@ -43,9 +45,8 @@ internal fun doLoadApp(setupEventQueue: () -> Unit) { PluginManagerCore.isUnitTestMode = true IdeaForkJoinWorkerThreadFactory.setupForkJoinCommonPool(true) - val loadedPluginFuture = CompletableFuture.supplyAsync(Supplier { - PluginManagerCore.getLoadedPlugins(PathManager::class.java.classLoader) - }, ForkJoinPool.commonPool()) + PluginManagerCore.scheduleDescriptorLoading() + val loadedModuleFuture = PluginManagerCore.initPlugins(PathManager::class.java.classLoader) setupEventQueue() @@ -55,15 +56,15 @@ internal fun doLoadApp(setupEventQueue: () -> Unit) { RecursionManager.assertOnMissedCache(app) } - val plugins: List + val pluginSet: PluginSet try { // 40 seconds - tests maybe executed on cloud agents where IO speed is a very slow - plugins = loadedPluginFuture.get(40, TimeUnit.SECONDS) - app.registerComponents(plugins, app, null, null) + pluginSet = loadedModuleFuture.get(40, TimeUnit.SECONDS) + app.registerComponents(modules = pluginSet.getEnabledModules(), app = app, precomputedExtensionModel = null, listenerCallbacks = null) initConfigurationStore(app) RegistryKeyBean.addKeysFromPlugins() Registry.markAsLoaded() - val preloadServiceFuture = preloadServices(plugins, app, activityPrefix = "") + val preloadServiceFuture = preloadServices(pluginSet.getEnabledModules(), app, activityPrefix = "") app.loadComponents(null) preloadServiceFuture.get(40, TimeUnit.SECONDS) diff --git a/platform/testFramework/src/com/intellij/testFramework/LightPlatformCodeInsightTestCase.java b/platform/testFramework/src/com/intellij/testFramework/LightPlatformCodeInsightTestCase.java index 34e99cf052f1..5dee45b2ce54 100644 --- a/platform/testFramework/src/com/intellij/testFramework/LightPlatformCodeInsightTestCase.java +++ b/platform/testFramework/src/com/intellij/testFramework/LightPlatformCodeInsightTestCase.java @@ -1,4 +1,4 @@ -// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.testFramework; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; @@ -291,10 +291,14 @@ public abstract class LightPlatformCodeInsightTestCase extends LightPlatformTest @Override protected void tearDown() throws Exception { try { - getIndexingMode().tearDownTest(getProject()); - FileEditorManager editorManager = FileEditorManager.getInstance(getProject()); - for (VirtualFile openFile : editorManager.getOpenFiles()) { - editorManager.closeFile(openFile); + Project project = getProject(); + if (myIndexingMode != null && project != null) { + myIndexingMode.tearDownTest(project); + + FileEditorManager editorManager = FileEditorManager.getInstance(project); + for (VirtualFile openFile : editorManager.getOpenFiles()) { + editorManager.closeFile(openFile); + } } deleteVFile(); myEditor = null; diff --git a/platform/util-class-loader/src/com/intellij/util/lang/Java11Shim.java b/platform/util-class-loader/src/com/intellij/util/lang/Java11Shim.java index 63e58de966d5..de04cffc0eaf 100644 --- a/platform/util-class-loader/src/com/intellij/util/lang/Java11Shim.java +++ b/platform/util-class-loader/src/com/intellij/util/lang/Java11Shim.java @@ -24,21 +24,14 @@ public abstract class Java11Shim { } @Override - public @NotNull List copyOf(List collection) { + public @NotNull List copyOfCollection(Collection collection) { return Collections.unmodifiableList(new ArrayList<>(collection)); } - - @Override - public @NotNull List listOf(E[] collection) { - return Arrays.asList(collection); - } }; public abstract <@NotNull K, @NotNull V> Map copyOf(Map map); public abstract <@NotNull E> @NotNull Set copyOf(Set collection); - public abstract <@NotNull E> @NotNull List copyOf(List collection); - - public abstract <@NotNull E> @NotNull List listOf(E[] collection); + public abstract <@NotNull E> @NotNull List copyOfCollection(Collection collection); } diff --git a/platform/util-class-loader/src/com/intellij/util/lang/UrlClassLoader.java b/platform/util-class-loader/src/com/intellij/util/lang/UrlClassLoader.java index 431f7d9e28c1..ca2cb6d06b4e 100644 --- a/platform/util-class-loader/src/com/intellij/util/lang/UrlClassLoader.java +++ b/platform/util-class-loader/src/com/intellij/util/lang/UrlClassLoader.java @@ -33,6 +33,7 @@ import java.util.function.Predicate; */ public class UrlClassLoader extends ClassLoader implements ClassPath.ClassDataConsumer { private static final boolean isParallelCapable = registerAsParallelCapable(); + private static final ClassLoader appClassLoader = UrlClassLoader.class.getClassLoader(); private static final ThreadLocal skipFindingResource = new ThreadLocal<>(); @@ -216,6 +217,10 @@ public class UrlClassLoader extends ClassLoader implements ClassPath.ClassDataCo @Override protected Class findClass(@NotNull String name) throws ClassNotFoundException { + if (name.startsWith("com.intellij.util.lang.")) { + return appClassLoader.loadClass(name); + } + Class clazz; try { clazz = classPath.findClass(name, classDataConsumer); @@ -361,7 +366,6 @@ public class UrlClassLoader extends ClassLoader implements ClassPath.ClassDataCo } @ApiStatus.Internal - @ApiStatus.Experimental public @Nullable BiPredicate resolveScopeManager; public @Nullable Class loadClassInsideSelf(@NotNull String name, boolean forceLoadFromSubPluginClassloader) throws IOException { @@ -425,7 +429,7 @@ public class UrlClassLoader extends ClassLoader implements ClassPath.ClassDataCo } } - // trying to speedup the common case when there are no "//" or "/." + // trying to speed up the common case when there are no "//" or "/." int index = -1; do { index = path.indexOf('/', index + 1); diff --git a/platform/util-class-loader/src/com/intellij/util/lang/ZipFilePool.java b/platform/util-class-loader/src/com/intellij/util/lang/ZipFilePool.java index c8a712933edf..d172312c7c02 100644 --- a/platform/util-class-loader/src/com/intellij/util/lang/ZipFilePool.java +++ b/platform/util-class-loader/src/com/intellij/util/lang/ZipFilePool.java @@ -1,4 +1,4 @@ -// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.util.lang; import org.jetbrains.annotations.ApiStatus; @@ -6,6 +6,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Path; // API and placement of classes are complicated due to java 8 requirement @@ -19,6 +20,6 @@ public abstract class ZipFilePool { public abstract @NotNull Object loadZipFile(@NotNull Path file) throws IOException; public interface EntryResolver { - byte @Nullable [] loadZipEntry(@NotNull String path) throws IOException; + @Nullable InputStream loadZipEntry(@NotNull String path) throws IOException; } } diff --git a/platform/util/src/com/intellij/platform/util/plugins/dataLoader.kt b/platform/util/src/com/intellij/platform/util/plugins/dataLoader.kt index f4b0ef7f214b..280a06bec016 100644 --- a/platform/util/src/com/intellij/platform/util/plugins/dataLoader.kt +++ b/platform/util/src/com/intellij/platform/util/plugins/dataLoader.kt @@ -1,9 +1,9 @@ -// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.platform.util.plugins import com.intellij.util.lang.ZipFilePool import org.jetbrains.annotations.ApiStatus -import java.io.IOException +import java.io.InputStream import java.nio.file.Files import java.nio.file.NoSuchFileException import java.nio.file.Path @@ -14,8 +14,7 @@ interface DataLoader { fun isExcludedFromSubSearch(jarFile: Path): Boolean = false - @Throws(IOException::class) - fun load(path: String): ByteArray? + fun load(path: String): InputStream? override fun toString(): String } @@ -25,9 +24,9 @@ class LocalFsDataLoader(val basePath: Path) : DataLoader { override val pool: ZipFilePool? get() = ZipFilePool.POOL - override fun load(path: String): ByteArray? { + override fun load(path: String): InputStream? { return try { - Files.readAllBytes(basePath.resolve(path)) + Files.newInputStream(basePath.resolve(path)) } catch (e: NoSuchFileException) { null diff --git a/platform/util/src/com/intellij/util/graph/DFSTBuilder.java b/platform/util/src/com/intellij/util/graph/DFSTBuilder.java index 766a07b9631c..f7a6d63409e1 100644 --- a/platform/util/src/com/intellij/util/graph/DFSTBuilder.java +++ b/platform/util/src/com/intellij/util/graph/DFSTBuilder.java @@ -1,33 +1,42 @@ -// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.util.graph; import com.intellij.util.ArrayUtil; +import com.intellij.util.ArrayUtilRt; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntStack; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.function.ObjIntConsumer; +import java.util.function.ToIntFunction; /** * @author dsl, ven */ public final class DFSTBuilder { - private final OutboundSemiGraph myGraph; - private final Object2IntMap myNodeToNNumber; // node -> node number in topological order [0..size). Independent nodes are in reversed loading order (loading order is the graph.getNodes() order) + private final DFSTBuilderAwareGraph graphAdapter; + + @SuppressWarnings("GrazieInspection") + private final ToIntFunction myNodeToNNumber; // node -> node number in topological order [0..size). Independent nodes are in reversed loading order (loading order is the graph.getNodes() order) + @SuppressWarnings("GrazieInspection") private final Node[] myInvN; // node number in topological order [0..size) -> node private Map.Entry myBackEdge; + private final Node[] allNodes; + private Comparator myNComparator; private Comparator myTComparator; private final IntList mySCCs = new IntArrayList(); // strongly connected component sizes - private final Object2IntMap myNodeToTNumber = new Object2IntOpenHashMap<>(); // node -> number in scc topological order. Independent scc are in reversed loading order + private final ToIntFunction myNodeToTNumber; // node -> number in scc topological order. Independent scc are in reversed loading order private final Node[] myInvT; // number in (enumerate all nodes scc by scc) order -> node - private final Node[] myAllNodes; /** * @see DFSTBuilder#DFSTBuilder(OutboundSemiGraph, Object) @@ -47,31 +56,93 @@ public final class DFSTBuilder { * @see DFSTBuilder#DFSTBuilder(OutboundSemiGraph, Object) */ public DFSTBuilder(@NotNull OutboundSemiGraph graph) { - this(graph, null); + this(graph, null, false); } - /** - * @param entryNode is a first node for Tarjan's algorithm. Different entry nodes produce different node numbers in topological ordering. - * if all nodes of the graph is reachable from the entry node and the entry node doesn't have incoming edges then - * passing the entry node could be used for finding "natural" back edges (like a loop back edge) - */ public DFSTBuilder(@NotNull OutboundSemiGraph graph, @Nullable Node entryNode) { + this(graph, entryNode, false); + } + + @ApiStatus.Internal + public DFSTBuilder(@NotNull OutboundSemiGraph graph, @Nullable Node entryNode, boolean useIdentityStrategy) { //noinspection unchecked - myAllNodes = (Node[])graph.getNodes().toArray(); + this((Node[])graph.getNodes().toArray(), entryNode, useIdentityStrategy, new DFSTBuilderAwareGraph() { + @Override + public int[] buildOuts(@NotNull ToIntFunction nodeIndex, @NotNull Node node) { + IntList list = new IntArrayList(); + Iterator out = graph.getOut(node); + while (out.hasNext()) { + list.add(nodeIndex.applyAsInt(out.next())); + } + return list.isEmpty() ? ArrayUtilRt.EMPTY_INT_ARRAY : list.toIntArray(); + } + }); + } + + @ApiStatus.Internal + public DFSTBuilder(Node[] allNodes, + @Nullable Node entryNode, + boolean useIdentityStrategy, + @Nullable DFSTBuilderAwareGraph graphAdapter) { + this.allNodes = allNodes; if (entryNode != null) { - int index = ArrayUtil.indexOf(myAllNodes, entryNode); + int index = useIdentityStrategy ? ArrayUtil.indexOfIdentity(allNodes, entryNode) : ArrayUtil.indexOf(allNodes, entryNode); if (index != -1) { - ArrayUtil.swap(myAllNodes, 0, index); + ArrayUtil.swap(allNodes, 0, index); } } - myGraph = graph; - int size = graph.getNodes().size(); - myNodeToNNumber = new Object2IntOpenHashMap<>(size * 2, 0.5f); + int size = allNodes.length; //noinspection unchecked myInvN = (Node[])new Object[size]; //noinspection unchecked myInvT = (Node[])new Object[size]; - new Tarjan().build(); + + this.graphAdapter = graphAdapter; + + if (useIdentityStrategy) { + Reference2IntOpenHashMap nMap = new Reference2IntOpenHashMap<>(size * 2, 0.5f); + Reference2IntOpenHashMap tMap = new Reference2IntOpenHashMap<>(); + myNodeToNNumber = nMap; + myNodeToTNumber = tMap; + new Tarjan(tMap::put, nMap::put, allNodes, true); + } + else { + //noinspection SSBasedInspection + Object2IntOpenHashMap nMap = new Object2IntOpenHashMap<>(size * 2, 0.5f); + //noinspection SSBasedInspection + Object2IntOpenHashMap tMap = new Object2IntOpenHashMap<>(); + myNodeToNNumber = nMap; + myNodeToTNumber = tMap; + new Tarjan(tMap::put, nMap::put, allNodes, false); + } + } + + @ApiStatus.Internal + public interface DFSTBuilderAwareGraph { + int[] buildOuts(@NotNull ToIntFunction nodeIndex, @NotNull Node node); + } + + private static final class TarjanFrame { + private final int nodeI; + private final Node[] allNodes; + private final int[] out; + int nextUnexploredIndex; + + TarjanFrame(int nodeI, Node[] allNodes, int[] out) { + this.nodeI = nodeI; + this.allNodes = allNodes; + this.out = out; + } + + @Override + public String toString() { + StringBuilder o = new StringBuilder(); + o.append(allNodes[nodeI]).append(" -> ["); + for (int id : out) { + o.append(allNodes[id]).append(", "); + } + return o.append(']').toString(); + } } /** @@ -90,54 +161,43 @@ public final class DFSTBuilder { private final IntStack nodesOnStack = new IntArrayList(); private final boolean[] isOnStack = new boolean[index.length]; - private final class Frame { - Frame(int nodeI) { - this.nodeI = nodeI; - Iterator outNodes = myGraph.getOut(myAllNodes[nodeI]); - IntList list = new IntArrayList(); - while (outNodes.hasNext()) { - Node node = outNodes.next(); - list.add(nodeIndex.getInt(node)); - } - out = list.toIntArray(); - } - - private final int nodeI; - private final int[] out; - private int nextUnexploredIndex; - - @Override - public String toString() { - StringBuilder o = new StringBuilder(); - o.append(myAllNodes[nodeI]).append(" -> ["); - for (int id : out) { - o.append(myAllNodes[id]).append(", "); - } - return o.append(']').toString(); - } - } - - private final Deque frames = new ArrayDeque<>(); // recursion stack - private final Object2IntMap nodeIndex = new Object2IntOpenHashMap<>(); + private final Deque> frames = new ArrayDeque<>(); // recursion stack private int dfsIndex; private int sccsSizeCombined; private final IntList topo = new IntArrayList(index.length); // nodes in reverse topological order + private ToIntFunction getNodeIndex; - private void build() { + private Tarjan(ObjIntConsumer putTNumber, ObjIntConsumer putNNumber, Node[] allNodes, boolean useIdentityStrategy) { + if (useIdentityStrategy) { + Reference2IntOpenHashMap nodeIndex = new Reference2IntOpenHashMap<>(allNodes.length); + build(putTNumber, putNNumber, allNodes, nodeIndex::put, nodeIndex); + } + else { + Object2IntMap nodeIndex = new Object2IntOpenHashMap<>(allNodes.length); + build(putTNumber, putNNumber, allNodes, nodeIndex::put, nodeIndex); + } + } + + private void build(ObjIntConsumer putTNumber, + ObjIntConsumer putNNumber, + Node[] allNodes, + @NotNull ObjIntConsumer putNodeIndex, + @NotNull ToIntFunction getNodeIndex) { + this.getNodeIndex = getNodeIndex; Arrays.fill(index, -1); - for (int i = 0; i < myAllNodes.length; i++) { - Node node = myAllNodes[i]; - nodeIndex.put(node, i); + for (int i = 0; i < allNodes.length; i++) { + Node node = allNodes[i]; + putNodeIndex.accept(node, i); } for (int i = 0; i < index.length; i++) { if (index[i] != -1) { continue; } - frames.addLast(new Frame(i)); + frames.addLast(new TarjanFrame<>(i, allNodes, graphAdapter.buildOuts(getNodeIndex, allNodes[i]))); List> sccs = new ArrayList<>(); - strongConnect(sccs); + strongConnect(sccs, allNodes); for (List scc : sccs) { int sccSize = scc.size(); @@ -146,7 +206,7 @@ public final class DFSTBuilder { int sccBase = index.length - sccsSizeCombined - sccSize; // root node should be first in scc for some reason - Node rootNode = myAllNodes[i]; + Node rootNode = allNodes[i]; int rIndex = scc.indexOf(rootNode); if (rIndex != -1) { Node e1 = scc.get(rIndex); @@ -159,7 +219,7 @@ public final class DFSTBuilder { Node sccNode = scc.get(j); int tIndex = sccBase + j; myInvT[tIndex] = sccNode; - myNodeToTNumber.put(sccNode, tIndex); + putTNumber.accept(sccNode, tIndex); } sccsSizeCombined += sccSize; } @@ -167,9 +227,9 @@ public final class DFSTBuilder { for (int i = 0; i < topo.size(); i++) { int nodeI = topo.getInt(i); - Node node = myAllNodes[nodeI]; + Node node = allNodes[nodeI]; - myNodeToNNumber.put(node, index.length - 1 - i); + putNNumber.accept(node, index.length - 1 - i); myInvN[index.length - 1 - i] = node; } @@ -181,11 +241,11 @@ public final class DFSTBuilder { } } - private void strongConnect(@NotNull List> sccs) { + private void strongConnect(@NotNull List> sccs, Node[] allNodes) { int successor = -1; nextNode: while (!frames.isEmpty()) { - Frame pair = frames.peekLast(); + TarjanFrame pair = frames.peekLast(); int i = pair.nodeI; // we have returned to the node @@ -206,14 +266,14 @@ public final class DFSTBuilder { while (pair.nextUnexploredIndex < pair.out.length) { int nextI = pair.out[pair.nextUnexploredIndex++]; if (index[nextI] == -1) { - frames.addLast(new Frame(nextI)); + frames.addLast(new TarjanFrame<>(nextI, allNodes, graphAdapter.buildOuts(getNodeIndex, allNodes[nextI]))); continue nextNode; } if (isOnStack[nextI]) { lowLink[i] = Math.min(lowLink[i], index[nextI]); if (myBackEdge == null) { - myBackEdge = new AbstractMap.SimpleImmutableEntry<>(myAllNodes[nextI], myAllNodes[i]); + myBackEdge = new AbstractMap.SimpleImmutableEntry<>(allNodes[nextI], allNodes[i]); } } } @@ -226,7 +286,7 @@ public final class DFSTBuilder { int pushedI; do { pushedI = nodesOnStack.popInt(); - Node pushed = myAllNodes[pushedI]; + Node pushed = allNodes[pushedI]; isOnStack[pushedI] = false; scc.add(pushed); } @@ -250,13 +310,13 @@ public final class DFSTBuilder { public Comparator comparator(boolean useNNumber) { if (useNNumber) { if (myNComparator == null) { - myNComparator = Comparator.comparingInt(myNodeToNNumber::getInt); + myNComparator = Comparator.comparingInt(myNodeToNNumber); } return myNComparator; } else { if (myTComparator == null) { - myTComparator = Comparator.comparingInt(myNodeToTNumber::getInt); + myTComparator = Comparator.comparingInt(myNodeToTNumber); } return myTComparator; } @@ -373,10 +433,9 @@ public final class DFSTBuilder { } } - @NotNull - public List getSortedNodes() { - List result = new ArrayList<>(myGraph.getNodes()); - result.sort(comparator()); - return result; + public @NotNull List getSortedNodes() { + Node[] result = allNodes.clone(); + Arrays.sort(result, comparator()); + return Arrays.asList(result); } } \ No newline at end of file diff --git a/platform/util/src/com/intellij/util/graph/OutboundSemiGraph.java b/platform/util/src/com/intellij/util/graph/OutboundSemiGraph.java index 630a59f349ea..706f1ff357fd 100644 --- a/platform/util/src/com/intellij/util/graph/OutboundSemiGraph.java +++ b/platform/util/src/com/intellij/util/graph/OutboundSemiGraph.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.util.graph; import org.jetbrains.annotations.NotNull; @@ -21,9 +7,7 @@ import java.util.Collection; import java.util.Iterator; public interface OutboundSemiGraph { - @NotNull - Collection getNodes(); + @NotNull Collection getNodes(); - @NotNull - Iterator getOut(Node n); + @NotNull Iterator getOut(Node n); } \ No newline at end of file diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/performance/TailRecursionInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/performance/TailRecursionInspection.java index 9d1c30c1e879..a7a1c3e8345a 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/performance/TailRecursionInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/performance/TailRecursionInspection.java @@ -36,8 +36,7 @@ import org.jetbrains.annotations.Nullable; import java.util.*; -public class TailRecursionInspection extends BaseInspection { - +public final class TailRecursionInspection extends BaseInspection { @Override @NotNull protected String buildErrorString(Object... infos) { @@ -67,8 +66,7 @@ public class TailRecursionInspection extends BaseInspection { return true; } - private static class RemoveTailRecursionFix extends InspectionGadgetsFix { - + private static final class RemoveTailRecursionFix extends InspectionGadgetsFix { @Override @NotNull public String getFamilyName() { diff --git a/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/editor/ResourceBundlePropertiesUpdateManager.java b/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/editor/ResourceBundlePropertiesUpdateManager.java index 495e5c0cbadb..ccd35c886179 100644 --- a/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/editor/ResourceBundlePropertiesUpdateManager.java +++ b/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/editor/ResourceBundlePropertiesUpdateManager.java @@ -1,4 +1,4 @@ -// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.lang.properties.editor; import com.intellij.lang.properties.IProperty; @@ -186,16 +186,16 @@ public final class ResourceBundlePropertiesUpdateManager { } })); DFSTBuilder dfstBuilder = new DFSTBuilder<>(generator); - final boolean acyclic = dfstBuilder.isAcyclic(); + boolean acyclic = dfstBuilder.isAcyclic(); if (acyclic) { + List sortedNodes = new ArrayList<>(generator.getNodes()); if (isAlphaSorted[0]) { - final List sortedNodes = new ArrayList<>(generator.getNodes()); sortedNodes.sort(String.CASE_INSENSITIVE_ORDER); - return Pair.create(sortedNodes, true); - } else { - final List dfsNodes = dfstBuilder.getSortedNodes(); - Collections.reverse(dfsNodes); - return Pair.create(dfsNodes, false); + return new Pair<>(sortedNodes, true); + } + else { + sortedNodes.sort(dfstBuilder.comparator().reversed()); + return new Pair<>(sortedNodes, false); } } else {