DumbModeTask - replace deprecated usages
[idea/community.git] / platform / core-impl / src / com / intellij / ide / plugins / PluginManagerCore.java
1 // 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.
2 package com.intellij.ide.plugins;
3
4 import com.intellij.diagnostic.Activity;
5 import com.intellij.diagnostic.LoadingState;
6 import com.intellij.diagnostic.PluginException;
7 import com.intellij.diagnostic.StartUpMeasurer;
8 import com.intellij.ide.plugins.cl.PluginClassLoader;
9 import com.intellij.openapi.application.Application;
10 import com.intellij.openapi.application.ApplicationManager;
11 import com.intellij.openapi.application.PathManager;
12 import com.intellij.openapi.application.impl.ApplicationInfoImpl;
13 import com.intellij.openapi.diagnostic.Logger;
14 import com.intellij.openapi.extensions.ExtensionInstantiationException;
15 import com.intellij.openapi.extensions.ExtensionsArea;
16 import com.intellij.openapi.extensions.PluginDescriptor;
17 import com.intellij.openapi.extensions.PluginId;
18 import com.intellij.openapi.extensions.impl.ExtensionPointImpl;
19 import com.intellij.openapi.extensions.impl.ExtensionsAreaImpl;
20 import com.intellij.openapi.project.Project;
21 import com.intellij.openapi.util.BuildNumber;
22 import com.intellij.openapi.util.io.FileUtilRt;
23 import com.intellij.openapi.util.text.StringUtil;
24 import com.intellij.reference.SoftReference;
25 import com.intellij.util.*;
26 import com.intellij.util.concurrency.AppExecutorUtil;
27 import com.intellij.util.containers.ContainerUtil;
28 import com.intellij.util.execution.ParametersListUtil;
29 import com.intellij.util.graph.DFSTBuilder;
30 import com.intellij.util.graph.GraphGenerator;
31 import com.intellij.util.lang.UrlClassLoader;
32 import org.jetbrains.annotations.ApiStatus;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
35 import org.jetbrains.annotations.TestOnly;
36
37 import java.io.*;
38 import java.lang.invoke.MethodHandle;
39 import java.lang.invoke.MethodHandles;
40 import java.lang.invoke.MethodType;
41 import java.lang.ref.Reference;
42 import java.net.MalformedURLException;
43 import java.net.URL;
44 import java.nio.charset.StandardCharsets;
45 import java.nio.file.*;
46 import java.util.*;
47 import java.util.concurrent.CompletableFuture;
48 import java.util.concurrent.CompletionStage;
49 import java.util.concurrent.ExecutionException;
50 import java.util.function.BiFunction;
51 import java.util.function.Function;
52 import java.util.stream.Collectors;
53 import java.util.stream.Stream;
54
55 // Prefer to use only JDK classes. Any post start-up functionality should be placed in PluginManager class.
56 public final class PluginManagerCore {
57   public static final String META_INF = "META-INF/";
58   public static final String IDEA_IS_INTERNAL_PROPERTY = "idea.is.internal";
59
60   public static final PluginId CORE_ID = PluginId.getId("com.intellij");
61   public static final String CORE_PLUGIN_ID = "com.intellij";
62
63   public static final PluginId JAVA_PLUGIN_ID = PluginId.getId("com.intellij.java");
64   private static final PluginId JAVA_MODULE_ID = PluginId.getId("com.intellij.modules.java");
65
66   public static final String PLUGIN_XML = "plugin.xml";
67   public static final String PLUGIN_XML_PATH = META_INF + PLUGIN_XML;
68   private static final PluginId ALL_MODULES_MARKER = PluginId.getId("com.intellij.modules.all");
69
70   public static final String VENDOR_JETBRAINS = "JetBrains";
71
72   private static final String MODULE_DEPENDENCY_PREFIX = "com.intellij.module";
73
74   private static final PluginId SPECIAL_IDEA_PLUGIN_ID = PluginId.getId("IDEA CORE");
75
76   static final String PROPERTY_PLUGIN_PATH = "plugin.path";
77
78   public static final String DISABLE = "disable";
79   public static final String ENABLE = "enable";
80   public static final String EDIT = "edit";
81
82   private static Reference<Map<PluginId, Set<String>>> ourBrokenPluginVersions;
83   private static volatile IdeaPluginDescriptorImpl[] ourPlugins;
84   static volatile List<IdeaPluginDescriptorImpl> ourLoadedPlugins;
85   private static List<PluginError> ourLoadingErrors;
86
87   private static Map<String, String[]> ourAdditionalLayoutMap = Collections.emptyMap();
88
89   @SuppressWarnings("StaticNonFinalField")
90   public static volatile boolean isUnitTestMode = Boolean.getBoolean("idea.is.unit.test");
91   @ApiStatus.Internal
92   static final boolean usePluginClassLoader = Boolean.getBoolean("idea.from.sources.plugins.class.loader");
93
94   @SuppressWarnings("StaticNonFinalField") @ApiStatus.Internal
95   public static String ourPluginError;
96
97   @SuppressWarnings("StaticNonFinalField")
98   @ApiStatus.Internal
99   public static Set<PluginId> ourPluginsToDisable;
100   @SuppressWarnings("StaticNonFinalField")
101   @ApiStatus.Internal
102   public static Set<PluginId> ourPluginsToEnable;
103
104   @ApiStatus.Internal
105   public static boolean ourDisableNonBundledPlugins;
106
107   /**
108    * Bundled plugins that were updated.
109    * When we update bundled plugin it becomes not bundled, so it is more difficult for analytics to use that data.
110    */
111   private static Set<PluginId> ourShadowedBundledPlugins;
112
113   private static Boolean isRunningFromSources;
114   private static volatile CompletableFuture<DescriptorListLoadingContext> descriptorListFuture;
115
116   private static BuildNumber ourBuildNumber;
117
118   @ApiStatus.Internal
119   public static @Nullable String getPluginsCompatibleBuild() {
120     return System.getProperty("idea.plugins.compatible.build");
121   }
122
123
124
125   /**
126    * Returns list of all available plugin descriptors (bundled and custom, include disabled ones). Use {@link #getLoadedPlugins()}
127    * if you need to get loaded plugins only.
128    *
129    * <p>
130    * Do not call this method during bootstrap, should be called in a copy of PluginManager, loaded by PluginClassLoader.
131    */
132   public static @NotNull IdeaPluginDescriptor @NotNull[] getPlugins() {
133     IdeaPluginDescriptor[] result = ourPlugins;
134     if (result == null) {
135       loadAndInitializePlugins(null, null);
136       return ourPlugins;
137     }
138     return result;
139   }
140
141   /**
142    * Returns descriptors of plugins which are successfully loaded into IDE. The result is sorted in a way that if each plugin comes after
143    * the plugins it depends on.
144    */
145   public static @NotNull List<? extends IdeaPluginDescriptor> getLoadedPlugins() {
146     return getLoadedPlugins(null);
147   }
148
149   @ApiStatus.Internal
150   public static @NotNull List<IdeaPluginDescriptorImpl> getLoadedPlugins(@Nullable ClassLoader coreClassLoader) {
151     List<IdeaPluginDescriptorImpl> result = ourLoadedPlugins;
152     if (result == null) {
153       loadAndInitializePlugins(null, coreClassLoader);
154       return ourLoadedPlugins;
155     }
156     return result;
157   }
158
159   @ApiStatus.Internal
160   public static boolean arePluginsInitialized() {
161     return ourPlugins != null;
162   }
163
164   @ApiStatus.Internal
165   static synchronized void doSetPlugins(@NotNull IdeaPluginDescriptorImpl @NotNull [] value) {
166     ourPlugins = value;
167     //noinspection NonPrivateFieldAccessedInSynchronizedContext
168     ourLoadedPlugins = Collections.unmodifiableList(getOnlyEnabledPlugins(value));
169   }
170
171   public static boolean isDisabled(@NotNull PluginId pluginId) {
172     return DisabledPluginsState.getDisabledIds().contains(pluginId);
173   }
174
175   /**
176    * @deprecated Use {@link #isDisabled(PluginId)}
177    */
178   @Deprecated
179   public static boolean isDisabled(@NotNull String pluginId) {
180     return DisabledPluginsState.getDisabledIds().contains(PluginId.getId(pluginId));
181   }
182
183   public static boolean isBrokenPlugin(@NotNull IdeaPluginDescriptor descriptor) {
184     PluginId pluginId = descriptor.getPluginId();
185     if (pluginId == null) {
186       return true;
187     }
188
189     Set<String> set = getBrokenPluginVersions().get(pluginId);
190     return set != null && set.contains(descriptor.getVersion());
191   }
192
193   private static @NotNull Map<PluginId, Set<String>> getBrokenPluginVersions() {
194     Map<PluginId, Set<String>> result = SoftReference.dereference(ourBrokenPluginVersions);
195     if (result != null) {
196       return result;
197     }
198
199     if (System.getProperty("idea.ignore.disabled.plugins") != null) {
200       result = Collections.emptyMap();
201       ourBrokenPluginVersions = new java.lang.ref.SoftReference<>(result);
202       return result;
203     }
204
205     result = new HashMap<>();
206     try (InputStream resource = PluginManagerCore.class.getResourceAsStream("/brokenPlugins.txt");
207          BufferedReader br = new BufferedReader(new InputStreamReader(resource, StandardCharsets.UTF_8))) {
208       String s;
209       while ((s = br.readLine()) != null) {
210         s = s.trim();
211         if (s.startsWith("//")) {
212           continue;
213         }
214
215         List<String> tokens = ParametersListUtil.parse(s);
216         if (tokens.isEmpty()) {
217           continue;
218         }
219
220         if (tokens.size() == 1) {
221           throw new RuntimeException("brokenPlugins.txt is broken. The line contains plugin name, but does not contains version: " + s);
222         }
223
224         PluginId pluginId = PluginId.getId(tokens.get(0));
225         List<String> versions = tokens.subList(1, tokens.size());
226         result.computeIfAbsent(pluginId, k -> new HashSet<>()).addAll(versions);
227       }
228     }
229     catch (IOException e) {
230       throw new RuntimeException("Failed to read /brokenPlugins.txt", e);
231     }
232
233     ourBrokenPluginVersions = new java.lang.ref.SoftReference<>(result);
234     return result;
235   }
236
237   public static void savePluginsList(@NotNull Collection<PluginId> ids, @NotNull Path file, boolean append) throws IOException {
238     Files.createDirectories(file.getParent());
239     try (BufferedWriter writer = (append ? Files.newBufferedWriter(file, StandardOpenOption.APPEND, StandardOpenOption.CREATE) : Files.newBufferedWriter(file))) {
240       writePluginsList(ids, writer);
241     }
242   }
243
244   public static void writePluginsList(@NotNull Collection<PluginId> ids, @NotNull Writer writer) throws IOException {
245     List<PluginId> sortedIds = new ArrayList<>(ids);
246     sortedIds.sort(null);
247     String separator = LineSeparator.getSystemLineSeparator().getSeparatorString();
248     for (PluginId id : sortedIds) {
249       writer.write(id.getIdString());
250       writer.write(separator);
251     }
252   }
253
254   /**
255    * @deprecated Use {@link #disablePlugin(PluginId)}
256    */
257   @Deprecated
258   public static boolean disablePlugin(@NotNull String id) {
259     return disablePlugin(PluginId.getId(id));
260   }
261
262   public static boolean disablePlugin(@NotNull PluginId id) {
263     return DisabledPluginsState.disablePlugin(id);
264   }
265
266   public static boolean enablePlugin(@NotNull PluginId id) {
267     return DisabledPluginsState.enablePlugin(id);
268   }
269
270   /**
271    * @deprecated Use {@link #enablePlugin(PluginId)}
272    */
273   @Deprecated
274   public static boolean enablePlugin(@NotNull String id) {
275     return enablePlugin(PluginId.getId(id));
276   }
277
278
279   public static boolean isModuleDependency(@NotNull PluginId dependentPluginId) {
280     return dependentPluginId.getIdString().startsWith(MODULE_DEPENDENCY_PREFIX);
281   }
282
283   /**
284    * This is an internal method, use {@link PluginException#createByClass(String, Throwable, Class)} instead.
285    */
286   @ApiStatus.Internal
287   public static @NotNull PluginException createPluginException(@NotNull String errorMessage, @Nullable Throwable cause,
288                                                       @NotNull Class<?> pluginClass) {
289     ClassLoader classLoader = pluginClass.getClassLoader();
290     PluginId pluginId = classLoader instanceof PluginClassLoader ? ((PluginClassLoader)classLoader).getPluginId()
291                                                                  : getPluginByClassName(pluginClass.getName());
292     return new PluginException(errorMessage, cause, pluginId);
293   }
294
295   public static @Nullable PluginId getPluginByClassName(@NotNull String className) {
296     PluginId id = getPluginOrPlatformByClassName(className);
297     return (id == null || CORE_ID == id) ? null : id;
298   }
299
300   public static @Nullable PluginId getPluginOrPlatformByClassName(@NotNull String className) {
301     PluginDescriptor result = getPluginDescriptorOrPlatformByClassName(className);
302     return result == null ? null : result.getPluginId();
303   }
304
305   @ApiStatus.Internal
306   public static @Nullable PluginDescriptor getPluginDescriptorOrPlatformByClassName(@NotNull String className) {
307     List<IdeaPluginDescriptorImpl> loadedPlugins = ourLoadedPlugins;
308     if (loadedPlugins == null ||
309         className.startsWith("java.") ||
310         className.startsWith("javax.") ||
311         className.startsWith("kotlin.") ||
312         className.startsWith("groovy.")) {
313       return null;
314     }
315
316     IdeaPluginDescriptor result = null;
317     for (IdeaPluginDescriptorImpl o : loadedPlugins) {
318       ClassLoader classLoader = o.getPluginClassLoader();
319       if (classLoader == null || !hasLoadedClass(className, classLoader)) {
320         continue;
321       }
322
323       result = o;
324       break;
325     }
326
327     if (result == null) {
328       return null;
329     }
330
331     // return if the found plugin is not "core" or the package is obviously "core"
332     if (result.getPluginId() != CORE_ID ||
333         className.startsWith("com.jetbrains.") || className.startsWith("org.jetbrains.") ||
334         className.startsWith("com.intellij.") || className.startsWith("org.intellij.") ||
335         className.startsWith("com.android.") ||
336         className.startsWith("git4idea.") || className.startsWith("org.angularjs.")) {
337       return result;
338     }
339
340     // otherwise we need to check plugins with use-idea-classloader="true"
341     String root = PathManager.getResourceRoot(result.getPluginClassLoader(), "/" + className.replace('.', '/') + ".class");
342     if (root == null) {
343       return null;
344     }
345
346     for (IdeaPluginDescriptorImpl o : loadedPlugins) {
347       if (!o.getUseIdeaClassLoader()) {
348         continue;
349       }
350
351       Path path = o.getPluginPath();
352       if (!root.startsWith(FileUtilRt.toSystemIndependentName(path.toString()))) {
353         continue;
354       }
355
356       result = o;
357       break;
358     }
359     return result;
360   }
361
362   private static boolean hasLoadedClass(@NotNull String className, @NotNull ClassLoader loader) {
363     if (loader instanceof UrlClassLoader) {
364       return ((UrlClassLoader)loader).hasLoadedClass(className);
365     }
366
367     // it can be an UrlClassLoader loaded by another class loader, so instanceof doesn't work
368     Class<? extends ClassLoader> aClass = loader.getClass();
369     if (isInstanceofUrlClassLoader(aClass)) {
370       try {
371         return (Boolean)aClass.getMethod("hasLoadedClass", String.class).invoke(loader, className);
372       }
373       catch (Exception ignored) {
374       }
375     }
376     return false;
377   }
378
379   private static boolean isInstanceofUrlClassLoader(Class<?> aClass) {
380     String urlClassLoaderName = UrlClassLoader.class.getName();
381     while (aClass != null) {
382       if (aClass.getName().equals(urlClassLoaderName)) return true;
383       aClass = aClass.getSuperclass();
384     }
385     return false;
386   }
387
388   /**
389    * 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
390    * classes without declaring explicit dependency on the Java module. This method is intended to add implicit dependency on the Java plugin
391    * for such plugins to avoid breaking compatibility with them.
392    */
393   private static @Nullable IdeaPluginDescriptorImpl getImplicitDependency(@NotNull IdeaPluginDescriptorImpl descriptor,
394                                                                 @Nullable IdeaPluginDescriptorImpl javaDep,
395                                                                 boolean hasAllModules) {
396     // skip our plugins as expected to be up-to-date whether bundled or not
397     if (descriptor.getPluginId() == CORE_ID || descriptor.getPluginId() == JAVA_PLUGIN_ID ||
398         VENDOR_JETBRAINS.equals(descriptor.getVendor()) ||
399         !hasAllModules ||
400         javaDep == null) {
401       return null;
402     }
403
404     // If a plugin does not include any module dependency tags in its plugin.xml, it's assumed to be a legacy plugin
405     // and is loaded only in IntelliJ IDEA, so it may use classes from Java plugin.
406     return hasModuleDependencies(descriptor) ? null : javaDep;
407   }
408
409   static boolean hasModuleDependencies(@NotNull IdeaPluginDescriptorImpl descriptor) {
410     if (descriptor.pluginDependencies == null) {
411       return false;
412     }
413
414     for (PluginDependency dependency : descriptor.pluginDependencies) {
415       PluginId depId = dependency.id;
416       if (depId == JAVA_PLUGIN_ID || depId == JAVA_MODULE_ID || isModuleDependency(depId)) {
417         return true;
418       }
419     }
420     return false;
421   }
422
423   private static @NotNull ClassLoader createPluginClassLoader(ClassLoader @NotNull [] parentLoaders,
424                                                               @NotNull IdeaPluginDescriptorImpl descriptor,
425                                                               @NotNull UrlClassLoader.Builder urlLoaderBuilder,
426                                                               @NotNull ClassLoader coreLoader,
427                                                               @NotNull Map<String, String[]> additionalLayoutMap) {
428     List<Path> classPath = descriptor.jarFiles;
429     if (classPath == null) {
430       classPath = descriptor.collectClassPath(additionalLayoutMap);
431     }
432     else {
433       descriptor.jarFiles = null;
434     }
435
436     if (descriptor.getUseIdeaClassLoader()) {
437       getLogger().warn(descriptor.getPluginId() + " uses deprecated `use-idea-classloader` attribute");
438       ClassLoader loader = PluginManagerCore.class.getClassLoader();
439       try {
440         // `UrlClassLoader#addURL` can't be invoked directly, because the core classloader is created at bootstrap in a "lost" branch
441         MethodHandle addURL = MethodHandles.lookup().findVirtual(loader.getClass(), "addURL", MethodType.methodType(void.class, URL.class));
442         for (Path pathElement : classPath) {
443           addURL.invoke(loader, localFileToUrl(pathElement, descriptor));
444         }
445         return loader;
446       }
447       catch (Throwable t) {
448         throw new IllegalStateException("An unexpected core classloader: " + loader.getClass(), t);
449       }
450     }
451     else {
452       List<URL> urls = new ArrayList<>(classPath.size());
453       for (Path pathElement : classPath) {
454         urls.add(localFileToUrl(pathElement, descriptor));
455       }
456       PluginClassLoader loader =
457         new PluginClassLoader(urlLoaderBuilder.urls(urls), parentLoaders, descriptor.getPluginId(), descriptor, descriptor.getVersion(),
458                               descriptor.getPluginPath());
459       if (usePluginClassLoader) {
460         loader.setCoreLoader(coreLoader);
461       }
462       return loader;
463     }
464   }
465
466   private static @NotNull URL localFileToUrl(@NotNull Path file, @NotNull IdeaPluginDescriptor descriptor) {
467     try {
468       return file.normalize().toUri().toURL();  // it is important not to have traversal elements in classpath
469     }
470     catch (MalformedURLException e) {
471       throw new PluginException("Corrupted path element: `" + file + '`', e, descriptor.getPluginId());
472     }
473   }
474
475   public static synchronized void invalidatePlugins() {
476     ourPlugins = null;
477     //noinspection NonPrivateFieldAccessedInSynchronizedContext
478     ourLoadedPlugins = null;
479     DisabledPluginsState.invalidate();
480     ourShadowedBundledPlugins = null;
481   }
482
483   private static void logPlugins(@NotNull IdeaPluginDescriptorImpl @NotNull[] plugins) {
484     StringBuilder bundled = new StringBuilder();
485     StringBuilder disabled = new StringBuilder();
486     StringBuilder custom = new StringBuilder();
487     for (IdeaPluginDescriptor descriptor : plugins) {
488       StringBuilder target;
489       if (!descriptor.isEnabled()) {
490         target = disabled;
491       }
492       else if (descriptor.isBundled() || descriptor.getPluginId() == SPECIAL_IDEA_PLUGIN_ID) {
493         target = bundled;
494       }
495       else {
496         target = custom;
497       }
498
499       if (target.length() > 0) {
500         target.append(", ");
501       }
502
503       target.append(descriptor.getName());
504       String version = descriptor.getVersion();
505       if (version != null) {
506         target.append(" (").append(version).append(')');
507       }
508     }
509
510     Logger logger = getLogger();
511     logger.info("Loaded bundled plugins: " + bundled);
512     if (custom.length() > 0) {
513       logger.info("Loaded custom plugins: " + custom);
514     }
515     if (disabled.length() > 0) {
516       logger.info("Disabled plugins: " + disabled);
517     }
518   }
519
520   public static boolean isRunningFromSources() {
521     Boolean result = isRunningFromSources;
522     if (result == null) {
523       result = Files.isDirectory(Paths.get(PathManager.getHomePath(), Project.DIRECTORY_STORE_FOLDER));
524       isRunningFromSources = result;
525     }
526     return result;
527   }
528
529   private static void prepareLoadingPluginsErrorMessage(@NotNull List<PluginError> errors, @NotNull List<String> actions) {
530     ourLoadingErrors = errors;
531     List<PluginError> errorsToReport = new ArrayList<>();
532     for (PluginError error : errors) {
533       if (error.isNotifyUser()) errorsToReport.add(error);
534     }
535
536     // Log includes all messages, not only those which need to be reported to the user
537     String message = "Problems found loading plugins:\n  " + errors.stream().map(PluginError::toString).collect(Collectors.joining("\n  "));
538     Application app = ApplicationManager.getApplication();
539     if (app == null || !app.isHeadlessEnvironment() || isUnitTestMode) {
540       if (!errorsToReport.isEmpty()) {
541         String errorMessage = Stream.concat(errorsToReport.stream().map(o -> o.toUserError() + "."), actions.stream()).collect(Collectors.joining("<p/>"));
542         if (ourPluginError == null) {
543           ourPluginError = errorMessage;
544         }
545         else {
546           ourPluginError += "<p/>\n" + errorMessage;
547         }
548       }
549
550       // as warn in tests
551       if (!errors.isEmpty()) {
552         getLogger().warn(message);
553       }
554     }
555     else {
556       if (!errors.isEmpty()) {
557         getLogger().error(message);
558       }
559     }
560   }
561
562   public static @Nullable String getLoadingError(@NotNull IdeaPluginDescriptor pluginDescriptor) {
563     PluginError error = findErrorForPlugin(ourLoadingErrors, pluginDescriptor.getPluginId());
564     if (error != null) {
565       String reason = error.getIncompatibleReason();
566       if (reason != null) {
567         return "Incompatible (" + reason + ")";
568       }
569       return error.getMessage();
570     }
571     return null;
572   }
573
574   public static @Nullable PluginId getFirstDisabledDependency(@NotNull IdeaPluginDescriptor pluginDescriptor) {
575     PluginError error = findErrorForPlugin(ourLoadingErrors, pluginDescriptor.getPluginId());
576     if (error != null) {
577       return error.getDisabledDependency();
578     }
579     return null;
580   }
581
582   private static @Nullable PluginError findErrorForPlugin(@Nullable List<PluginError> errors, @NotNull PluginId pluginId) {
583     if (errors == null) return null;
584     for (PluginError error : errors) {
585       if (error.plugin != null && error.plugin.getPluginId().equals(pluginId)) {
586         return error;
587       }
588     }
589     return null;
590   }
591
592   private static @NotNull CachingSemiGraph<IdeaPluginDescriptorImpl> createPluginIdGraph(@NotNull List<IdeaPluginDescriptorImpl> descriptors,
593                                                                                          @NotNull Map<PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap,
594                                                                                          boolean withOptional) {
595     IdeaPluginDescriptorImpl javaDep = idToDescriptorMap.get(JAVA_MODULE_ID);
596     boolean hasAllModules = idToDescriptorMap.containsKey(ALL_MODULES_MARKER);
597     Set<IdeaPluginDescriptorImpl> uniqueCheck = new HashSet<>();
598     return new CachingSemiGraph<>(descriptors, rootDescriptor -> {
599       List<PluginDependency> dependencies = rootDescriptor.pluginDependencies;
600       if (dependencies == null) {
601         dependencies = Collections.emptyList();
602       }
603
604       IdeaPluginDescriptorImpl implicitDep = getImplicitDependency(rootDescriptor, javaDep, hasAllModules);
605       int capacity = dependencies.size();
606       if (!withOptional) {
607         for (PluginDependency dependency : dependencies) {
608           if (dependency.isOptional) {
609             capacity--;
610           }
611         }
612       }
613       if (capacity == 0) {
614         return implicitDep == null ? Collections.emptyList() : Collections.singletonList(implicitDep);
615       }
616
617       uniqueCheck.clear();
618
619       List<IdeaPluginDescriptorImpl> plugins = new ArrayList<>(capacity + (implicitDep == null ? 0 : 1));
620       if (implicitDep != null) {
621         if (rootDescriptor == implicitDep) {
622           getLogger().error("Plugin " + rootDescriptor + " depends on self");
623         }
624         else {
625           uniqueCheck.add(implicitDep);
626           plugins.add(implicitDep);
627         }
628       }
629
630       for (PluginDependency dependency : dependencies) {
631         if (!withOptional && dependency.isOptional) {
632           continue;
633         }
634
635         // check for missing optional dependency
636         IdeaPluginDescriptorImpl dep = idToDescriptorMap.get(dependency.id);
637         // if 'dep' refers to a module we need to check the real plugin containing this module only if it's still enabled,
638         // otherwise the graph will be inconsistent
639         if (dep == null) {
640           continue;
641         }
642
643         // ultimate plugin it is combined plugin, where some included XML can define dependency on ultimate explicitly and for now not clear,
644         // can be such requirements removed or not
645         if (rootDescriptor == dep) {
646           if (rootDescriptor.getPluginId() != CORE_ID) {
647             getLogger().error("Plugin " + rootDescriptor + " depends on self");
648           }
649         }
650         else if (uniqueCheck.add(dep)) {
651           plugins.add(dep);
652         }
653       }
654       return plugins;
655     });
656   }
657
658   private static void checkPluginCycles(@NotNull List<IdeaPluginDescriptorImpl> descriptors,
659                                         @NotNull Map<PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap,
660                                         @NotNull List<PluginError> errors) {
661     CachingSemiGraph<IdeaPluginDescriptorImpl> graph = createPluginIdGraph(descriptors, idToDescriptorMap, true);
662     DFSTBuilder<IdeaPluginDescriptorImpl> builder = new DFSTBuilder<>(GraphGenerator.generate(graph));
663     if (builder.isAcyclic()) {
664       return;
665     }
666
667     StringBuilder cyclePresentation = new StringBuilder();
668     for (Collection<IdeaPluginDescriptorImpl> component : builder.getComponents()) {
669       if (component.size() < 2) {
670         continue;
671       }
672
673       if (cyclePresentation.length() > 0) {
674         cyclePresentation.append(", ");
675       }
676
677       String separator = " <-> ";
678       for (IdeaPluginDescriptor descriptor : component) {
679         descriptor.setEnabled(false);
680
681         cyclePresentation.append(descriptor.getPluginId());
682         cyclePresentation.append(separator);
683       }
684
685       cyclePresentation.setLength(cyclePresentation.length() - separator.length());
686     }
687
688     if (cyclePresentation.length() > 0) {
689       errors.add(new PluginError(null, "Plugins should not have cyclic dependencies: " + cyclePresentation, null));
690     }
691   }
692
693   @Nullable
694   static PathBasedJdomXIncluder.PathResolver<Path> createPluginJarsPathResolver(@NotNull Path pluginDir, @NotNull DescriptorLoadingContext context) {
695     List<Path> pluginJarFiles = new ArrayList<>(), dirs = new ArrayList<>();
696     if (!PluginDescriptorLoader.collectPluginDirectoryContents(pluginDir, pluginJarFiles, dirs)) return null;
697     return new PluginXmlPathResolver(pluginJarFiles, context);
698   }
699
700   public static void getDescriptorsToMigrate(@NotNull Path dir,
701                                              @Nullable BuildNumber compatibleBuildNumber,
702                                              @Nullable String bundledPluginsPath,
703                                              @Nullable Map<PluginId, Set<String>> brokenPluginVersions,
704                                              List<IdeaPluginDescriptorImpl> pluginsToMigrate,
705                                              List<IdeaPluginDescriptorImpl> incompatiblePlugins) throws ExecutionException, InterruptedException {
706     PluginLoadingResult loadingResult = new PluginLoadingResult(
707       brokenPluginVersions != null ? brokenPluginVersions : getBrokenPluginVersions(),
708       () -> compatibleBuildNumber == null ? getBuildNumber() : compatibleBuildNumber
709     );
710     DescriptorListLoadingContext context = new DescriptorListLoadingContext(0, Collections.emptySet(), loadingResult);
711     if (bundledPluginsPath != null) {
712       context.loadBundledPlugins = true;
713       context.bundledPluginsPath = bundledPluginsPath;
714     }
715     PluginDescriptorLoader.loadBundledDescriptorsAndDescriptorsFromDir(context, dir);
716
717     for (IdeaPluginDescriptorImpl descriptor : loadingResult.idMap.values()) {
718       if (!descriptor.isBundled()) {
719         if (loadingResult.isBroken(descriptor.getPluginId())) {
720           incompatiblePlugins.add(descriptor);
721         }
722         else {
723           pluginsToMigrate.add(descriptor);
724         }
725       }
726     }
727     for (IdeaPluginDescriptorImpl descriptor : loadingResult.incompletePlugins.values()) {
728       if (!descriptor.isBundled()) {
729         incompatiblePlugins.add(descriptor);
730       }
731     }
732   }
733
734   private static void prepareLoadingPluginsErrorMessage(@NotNull Map<PluginId, String> disabledIds,
735                                                         @NotNull Set<PluginId> disabledRequiredIds,
736                                                         @NotNull Map<PluginId, ? extends IdeaPluginDescriptor> idMap,
737                                                         @NotNull List<PluginError> errors) {
738     List<String> actions = new ArrayList<>();
739     if (!disabledIds.isEmpty()) {
740       String text = "<br><a href=\"" + DISABLE + "\">Disable ";
741       if (disabledIds.size() == 1) {
742         PluginId id = disabledIds.keySet().iterator().next();
743         text += idMap.containsKey(id) ? toPresentableName(idMap.get(id)) : toPresentableName(id.getIdString());
744       }
745       else {
746         text += "not loaded plugins";
747       }
748       actions.add(text + "</a>");
749       if (!disabledRequiredIds.isEmpty()) {
750         String name = disabledRequiredIds.size() == 1
751                       ? toPresentableName(idMap.get(disabledRequiredIds.iterator().next()))
752                       : "all necessary plugins";
753         actions.add("<a href=\"" + ENABLE + "\">Enable " + name + "</a>");
754       }
755       actions.add("<a href=\"" + EDIT + "\">Open plugin manager</a>");
756     }
757     prepareLoadingPluginsErrorMessage(errors, actions);
758   }
759
760   @TestOnly
761   public static @NotNull List<? extends IdeaPluginDescriptor> testLoadDescriptorsFromClassPath(@NotNull ClassLoader loader)
762     throws ExecutionException, InterruptedException {
763     Map<URL, String> urlsFromClassPath = new LinkedHashMap<>();
764     PluginDescriptorLoader.collectPluginFilesInClassPath(loader, urlsFromClassPath);
765     BuildNumber buildNumber = BuildNumber.fromString("2042.42");
766     DescriptorListLoadingContext context = new DescriptorListLoadingContext(0, Collections.emptySet(), new PluginLoadingResult(Collections.emptyMap(), () -> buildNumber, false));
767     try (DescriptorLoadingContext loadingContext = new DescriptorLoadingContext(context, true, true, new ClassPathXmlPathResolver(loader))) {
768       PluginDescriptorLoader.loadDescriptorsFromClassPath(urlsFromClassPath, loadingContext, null);
769     }
770
771     context.result.finishLoading();
772     return context.result.getEnabledPlugins();
773   }
774
775   public static void scheduleDescriptorLoading() {
776     getOrScheduleLoading();
777   }
778
779   private static synchronized @NotNull CompletableFuture<DescriptorListLoadingContext> getOrScheduleLoading() {
780     CompletableFuture<DescriptorListLoadingContext> future = descriptorListFuture;
781     if (future != null) {
782       return future;
783     }
784
785     future = CompletableFuture.supplyAsync(() -> {
786       Activity activity = StartUpMeasurer.startActivity("plugin descriptor loading");
787       DescriptorListLoadingContext context = PluginDescriptorLoader.loadDescriptors();
788       activity.end();
789       return context;
790     }, AppExecutorUtil.getAppExecutorService());
791     descriptorListFuture = future;
792     return future;
793   }
794
795   /**
796    * Think twice before use and get approve from core team. Returns enabled plugins only.
797    */
798   @ApiStatus.Internal
799   public static @NotNull List<IdeaPluginDescriptorImpl> getEnabledPluginRawList() {
800     return getOrScheduleLoading().join().result.getEnabledPlugins();
801   }
802
803   @ApiStatus.Internal
804   public static @NotNull CompletionStage<List<IdeaPluginDescriptorImpl>> initPlugins(@NotNull ClassLoader coreClassLoader) {
805     CompletableFuture<DescriptorListLoadingContext> future = descriptorListFuture;
806     if (future == null) {
807       future = CompletableFuture.completedFuture(null);
808     }
809     return future.thenApply(context -> {
810       loadAndInitializePlugins(context, coreClassLoader);
811       return ourLoadedPlugins;
812     });
813   }
814
815   static @NotNull PluginLoadingResult createLoadingResult(@Nullable BuildNumber buildNumber) {
816     return new PluginLoadingResult(getBrokenPluginVersions(), () -> buildNumber == null ? getBuildNumber() : buildNumber);
817   }
818
819   private static void mergeOptionalConfigs(@NotNull List<IdeaPluginDescriptorImpl> enabledPlugins,
820                                            @NotNull Map<PluginId, IdeaPluginDescriptorImpl> idMap) {
821     if (isRunningFromSources()) {
822       // fix optional configs
823       for (IdeaPluginDescriptorImpl descriptor : enabledPlugins) {
824         if (!descriptor.isUseCoreClassLoader() || descriptor.pluginDependencies == null) {
825           continue;
826         }
827
828         for (PluginDependency dependency : descriptor.pluginDependencies) {
829           if (dependency.subDescriptor == null) {
830             continue;
831           }
832
833           IdeaPluginDescriptorImpl dependent = idMap.get(dependency.id);
834           if (dependent != null && !dependent.isUseCoreClassLoader()) {
835             // for what?
836             dependency.subDescriptor = null;
837           }
838         }
839       }
840     }
841
842     for (IdeaPluginDescriptorImpl mainDescriptor : enabledPlugins) {
843       List<PluginDependency> pluginDependencies = mainDescriptor.pluginDependencies;
844       if (pluginDependencies != null) {
845         mergeOptionalDescriptors(mainDescriptor, pluginDependencies, idMap);
846       }
847     }
848   }
849
850   private static void mergeOptionalDescriptors(@NotNull IdeaPluginDescriptorImpl mergedDescriptor,
851                                                @NotNull List<PluginDependency> pluginDependencies,
852                                                @NotNull Map<PluginId, IdeaPluginDescriptorImpl> idMap) {
853     loop:
854     for (PluginDependency dependency : pluginDependencies) {
855       IdeaPluginDescriptorImpl subDescriptor = dependency.subDescriptor;
856       dependency.subDescriptor = null;
857
858       if (subDescriptor == null || dependency.isDisabledOrBroken) {
859         continue;
860       }
861
862       IdeaPluginDescriptorImpl dependencyDescriptor = idMap.get(dependency.id);
863       if (dependencyDescriptor == null || !dependencyDescriptor.isEnabled()) {
864         continue;
865       }
866
867       // check that plugin doesn't depend on unavailable plugin
868       if (subDescriptor.pluginDependencies != null) {
869         for (PluginDependency pluginDependency : subDescriptor.pluginDependencies) {
870           // ignore if optional
871           if (!pluginDependency.isOptional) {
872             if (pluginDependency.isDisabledOrBroken) {
873               continue loop;
874             }
875
876             IdeaPluginDescriptorImpl dependentDescriptor = idMap.get(pluginDependency.id);
877             if (dependentDescriptor == null || !dependentDescriptor.isEnabled()) {
878               continue loop;
879             }
880           }
881         }
882       }
883
884       mergedDescriptor.mergeOptionalConfig(subDescriptor);
885       List<PluginDependency> childDependencies = subDescriptor.pluginDependencies;
886       if (childDependencies != null) {
887         mergeOptionalDescriptors(mergedDescriptor, childDependencies, idMap);
888       }
889     }
890   }
891
892   private static @NotNull Map<String, String[]> loadAdditionalLayoutMap() {
893     Path fileWithLayout = PluginManagerCore.usePluginClassLoader
894                           ? Paths.get(PathManager.getSystemPath(), PlatformUtils.getPlatformPrefix() + ".txt")
895                           : null;
896     if (fileWithLayout == null || !Files.exists(fileWithLayout)) {
897       return Collections.emptyMap();
898     }
899
900     Map<String, String[]> additionalLayoutMap = new LinkedHashMap<>();
901     try (BufferedReader bufferedReader = Files.newBufferedReader(fileWithLayout)) {
902       String line;
903       while ((line = bufferedReader.readLine()) != null) {
904         List<String> parameters = ParametersListUtil.parse(line.trim());
905         if (parameters.size() < 2) {
906           continue;
907         }
908         additionalLayoutMap.put(parameters.get(0), ArrayUtilRt.toStringArray(parameters.subList(1, parameters.size())));
909       }
910     }
911     catch (Exception ignored) {
912     }
913     return additionalLayoutMap;
914   }
915
916   // not used by plugin manager - only for dynamic plugin reloading
917   @ApiStatus.Internal
918   public static void initClassLoader(@NotNull IdeaPluginDescriptorImpl rootDescriptor) {
919     Map<PluginId, IdeaPluginDescriptorImpl> idMap = buildPluginIdMap(ContainerUtil.concat(getLoadedPlugins(null), Collections.singletonList(rootDescriptor)));
920
921     Set<ClassLoader> loaders = new LinkedHashSet<>();
922     processAllDependencies(rootDescriptor, true, idMap, descriptor -> {
923       ClassLoader loader = descriptor.getPluginClassLoader();
924       if (loader == null) {
925         getLogger().error(rootDescriptor.formatErrorMessage("requires missing class loader for " + toPresentableName(descriptor)));
926       }
927       else {
928         loaders.add(loader);
929       }
930       // see configureClassLoaders about why we don't need to process recursively
931       return FileVisitResult.SKIP_SUBTREE;
932     });
933
934     IdeaPluginDescriptorImpl javaDep = idMap.get(JAVA_MODULE_ID);
935     boolean hasAllModules = idMap.containsKey(ALL_MODULES_MARKER);
936     IdeaPluginDescriptorImpl implicitDependency = getImplicitDependency(rootDescriptor, javaDep, hasAllModules);
937     if (implicitDependency != null && implicitDependency.getPluginClassLoader() != null) {
938       loaders.add(implicitDependency.getPluginClassLoader());
939     }
940
941     ClassLoader[] array = loaders.isEmpty()
942                           ? new ClassLoader[]{PluginManagerCore.class.getClassLoader()}
943                           : loaders.toArray(new ClassLoader[0]);
944     rootDescriptor.setLoader(createPluginClassLoader(array, rootDescriptor, createUrlClassLoaderBuilder(), PluginManagerCore.class.getClassLoader(), ourAdditionalLayoutMap));
945   }
946
947   private static @NotNull UrlClassLoader.Builder createUrlClassLoaderBuilder() {
948     return UrlClassLoader.build().allowLock().useCache().urlsInterned();
949   }
950
951   static @NotNull BuildNumber getBuildNumber() {
952     BuildNumber result = ourBuildNumber;
953     if (result == null) {
954       result = BuildNumber.fromString(getPluginsCompatibleBuild());
955       if (result == null) {
956         if (isUnitTestMode) {
957           result = BuildNumber.currentVersion();
958         }
959         else {
960           try {
961             result = ApplicationInfoImpl.getShadowInstance().getApiVersionAsNumber();
962           }
963           catch (RuntimeException ignore) {
964             // no need to log error - ApplicationInfo is required in production in any case, so, will be logged if really needed
965             result = BuildNumber.currentVersion();
966           }
967         }
968       }
969       ourBuildNumber = result;
970     }
971     return result;
972   }
973
974   private static void disableIncompatiblePlugins(@NotNull List<IdeaPluginDescriptorImpl> descriptors,
975                                                  @NotNull Map<PluginId, IdeaPluginDescriptorImpl> idMap,
976                                                  @NotNull List<PluginError> errors) {
977     if (ourDisableNonBundledPlugins) {
978       getLogger().info("Running with disableThirdPartyPlugins argument, third-party plugins will be disabled");
979     }
980     String selectedIds = System.getProperty("idea.load.plugins.id");
981     String selectedCategory = System.getProperty("idea.load.plugins.category");
982
983     IdeaPluginDescriptorImpl coreDescriptor = idMap.get(CORE_ID);
984     Set<IdeaPluginDescriptorImpl> explicitlyEnabled = null;
985     if (selectedIds != null) {
986       Set<PluginId> set = new HashSet<>();
987       List<String> strings = StringUtil.split(selectedIds, ",");
988       for (String it : strings) {
989         set.add(PluginId.getId(it));
990       }
991       set.addAll(((ApplicationInfoImpl)ApplicationInfoImpl.getShadowInstance()).getEssentialPluginsIds());
992
993       explicitlyEnabled = new LinkedHashSet<>(set.size());
994       for (PluginId id : set) {
995         IdeaPluginDescriptorImpl descriptor = idMap.get(id);
996         if (descriptor != null) {
997           explicitlyEnabled.add(descriptor);
998         }
999       }
1000     }
1001     else if (selectedCategory != null) {
1002       explicitlyEnabled = new LinkedHashSet<>();
1003       for (IdeaPluginDescriptorImpl descriptor : descriptors) {
1004         if (selectedCategory.equals(descriptor.getCategory())) {
1005           explicitlyEnabled.add(descriptor);
1006         }
1007       }
1008     }
1009
1010     if (explicitlyEnabled != null) {
1011       // add all required dependencies
1012       Set<IdeaPluginDescriptorImpl> finalExplicitlyEnabled = explicitlyEnabled;
1013       Set<IdeaPluginDescriptor> depProcessed = new HashSet<>();
1014       for (IdeaPluginDescriptorImpl descriptor : new ArrayList<>(explicitlyEnabled)) {
1015         processAllDependencies(descriptor, false, idMap, depProcessed, (id, dependency) -> {
1016           finalExplicitlyEnabled.add(dependency);
1017           return FileVisitResult.CONTINUE;
1018         });
1019       }
1020     }
1021
1022     Map<PluginId, Set<String>> brokenPluginVersions = getBrokenPluginVersions();
1023     boolean shouldLoadPlugins = Boolean.parseBoolean(System.getProperty("idea.load.plugins", "true"));
1024     for (IdeaPluginDescriptorImpl descriptor : descriptors) {
1025       if (descriptor == coreDescriptor) {
1026         continue;
1027       }
1028
1029       Set<String> set = brokenPluginVersions.get(descriptor.getPluginId());
1030       if (set != null && set.contains(descriptor.getVersion())) {
1031         descriptor.setEnabled(false);
1032         errors.add(new PluginError(descriptor, "was marked as broken", "marked as broken"));
1033       }
1034       else if (explicitlyEnabled != null) {
1035         if (!explicitlyEnabled.contains(descriptor)) {
1036           descriptor.setEnabled(false);
1037           getLogger().info("Plugin " + toPresentableName(descriptor) + " " +
1038                            (selectedIds != null
1039                             ? "is not in 'idea.load.plugins.id' system property"
1040                             : "category doesn't match 'idea.load.plugins.category' system property"));
1041         }
1042       }
1043       else if (!shouldLoadPlugins) {
1044         descriptor.setEnabled(false);
1045         errors.add(new PluginError(descriptor, "is skipped (plugins loading disabled)", null));
1046       }
1047       else if (!descriptor.isBundled() && ourDisableNonBundledPlugins) {
1048         descriptor.setEnabled(false);
1049         errors.add(new PluginError(descriptor, "is skipped (third-party plugins loading disabled)", null, false));
1050       }
1051     }
1052   }
1053
1054   public static boolean isCompatible(@NotNull IdeaPluginDescriptor descriptor) {
1055     return !isIncompatible(descriptor);
1056   }
1057
1058   public static boolean isCompatible(@NotNull IdeaPluginDescriptor descriptor, @Nullable BuildNumber buildNumber) {
1059     return !isIncompatible(descriptor, buildNumber);
1060   }
1061
1062   public static boolean isIncompatible(@NotNull IdeaPluginDescriptor descriptor) {
1063     return isIncompatible(descriptor, getBuildNumber());
1064   }
1065
1066   public static boolean isIncompatible(@NotNull IdeaPluginDescriptor descriptor, @Nullable BuildNumber buildNumber) {
1067     if (buildNumber == null) {
1068       buildNumber = getBuildNumber();
1069     }
1070     return getIncompatibleMessage(buildNumber, descriptor.getSinceBuild(), descriptor.getUntilBuild()) != null;
1071   }
1072
1073   static @Nullable String getIncompatibleMessage(@NotNull BuildNumber buildNumber, @Nullable String sinceBuild, @Nullable String untilBuild) {
1074     try {
1075       String message = null;
1076       BuildNumber sinceBuildNumber = sinceBuild == null ? null : BuildNumber.fromString(sinceBuild, null, null);
1077       if (sinceBuildNumber != null && sinceBuildNumber.compareTo(buildNumber) > 0) {
1078         message = "since build " + sinceBuildNumber + " > " + buildNumber;
1079       }
1080
1081       BuildNumber untilBuildNumber = untilBuild == null ? null : BuildNumber.fromString(untilBuild, null, null);
1082       if (untilBuildNumber != null && untilBuildNumber.compareTo(buildNumber) < 0) {
1083         if (message == null) {
1084           message = "";
1085         }
1086         else {
1087           message += ", ";
1088         }
1089         message += "until build " + untilBuildNumber + " < " + buildNumber;
1090       }
1091       return message;
1092     }
1093     catch (Exception e) {
1094       getLogger().error(e);
1095       return "version check failed";
1096     }
1097   }
1098
1099   private static void checkEssentialPluginsAreAvailable(@NotNull Map<PluginId, IdeaPluginDescriptorImpl> idMap) {
1100     List<PluginId> required = ((ApplicationInfoImpl)ApplicationInfoImpl.getShadowInstance()).getEssentialPluginsIds();
1101     List<String> missing = null;
1102     for (PluginId id : required) {
1103       IdeaPluginDescriptorImpl descriptor = idMap.get(id);
1104       if (descriptor == null || !descriptor.isEnabled()) {
1105         if (missing == null) {
1106           missing = new ArrayList<>();
1107         }
1108         missing.add(id.getIdString());
1109       }
1110     }
1111
1112     if (missing != null) {
1113       throw new EssentialPluginMissingException(missing);
1114     }
1115   }
1116
1117   static @NotNull PluginManagerState initializePlugins(@NotNull DescriptorListLoadingContext context, @NotNull ClassLoader coreLoader, boolean checkEssentialPlugins) {
1118     PluginLoadingResult loadingResult = context.result;
1119     List<PluginError> errors = new ArrayList<>(loadingResult.getErrors());
1120
1121     if (loadingResult.duplicateModuleMap != null) {
1122       for (Map.Entry<PluginId, List<IdeaPluginDescriptorImpl>> entry : loadingResult.duplicateModuleMap.entrySet()) {
1123         errors.add(new PluginError(null, "Module " + entry.getKey() + " is declared by plugins:\n  " + StringUtil.join(entry.getValue(), "\n  "), null));
1124       }
1125     }
1126
1127     Map<PluginId, IdeaPluginDescriptorImpl> idMap = loadingResult.idMap;
1128     IdeaPluginDescriptorImpl coreDescriptor = idMap.get(CORE_ID);
1129     if (checkEssentialPlugins && coreDescriptor == null) {
1130       throw new EssentialPluginMissingException(Collections.singletonList(CORE_ID + " (platform prefix: " + System.getProperty(PlatformUtils.PLATFORM_PREFIX_KEY) + ")"));
1131     }
1132
1133     List<IdeaPluginDescriptorImpl> descriptors = loadingResult.getEnabledPlugins();
1134     disableIncompatiblePlugins(descriptors, idMap, errors);
1135     checkPluginCycles(descriptors, idMap, errors);
1136
1137     // topological sort based on required dependencies only
1138     IdeaPluginDescriptorImpl[] sortedRequired = getTopologicallySorted(createPluginIdGraph(descriptors, idMap, false));
1139
1140     Set<PluginId> enabledIds = new LinkedHashSet<>();
1141     Map<PluginId, String> disabledIds = new LinkedHashMap<>();
1142     Set<PluginId> disabledRequiredIds = new LinkedHashSet<>();
1143
1144     for (IdeaPluginDescriptorImpl descriptor : sortedRequired) {
1145       boolean wasEnabled = descriptor.isEnabled();
1146       if (wasEnabled && computePluginEnabled(descriptor, enabledIds, idMap, disabledRequiredIds, context.disabledPlugins, errors)) {
1147         enabledIds.add(descriptor.getPluginId());
1148         enabledIds.addAll(descriptor.getModules());
1149       }
1150       else {
1151         descriptor.setEnabled(false);
1152         if (wasEnabled) {
1153           disabledIds.put(descriptor.getPluginId(), descriptor.getName());
1154         }
1155       }
1156     }
1157
1158     prepareLoadingPluginsErrorMessage(disabledIds, disabledRequiredIds, idMap, errors);
1159
1160     // topological sort based on all (required and optional) dependencies
1161     CachingSemiGraph<IdeaPluginDescriptorImpl> graph = createPluginIdGraph(Arrays.asList(sortedRequired), idMap, true);
1162     IdeaPluginDescriptorImpl[] sortedAll = getTopologicallySorted(graph);
1163
1164     List<IdeaPluginDescriptorImpl> enabledPlugins = getOnlyEnabledPlugins(sortedAll);
1165
1166     mergeOptionalConfigs(enabledPlugins, idMap);
1167     Map<String, String[]> additionalLayoutMap = loadAdditionalLayoutMap();
1168     ourAdditionalLayoutMap = additionalLayoutMap;
1169     configureClassLoaders(coreLoader, graph, coreDescriptor, enabledPlugins, additionalLayoutMap, context.usePluginClassLoader);
1170
1171     if (checkEssentialPlugins) {
1172       checkEssentialPluginsAreAvailable(idMap);
1173     }
1174
1175     Set<PluginId> effectiveDisabledIds = disabledIds.isEmpty() ? Collections.emptySet() : new HashSet<>(disabledIds.keySet());
1176     return new PluginManagerState(sortedAll, enabledPlugins, disabledRequiredIds, effectiveDisabledIds, idMap);
1177   }
1178
1179   private static void configureClassLoaders(@NotNull ClassLoader coreLoader,
1180                                             @NotNull CachingSemiGraph<IdeaPluginDescriptorImpl> graph,
1181                                             @Nullable IdeaPluginDescriptor coreDescriptor,
1182                                             @NotNull List<IdeaPluginDescriptorImpl> enabledPlugins,
1183                                             @NotNull Map<String, String[]> additionalLayoutMap,
1184                                             boolean usePluginClassLoader) {
1185     ArrayList<ClassLoader> loaders = new ArrayList<>();
1186     ClassLoader[] emptyClassLoaderArray = new ClassLoader[0];
1187     UrlClassLoader.Builder urlClassLoaderBuilder = createUrlClassLoaderBuilder();
1188     for (IdeaPluginDescriptorImpl rootDescriptor : enabledPlugins) {
1189       if (rootDescriptor == coreDescriptor || rootDescriptor.isUseCoreClassLoader()) {
1190         rootDescriptor.setLoader(coreLoader);
1191         continue;
1192       }
1193
1194       if (!usePluginClassLoader) {
1195         rootDescriptor.setLoader(null);
1196         continue;
1197       }
1198
1199       loaders.clear();
1200
1201       // no need to process dependencies recursively because dependency will use own classloader
1202       // (that in turn will delegate class searching to parent class loader if needed)
1203       List<IdeaPluginDescriptorImpl> dependencies = graph.getInList(rootDescriptor);
1204       if (!dependencies.isEmpty()) {
1205         loaders.ensureCapacity(dependencies.size());
1206
1207         // do not add core loader - will be added to some dependency
1208         for (IdeaPluginDescriptorImpl descriptor : dependencies) {
1209           ClassLoader loader = descriptor.getPluginClassLoader();
1210           if (loader == null) {
1211             getLogger().error(rootDescriptor.formatErrorMessage("requires missing class loader for " + toPresentableName(descriptor)));
1212           }
1213           else {
1214             loaders.add(loader);
1215           }
1216         }
1217       }
1218
1219       ClassLoader[] parentLoaders = loaders.isEmpty() ? new ClassLoader[]{coreLoader} : loaders.toArray(emptyClassLoaderArray);
1220       rootDescriptor.setLoader(createPluginClassLoader(parentLoaders, rootDescriptor, urlClassLoaderBuilder, coreLoader, additionalLayoutMap));
1221     }
1222   }
1223
1224   private static @NotNull IdeaPluginDescriptorImpl @NotNull [] getTopologicallySorted(@NotNull CachingSemiGraph<IdeaPluginDescriptorImpl> graph) {
1225     DFSTBuilder<IdeaPluginDescriptorImpl> requiredOnlyGraph = new DFSTBuilder<>(GraphGenerator.generate(graph));
1226     IdeaPluginDescriptorImpl[] sortedRequired = graph.getNodes().toArray(IdeaPluginDescriptorImpl.EMPTY_ARRAY);
1227     Comparator<IdeaPluginDescriptorImpl> comparator = requiredOnlyGraph.comparator();
1228     // there is circular reference between core and implementation-detail plugin, as not all such plugins extracted from core,
1229     // so, ensure that core plugin is always first (otherwise not possible to register actions - parent group not defined)
1230     Arrays.sort(sortedRequired, (o1, o2) -> {
1231       if (o1.getPluginId() == CORE_ID) {
1232         return -1;
1233       }
1234       else if (o2.getPluginId() == CORE_ID) {
1235         return 1;
1236       }
1237       else {
1238         return comparator.compare(o1, o2);
1239       }
1240     });
1241     return sortedRequired;
1242   }
1243
1244   @ApiStatus.Internal
1245   public static @NotNull Map<PluginId, IdeaPluginDescriptorImpl> buildPluginIdMap(@NotNull List<IdeaPluginDescriptorImpl> descriptors) {
1246     Map<PluginId, IdeaPluginDescriptorImpl> idMap = new LinkedHashMap<>(descriptors.size());
1247     Map<PluginId, List<IdeaPluginDescriptorImpl>> duplicateMap = null;
1248     for (IdeaPluginDescriptorImpl descriptor : descriptors) {
1249       Map<PluginId, List<IdeaPluginDescriptorImpl>> newDuplicateMap = checkAndPut(descriptor, descriptor.getPluginId(), idMap, duplicateMap);
1250       if (newDuplicateMap != null) {
1251         duplicateMap = newDuplicateMap;
1252         continue;
1253       }
1254
1255       for (PluginId module : descriptor.getModules()) {
1256         newDuplicateMap = checkAndPut(descriptor, module, idMap, duplicateMap);
1257         if (newDuplicateMap != null) {
1258           duplicateMap = newDuplicateMap;
1259         }
1260       }
1261     }
1262     return idMap;
1263   }
1264
1265   @SuppressWarnings("DuplicatedCode")
1266   private static @Nullable Map<PluginId, List<IdeaPluginDescriptorImpl>> checkAndPut(@NotNull IdeaPluginDescriptorImpl descriptor,
1267                                                                                      @NotNull PluginId id,
1268                                                                                      @NotNull Map<PluginId, IdeaPluginDescriptorImpl> idMap,
1269                                                                                      @Nullable Map<PluginId, List<IdeaPluginDescriptorImpl>> duplicateMap) {
1270     if (duplicateMap != null) {
1271       List<IdeaPluginDescriptorImpl> duplicates = duplicateMap.get(id);
1272       if (duplicates != null) {
1273         duplicates.add(descriptor);
1274         return duplicateMap;
1275       }
1276     }
1277
1278     IdeaPluginDescriptorImpl existingDescriptor = idMap.put(id, descriptor);
1279     if (existingDescriptor == null) {
1280       return null;
1281     }
1282
1283     // if duplicated, both are removed
1284     idMap.remove(id);
1285     if (duplicateMap == null) {
1286       duplicateMap = new LinkedHashMap<>();
1287     }
1288
1289     List<IdeaPluginDescriptorImpl> list = new ArrayList<>();
1290     list.add(existingDescriptor);
1291     list.add(descriptor);
1292     duplicateMap.put(id, list);
1293     return duplicateMap;
1294   }
1295
1296   private static boolean computePluginEnabled(@NotNull IdeaPluginDescriptorImpl descriptor,
1297                                               @NotNull Set<PluginId> loadedIds,
1298                                               @NotNull Map<PluginId, IdeaPluginDescriptorImpl> idMap,
1299                                               @NotNull Set<PluginId> disabledRequiredIds,
1300                                               @NotNull Set<PluginId> disabledPlugins,
1301                                               @NotNull List<PluginError> errors) {
1302     if (descriptor.getPluginId() == CORE_ID || descriptor.isImplementationDetail()) {
1303       return true;
1304     }
1305
1306     // no deps at all
1307     if (descriptor.pluginDependencies == null) {
1308       return true;
1309     }
1310
1311     boolean result = true;
1312     for (PluginDependency dependency : descriptor.pluginDependencies) {
1313       PluginId depId = dependency.id;
1314       if (dependency.isOptional || loadedIds.contains(depId)) {
1315         continue;
1316       }
1317
1318       result = false;
1319       IdeaPluginDescriptor dep = idMap.get(depId);
1320       if (dep != null && disabledPlugins.contains(depId)) {
1321         // broken/incompatible plugins can be updated, add them anyway
1322         disabledRequiredIds.add(dep.getPluginId());
1323       }
1324
1325       String depName = dep == null ? null : dep.getName();
1326       if (depName == null) {
1327         if (findErrorForPlugin(errors, depId) != null) {
1328           errors.add(new PluginError(descriptor, "depends on plugin " + toPresentableName(depId.getIdString()) + " that failed to load", null));
1329         }
1330         else {
1331           errors.add(new PluginError(descriptor, "requires " + toPresentableName(depId.getIdString()) + " plugin to be installed", null));
1332         }
1333       }
1334       else {
1335         PluginError error = new PluginError(descriptor, "requires " + toPresentableName(depName) + " plugin to be enabled", null);
1336         error.setDisabledDependency(dep.getPluginId());
1337         errors.add(error);
1338       }
1339     }
1340     return result;
1341   }
1342
1343   private static String toPresentableName(@Nullable IdeaPluginDescriptor descriptor) {
1344     return toPresentableName(descriptor == null ? null : descriptor.getName());
1345   }
1346
1347   private static @NotNull String toPresentableName(@Nullable String s) {
1348     return "\"" + (s == null ? "" : s) + "\"";
1349   }
1350
1351   /**
1352    * Load extensions points and extensions from a configuration file in plugin.xml format
1353    * <p>
1354    * Use it only for CoreApplicationEnvironment. Do not use otherwise. For IntelliJ Platform application and tests plugins are loaded in parallel
1355    * (including other optimizations).
1356    *
1357    * @param pluginRoot jar file or directory which contains the configuration file
1358    * @param fileName   name of the configuration file located in 'META-INF' directory under {@code pluginRoot}
1359    * @param area       area which extension points and extensions should be registered (e.g. {@link com.intellij.openapi.components.ComponentManager#getRootArea()} for application-level extensions)
1360    */
1361   public static void registerExtensionPointAndExtensions(@NotNull Path pluginRoot, @NotNull String fileName, @NotNull ExtensionsArea area) {
1362     IdeaPluginDescriptorImpl descriptor;
1363     DescriptorListLoadingContext parentContext = DescriptorListLoadingContext.createSingleDescriptorContext(
1364       DisabledPluginsState.disabledPlugins());
1365     try (DescriptorLoadingContext context = new DescriptorLoadingContext(parentContext, true, true, PathBasedJdomXIncluder.DEFAULT_PATH_RESOLVER)) {
1366       if (Files.isDirectory(pluginRoot)) {
1367         descriptor = PluginDescriptorLoader.loadDescriptorFromDir(pluginRoot, META_INF + fileName, null, context);
1368       }
1369       else {
1370         descriptor = PluginDescriptorLoader.loadDescriptorFromJar(pluginRoot, fileName, PathBasedJdomXIncluder.DEFAULT_PATH_RESOLVER, context, null);
1371       }
1372     }
1373
1374     if (descriptor == null) {
1375       getLogger().error("Cannot load " + fileName + " from " + pluginRoot);
1376       return;
1377     }
1378
1379     List<ExtensionPointImpl<?>> extensionPoints = descriptor.appContainerDescriptor.extensionPoints;
1380     if (extensionPoints != null) {
1381       ((ExtensionsAreaImpl)area).registerExtensionPoints(extensionPoints, false);
1382     }
1383     descriptor.registerExtensions((ExtensionsAreaImpl)area, descriptor, descriptor.appContainerDescriptor, null);
1384   }
1385
1386   @SuppressWarnings("NonPrivateFieldAccessedInSynchronizedContext")
1387   private static synchronized void loadAndInitializePlugins(@Nullable DescriptorListLoadingContext context, @Nullable ClassLoader coreLoader) {
1388     if (coreLoader == null) {
1389       Class<?> callerClass = ReflectionUtil.findCallerClass(1);
1390       assert callerClass != null;
1391       coreLoader = callerClass.getClassLoader();
1392     }
1393
1394     try {
1395       if (context == null) {
1396         context = PluginDescriptorLoader.loadDescriptors();
1397       }
1398       Activity activity = StartUpMeasurer.startActivity("plugin initialization");
1399       PluginManagerState initResult = initializePlugins(context, coreLoader, !isUnitTestMode);
1400
1401       ourPlugins = initResult.sortedPlugins;
1402       PluginLoadingResult result = context.result;
1403       if (!result.incompletePlugins.isEmpty()) {
1404         int oldSize = initResult.sortedPlugins.length;
1405         IdeaPluginDescriptorImpl[] all = new IdeaPluginDescriptorImpl[oldSize + result.incompletePlugins.size()];
1406         System.arraycopy(initResult.sortedPlugins, 0, all, 0, oldSize);
1407         ArrayUtil.copy(result.incompletePlugins.values(), all, oldSize);
1408         ourPlugins = all;
1409       }
1410
1411       ourPluginsToDisable = initResult.effectiveDisabledIds;
1412       ourPluginsToEnable = initResult.disabledRequiredIds;
1413       ourLoadedPlugins = initResult.sortedEnabledPlugins;
1414       ourShadowedBundledPlugins = result.getShadowedBundledIds();
1415
1416       activity.end();
1417       activity.setDescription("plugin count: " + ourLoadedPlugins.size());
1418       logPlugins(initResult.sortedPlugins);
1419     }
1420     catch (ExtensionInstantiationException e) {
1421       throw new PluginException(e, e.getExtensionOwnerId());
1422     }
1423     catch (RuntimeException e) {
1424       getLogger().error(e);
1425       throw e;
1426     }
1427   }
1428
1429   @SuppressWarnings("RedundantSuppression")
1430   public static @NotNull Logger getLogger() {
1431     // do not use class reference here
1432     //noinspection SSBasedInspection
1433     return Logger.getInstance("#com.intellij.ide.plugins.PluginManager");
1434   }
1435
1436   public static final class EssentialPluginMissingException extends RuntimeException {
1437     public final List<String> pluginIds;
1438
1439     EssentialPluginMissingException(@NotNull List<String> ids) {
1440       super("Missing essential plugins: " + String.join(", ", ids));
1441
1442       pluginIds = ids;
1443     }
1444   }
1445
1446   public static @Nullable IdeaPluginDescriptor getPlugin(@Nullable PluginId id) {
1447     if (id != null) {
1448       for (IdeaPluginDescriptor plugin : getPlugins()) {
1449         if (id == plugin.getPluginId()) {
1450           return plugin;
1451         }
1452       }
1453     }
1454     return null;
1455   }
1456
1457   public static @Nullable IdeaPluginDescriptor findPluginByModuleDependency(@NotNull PluginId id) {
1458     for (IdeaPluginDescriptor descriptor : getPlugins()) {
1459       if (descriptor instanceof IdeaPluginDescriptorImpl) {
1460         if (((IdeaPluginDescriptorImpl)descriptor).getModules().contains(id)) {
1461           return descriptor;
1462         }
1463       }
1464     }
1465     return null;
1466   }
1467
1468   public static boolean isPluginInstalled(PluginId id) {
1469     return getPlugin(id) != null;
1470   }
1471
1472   @ApiStatus.Internal
1473   public static @NotNull Map<PluginId, IdeaPluginDescriptorImpl> buildPluginIdMap() {
1474     LoadingState.COMPONENTS_REGISTERED.checkOccurred();
1475     return buildPluginIdMap(Arrays.asList(ourPlugins));
1476   }
1477
1478   /**
1479    * You must not use this method in cycle, in this case use {@link #processAllDependencies(IdeaPluginDescriptor, boolean, Map, Function)} instead
1480    * (to reuse result of {@link #buildPluginIdMap()}).
1481    *
1482    * {@link FileVisitResult#SKIP_SIBLINGS} is not supported.
1483    *
1484    * Returns {@code false} if processing was terminated because of {@link FileVisitResult#TERMINATE}, and {@code true} otherwise.
1485    */
1486   @SuppressWarnings("UnusedReturnValue")
1487   @ApiStatus.Internal
1488   public static boolean processAllDependencies(@NotNull IdeaPluginDescriptorImpl rootDescriptor,
1489                                                boolean withOptionalDeps,
1490                                                @NotNull Function<IdeaPluginDescriptor, FileVisitResult> consumer) {
1491     return processAllDependencies(rootDescriptor, withOptionalDeps, buildPluginIdMap(), consumer);
1492   }
1493
1494   @ApiStatus.Internal
1495   public static boolean processAllDependencies(@NotNull IdeaPluginDescriptorImpl rootDescriptor,
1496                                                boolean withOptionalDeps,
1497                                                @NotNull Map<PluginId, IdeaPluginDescriptorImpl> idToMap,
1498                                                @NotNull Function<IdeaPluginDescriptor, FileVisitResult> consumer) {
1499     return processAllDependencies(rootDescriptor, withOptionalDeps, idToMap, new HashSet<>(), (id, descriptor) -> descriptor != null ? consumer.apply(descriptor) : FileVisitResult.SKIP_SUBTREE);
1500   }
1501
1502   @SuppressWarnings("UnusedReturnValue")
1503   @ApiStatus.Internal
1504   public static boolean processAllDependencies(@NotNull IdeaPluginDescriptorImpl rootDescriptor,
1505                                                boolean withOptionalDeps,
1506                                                @NotNull Map<PluginId, IdeaPluginDescriptorImpl> idToMap,
1507                                                @NotNull BiFunction<? super @NotNull PluginId, ? super @Nullable IdeaPluginDescriptor, FileVisitResult> consumer) {
1508     return processAllDependencies(rootDescriptor, withOptionalDeps, idToMap, new HashSet<>(), consumer);
1509   }
1510
1511   @ApiStatus.Internal
1512   private static boolean processAllDependencies(@NotNull IdeaPluginDescriptorImpl rootDescriptor,
1513                                                boolean withOptionalDeps,
1514                                                @NotNull Map<PluginId, IdeaPluginDescriptorImpl> idToMap,
1515                                                @NotNull Set<IdeaPluginDescriptor> depProcessed,
1516                                                @NotNull BiFunction<? super PluginId, ? super IdeaPluginDescriptorImpl, FileVisitResult> consumer) {
1517
1518     if (rootDescriptor.pluginDependencies == null) {
1519       return true;
1520     }
1521
1522     for (PluginDependency dependency : rootDescriptor.pluginDependencies) {
1523       if (!withOptionalDeps && dependency.isOptional) {
1524         continue;
1525       }
1526
1527       IdeaPluginDescriptorImpl descriptor = idToMap.get(dependency.id);
1528       PluginId pluginId = descriptor == null ? dependency.id : descriptor.getPluginId();
1529       switch (consumer.apply(pluginId, descriptor)) {
1530         case TERMINATE:
1531           return false;
1532         case CONTINUE:
1533           if (descriptor != null && depProcessed.add(descriptor)) {
1534             processAllDependencies(descriptor, withOptionalDeps, idToMap, depProcessed, consumer);
1535           }
1536           break;
1537         case SKIP_SUBTREE:
1538           break;
1539         case SKIP_SIBLINGS:
1540           throw new UnsupportedOperationException("FileVisitResult.SKIP_SIBLINGS is not supported");
1541       }
1542     }
1543
1544     return true;
1545   }
1546
1547   private static @NotNull List<IdeaPluginDescriptorImpl> getOnlyEnabledPlugins(@NotNull IdeaPluginDescriptorImpl @NotNull[] sortedAll) {
1548     List<IdeaPluginDescriptorImpl> enabledPlugins = new ArrayList<>(sortedAll.length);
1549     for (IdeaPluginDescriptorImpl descriptor : sortedAll) {
1550       if (descriptor.isEnabled()) {
1551         enabledPlugins.add(descriptor);
1552        }
1553      }
1554      return enabledPlugins;
1555    }
1556
1557   /**
1558    * @deprecated Use {@link PluginManager#addDisablePluginListener}
1559    */
1560   @Deprecated
1561   public static void addDisablePluginListener(@NotNull Runnable listener) {
1562     PluginManager.getInstance().addDisablePluginListener(listener);
1563   }
1564
1565   /**
1566    * @deprecated Use {@link PluginManager#addDisablePluginListener}
1567    */
1568   @Deprecated
1569   public static void removeDisablePluginListener(@NotNull Runnable listener) {
1570     PluginManager.getInstance().removeDisablePluginListener(listener);
1571   }
1572
1573   public static synchronized boolean isUpdatedBundledPlugin(@NotNull PluginDescriptor plugin) {
1574     return ourShadowedBundledPlugins != null && ourShadowedBundledPlugins.contains(plugin.getPluginId());
1575   }
1576 }