Merge branch 'slava/plugin-incompatible-with'
[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; //ContainerUtil.newArrayList(rootDescriptor.getDependentPluginIds())
600       List<PluginId> incompatibleModuleIds = rootDescriptor.getIncompatibleModuleIds();
601       if (dependencies == null) {
602         dependencies = Collections.emptyList();
603       }
604
605       IdeaPluginDescriptorImpl implicitDep = getImplicitDependency(rootDescriptor, javaDep, hasAllModules);
606       int capacity = dependencies.size() + incompatibleModuleIds.size();
607       if (!withOptional) {
608         for (PluginDependency dependency : dependencies) {
609           if (dependency.isOptional) {
610             capacity--;
611           }
612         }
613       }
614       if (capacity == 0) {
615         return implicitDep == null ? Collections.emptyList() : Collections.singletonList(implicitDep);
616       }
617
618       uniqueCheck.clear();
619
620       List<IdeaPluginDescriptorImpl> plugins = new ArrayList<>(capacity + (implicitDep == null ? 0 : 1));
621       if (implicitDep != null) {
622         if (rootDescriptor == implicitDep) {
623           getLogger().error("Plugin " + rootDescriptor + " depends on self");
624         }
625         else {
626           uniqueCheck.add(implicitDep);
627           plugins.add(implicitDep);
628         }
629       }
630
631       //dependencies.addAll(incompatibleModuleIds);
632       for (PluginDependency dependency : dependencies) {
633         if (!withOptional && dependency.isOptional) {
634           continue;
635         }
636
637         // check for missing optional dependency
638         IdeaPluginDescriptorImpl dep = idToDescriptorMap.get(dependency.id);
639         // if 'dep' refers to a module we need to check the real plugin containing this module only if it's still enabled,
640         // otherwise the graph will be inconsistent
641         if (dep == null) {
642           continue;
643         }
644
645         // ultimate plugin it is combined plugin, where some included XML can define dependency on ultimate explicitly and for now not clear,
646         // can be such requirements removed or not
647         if (rootDescriptor == dep) {
648           if (rootDescriptor.getPluginId() != CORE_ID) {
649             getLogger().error("Plugin " + rootDescriptor + " depends on self");
650           }
651         }
652         else if (uniqueCheck.add(dep)) {
653           plugins.add(dep);
654         }
655       }
656       return plugins;
657     });
658   }
659
660   private static void checkPluginCycles(@NotNull List<IdeaPluginDescriptorImpl> descriptors,
661                                         @NotNull Map<PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap,
662                                         @NotNull List<PluginError> errors) {
663     CachingSemiGraph<IdeaPluginDescriptorImpl> graph = createPluginIdGraph(descriptors, idToDescriptorMap, true);
664     DFSTBuilder<IdeaPluginDescriptorImpl> builder = new DFSTBuilder<>(GraphGenerator.generate(graph));
665     if (builder.isAcyclic()) {
666       return;
667     }
668
669     StringBuilder cyclePresentation = new StringBuilder();
670     for (Collection<IdeaPluginDescriptorImpl> component : builder.getComponents()) {
671       if (component.size() < 2) {
672         continue;
673       }
674
675       if (cyclePresentation.length() > 0) {
676         cyclePresentation.append(", ");
677       }
678
679       String separator = " <-> ";
680       for (IdeaPluginDescriptor descriptor : component) {
681         descriptor.setEnabled(false);
682
683         cyclePresentation.append(descriptor.getPluginId());
684         cyclePresentation.append(separator);
685       }
686
687       cyclePresentation.setLength(cyclePresentation.length() - separator.length());
688     }
689
690     if (cyclePresentation.length() > 0) {
691       errors.add(new PluginError(null, "Plugins should not have cyclic dependencies: " + cyclePresentation, null));
692     }
693   }
694
695   @Nullable
696   static PathBasedJdomXIncluder.PathResolver<Path> createPluginJarsPathResolver(@NotNull Path pluginDir, @NotNull DescriptorLoadingContext context) {
697     List<Path> pluginJarFiles = new ArrayList<>(), dirs = new ArrayList<>();
698     if (!PluginDescriptorLoader.collectPluginDirectoryContents(pluginDir, pluginJarFiles, dirs)) return null;
699     return new PluginXmlPathResolver(pluginJarFiles, context);
700   }
701
702   public static void getDescriptorsToMigrate(@NotNull Path dir,
703                                              @Nullable BuildNumber compatibleBuildNumber,
704                                              @Nullable String bundledPluginsPath,
705                                              @Nullable Map<PluginId, Set<String>> brokenPluginVersions,
706                                              List<IdeaPluginDescriptorImpl> pluginsToMigrate,
707                                              List<IdeaPluginDescriptorImpl> incompatiblePlugins) throws ExecutionException, InterruptedException {
708     PluginLoadingResult loadingResult = new PluginLoadingResult(
709       brokenPluginVersions != null ? brokenPluginVersions : getBrokenPluginVersions(),
710       () -> compatibleBuildNumber == null ? getBuildNumber() : compatibleBuildNumber
711     );
712     DescriptorListLoadingContext context = new DescriptorListLoadingContext(0, Collections.emptySet(), loadingResult);
713     if (bundledPluginsPath != null) {
714       context.loadBundledPlugins = true;
715       context.bundledPluginsPath = bundledPluginsPath;
716     }
717     PluginDescriptorLoader.loadBundledDescriptorsAndDescriptorsFromDir(context, dir);
718
719     for (IdeaPluginDescriptorImpl descriptor : loadingResult.idMap.values()) {
720       if (!descriptor.isBundled()) {
721         if (loadingResult.isBroken(descriptor.getPluginId())) {
722           incompatiblePlugins.add(descriptor);
723         }
724         else {
725           pluginsToMigrate.add(descriptor);
726         }
727       }
728     }
729     for (IdeaPluginDescriptorImpl descriptor : loadingResult.incompletePlugins.values()) {
730       if (!descriptor.isBundled()) {
731         incompatiblePlugins.add(descriptor);
732       }
733     }
734   }
735
736   private static void prepareLoadingPluginsErrorMessage(@NotNull Map<PluginId, String> disabledIds,
737                                                         @NotNull Set<PluginId> disabledRequiredIds,
738                                                         @NotNull Map<PluginId, ? extends IdeaPluginDescriptor> idMap,
739                                                         @NotNull List<PluginError> errors) {
740     List<String> actions = new ArrayList<>();
741     if (!disabledIds.isEmpty()) {
742       String text = "<br><a href=\"" + DISABLE + "\">Disable ";
743       if (disabledIds.size() == 1) {
744         PluginId id = disabledIds.keySet().iterator().next();
745         text += idMap.containsKey(id) ? toPresentableName(idMap.get(id)) : toPresentableName(id.getIdString());
746       }
747       else {
748         text += "not loaded plugins";
749       }
750       actions.add(text + "</a>");
751       if (!disabledRequiredIds.isEmpty()) {
752         String name = disabledRequiredIds.size() == 1
753                       ? toPresentableName(idMap.get(disabledRequiredIds.iterator().next()))
754                       : "all necessary plugins";
755         actions.add("<a href=\"" + ENABLE + "\">Enable " + name + "</a>");
756       }
757       actions.add("<a href=\"" + EDIT + "\">Open plugin manager</a>");
758     }
759     prepareLoadingPluginsErrorMessage(errors, actions);
760   }
761
762   @TestOnly
763   public static @NotNull List<? extends IdeaPluginDescriptor> testLoadDescriptorsFromClassPath(@NotNull ClassLoader loader)
764     throws ExecutionException, InterruptedException {
765     Map<URL, String> urlsFromClassPath = new LinkedHashMap<>();
766     PluginDescriptorLoader.collectPluginFilesInClassPath(loader, urlsFromClassPath);
767     BuildNumber buildNumber = BuildNumber.fromString("2042.42");
768     DescriptorListLoadingContext context = new DescriptorListLoadingContext(0, Collections.emptySet(), new PluginLoadingResult(Collections.emptyMap(), () -> buildNumber, false));
769     try (DescriptorLoadingContext loadingContext = new DescriptorLoadingContext(context, true, true, new ClassPathXmlPathResolver(loader))) {
770       PluginDescriptorLoader.loadDescriptorsFromClassPath(urlsFromClassPath, loadingContext, null);
771     }
772
773     context.result.finishLoading();
774     return context.result.getEnabledPlugins();
775   }
776
777   public static void scheduleDescriptorLoading() {
778     getOrScheduleLoading();
779   }
780
781   private static synchronized @NotNull CompletableFuture<DescriptorListLoadingContext> getOrScheduleLoading() {
782     CompletableFuture<DescriptorListLoadingContext> future = descriptorListFuture;
783     if (future != null) {
784       return future;
785     }
786
787     future = CompletableFuture.supplyAsync(() -> {
788       Activity activity = StartUpMeasurer.startActivity("plugin descriptor loading");
789       DescriptorListLoadingContext context = PluginDescriptorLoader.loadDescriptors();
790       activity.end();
791       return context;
792     }, AppExecutorUtil.getAppExecutorService());
793     descriptorListFuture = future;
794     return future;
795   }
796
797   /**
798    * Think twice before use and get approve from core team. Returns enabled plugins only.
799    */
800   @ApiStatus.Internal
801   public static @NotNull List<IdeaPluginDescriptorImpl> getEnabledPluginRawList() {
802     return getOrScheduleLoading().join().result.getEnabledPlugins();
803   }
804
805   @ApiStatus.Internal
806   public static @NotNull CompletionStage<List<IdeaPluginDescriptorImpl>> initPlugins(@NotNull ClassLoader coreClassLoader) {
807     CompletableFuture<DescriptorListLoadingContext> future = descriptorListFuture;
808     if (future == null) {
809       future = CompletableFuture.completedFuture(null);
810     }
811     return future.thenApply(context -> {
812       loadAndInitializePlugins(context, coreClassLoader);
813       return ourLoadedPlugins;
814     });
815   }
816
817   static @NotNull PluginLoadingResult createLoadingResult(@Nullable BuildNumber buildNumber) {
818     return new PluginLoadingResult(getBrokenPluginVersions(), () -> buildNumber == null ? getBuildNumber() : buildNumber);
819   }
820
821   private static void mergeOptionalConfigs(@NotNull List<IdeaPluginDescriptorImpl> enabledPlugins,
822                                            @NotNull Map<PluginId, IdeaPluginDescriptorImpl> idMap) {
823     if (isRunningFromSources()) {
824       // fix optional configs
825       for (IdeaPluginDescriptorImpl descriptor : enabledPlugins) {
826         if (!descriptor.isUseCoreClassLoader() || descriptor.pluginDependencies == null) {
827           continue;
828         }
829
830         for (PluginDependency dependency : descriptor.pluginDependencies) {
831           if (dependency.subDescriptor == null) {
832             continue;
833           }
834
835           IdeaPluginDescriptorImpl dependent = idMap.get(dependency.id);
836           if (dependent != null && !dependent.isUseCoreClassLoader()) {
837             // for what?
838             dependency.subDescriptor = null;
839           }
840         }
841       }
842     }
843
844     for (IdeaPluginDescriptorImpl mainDescriptor : enabledPlugins) {
845       List<PluginDependency> pluginDependencies = mainDescriptor.pluginDependencies;
846       if (pluginDependencies != null) {
847         mergeOptionalDescriptors(mainDescriptor, pluginDependencies, idMap);
848       }
849     }
850   }
851
852   private static void mergeOptionalDescriptors(@NotNull IdeaPluginDescriptorImpl mergedDescriptor,
853                                                @NotNull List<PluginDependency> pluginDependencies,
854                                                @NotNull Map<PluginId, IdeaPluginDescriptorImpl> idMap) {
855     loop:
856     for (PluginDependency dependency : pluginDependencies) {
857       IdeaPluginDescriptorImpl subDescriptor = dependency.subDescriptor;
858       dependency.subDescriptor = null;
859
860       if (subDescriptor == null || dependency.isDisabledOrBroken) {
861         continue;
862       }
863
864       IdeaPluginDescriptorImpl dependencyDescriptor = idMap.get(dependency.id);
865       if (dependencyDescriptor == null || !dependencyDescriptor.isEnabled()) {
866         continue;
867       }
868
869       // check that plugin doesn't depend on unavailable plugin
870       if (subDescriptor.pluginDependencies != null) {
871         for (PluginDependency pluginDependency : subDescriptor.pluginDependencies) {
872           // ignore if optional
873           if (!pluginDependency.isOptional) {
874             if (pluginDependency.isDisabledOrBroken) {
875               continue loop;
876             }
877
878             IdeaPluginDescriptorImpl dependentDescriptor = idMap.get(pluginDependency.id);
879             if (dependentDescriptor == null || !dependentDescriptor.isEnabled()) {
880               continue loop;
881             }
882           }
883         }
884       }
885
886       mergedDescriptor.mergeOptionalConfig(subDescriptor);
887       List<PluginDependency> childDependencies = subDescriptor.pluginDependencies;
888       if (childDependencies != null) {
889         mergeOptionalDescriptors(mergedDescriptor, childDependencies, idMap);
890       }
891     }
892   }
893
894   private static @NotNull Map<String, String[]> loadAdditionalLayoutMap() {
895     Path fileWithLayout = PluginManagerCore.usePluginClassLoader
896                           ? Paths.get(PathManager.getSystemPath(), PlatformUtils.getPlatformPrefix() + ".txt")
897                           : null;
898     if (fileWithLayout == null || !Files.exists(fileWithLayout)) {
899       return Collections.emptyMap();
900     }
901
902     Map<String, String[]> additionalLayoutMap = new LinkedHashMap<>();
903     try (BufferedReader bufferedReader = Files.newBufferedReader(fileWithLayout)) {
904       String line;
905       while ((line = bufferedReader.readLine()) != null) {
906         List<String> parameters = ParametersListUtil.parse(line.trim());
907         if (parameters.size() < 2) {
908           continue;
909         }
910         additionalLayoutMap.put(parameters.get(0), ArrayUtilRt.toStringArray(parameters.subList(1, parameters.size())));
911       }
912     }
913     catch (Exception ignored) {
914     }
915     return additionalLayoutMap;
916   }
917
918   // not used by plugin manager - only for dynamic plugin reloading
919   @ApiStatus.Internal
920   public static void initClassLoader(@NotNull IdeaPluginDescriptorImpl rootDescriptor) {
921     Map<PluginId, IdeaPluginDescriptorImpl> idMap = buildPluginIdMap(ContainerUtil.concat(getLoadedPlugins(null), Collections.singletonList(rootDescriptor)));
922
923     Set<ClassLoader> loaders = new LinkedHashSet<>();
924     processAllDependencies(rootDescriptor, true, idMap, descriptor -> {
925       ClassLoader loader = descriptor.getPluginClassLoader();
926       if (loader == null) {
927         getLogger().error(rootDescriptor.formatErrorMessage("requires missing class loader for " + toPresentableName(descriptor)));
928       }
929       else {
930         loaders.add(loader);
931       }
932       // see configureClassLoaders about why we don't need to process recursively
933       return FileVisitResult.SKIP_SUBTREE;
934     });
935
936     IdeaPluginDescriptorImpl javaDep = idMap.get(JAVA_MODULE_ID);
937     boolean hasAllModules = idMap.containsKey(ALL_MODULES_MARKER);
938     IdeaPluginDescriptorImpl implicitDependency = getImplicitDependency(rootDescriptor, javaDep, hasAllModules);
939     if (implicitDependency != null && implicitDependency.getPluginClassLoader() != null) {
940       loaders.add(implicitDependency.getPluginClassLoader());
941     }
942
943     ClassLoader[] array = loaders.isEmpty()
944                           ? new ClassLoader[]{PluginManagerCore.class.getClassLoader()}
945                           : loaders.toArray(new ClassLoader[0]);
946     rootDescriptor.setLoader(createPluginClassLoader(array, rootDescriptor, createUrlClassLoaderBuilder(), PluginManagerCore.class.getClassLoader(), ourAdditionalLayoutMap));
947   }
948
949   private static @NotNull UrlClassLoader.Builder createUrlClassLoaderBuilder() {
950     return UrlClassLoader.build().allowLock().useCache().urlsInterned();
951   }
952
953   static @NotNull BuildNumber getBuildNumber() {
954     BuildNumber result = ourBuildNumber;
955     if (result == null) {
956       result = BuildNumber.fromString(getPluginsCompatibleBuild());
957       if (result == null) {
958         if (isUnitTestMode) {
959           result = BuildNumber.currentVersion();
960         }
961         else {
962           try {
963             result = ApplicationInfoImpl.getShadowInstance().getApiVersionAsNumber();
964           }
965           catch (RuntimeException ignore) {
966             // no need to log error - ApplicationInfo is required in production in any case, so, will be logged if really needed
967             result = BuildNumber.currentVersion();
968           }
969         }
970       }
971       ourBuildNumber = result;
972     }
973     return result;
974   }
975
976   private static void disableIncompatiblePlugins(@NotNull List<IdeaPluginDescriptorImpl> descriptors,
977                                                  @NotNull Map<PluginId, IdeaPluginDescriptorImpl> idMap,
978                                                  @NotNull List<PluginError> errors) {
979     if (ourDisableNonBundledPlugins) {
980       getLogger().info("Running with disableThirdPartyPlugins argument, third-party plugins will be disabled");
981     }
982     String selectedIds = System.getProperty("idea.load.plugins.id");
983     String selectedCategory = System.getProperty("idea.load.plugins.category");
984
985     IdeaPluginDescriptorImpl coreDescriptor = idMap.get(CORE_ID);
986     Set<IdeaPluginDescriptorImpl> explicitlyEnabled = null;
987     if (selectedIds != null) {
988       Set<PluginId> set = new HashSet<>();
989       List<String> strings = StringUtil.split(selectedIds, ",");
990       for (String it : strings) {
991         set.add(PluginId.getId(it));
992       }
993       set.addAll(((ApplicationInfoImpl)ApplicationInfoImpl.getShadowInstance()).getEssentialPluginsIds());
994
995       explicitlyEnabled = new LinkedHashSet<>(set.size());
996       for (PluginId id : set) {
997         IdeaPluginDescriptorImpl descriptor = idMap.get(id);
998         if (descriptor != null) {
999           explicitlyEnabled.add(descriptor);
1000         }
1001       }
1002     }
1003     else if (selectedCategory != null) {
1004       explicitlyEnabled = new LinkedHashSet<>();
1005       for (IdeaPluginDescriptorImpl descriptor : descriptors) {
1006         if (selectedCategory.equals(descriptor.getCategory())) {
1007           explicitlyEnabled.add(descriptor);
1008         }
1009       }
1010     }
1011
1012     if (explicitlyEnabled != null) {
1013       // add all required dependencies
1014       Set<IdeaPluginDescriptorImpl> finalExplicitlyEnabled = explicitlyEnabled;
1015       Set<IdeaPluginDescriptor> depProcessed = new HashSet<>();
1016       for (IdeaPluginDescriptorImpl descriptor : new ArrayList<>(explicitlyEnabled)) {
1017         processAllDependencies(descriptor, false, idMap, depProcessed, (id, dependency) -> {
1018           finalExplicitlyEnabled.add(dependency);
1019           return FileVisitResult.CONTINUE;
1020         });
1021       }
1022     }
1023
1024     Map<PluginId, Set<String>> brokenPluginVersions = getBrokenPluginVersions();
1025     boolean shouldLoadPlugins = Boolean.parseBoolean(System.getProperty("idea.load.plugins", "true"));
1026     for (IdeaPluginDescriptorImpl descriptor : descriptors) {
1027       if (descriptor == coreDescriptor) {
1028         continue;
1029       }
1030
1031       Set<String> set = brokenPluginVersions.get(descriptor.getPluginId());
1032       if (set != null && set.contains(descriptor.getVersion())) {
1033         descriptor.setEnabled(false);
1034         errors.add(new PluginError(descriptor, "was marked as broken", "marked as broken"));
1035       }
1036       else if (explicitlyEnabled != null) {
1037         if (!explicitlyEnabled.contains(descriptor)) {
1038           descriptor.setEnabled(false);
1039           getLogger().info("Plugin " + toPresentableName(descriptor) + " " +
1040                            (selectedIds != null
1041                             ? "is not in 'idea.load.plugins.id' system property"
1042                             : "category doesn't match 'idea.load.plugins.category' system property"));
1043         }
1044       }
1045       else if (!shouldLoadPlugins) {
1046         descriptor.setEnabled(false);
1047         errors.add(new PluginError(descriptor, "is skipped (plugins loading disabled)", null));
1048       }
1049       else if (!descriptor.isBundled() && ourDisableNonBundledPlugins) {
1050         descriptor.setEnabled(false);
1051         errors.add(new PluginError(descriptor, "is skipped (third-party plugins loading disabled)", null, false));
1052       }
1053     }
1054   }
1055
1056   public static boolean isCompatible(@NotNull IdeaPluginDescriptor descriptor) {
1057     return !isIncompatible(descriptor);
1058   }
1059
1060   public static boolean isCompatible(@NotNull IdeaPluginDescriptor descriptor, @Nullable BuildNumber buildNumber) {
1061     return !isIncompatible(descriptor, buildNumber);
1062   }
1063
1064   public static boolean isIncompatible(@NotNull IdeaPluginDescriptor descriptor) {
1065     return isIncompatible(descriptor, getBuildNumber());
1066   }
1067
1068   public static boolean isIncompatible(@NotNull IdeaPluginDescriptor descriptor, @Nullable BuildNumber buildNumber) {
1069     if (buildNumber == null) {
1070       buildNumber = getBuildNumber();
1071     }
1072     return getIncompatibleMessage(buildNumber, descriptor.getSinceBuild(), descriptor.getUntilBuild()) != null;
1073   }
1074
1075   static @Nullable String getIncompatibleMessage(@NotNull BuildNumber buildNumber, @Nullable String sinceBuild, @Nullable String untilBuild) {
1076     try {
1077       String message = null;
1078       BuildNumber sinceBuildNumber = sinceBuild == null ? null : BuildNumber.fromString(sinceBuild, null, null);
1079       if (sinceBuildNumber != null && sinceBuildNumber.compareTo(buildNumber) > 0) {
1080         message = "since build " + sinceBuildNumber + " > " + buildNumber;
1081       }
1082
1083       BuildNumber untilBuildNumber = untilBuild == null ? null : BuildNumber.fromString(untilBuild, null, null);
1084       if (untilBuildNumber != null && untilBuildNumber.compareTo(buildNumber) < 0) {
1085         if (message == null) {
1086           message = "";
1087         }
1088         else {
1089           message += ", ";
1090         }
1091         message += "until build " + untilBuildNumber + " < " + buildNumber;
1092       }
1093       return message;
1094     }
1095     catch (Exception e) {
1096       getLogger().error(e);
1097       return "version check failed";
1098     }
1099   }
1100
1101   private static void checkEssentialPluginsAreAvailable(@NotNull Map<PluginId, IdeaPluginDescriptorImpl> idMap) {
1102     List<PluginId> required = ((ApplicationInfoImpl)ApplicationInfoImpl.getShadowInstance()).getEssentialPluginsIds();
1103     List<String> missing = null;
1104     for (PluginId id : required) {
1105       IdeaPluginDescriptorImpl descriptor = idMap.get(id);
1106       if (descriptor == null || !descriptor.isEnabled()) {
1107         if (missing == null) {
1108           missing = new ArrayList<>();
1109         }
1110         missing.add(id.getIdString());
1111       }
1112     }
1113
1114     if (missing != null) {
1115       throw new EssentialPluginMissingException(missing);
1116     }
1117   }
1118
1119   static @NotNull PluginManagerState initializePlugins(@NotNull DescriptorListLoadingContext context, @NotNull ClassLoader coreLoader, boolean checkEssentialPlugins) {
1120     PluginLoadingResult loadingResult = context.result;
1121     List<PluginError> errors = new ArrayList<>(loadingResult.getErrors());
1122
1123     if (loadingResult.duplicateModuleMap != null) {
1124       for (Map.Entry<PluginId, List<IdeaPluginDescriptorImpl>> entry : loadingResult.duplicateModuleMap.entrySet()) {
1125         errors.add(new PluginError(null, "Module " + entry.getKey() + " is declared by plugins:\n  " + StringUtil.join(entry.getValue(), "\n  "), null));
1126       }
1127     }
1128
1129     Map<PluginId, IdeaPluginDescriptorImpl> idMap = loadingResult.idMap;
1130     IdeaPluginDescriptorImpl coreDescriptor = idMap.get(CORE_ID);
1131     if (checkEssentialPlugins && coreDescriptor == null) {
1132       throw new EssentialPluginMissingException(Collections.singletonList(CORE_ID + " (platform prefix: " + System.getProperty(PlatformUtils.PLATFORM_PREFIX_KEY) + ")"));
1133     }
1134
1135     List<IdeaPluginDescriptorImpl> descriptors = loadingResult.getEnabledPlugins();
1136     disableIncompatiblePlugins(descriptors, idMap, errors);
1137     checkPluginCycles(descriptors, idMap, errors);
1138
1139     // topological sort based on required dependencies only
1140     IdeaPluginDescriptorImpl[] sortedRequired = getTopologicallySorted(createPluginIdGraph(descriptors, idMap, false));
1141
1142     Set<PluginId> enabledPluginIds = new LinkedHashSet<>();
1143     Set<PluginId> enabledModuleIds = new LinkedHashSet<>();
1144     Map<PluginId, String> disabledIds = new LinkedHashMap<>();
1145     Set<PluginId> disabledRequiredIds = new LinkedHashSet<>();
1146
1147     for (IdeaPluginDescriptorImpl descriptor : sortedRequired) {
1148       boolean wasEnabled = descriptor.isEnabled();
1149       if (wasEnabled && computePluginEnabled(descriptor, enabledPluginIds, enabledModuleIds, idMap, disabledRequiredIds, context.disabledPlugins, errors)) {
1150         enabledPluginIds.add(descriptor.getPluginId());
1151         enabledModuleIds.addAll(descriptor.getModules());
1152       }
1153       else {
1154         descriptor.setEnabled(false);
1155         if (wasEnabled) {
1156           disabledIds.put(descriptor.getPluginId(), descriptor.getName());
1157         }
1158       }
1159     }
1160
1161     prepareLoadingPluginsErrorMessage(disabledIds, disabledRequiredIds, idMap, errors);
1162
1163     // topological sort based on all (required and optional) dependencies
1164     CachingSemiGraph<IdeaPluginDescriptorImpl> graph = createPluginIdGraph(Arrays.asList(sortedRequired), idMap, true);
1165     IdeaPluginDescriptorImpl[] sortedAll = getTopologicallySorted(graph);
1166
1167     List<IdeaPluginDescriptorImpl> enabledPlugins = getOnlyEnabledPlugins(sortedAll);
1168
1169     mergeOptionalConfigs(enabledPlugins, idMap);
1170     Map<String, String[]> additionalLayoutMap = loadAdditionalLayoutMap();
1171     ourAdditionalLayoutMap = additionalLayoutMap;
1172     configureClassLoaders(coreLoader, graph, coreDescriptor, enabledPlugins, additionalLayoutMap, context.usePluginClassLoader);
1173
1174     if (checkEssentialPlugins) {
1175       checkEssentialPluginsAreAvailable(idMap);
1176     }
1177
1178     Set<PluginId> effectiveDisabledIds = disabledIds.isEmpty() ? Collections.emptySet() : new HashSet<>(disabledIds.keySet());
1179     return new PluginManagerState(sortedAll, enabledPlugins, disabledRequiredIds, effectiveDisabledIds, idMap);
1180   }
1181
1182   private static void configureClassLoaders(@NotNull ClassLoader coreLoader,
1183                                             @NotNull CachingSemiGraph<IdeaPluginDescriptorImpl> graph,
1184                                             @Nullable IdeaPluginDescriptor coreDescriptor,
1185                                             @NotNull List<IdeaPluginDescriptorImpl> enabledPlugins,
1186                                             @NotNull Map<String, String[]> additionalLayoutMap,
1187                                             boolean usePluginClassLoader) {
1188     ArrayList<ClassLoader> loaders = new ArrayList<>();
1189     ClassLoader[] emptyClassLoaderArray = new ClassLoader[0];
1190     UrlClassLoader.Builder urlClassLoaderBuilder = createUrlClassLoaderBuilder();
1191     for (IdeaPluginDescriptorImpl rootDescriptor : enabledPlugins) {
1192       if (rootDescriptor == coreDescriptor || rootDescriptor.isUseCoreClassLoader()) {
1193         rootDescriptor.setLoader(coreLoader);
1194         continue;
1195       }
1196
1197       if (!usePluginClassLoader) {
1198         rootDescriptor.setLoader(null);
1199         continue;
1200       }
1201
1202       loaders.clear();
1203
1204       // no need to process dependencies recursively because dependency will use own classloader
1205       // (that in turn will delegate class searching to parent class loader if needed)
1206       List<IdeaPluginDescriptorImpl> dependencies = graph.getInList(rootDescriptor);
1207       if (!dependencies.isEmpty()) {
1208         loaders.ensureCapacity(dependencies.size());
1209
1210         // do not add core loader - will be added to some dependency
1211         for (IdeaPluginDescriptorImpl descriptor : dependencies) {
1212           ClassLoader loader = descriptor.getPluginClassLoader();
1213           if (loader == null) {
1214             getLogger().error(rootDescriptor.formatErrorMessage("requires missing class loader for " + toPresentableName(descriptor)));
1215           }
1216           else {
1217             loaders.add(loader);
1218           }
1219         }
1220       }
1221
1222       ClassLoader[] parentLoaders = loaders.isEmpty() ? new ClassLoader[]{coreLoader} : loaders.toArray(emptyClassLoaderArray);
1223       rootDescriptor.setLoader(createPluginClassLoader(parentLoaders, rootDescriptor, urlClassLoaderBuilder, coreLoader, additionalLayoutMap));
1224     }
1225   }
1226
1227   private static @NotNull IdeaPluginDescriptorImpl @NotNull [] getTopologicallySorted(@NotNull CachingSemiGraph<IdeaPluginDescriptorImpl> graph) {
1228     DFSTBuilder<IdeaPluginDescriptorImpl> requiredOnlyGraph = new DFSTBuilder<>(GraphGenerator.generate(graph));
1229     IdeaPluginDescriptorImpl[] sortedRequired = graph.getNodes().toArray(IdeaPluginDescriptorImpl.EMPTY_ARRAY);
1230     Comparator<IdeaPluginDescriptorImpl> comparator = requiredOnlyGraph.comparator();
1231     // there is circular reference between core and implementation-detail plugin, as not all such plugins extracted from core,
1232     // so, ensure that core plugin is always first (otherwise not possible to register actions - parent group not defined)
1233     Arrays.sort(sortedRequired, (o1, o2) -> {
1234       if (o1.getPluginId() == CORE_ID) {
1235         return -1;
1236       }
1237       else if (o2.getPluginId() == CORE_ID) {
1238         return 1;
1239       }
1240       else {
1241         return comparator.compare(o1, o2);
1242       }
1243     });
1244     return sortedRequired;
1245   }
1246
1247   @ApiStatus.Internal
1248   public static @NotNull Map<PluginId, IdeaPluginDescriptorImpl> buildPluginIdMap(@NotNull List<IdeaPluginDescriptorImpl> descriptors) {
1249     Map<PluginId, IdeaPluginDescriptorImpl> idMap = new LinkedHashMap<>(descriptors.size());
1250     Map<PluginId, List<IdeaPluginDescriptorImpl>> duplicateMap = null;
1251     for (IdeaPluginDescriptorImpl descriptor : descriptors) {
1252       Map<PluginId, List<IdeaPluginDescriptorImpl>> newDuplicateMap = checkAndPut(descriptor, descriptor.getPluginId(), idMap, duplicateMap);
1253       if (newDuplicateMap != null) {
1254         duplicateMap = newDuplicateMap;
1255         continue;
1256       }
1257
1258       for (PluginId module : descriptor.getModules()) {
1259         newDuplicateMap = checkAndPut(descriptor, module, idMap, duplicateMap);
1260         if (newDuplicateMap != null) {
1261           duplicateMap = newDuplicateMap;
1262         }
1263       }
1264     }
1265     return idMap;
1266   }
1267
1268   @SuppressWarnings("DuplicatedCode")
1269   private static @Nullable Map<PluginId, List<IdeaPluginDescriptorImpl>> checkAndPut(@NotNull IdeaPluginDescriptorImpl descriptor,
1270                                                                                      @NotNull PluginId id,
1271                                                                                      @NotNull Map<PluginId, IdeaPluginDescriptorImpl> idMap,
1272                                                                                      @Nullable Map<PluginId, List<IdeaPluginDescriptorImpl>> duplicateMap) {
1273     if (duplicateMap != null) {
1274       List<IdeaPluginDescriptorImpl> duplicates = duplicateMap.get(id);
1275       if (duplicates != null) {
1276         duplicates.add(descriptor);
1277         return duplicateMap;
1278       }
1279     }
1280
1281     IdeaPluginDescriptorImpl existingDescriptor = idMap.put(id, descriptor);
1282     if (existingDescriptor == null) {
1283       return null;
1284     }
1285
1286     // if duplicated, both are removed
1287     idMap.remove(id);
1288     if (duplicateMap == null) {
1289       duplicateMap = new LinkedHashMap<>();
1290     }
1291
1292     List<IdeaPluginDescriptorImpl> list = new ArrayList<>();
1293     list.add(existingDescriptor);
1294     list.add(descriptor);
1295     duplicateMap.put(id, list);
1296     return duplicateMap;
1297   }
1298
1299   private static boolean computePluginEnabled(@NotNull IdeaPluginDescriptorImpl descriptor,
1300                                               @NotNull Set<PluginId> loadedPluginIds,
1301                                               @NotNull Set<PluginId> loadedModuleIds,
1302                                               @NotNull Map<PluginId, IdeaPluginDescriptorImpl> idMap,
1303                                               @NotNull Set<PluginId> disabledRequiredIds,
1304                                               @NotNull Set<PluginId> disabledPlugins,
1305                                               @NotNull List<PluginError> errors) {
1306     if (descriptor.getPluginId() == CORE_ID || descriptor.isImplementationDetail()) {
1307       return true;
1308     }
1309
1310     boolean result = true;
1311
1312     for (PluginId incompatibleId : descriptor.getIncompatibleModuleIds()) {
1313       if (!loadedModuleIds.contains(incompatibleId) || disabledPlugins.contains(incompatibleId)) continue;
1314
1315       result = false;
1316       String presentableName = toPresentableName(incompatibleId.getIdString());
1317       errors.add(descriptor.formatErrorMessage("is incompatible with the IDE containing module " + presentableName));
1318     }
1319
1320     // no deps at all
1321     if (result && descriptor.pluginDependencies == null) {
1322       return true;
1323     }
1324
1325     for (PluginDependency dependency : descriptor.pluginDependencies) {
1326       PluginId depId = dependency.id;
1327       if (dependency.isOptional || loadedPluginIds.contains(depId)) {
1328         continue;
1329       }
1330
1331       result = false;
1332       IdeaPluginDescriptor dep = idMap.get(depId);
1333       if (dep != null && disabledPlugins.contains(depId)) {
1334         // broken/incompatible plugins can be updated, add them anyway
1335         disabledRequiredIds.add(dep.getPluginId());
1336       }
1337
1338       String depName = dep == null ? null : dep.getName();
1339       if (depName == null) {
1340         if (findErrorForPlugin(errors, depId) != null) {
1341           errors.add(new PluginError(descriptor, "depends on plugin " + toPresentableName(depId.getIdString()) + " that failed to load", null));
1342         }
1343         else {
1344           errors.add(new PluginError(descriptor, "requires " + toPresentableName(depId.getIdString()) + " plugin to be installed", null));
1345         }
1346       }
1347       else {
1348         PluginError error = new PluginError(descriptor, "requires " + toPresentableName(depName) + " plugin to be enabled", null);
1349         error.setDisabledDependency(dep.getPluginId());
1350         errors.add(error);
1351       }
1352     }
1353     return result;
1354   }
1355
1356   private static String toPresentableName(@Nullable IdeaPluginDescriptor descriptor) {
1357     return toPresentableName(descriptor == null ? null : descriptor.getName());
1358   }
1359
1360   private static @NotNull String toPresentableName(@Nullable String s) {
1361     return "\"" + (s == null ? "" : s) + "\"";
1362   }
1363
1364   /**
1365    * Load extensions points and extensions from a configuration file in plugin.xml format
1366    * <p>
1367    * Use it only for CoreApplicationEnvironment. Do not use otherwise. For IntelliJ Platform application and tests plugins are loaded in parallel
1368    * (including other optimizations).
1369    *
1370    * @param pluginRoot jar file or directory which contains the configuration file
1371    * @param fileName   name of the configuration file located in 'META-INF' directory under {@code pluginRoot}
1372    * @param area       area which extension points and extensions should be registered (e.g. {@link com.intellij.openapi.components.ComponentManager#getRootArea()} for application-level extensions)
1373    */
1374   public static void registerExtensionPointAndExtensions(@NotNull Path pluginRoot, @NotNull String fileName, @NotNull ExtensionsArea area) {
1375     IdeaPluginDescriptorImpl descriptor;
1376     DescriptorListLoadingContext parentContext = DescriptorListLoadingContext.createSingleDescriptorContext(
1377       DisabledPluginsState.disabledPlugins());
1378     try (DescriptorLoadingContext context = new DescriptorLoadingContext(parentContext, true, true, PathBasedJdomXIncluder.DEFAULT_PATH_RESOLVER)) {
1379       if (Files.isDirectory(pluginRoot)) {
1380         descriptor = PluginDescriptorLoader.loadDescriptorFromDir(pluginRoot, META_INF + fileName, null, context);
1381       }
1382       else {
1383         descriptor = PluginDescriptorLoader.loadDescriptorFromJar(pluginRoot, fileName, PathBasedJdomXIncluder.DEFAULT_PATH_RESOLVER, context, null);
1384       }
1385     }
1386
1387     if (descriptor == null) {
1388       getLogger().error("Cannot load " + fileName + " from " + pluginRoot);
1389       return;
1390     }
1391
1392     List<ExtensionPointImpl<?>> extensionPoints = descriptor.appContainerDescriptor.extensionPoints;
1393     if (extensionPoints != null) {
1394       ((ExtensionsAreaImpl)area).registerExtensionPoints(extensionPoints, false);
1395     }
1396     descriptor.registerExtensions((ExtensionsAreaImpl)area, descriptor, descriptor.appContainerDescriptor, null);
1397   }
1398
1399   @SuppressWarnings("NonPrivateFieldAccessedInSynchronizedContext")
1400   private static synchronized void loadAndInitializePlugins(@Nullable DescriptorListLoadingContext context, @Nullable ClassLoader coreLoader) {
1401     if (coreLoader == null) {
1402       Class<?> callerClass = ReflectionUtil.findCallerClass(1);
1403       assert callerClass != null;
1404       coreLoader = callerClass.getClassLoader();
1405     }
1406
1407     try {
1408       if (context == null) {
1409         context = PluginDescriptorLoader.loadDescriptors();
1410       }
1411       Activity activity = StartUpMeasurer.startActivity("plugin initialization");
1412       PluginManagerState initResult = initializePlugins(context, coreLoader, !isUnitTestMode);
1413
1414       ourPlugins = initResult.sortedPlugins;
1415       PluginLoadingResult result = context.result;
1416       if (!result.incompletePlugins.isEmpty()) {
1417         int oldSize = initResult.sortedPlugins.length;
1418         IdeaPluginDescriptorImpl[] all = new IdeaPluginDescriptorImpl[oldSize + result.incompletePlugins.size()];
1419         System.arraycopy(initResult.sortedPlugins, 0, all, 0, oldSize);
1420         ArrayUtil.copy(result.incompletePlugins.values(), all, oldSize);
1421         ourPlugins = all;
1422       }
1423
1424       ourPluginsToDisable = initResult.effectiveDisabledIds;
1425       ourPluginsToEnable = initResult.disabledRequiredIds;
1426       ourLoadedPlugins = initResult.sortedEnabledPlugins;
1427       ourShadowedBundledPlugins = result.getShadowedBundledIds();
1428
1429       activity.end();
1430       activity.setDescription("plugin count: " + ourLoadedPlugins.size());
1431       logPlugins(initResult.sortedPlugins);
1432     }
1433     catch (ExtensionInstantiationException e) {
1434       throw new PluginException(e, e.getExtensionOwnerId());
1435     }
1436     catch (RuntimeException e) {
1437       getLogger().error(e);
1438       throw e;
1439     }
1440   }
1441
1442   @SuppressWarnings("RedundantSuppression")
1443   public static @NotNull Logger getLogger() {
1444     // do not use class reference here
1445     //noinspection SSBasedInspection
1446     return Logger.getInstance("#com.intellij.ide.plugins.PluginManager");
1447   }
1448
1449   public static final class EssentialPluginMissingException extends RuntimeException {
1450     public final List<String> pluginIds;
1451
1452     EssentialPluginMissingException(@NotNull List<String> ids) {
1453       super("Missing essential plugins: " + String.join(", ", ids));
1454
1455       pluginIds = ids;
1456     }
1457   }
1458
1459   public static @Nullable IdeaPluginDescriptor getPlugin(@Nullable PluginId id) {
1460     if (id != null) {
1461       for (IdeaPluginDescriptor plugin : getPlugins()) {
1462         if (id == plugin.getPluginId()) {
1463           return plugin;
1464         }
1465       }
1466     }
1467     return null;
1468   }
1469
1470   public static @Nullable IdeaPluginDescriptor findPluginByModuleDependency(@NotNull PluginId id) {
1471     for (IdeaPluginDescriptor descriptor : getPlugins()) {
1472       if (descriptor instanceof IdeaPluginDescriptorImpl) {
1473         if (((IdeaPluginDescriptorImpl)descriptor).getModules().contains(id)) {
1474           return descriptor;
1475         }
1476       }
1477     }
1478     return null;
1479   }
1480
1481   public static boolean isPluginInstalled(PluginId id) {
1482     return getPlugin(id) != null;
1483   }
1484
1485   @ApiStatus.Internal
1486   public static @NotNull Map<PluginId, IdeaPluginDescriptorImpl> buildPluginIdMap() {
1487     LoadingState.COMPONENTS_REGISTERED.checkOccurred();
1488     return buildPluginIdMap(Arrays.asList(ourPlugins));
1489   }
1490
1491   /**
1492    * You must not use this method in cycle, in this case use {@link #processAllDependencies(IdeaPluginDescriptor, boolean, Map, Function)} instead
1493    * (to reuse result of {@link #buildPluginIdMap()}).
1494    *
1495    * {@link FileVisitResult#SKIP_SIBLINGS} is not supported.
1496    *
1497    * Returns {@code false} if processing was terminated because of {@link FileVisitResult#TERMINATE}, and {@code true} otherwise.
1498    */
1499   @SuppressWarnings("UnusedReturnValue")
1500   @ApiStatus.Internal
1501   public static boolean processAllDependencies(@NotNull IdeaPluginDescriptorImpl rootDescriptor,
1502                                                boolean withOptionalDeps,
1503                                                @NotNull Function<IdeaPluginDescriptor, FileVisitResult> consumer) {
1504     return processAllDependencies(rootDescriptor, withOptionalDeps, buildPluginIdMap(), consumer);
1505   }
1506
1507   @ApiStatus.Internal
1508   public static boolean processAllDependencies(@NotNull IdeaPluginDescriptorImpl rootDescriptor,
1509                                                boolean withOptionalDeps,
1510                                                @NotNull Map<PluginId, IdeaPluginDescriptorImpl> idToMap,
1511                                                @NotNull Function<IdeaPluginDescriptor, FileVisitResult> consumer) {
1512     return processAllDependencies(rootDescriptor, withOptionalDeps, idToMap, new HashSet<>(), (id, descriptor) -> descriptor != null ? consumer.apply(descriptor) : FileVisitResult.SKIP_SUBTREE);
1513   }
1514
1515   @SuppressWarnings("UnusedReturnValue")
1516   @ApiStatus.Internal
1517   public static boolean processAllDependencies(@NotNull IdeaPluginDescriptorImpl rootDescriptor,
1518                                                boolean withOptionalDeps,
1519                                                @NotNull Map<PluginId, IdeaPluginDescriptorImpl> idToMap,
1520                                                @NotNull BiFunction<? super @NotNull PluginId, ? super @Nullable IdeaPluginDescriptor, FileVisitResult> consumer) {
1521     return processAllDependencies(rootDescriptor, withOptionalDeps, idToMap, new HashSet<>(), consumer);
1522   }
1523
1524   @ApiStatus.Internal
1525   private static boolean processAllDependencies(@NotNull IdeaPluginDescriptorImpl rootDescriptor,
1526                                                boolean withOptionalDeps,
1527                                                @NotNull Map<PluginId, IdeaPluginDescriptorImpl> idToMap,
1528                                                @NotNull Set<IdeaPluginDescriptor> depProcessed,
1529                                                @NotNull BiFunction<? super PluginId, ? super IdeaPluginDescriptorImpl, FileVisitResult> consumer) {
1530
1531     if (rootDescriptor.pluginDependencies == null) {
1532       return true;
1533     }
1534
1535     for (PluginDependency dependency : rootDescriptor.pluginDependencies) {
1536       if (!withOptionalDeps && dependency.isOptional) {
1537         continue;
1538       }
1539
1540       IdeaPluginDescriptorImpl descriptor = idToMap.get(dependency.id);
1541       PluginId pluginId = descriptor == null ? dependency.id : descriptor.getPluginId();
1542       switch (consumer.apply(pluginId, descriptor)) {
1543         case TERMINATE:
1544           return false;
1545         case CONTINUE:
1546           if (descriptor != null && depProcessed.add(descriptor)) {
1547             processAllDependencies(descriptor, withOptionalDeps, idToMap, depProcessed, consumer);
1548           }
1549           break;
1550         case SKIP_SUBTREE:
1551           break;
1552         case SKIP_SIBLINGS:
1553           throw new UnsupportedOperationException("FileVisitResult.SKIP_SIBLINGS is not supported");
1554       }
1555     }
1556
1557     return true;
1558   }
1559
1560   private static @NotNull List<IdeaPluginDescriptorImpl> getOnlyEnabledPlugins(@NotNull IdeaPluginDescriptorImpl @NotNull[] sortedAll) {
1561     List<IdeaPluginDescriptorImpl> enabledPlugins = new ArrayList<>(sortedAll.length);
1562     for (IdeaPluginDescriptorImpl descriptor : sortedAll) {
1563       if (descriptor.isEnabled()) {
1564         enabledPlugins.add(descriptor);
1565        }
1566      }
1567      return enabledPlugins;
1568    }
1569
1570   /**
1571    * @deprecated Use {@link PluginManager#addDisablePluginListener}
1572    */
1573   @Deprecated
1574   public static void addDisablePluginListener(@NotNull Runnable listener) {
1575     PluginManager.getInstance().addDisablePluginListener(listener);
1576   }
1577
1578   /**
1579    * @deprecated Use {@link PluginManager#addDisablePluginListener}
1580    */
1581   @Deprecated
1582   public static void removeDisablePluginListener(@NotNull Runnable listener) {
1583     PluginManager.getInstance().removeDisablePluginListener(listener);
1584   }
1585
1586   public static synchronized boolean isUpdatedBundledPlugin(@NotNull PluginDescriptor plugin) {
1587     return ourShadowedBundledPlugins != null && ourShadowedBundledPlugins.contains(plugin.getPluginId());
1588   }
1589 }