diff --git a/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java b/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java index b417bb258e8c..3c05ce8f2f84 100644 --- a/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java +++ b/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java @@ -5,11 +5,13 @@ import com.google.common.collect.Lists; import com.intellij.execution.ExecutionException; import com.intellij.execution.process.ProcessOutput; import com.intellij.execution.util.ExecUtil; +import com.intellij.icons.AllIcons; import com.intellij.notification.Notification; import com.intellij.notification.NotificationListener; import com.intellij.notification.NotificationType; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.progress.ProgressIndicator; @@ -20,6 +22,7 @@ import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.SdkAdditionalData; import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl; import com.intellij.openapi.roots.OrderRootType; +import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.SystemInfo; @@ -36,6 +39,7 @@ import com.intellij.remotesdk.RemoteSdkData; import com.intellij.util.ArrayUtil; import com.intellij.util.Function; import com.intellij.util.SystemProperties; +import com.intellij.util.containers.HashSet; import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.net.HttpConfigurable; import com.intellij.webcore.packaging.PackagesNotificationPanel; @@ -98,6 +102,7 @@ public class PyPackageManagerImpl extends PyPackageManager { public static final String PIP = "pip-1.1"; private List myPackagesCache = null; + private Map> myDependenciesCache = null; private PyExternalProcessException myExceptionCache = null; private Sdk mySdk; @@ -180,6 +185,7 @@ public class PyPackageManagerImpl extends PyPackageManager { return "'" + pkg.getName() + "'"; } }, ", "); + if (checkDependents(packages)) return; run(new MultiExternalRunnable() { @Override @@ -200,6 +206,51 @@ public class PyPackageManagerImpl extends PyPackageManager { "Uninstall packages failed"); } + private boolean checkDependents(@NotNull final List packages) { + try { + final Map> dependentPackages = collectDependents(packages, mySdk); + final int[] warning = {0}; + if (!dependentPackages.isEmpty()) { + ApplicationManager.getApplication().invokeAndWait(new Runnable() { + @Override + public void run() { + if (dependentPackages.size() == 1) { + String message = "You are attempting to uninstall "; + List dep = new ArrayList(); + int size = 1; + for (Map.Entry> entry : dependentPackages.entrySet()) { + final Set value = entry.getValue(); + size = value.size(); + dep.add(entry.getKey() + " package which is required for " + StringUtil.join(value, ", ")); + } + message += StringUtil.join(dep, "\n"); + message += size == 1 ? " package" : " packages"; + message += "\n\nDo you want to proceed?"; + warning[0] = Messages.showYesNoDialog(message, "Warning", + AllIcons.General.BalloonWarning); + } + else { + String message = "You are attempting to uninstall packages which are required for another packages.\n\n"; + List dep = new ArrayList(); + for (Map.Entry> entry : dependentPackages.entrySet()) { + dep.add(entry.getKey() + " -> " + StringUtil.join(entry.getValue(), ", ")); + } + message += StringUtil.join(dep, "\n"); + message += "\n\nDo you want to proceed?"; + warning[0] = Messages.showYesNoDialog(message, "Warning", + AllIcons.General.BalloonWarning); + } + } + }, ModalityState.current()); + } + if (warning[0] != 0) return true; + } + catch (PyExternalProcessException e) { + LOG.info("Error loading packages dependents: " + e.getMessage(), e); + } + return false; + } + private interface MultiExternalRunnable { List run(@NotNull ProgressIndicator indicator); } @@ -402,6 +453,22 @@ public class PyPackageManagerImpl extends PyPackageManager { } } + private static Map> collectDependents(@NotNull final List packages, Sdk sdk) throws PyExternalProcessException { + Map> dependentPackages = new HashMap>(); + for (PyPackage pkg : packages) { + final Set dependents = + ((PyPackageManagerImpl)PyPackageManager.getInstance(sdk)).getDependents(pkg.getName()); + if (dependents != null && !dependents.isEmpty()) { + for (PyPackage dependent : dependents) { + if (!packages.contains(dependent)) + dependentPackages.put(pkg.getName(), dependents); + } + + } + } + return dependentPackages; + } + public static String getUserSite() { if (SystemInfo.isWindows) { final String appdata = System.getenv("APPDATA"); @@ -447,6 +514,17 @@ public class PyPackageManagerImpl extends PyPackageManager { return myPackagesCache; } + public synchronized Set getDependents(String pkg) throws PyExternalProcessException { + if (myDependenciesCache == null) { + if (myExceptionCache != null) { + throw myExceptionCache; + } + + loadPackages(); + } + return myDependenciesCache.get(pkg); + } + public synchronized void loadPackages() throws PyExternalProcessException { try { final String output = runPythonHelper(PACKAGING_TOOL, list("list")); @@ -457,6 +535,8 @@ public class PyPackageManagerImpl extends PyPackageManager { return aPackage.getName().compareTo(aPackage1.getName()); } }); + + calculateDependents(); } catch (PyExternalProcessException e) { myExceptionCache = e; @@ -465,6 +545,20 @@ public class PyPackageManagerImpl extends PyPackageManager { } } + private void calculateDependents() { + myDependenciesCache = new HashMap>(); + for (PyPackage p : myPackagesCache) { + final List requirements = p.getRequirements(); + for (PyRequirement requirement : requirements) { + final String name = requirement.getName(); + Set value = myDependenciesCache.get(name); + if (value == null) value = new HashSet(); + value.add(p); + myDependenciesCache.put(name, value); + } + } + } + @Nullable public PyPackage findPackage(String name) throws PyExternalProcessException { return findPackageByName(name, getPackages()); @@ -577,6 +671,7 @@ public class PyPackageManagerImpl extends PyPackageManager { private void clearCaches() { myPackagesCache = null; + myDependenciesCache = null; myExceptionCache = null; }