2 * Copyright 2000-2014 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.ide.plugins;
18 import com.intellij.ide.ClassUtilCore;
19 import com.intellij.ide.IdeBundle;
20 import com.intellij.ide.StartupProgress;
21 import com.intellij.ide.plugins.cl.PluginClassLoader;
22 import com.intellij.openapi.application.Application;
23 import com.intellij.openapi.application.ApplicationManager;
24 import com.intellij.openapi.application.PathManager;
25 import com.intellij.openapi.components.ExtensionAreas;
26 import com.intellij.openapi.diagnostic.Logger;
27 import com.intellij.openapi.extensions.*;
28 import com.intellij.openapi.util.*;
29 import com.intellij.openapi.util.io.FileUtil;
30 import com.intellij.openapi.util.io.StreamUtil;
31 import com.intellij.openapi.util.io.ZipFileCache;
32 import com.intellij.openapi.util.text.StringUtil;
33 import com.intellij.util.*;
34 import com.intellij.util.containers.MultiMap;
35 import com.intellij.util.execution.ParametersListUtil;
36 import com.intellij.util.graph.CachingSemiGraph;
37 import com.intellij.util.graph.DFSTBuilder;
38 import com.intellij.util.graph.Graph;
39 import com.intellij.util.graph.GraphGenerator;
40 import com.intellij.util.xmlb.XmlSerializationException;
41 import gnu.trove.THashMap;
42 import gnu.trove.THashSet;
43 import gnu.trove.TIntProcedure;
44 import org.jdom.Document;
45 import org.jetbrains.annotations.NonNls;
46 import org.jetbrains.annotations.NotNull;
47 import org.jetbrains.annotations.Nullable;
50 import java.lang.reflect.InvocationTargetException;
51 import java.lang.reflect.Method;
52 import java.net.MalformedURLException;
54 import java.net.URLClassLoader;
55 import java.net.URLDecoder;
57 import java.util.zip.ZipEntry;
58 import java.util.zip.ZipFile;
60 public class PluginManagerCore {
61 @NonNls public static final String DISABLED_PLUGINS_FILENAME = "disabled_plugins.txt";
62 @NonNls public static final String CORE_PLUGIN_ID = "com.intellij";
63 @NonNls public static final String META_INF = "META-INF";
64 @NonNls public static final String PLUGIN_XML = "plugin.xml";
65 public static final float PLUGINS_PROGRESS_MAX_VALUE = 0.3f;
66 private static final Map<PluginId,Integer> ourId2Index = new THashMap<PluginId, Integer>();
67 @NonNls static final String MODULE_DEPENDENCY_PREFIX = "com.intellij.module";
68 private static final Map<String, IdeaPluginDescriptorImpl> ourModulesToContainingPlugins = new THashMap<String, IdeaPluginDescriptorImpl>();
69 private static final PluginClassCache ourPluginClasses = new PluginClassCache();
70 @NonNls static final String SPECIAL_IDEA_PLUGIN = "IDEA CORE";
71 static final String DISABLE = "disable";
72 static final String ENABLE = "enable";
73 static final String EDIT = "edit";
74 @NonNls private static final String PROPERTY_PLUGIN_PATH = "plugin.path";
75 static List<String> ourDisabledPlugins;
76 private static MultiMap<String, String> ourBrokenPluginVersions;
77 private static IdeaPluginDescriptor[] ourPlugins;
78 static String myPluginError;
79 static List<String> myPlugins2Disable = null;
80 static LinkedHashSet<String> myPlugins2Enable = null;
81 public static String BUILD_NUMBER;
82 private static BuildNumber ourBuildNumber;
85 * do not call this method during bootstrap, should be called in a copy of PluginManager, loaded by IdeaClassLoader
87 public static synchronized IdeaPluginDescriptor[] getPlugins() {
88 if (ourPlugins == null) {
94 public static void loadDisabledPlugins(final String configPath, final Collection<String> disabledPlugins) {
95 final File file = new File(configPath, DISABLED_PLUGINS_FILENAME);
98 BufferedReader reader = new BufferedReader(new FileReader(file));
101 while ((id = reader.readLine()) != null) {
102 disabledPlugins.add(id.trim());
109 catch (IOException ignored) { }
114 public static List<String> getDisabledPlugins() {
115 if (ourDisabledPlugins == null) {
116 ourDisabledPlugins = new ArrayList<String>();
117 if (System.getProperty("idea.ignore.disabled.plugins") == null && !isUnitTestMode()) {
118 loadDisabledPlugins(PathManager.getConfigPath(), ourDisabledPlugins);
121 return ourDisabledPlugins;
124 public static boolean isBrokenPlugin(IdeaPluginDescriptor descriptor) {
125 return getBrokenPluginVersions().get(descriptor.getPluginId().getIdString()).contains(descriptor.getVersion());
128 public static MultiMap<String, String> getBrokenPluginVersions() {
129 if (ourBrokenPluginVersions == null) {
130 ourBrokenPluginVersions = MultiMap.createSet();
132 if (System.getProperty("idea.ignore.disabled.plugins") == null && !isUnitTestMode()) {
133 BufferedReader br = new BufferedReader(new InputStreamReader(PluginManagerCore.class.getResourceAsStream("/brokenPlugins.txt")));
136 while ((s = br.readLine()) != null) {
138 if (s.startsWith("//")) continue;
140 List<String> tokens = ParametersListUtil.parse(s);
141 if (tokens.isEmpty()) continue;
143 if (tokens.size() == 1) {
144 throw new RuntimeException("brokenPlugins.txt is broken. The line contains plugin name, but does not contains version: " + s);
147 String pluginId = tokens.get(0);
148 List<String> versions = tokens.subList(1, tokens.size());
150 ourBrokenPluginVersions.putValues(pluginId, versions);
153 catch (IOException e) {
154 throw new RuntimeException("Failed to read /brokenPlugins.txt", e);
157 StreamUtil.closeStream(br);
161 return ourBrokenPluginVersions;
164 static boolean isUnitTestMode() {
165 final Application app = ApplicationManager.getApplication();
166 return app != null && app.isUnitTestMode();
169 public static void savePluginsList(@NotNull Collection<String> ids, boolean append, @NotNull File plugins) throws IOException {
170 if (!plugins.isFile()) {
171 FileUtil.ensureCanCreateFile(plugins);
173 PrintWriter printWriter = new PrintWriter(new BufferedWriter(new FileWriter(plugins, append)));
175 for (String id : ids) {
176 printWriter.println(id);
185 public static boolean disablePlugin(String id) {
186 List<String> disabledPlugins = getDisabledPlugins();
187 if (disabledPlugins.contains(id)) {
190 disabledPlugins.add(id);
192 saveDisabledPlugins(disabledPlugins, false);
194 catch (IOException e) {
200 public static boolean enablePlugin(String id) {
201 if (!getDisabledPlugins().contains(id)) return false;
202 getDisabledPlugins().remove(id);
204 saveDisabledPlugins(getDisabledPlugins(), false);
206 catch (IOException e) {
212 public static void saveDisabledPlugins(Collection<String> ids, boolean append) throws IOException {
213 File plugins = new File(PathManager.getConfigPath(), DISABLED_PLUGINS_FILENAME);
214 savePluginsList(ids, append, plugins);
215 ourDisabledPlugins = null;
218 public static Logger getLogger() {
219 return LoggerHolder.ourLogger;
222 public static int getPluginLoadingOrder(PluginId id) {
223 return ourId2Index.get(id);
226 public static boolean isModuleDependency(final PluginId dependentPluginId) {
227 return dependentPluginId.getIdString().startsWith(MODULE_DEPENDENCY_PREFIX);
230 public static void checkDependants(final IdeaPluginDescriptor pluginDescriptor,
231 final Function<PluginId, IdeaPluginDescriptor> pluginId2Descriptor,
232 final Condition<PluginId> check) {
233 checkDependants(pluginDescriptor, pluginId2Descriptor, check, new THashSet<PluginId>());
236 private static boolean checkDependants(final IdeaPluginDescriptor pluginDescriptor,
237 final Function<PluginId, IdeaPluginDescriptor> pluginId2Descriptor,
238 final Condition<PluginId> check,
239 final Set<PluginId> processed) {
240 processed.add(pluginDescriptor.getPluginId());
241 final PluginId[] dependentPluginIds = pluginDescriptor.getDependentPluginIds();
242 final Set<PluginId> optionalDependencies = new THashSet<PluginId>(Arrays.asList(pluginDescriptor.getOptionalDependentPluginIds()));
243 for (final PluginId dependentPluginId : dependentPluginIds) {
244 if (processed.contains(dependentPluginId)) {
248 if (isModuleDependency(dependentPluginId) && (ourModulesToContainingPlugins.isEmpty() || ourModulesToContainingPlugins.containsKey(
249 dependentPluginId.getIdString()))) {
252 if (!optionalDependencies.contains(dependentPluginId)) {
253 if (!check.value(dependentPluginId)) {
256 final IdeaPluginDescriptor dependantPluginDescriptor = pluginId2Descriptor.fun(dependentPluginId);
257 if (dependantPluginDescriptor != null && !checkDependants(dependantPluginDescriptor, pluginId2Descriptor, check, processed)) {
265 public static void addPluginClass(@NotNull String className, PluginId pluginId, boolean loaded) {
266 ourPluginClasses.addPluginClass(className, pluginId, loaded);
270 public static PluginId getPluginByClassName(@NotNull String className) {
271 return ourPluginClasses.getPluginByClassName(className);
274 public static void dumpPluginClassStatistics() {
275 ourPluginClasses.dumpPluginClassStatistics();
278 static boolean isDependent(final IdeaPluginDescriptor descriptor,
280 Map<PluginId, IdeaPluginDescriptor> map,
281 final boolean checkModuleDependencies) {
282 for (PluginId id: descriptor.getDependentPluginIds()) {
283 if (ArrayUtil.contains(id, (Object[])descriptor.getOptionalDependentPluginIds())) {
286 if (!checkModuleDependencies && isModuleDependency(id)) {
292 final IdeaPluginDescriptor depDescriptor = map.get(id);
293 if (depDescriptor != null && isDependent(depDescriptor, on, map, checkModuleDependencies)) {
300 static boolean hasModuleDependencies(final IdeaPluginDescriptor descriptor) {
301 final PluginId[] dependentPluginIds = descriptor.getDependentPluginIds();
302 for (PluginId dependentPluginId : dependentPluginIds) {
303 if (isModuleDependency(dependentPluginId)) {
310 static boolean shouldLoadPlugins() {
312 // no plugins during bootstrap
313 Class.forName("com.intellij.openapi.extensions.Extensions");
315 catch (ClassNotFoundException e) {
318 //noinspection HardCodedStringLiteral
319 final String loadPlugins = System.getProperty("idea.load.plugins");
320 return loadPlugins == null || Boolean.TRUE.toString().equals(loadPlugins);
323 static void configureExtensions() {
324 Extensions.setLogProvider(new IdeaLogProvider());
325 Extensions.registerAreaClass(ExtensionAreas.IDEA_PROJECT, null);
326 Extensions.registerAreaClass(ExtensionAreas.IDEA_MODULE, ExtensionAreas.IDEA_PROJECT);
329 private static Method getAddUrlMethod(final ClassLoader loader) {
330 return ReflectionUtil.getDeclaredMethod(loader instanceof URLClassLoader ? URLClassLoader.class : loader.getClass(), "addURL", URL.class);
334 static ClassLoader createPluginClassLoader(@NotNull File[] classPath,
335 @NotNull ClassLoader[] parentLoaders,
336 @NotNull IdeaPluginDescriptor pluginDescriptor) {
338 if (pluginDescriptor.getUseIdeaClassLoader()) {
340 final ClassLoader loader = PluginManagerCore.class.getClassLoader();
341 final Method addUrlMethod = getAddUrlMethod(loader);
344 for (File aClassPath : classPath) {
345 final File file = aClassPath.getCanonicalFile();
346 addUrlMethod.invoke(loader, file.toURI().toURL());
351 catch (IOException e) {
354 catch (IllegalAccessException e) {
357 catch (InvocationTargetException e) {
362 PluginId pluginId = pluginDescriptor.getPluginId();
363 File pluginRoot = pluginDescriptor.getPath();
365 //if (classPath.length == 0) return null;
366 if (isUnitTestMode()) return null;
368 final List<URL> urls = new ArrayList<URL>(classPath.length);
369 for (File aClassPath : classPath) {
370 final File file = aClassPath.getCanonicalFile(); // it is critical not to have "." and ".." in classpath elements
371 urls.add(file.toURI().toURL());
373 return new PluginClassLoader(urls, parentLoaders, pluginId, pluginDescriptor.getVersion(), pluginRoot);
375 catch (MalformedURLException e) {
378 catch (IOException e) {
384 public static void invalidatePlugins() {
386 ourDisabledPlugins = null;
389 public static boolean isPluginClass(String className) {
390 return ourPlugins != null && getPluginByClassName(className) != null;
393 static void logPlugins() {
394 List<String> loadedBundled = new ArrayList<String>();
395 List<String> disabled = new ArrayList<String>();
396 List<String> loadedCustom = new ArrayList<String>();
398 for (IdeaPluginDescriptor descriptor : ourPlugins) {
399 final String version = descriptor.getVersion();
400 String s = descriptor.getName() + (version != null ? " (" + version + ")" : "");
401 if (descriptor.isEnabled()) {
402 if (descriptor.isBundled() || SPECIAL_IDEA_PLUGIN.equals(descriptor.getName())) loadedBundled.add(s);
403 else loadedCustom.add(s);
410 Collections.sort(loadedBundled);
411 Collections.sort(loadedCustom);
412 Collections.sort(disabled);
414 getLogger().info("Loaded bundled plugins: " + StringUtil.join(loadedBundled, ", "));
415 if (!loadedCustom.isEmpty()) {
416 getLogger().info("Loaded custom plugins: " + StringUtil.join(loadedCustom, ", "));
418 if (!disabled.isEmpty()) {
419 getLogger().info("Disabled plugins: " + StringUtil.join(disabled, ", "));
423 static ClassLoader[] getParentLoaders(Map<PluginId, ? extends IdeaPluginDescriptor> idToDescriptorMap, PluginId[] pluginIds) {
424 if (isUnitTestMode()) return new ClassLoader[0];
425 final List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();
426 for (final PluginId id : pluginIds) {
427 IdeaPluginDescriptor pluginDescriptor = idToDescriptorMap.get(id);
428 if (pluginDescriptor == null) {
429 continue; // Might be an optional dependency
432 final ClassLoader loader = pluginDescriptor.getPluginClassLoader();
433 if (loader == null) {
434 getLogger().error("Plugin class loader should be initialized for plugin " + id);
436 classLoaders.add(loader);
438 return classLoaders.toArray(new ClassLoader[classLoaders.size()]);
441 static int countPlugins(String pluginsPath) {
442 File configuredPluginsDir = new File(pluginsPath);
443 if (configuredPluginsDir.exists()) {
444 String[] list = configuredPluginsDir.list();
452 static Collection<URL> getClassLoaderUrls() {
453 final ClassLoader classLoader = PluginManagerCore.class.getClassLoader();
454 final Class<? extends ClassLoader> aClass = classLoader.getClass();
456 @SuppressWarnings("unchecked") List<URL> urls = (List<URL>)aClass.getMethod("getUrls").invoke(classLoader);
459 catch (IllegalAccessException ignored) { }
460 catch (InvocationTargetException ignored) { }
461 catch (NoSuchMethodException ignored) { }
463 if (classLoader instanceof URLClassLoader) {
464 return Arrays.asList(((URLClassLoader)classLoader).getURLs());
467 return Collections.emptyList();
470 static void prepareLoadingPluginsErrorMessage(final String errorMessage) {
471 if (!StringUtil.isEmptyOrSpaces(errorMessage)) {
472 if (ApplicationManager.getApplication() != null
473 && !ApplicationManager.getApplication().isHeadlessEnvironment()
474 && !ApplicationManager.getApplication().isUnitTestMode()) {
475 if (myPluginError == null) {
476 myPluginError = errorMessage;
479 myPluginError += "\n" + errorMessage;
482 getLogger().error(errorMessage);
487 private static void addModulesAsDependents(Map<PluginId, ? super IdeaPluginDescriptorImpl> map) {
488 for (Map.Entry<String, IdeaPluginDescriptorImpl> entry : ourModulesToContainingPlugins.entrySet()) {
489 map.put(PluginId.getId(entry.getKey()), entry.getValue());
493 static Comparator<IdeaPluginDescriptor> getPluginDescriptorComparator(final Map<PluginId, ? extends IdeaPluginDescriptor> idToDescriptorMap) {
494 final Graph<PluginId> graph = createPluginIdGraph(idToDescriptorMap);
495 final DFSTBuilder<PluginId> builder = new DFSTBuilder<PluginId>(graph);
496 if (!builder.isAcyclic()) {
497 builder.getSCCs().forEach(new TIntProcedure() {
500 public boolean execute(int size) {
502 for (int j = 0; j < size; j++) {
503 idToDescriptorMap.get(builder.getNodeByTNumber(myTNumber + j)).setEnabled(false);
512 final Comparator<PluginId> idComparator = builder.comparator();
513 return new Comparator<IdeaPluginDescriptor>() {
515 public int compare(@NotNull IdeaPluginDescriptor o1, @NotNull IdeaPluginDescriptor o2) {
516 final PluginId pluginId1 = o1.getPluginId();
517 final PluginId pluginId2 = o2.getPluginId();
518 if (pluginId1.getIdString().equals(CORE_PLUGIN_ID)) return -1;
519 if (pluginId2.getIdString().equals(CORE_PLUGIN_ID)) return 1;
520 return idComparator.compare(pluginId1, pluginId2);
525 private static Graph<PluginId> createPluginIdGraph(final Map<PluginId, ? extends IdeaPluginDescriptor> idToDescriptorMap) {
526 final List<PluginId> ids = new ArrayList<PluginId>(idToDescriptorMap.keySet());
527 // this magic ensures that the dependent plugins always follow their dependencies in lexicographic order
528 // needed to make sure that extensions are always in the same order
529 Collections.sort(ids, new Comparator<PluginId>() {
531 public int compare(@NotNull PluginId o1, @NotNull PluginId o2) {
532 return o2.getIdString().compareTo(o1.getIdString());
535 return GraphGenerator.create(CachingSemiGraph.create(new GraphGenerator.SemiGraph<PluginId>() {
537 public Collection<PluginId> getNodes() {
542 public Iterator<PluginId> getIn(PluginId pluginId) {
543 final IdeaPluginDescriptor descriptor = idToDescriptorMap.get(pluginId);
544 ArrayList<PluginId> plugins = new ArrayList<PluginId>();
545 for (PluginId dependentPluginId : descriptor.getDependentPluginIds()) {
546 // check for missing optional dependency
547 IdeaPluginDescriptor dep = idToDescriptorMap.get(dependentPluginId);
549 plugins.add(dep.getPluginId());
552 return plugins.iterator();
558 static IdeaPluginDescriptorImpl loadDescriptorFromDir(@NotNull File file, @NotNull String fileName) {
559 File descriptorFile = new File(file, META_INF + File.separator + fileName);
560 if (descriptorFile.exists()) {
562 IdeaPluginDescriptorImpl descriptor = new IdeaPluginDescriptorImpl(file);
563 descriptor.readExternal(descriptorFile.toURI().toURL());
566 catch (XmlSerializationException e) {
567 getLogger().info("Cannot load " + file, e);
568 prepareLoadingPluginsErrorMessage("File '" + file.getName() + "' contains invalid plugin descriptor.");
570 catch (Throwable e) {
571 getLogger().info("Cannot load " + file, e);
579 static IdeaPluginDescriptorImpl loadDescriptorFromJar(@NotNull File file, @NotNull String fileName) {
581 String fileURL = StringUtil.replace(file.toURI().toASCIIString(), "!", "%21");
582 URL jarURL = new URL("jar:" + fileURL + "!/META-INF/" + fileName);
584 ZipFile zipFile = ZipFileCache.acquire(file.getPath());
586 ZipEntry entry = zipFile.getEntry("META-INF/" + fileName);
588 Document document = JDOMUtil.loadDocument(zipFile.getInputStream(entry));
589 IdeaPluginDescriptorImpl descriptor = new IdeaPluginDescriptorImpl(file);
590 descriptor.readExternal(document, jarURL);
595 ZipFileCache.release(zipFile);
598 catch (XmlSerializationException e) {
599 getLogger().info("Cannot load " + file, e);
600 prepareLoadingPluginsErrorMessage("File '" + file.getName() + "' contains invalid plugin descriptor.");
602 catch (Throwable e) {
603 getLogger().info("Cannot load " + file, e);
610 public static IdeaPluginDescriptorImpl loadDescriptorFromJar(@NotNull File file) {
611 return loadDescriptorFromJar(file, PLUGIN_XML);
615 public static IdeaPluginDescriptorImpl loadDescriptor(@NotNull final File file, @NotNull String fileName) {
616 IdeaPluginDescriptorImpl descriptor = null;
618 if (file.isDirectory()) {
619 descriptor = loadDescriptorFromDir(file, fileName);
621 if (descriptor == null) {
622 File libDir = new File(file, "lib");
623 if (!libDir.isDirectory()) {
626 final File[] files = libDir.listFiles();
627 if (files == null || files.length == 0) {
630 Arrays.sort(files, new Comparator<File>() {
632 public int compare(@NotNull File o1, @NotNull File o2) {
633 if (o2.getName().startsWith(file.getName())) return Integer.MAX_VALUE;
634 if (o1.getName().startsWith(file.getName())) return -Integer.MAX_VALUE;
635 if (o2.getName().startsWith("resources")) return -Integer.MAX_VALUE;
636 if (o1.getName().startsWith("resources")) return Integer.MAX_VALUE;
640 for (final File f : files) {
641 if (FileUtil.isJarOrZip(f)) {
642 descriptor = loadDescriptorFromJar(f, fileName);
643 if (descriptor != null) {
644 descriptor.setPath(file);
647 // getLogger().warn("Cannot load descriptor from " + f.getName() + "");
649 else if (f.isDirectory()) {
650 IdeaPluginDescriptorImpl descriptor1 = loadDescriptorFromDir(f, fileName);
651 if (descriptor1 != null) {
652 if (descriptor != null) {
653 getLogger().info("Cannot load " + file + " because two or more plugin.xml's detected");
656 descriptor = descriptor1;
657 descriptor.setPath(file);
663 else if (StringUtil.endsWithIgnoreCase(file.getName(), ".jar") && file.exists()) {
664 descriptor = loadDescriptorFromJar(file, fileName);
667 if (descriptor != null && descriptor.getOptionalConfigs() != null && !descriptor.getOptionalConfigs().isEmpty()) {
668 final Map<PluginId, IdeaPluginDescriptorImpl> descriptors =
669 new THashMap<PluginId, IdeaPluginDescriptorImpl>(descriptor.getOptionalConfigs().size());
670 for (Map.Entry<PluginId, String> entry : descriptor.getOptionalConfigs().entrySet()) {
671 String optionalDescriptorName = entry.getValue();
672 assert !Comparing.equal(fileName, optionalDescriptorName) : "recursive dependency: " + fileName;
674 IdeaPluginDescriptorImpl optionalDescriptor = loadDescriptor(file, optionalDescriptorName);
675 if (optionalDescriptor == null && !FileUtil.isJarOrZip(file)) {
676 for (URL url : getClassLoaderUrls()) {
677 if ("file".equals(url.getProtocol())) {
678 optionalDescriptor = loadDescriptor(new File(decodeUrl(url.getFile())), optionalDescriptorName);
679 if (optionalDescriptor != null) {
685 if (optionalDescriptor != null) {
686 descriptors.put(entry.getKey(), optionalDescriptor);
689 getLogger().info("Cannot find optional descriptor " + optionalDescriptorName);
692 descriptor.setOptionalDescriptors(descriptors);
698 public static void loadDescriptors(String pluginsPath,
699 List<IdeaPluginDescriptorImpl> result,
700 @Nullable StartupProgress progress,
702 loadDescriptors(new File(pluginsPath), result, progress, pluginsCount);
705 public static void loadDescriptors(@NotNull File pluginsHome,
706 List<IdeaPluginDescriptorImpl> result,
707 @Nullable StartupProgress progress,
709 final File[] files = pluginsHome.listFiles();
711 int i = result.size();
712 for (File file : files) {
713 final IdeaPluginDescriptorImpl descriptor = loadDescriptor(file, PLUGIN_XML);
714 if (descriptor == null) continue;
715 if (progress != null) {
716 progress.showProgress(descriptor.getName(), PLUGINS_PROGRESS_MAX_VALUE * ((float)++i / pluginsCount));
718 int oldIndex = result.indexOf(descriptor);
720 final IdeaPluginDescriptorImpl oldDescriptor = result.get(oldIndex);
721 if (StringUtil.compareVersionNumbers(oldDescriptor.getVersion(), descriptor.getVersion()) < 0) {
722 result.set(oldIndex, descriptor);
726 result.add(descriptor);
733 static String filterBadPlugins(List<? extends IdeaPluginDescriptor> result, final Map<String, String> disabledPluginNames) {
734 final Map<PluginId, IdeaPluginDescriptor> idToDescriptorMap = new THashMap<PluginId, IdeaPluginDescriptor>();
735 final StringBuilder message = new StringBuilder();
736 boolean pluginsWithoutIdFound = false;
737 for (Iterator<? extends IdeaPluginDescriptor> it = result.iterator(); it.hasNext();) {
738 final IdeaPluginDescriptor descriptor = it.next();
739 final PluginId id = descriptor.getPluginId();
741 pluginsWithoutIdFound = true;
743 else if (idToDescriptorMap.containsKey(id)) {
744 message.append("<br>");
745 message.append(IdeBundle.message("message.duplicate.plugin.id"));
749 else if (descriptor.isEnabled()) {
750 idToDescriptorMap.put(id, descriptor);
753 addModulesAsDependents(idToDescriptorMap);
754 final List<String> disabledPluginIds = new SmartList<String>();
755 final LinkedHashSet<String> faultyDescriptors = new LinkedHashSet<String>();
756 for (final Iterator<? extends IdeaPluginDescriptor> it = result.iterator(); it.hasNext();) {
757 final IdeaPluginDescriptor pluginDescriptor = it.next();
758 checkDependants(pluginDescriptor, new Function<PluginId, IdeaPluginDescriptor>() {
760 public IdeaPluginDescriptor fun(final PluginId pluginId) {
761 return idToDescriptorMap.get(pluginId);
763 }, new Condition<PluginId>() {
765 public boolean value(final PluginId pluginId) {
766 if (!idToDescriptorMap.containsKey(pluginId)) {
767 pluginDescriptor.setEnabled(false);
768 if (!pluginId.getIdString().startsWith(MODULE_DEPENDENCY_PREFIX)) {
769 faultyDescriptors.add(pluginId.getIdString());
770 disabledPluginIds.add(pluginDescriptor.getPluginId().getIdString());
771 message.append("<br>");
772 final String name = pluginDescriptor.getName();
773 final IdeaPluginDescriptor descriptor = idToDescriptorMap.get(pluginId);
775 if (descriptor == null) {
776 pluginName = pluginId.getIdString();
777 if (disabledPluginNames.containsKey(pluginName)) {
778 pluginName = disabledPluginNames.get(pluginName);
782 pluginName = descriptor.getName();
785 message.append(getDisabledPlugins().contains(pluginId.getIdString())
786 ? IdeBundle.message("error.required.plugin.disabled", name, pluginName)
787 : IdeBundle.message("error.required.plugin.not.installed", name, pluginName));
796 if (!disabledPluginIds.isEmpty()) {
797 myPlugins2Disable = disabledPluginIds;
798 myPlugins2Enable = faultyDescriptors;
799 message.append("<br>");
800 message.append("<br>").append("<a href=\"" + DISABLE + "\">Disable ");
801 if (disabledPluginIds.size() == 1) {
802 final PluginId pluginId2Disable = PluginId.getId(disabledPluginIds.iterator().next());
803 message.append(idToDescriptorMap.containsKey(pluginId2Disable) ? idToDescriptorMap.get(pluginId2Disable).getName() : pluginId2Disable.getIdString());
806 message.append("not loaded plugins");
808 message.append("</a>");
809 boolean possibleToEnable = true;
810 for (String descriptor : faultyDescriptors) {
811 if (disabledPluginNames.get(descriptor) == null) {
812 possibleToEnable = false;
816 if (possibleToEnable) {
817 message.append("<br>").append("<a href=\"" + ENABLE + "\">Enable ").append(faultyDescriptors.size() == 1 ? disabledPluginNames.get(faultyDescriptors.iterator().next()) : " all necessary plugins").append("</a>");
819 message.append("<br>").append("<a href=\"" + EDIT + "\">Open plugin manager</a>");
821 if (pluginsWithoutIdFound) {
822 message.append("<br>");
823 message.append(IdeBundle.message("error.plugins.without.id.found"));
825 if (message.length() > 0) {
826 message.insert(0, IdeBundle.message("error.problems.found.loading.plugins"));
827 return message.toString();
832 static void loadDescriptorsFromClassPath(@NotNull List<IdeaPluginDescriptorImpl> result, @Nullable StartupProgress progress) {
833 Collection<URL> urls = getClassLoaderUrls();
834 String platformPrefix = System.getProperty(PlatformUtils.PLATFORM_PREFIX_KEY);
836 for (URL url : urls) {
838 if ("file".equals(url.getProtocol())) {
839 File file = new File(decodeUrl(url.getFile()));
841 IdeaPluginDescriptorImpl platformPluginDescriptor = null;
842 if (platformPrefix != null) {
843 platformPluginDescriptor = loadDescriptor(file, platformPrefix + "Plugin.xml");
844 if (platformPluginDescriptor != null && !result.contains(platformPluginDescriptor)) {
845 platformPluginDescriptor.setUseCoreClassLoader(true);
846 result.add(platformPluginDescriptor);
850 IdeaPluginDescriptorImpl pluginDescriptor = loadDescriptor(file, PLUGIN_XML);
851 if (platformPrefix != null && pluginDescriptor != null && pluginDescriptor.getName().equals(SPECIAL_IDEA_PLUGIN)) {
854 if (pluginDescriptor != null && !result.contains(pluginDescriptor)) {
855 if (platformPluginDescriptor != null) {
856 // if we found a regular plugin.xml in the same .jar/root as a platform-prefixed descriptor, use the core loader for it too
857 pluginDescriptor.setUseCoreClassLoader(true);
859 result.add(pluginDescriptor);
860 if (progress != null) {
861 progress.showProgress("Plugin loaded: " + pluginDescriptor.getName(), PLUGINS_PROGRESS_MAX_VALUE * ((float)i / urls.size()));
868 @SuppressWarnings("deprecation")
869 private static String decodeUrl(String file) {
870 String quotePluses = StringUtil.replace(file, "+", "%2B");
871 return URLDecoder.decode(quotePluses);
874 static void loadDescriptorsFromProperty(final List<IdeaPluginDescriptorImpl> result) {
875 final String pathProperty = System.getProperty(PROPERTY_PLUGIN_PATH);
876 if (pathProperty == null) return;
878 for (StringTokenizer t = new StringTokenizer(pathProperty, File.pathSeparator + ","); t.hasMoreTokens();) {
879 String s = t.nextToken();
880 final IdeaPluginDescriptorImpl ideaPluginDescriptor = loadDescriptor(new File(s), PLUGIN_XML);
881 if (ideaPluginDescriptor != null) {
882 result.add(ideaPluginDescriptor);
887 public static IdeaPluginDescriptorImpl[] loadDescriptors(@Nullable StartupProgress progress) {
888 if (ClassUtilCore.isLoadingOfExternalPluginsDisabled()) {
889 return IdeaPluginDescriptorImpl.EMPTY_ARRAY;
892 final List<IdeaPluginDescriptorImpl> result = new ArrayList<IdeaPluginDescriptorImpl>();
894 int pluginsCount = countPlugins(PathManager.getPluginsPath()) + countPlugins(PathManager.getPreInstalledPluginsPath());
895 loadDescriptors(PathManager.getPluginsPath(), result, progress, pluginsCount);
896 Application application = ApplicationManager.getApplication();
897 boolean fromSources = false;
898 if (application == null || !application.isUnitTestMode()) {
899 int size = result.size();
900 loadDescriptors(PathManager.getPreInstalledPluginsPath(), result, progress, pluginsCount);
901 fromSources = size == result.size();
904 loadDescriptorsFromProperty(result);
906 loadDescriptorsFromClassPath(result, fromSources ? progress : null);
908 IdeaPluginDescriptorImpl[] pluginDescriptors = result.toArray(new IdeaPluginDescriptorImpl[result.size()]);
909 final Map<PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap = new THashMap<PluginId, IdeaPluginDescriptorImpl>();
910 for (IdeaPluginDescriptorImpl descriptor : pluginDescriptors) {
911 idToDescriptorMap.put(descriptor.getPluginId(), descriptor);
914 Arrays.sort(pluginDescriptors, getPluginDescriptorComparator(idToDescriptorMap));
915 return pluginDescriptors;
918 static void mergeOptionalConfigs(Map<PluginId, IdeaPluginDescriptorImpl> descriptors) {
919 final Map<PluginId, IdeaPluginDescriptorImpl> descriptorsWithModules = new THashMap<PluginId, IdeaPluginDescriptorImpl>(descriptors);
920 addModulesAsDependents(descriptorsWithModules);
921 for (IdeaPluginDescriptorImpl descriptor : descriptors.values()) {
922 final Map<PluginId, IdeaPluginDescriptorImpl> optionalDescriptors = descriptor.getOptionalDescriptors();
923 if (optionalDescriptors != null && !optionalDescriptors.isEmpty()) {
924 for (Map.Entry<PluginId, IdeaPluginDescriptorImpl> entry: optionalDescriptors.entrySet()) {
925 if (descriptorsWithModules.containsKey(entry.getKey())) {
926 descriptor.mergeOptionalConfig(entry.getValue());
933 public static void initClassLoader(@NotNull ClassLoader parentLoader, @NotNull IdeaPluginDescriptorImpl descriptor) {
934 final List<File> classPath = descriptor.getClassPath();
935 final ClassLoader loader =
936 createPluginClassLoader(classPath.toArray(new File[classPath.size()]), new ClassLoader[]{parentLoader}, descriptor);
937 descriptor.setLoader(loader);
940 static BuildNumber getBuildNumber() {
941 if (ourBuildNumber == null) {
942 ourBuildNumber = BuildNumber.fromString(System.getProperty("idea.plugins.compatible.build"));
943 if (ourBuildNumber == null) {
944 ourBuildNumber = BUILD_NUMBER == null ? null : BuildNumber.fromString(BUILD_NUMBER);
945 if (ourBuildNumber == null) {
946 ourBuildNumber = BuildNumber.fallback();
950 return ourBuildNumber;
953 static boolean shouldSkipPlugin(final IdeaPluginDescriptor descriptor, IdeaPluginDescriptor[] loaded) {
954 final String idString = descriptor.getPluginId().getIdString();
955 if (CORE_PLUGIN_ID.equals(idString)) {
959 //noinspection HardCodedStringLiteral
960 final String pluginId = System.getProperty("idea.load.plugins.id");
961 if (pluginId == null) {
962 if (descriptor instanceof IdeaPluginDescriptorImpl && !descriptor.isEnabled()) return true;
964 if (!shouldLoadPlugins()) return true;
966 final List<String> pluginIds = pluginId == null ? null : StringUtil.split(pluginId, ",");
968 final boolean checkModuleDependencies = !ourModulesToContainingPlugins.isEmpty() && !ourModulesToContainingPlugins.containsKey("com.intellij.modules.all");
969 if (checkModuleDependencies && !hasModuleDependencies(descriptor)) {
974 //noinspection HardCodedStringLiteral
975 final String loadPluginCategory = System.getProperty("idea.load.plugins.category");
976 if (loadPluginCategory != null) {
977 shouldLoad = loadPluginCategory.equals(descriptor.getCategory());
980 if (pluginIds != null) {
981 shouldLoad = pluginIds.contains(idString);
983 Map<PluginId,IdeaPluginDescriptor> map = new THashMap<PluginId, IdeaPluginDescriptor>();
984 for (IdeaPluginDescriptor pluginDescriptor : loaded) {
985 map.put(pluginDescriptor.getPluginId(), pluginDescriptor);
987 addModulesAsDependents(map);
988 for (String id : pluginIds) {
989 final IdeaPluginDescriptor descriptorFromProperty = map.get(PluginId.getId(id));
990 if (descriptorFromProperty != null && isDependent(descriptorFromProperty, descriptor.getPluginId(), map, checkModuleDependencies)) {
997 shouldLoad = !getDisabledPlugins().contains(idString);
999 if (shouldLoad && descriptor instanceof IdeaPluginDescriptorImpl) {
1000 if (isIncompatible(descriptor)) return true;
1007 public static boolean isIncompatible(final IdeaPluginDescriptor descriptor) {
1008 return isIncompatible(descriptor, getBuildNumber());
1011 public static boolean isIncompatible(final IdeaPluginDescriptor descriptor, @Nullable BuildNumber buildNumber) {
1013 if (buildNumber == null) {
1014 buildNumber = getBuildNumber();
1018 if (!StringUtil.isEmpty(descriptor.getSinceBuild())) {
1019 BuildNumber sinceBuild = BuildNumber.fromString(descriptor.getSinceBuild(), descriptor.getName());
1020 if (sinceBuild.compareTo(buildNumber) > 0) {
1025 if (!StringUtil.isEmpty(descriptor.getUntilBuild()) && !buildNumber.isSnapshot()) {
1026 BuildNumber untilBuild = BuildNumber.fromString(descriptor.getUntilBuild(), descriptor.getName());
1027 if (untilBuild.compareTo(buildNumber) < 0) {
1032 catch (RuntimeException ignored) { }
1037 public static boolean shouldSkipPlugin(final IdeaPluginDescriptor descriptor) {
1038 if (descriptor instanceof IdeaPluginDescriptorImpl) {
1039 IdeaPluginDescriptorImpl descriptorImpl = (IdeaPluginDescriptorImpl)descriptor;
1040 Boolean skipped = descriptorImpl.getSkipped();
1041 if (skipped != null) {
1042 return skipped.booleanValue();
1044 boolean result = shouldSkipPlugin(descriptor, ourPlugins) || isBrokenPlugin(descriptor);
1045 descriptorImpl.setSkipped(result);
1048 return shouldSkipPlugin(descriptor, ourPlugins) || isBrokenPlugin(descriptor);
1051 static void initializePlugins(@Nullable StartupProgress progress) {
1052 configureExtensions();
1054 final IdeaPluginDescriptorImpl[] pluginDescriptors = loadDescriptors(progress);
1056 final Class callerClass = ReflectionUtil.findCallerClass(1);
1057 assert callerClass != null;
1058 final ClassLoader parentLoader = callerClass.getClassLoader();
1060 final List<IdeaPluginDescriptorImpl> result = new ArrayList<IdeaPluginDescriptorImpl>();
1061 final Map<String, String> disabledPluginNames = new THashMap<String, String>();
1062 List<String> brokenPluginsList = new SmartList<String>();
1063 for (IdeaPluginDescriptorImpl descriptor : pluginDescriptors) {
1064 boolean skipped = shouldSkipPlugin(descriptor, pluginDescriptors);
1066 if (isBrokenPlugin(descriptor)) {
1067 brokenPluginsList.add(descriptor.getName());
1073 final List<String> modules = descriptor.getModules();
1074 if (modules != null) {
1075 for (String module : modules) {
1076 if (!ourModulesToContainingPlugins.containsKey(module)) {
1077 ourModulesToContainingPlugins.put(module, descriptor);
1081 result.add(descriptor);
1084 descriptor.setEnabled(false);
1085 disabledPluginNames.put(descriptor.getPluginId().getIdString(), descriptor.getName());
1086 initClassLoader(parentLoader, descriptor);
1090 String errorMessage = filterBadPlugins(result, disabledPluginNames);
1092 if (!brokenPluginsList.isEmpty()) {
1093 if (!StringUtil.isEmptyOrSpaces(errorMessage)) {
1094 errorMessage += "<br>";
1096 errorMessage += "Following plugins are incompatible with current IDE build: " + StringUtil.join(brokenPluginsList, ", ")
1097 + "<br>\n" + StringUtil.notNullize(errorMessage);
1100 final Map<PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap = new THashMap<PluginId, IdeaPluginDescriptorImpl>();
1101 for (IdeaPluginDescriptorImpl descriptor : result) {
1102 idToDescriptorMap.put(descriptor.getPluginId(), descriptor);
1105 final IdeaPluginDescriptor corePluginDescriptor = idToDescriptorMap.get(PluginId.getId(CORE_PLUGIN_ID));
1106 assert corePluginDescriptor != null : CORE_PLUGIN_ID + " not found; platform prefix is " + System.getProperty(PlatformUtils.PLATFORM_PREFIX_KEY);
1107 for (IdeaPluginDescriptorImpl descriptor : result) {
1108 if (descriptor != corePluginDescriptor) {
1109 descriptor.insertDependency(corePluginDescriptor);
1113 mergeOptionalConfigs(idToDescriptorMap);
1114 addModulesAsDependents(idToDescriptorMap);
1116 final Graph<PluginId> graph = createPluginIdGraph(idToDescriptorMap);
1117 final DFSTBuilder<PluginId> builder = new DFSTBuilder<PluginId>(graph);
1118 if (!builder.isAcyclic()) {
1119 if (!StringUtil.isEmptyOrSpaces(errorMessage)) {
1120 errorMessage += "<br>";
1123 final String cyclePresentation;
1124 if (ApplicationManager.getApplication().isInternal()) {
1125 final List<String> cycles = new ArrayList<String>();
1126 builder.getSCCs().forEach(new TIntProcedure() {
1129 public boolean execute(int size) {
1132 for (int j = 0; j < size; j++) {
1133 cycle += builder.getNodeByTNumber(myTNumber + j).getIdString() + " ";
1141 cyclePresentation = ": " + StringUtil.join(cycles, ";");
1143 final Couple<PluginId> circularDependency = builder.getCircularDependency();
1144 final PluginId id = circularDependency.getFirst();
1145 final PluginId parentId = circularDependency.getSecond();
1146 cyclePresentation = id + "->" + parentId + "->...->" + id;
1148 errorMessage += IdeBundle.message("error.plugins.should.not.have.cyclic.dependencies") + cyclePresentation;
1151 prepareLoadingPluginsErrorMessage(errorMessage);
1153 final Comparator<PluginId> idComparator = builder.comparator();
1154 // sort descriptors according to plugin dependencies
1155 Collections.sort(result, new Comparator<IdeaPluginDescriptor>() {
1157 public int compare(@NotNull IdeaPluginDescriptor o1, @NotNull IdeaPluginDescriptor o2) {
1158 return idComparator.compare(o1.getPluginId(), o2.getPluginId());
1162 for (int i = 0; i < result.size(); i++) {
1163 ourId2Index.put(result.get(i).getPluginId(), i);
1167 for (final IdeaPluginDescriptorImpl pluginDescriptor : result) {
1168 if (pluginDescriptor.getPluginId().getIdString().equals(CORE_PLUGIN_ID) || pluginDescriptor.isUseCoreClassLoader()) {
1169 pluginDescriptor.setLoader(parentLoader);
1172 final List<File> classPath = pluginDescriptor.getClassPath();
1173 final PluginId[] dependentPluginIds = pluginDescriptor.getDependentPluginIds();
1174 final ClassLoader[] parentLoaders = getParentLoaders(idToDescriptorMap, dependentPluginIds);
1176 final ClassLoader pluginClassLoader = createPluginClassLoader(classPath.toArray(new File[classPath.size()]),
1177 parentLoaders.length > 0 ? parentLoaders : new ClassLoader[] {parentLoader},
1179 pluginDescriptor.setLoader(pluginClassLoader);
1182 if (progress != null) {
1183 progress.showProgress("", PLUGINS_PROGRESS_MAX_VALUE + (i++ / (float)result.size()) * 0.35f);
1187 registerExtensionPointsAndExtensions(Extensions.getRootArea(), result);
1188 Extensions.getRootArea().getExtensionPoint(Extensions.AREA_LISTENER_EXTENSION_POINT).registerExtension(new AreaListener() {
1190 public void areaCreated(@NotNull String areaClass, @NotNull AreaInstance areaInstance) {
1191 registerExtensionPointsAndExtensions(Extensions.getArea(areaInstance), result);
1195 public void areaDisposing(@NotNull String areaClass, @NotNull AreaInstance areaInstance) {
1200 ourPlugins = pluginDescriptors;
1203 private static void registerExtensionPointsAndExtensions(ExtensionsArea area, List<IdeaPluginDescriptorImpl> loadedPlugins) {
1204 for (IdeaPluginDescriptorImpl descriptor : loadedPlugins) {
1205 descriptor.registerExtensionPoints(area);
1208 ExtensionPoint[] extensionPoints = area.getExtensionPoints();
1209 Set<String> epNames = new THashSet<String>(extensionPoints.length);
1210 for (ExtensionPoint point : extensionPoints) {
1211 epNames.add(point.getName());
1214 for (IdeaPluginDescriptorImpl descriptor : loadedPlugins) {
1215 for (String epName : epNames) {
1216 descriptor.registerExtensions(area, epName);
1221 public static void initPlugins(@Nullable StartupProgress progress) {
1222 long start = System.currentTimeMillis();
1224 initializePlugins(progress);
1226 catch (RuntimeException e) {
1227 getLogger().error(e);
1230 getLogger().info(ourPlugins.length + " plugins initialized in " + (System.currentTimeMillis() - start) + " ms");
1232 ClassUtilCore.clearJarURLCache();
1235 private static class LoggerHolder {
1236 private static final Logger ourLogger = Logger.getInstance("#com.intellij.ide.plugins.PluginManager");
1239 private static class IdeaLogProvider implements LogProvider {
1241 public void error(String message) {
1242 getLogger().error(message);
1246 public void error(String message, Throwable t) {
1247 getLogger().error(message, t);
1251 public void error(Throwable t) {
1252 getLogger().error(t);
1256 public void warn(String message) {
1257 getLogger().info(message);
1261 public void warn(String message, Throwable t) {
1262 getLogger().info(message, t);
1266 public void warn(Throwable t) {
1267 getLogger().info(t);