diff --git a/platform/lang-impl/src/com/intellij/webcore/packaging/InstalledPackagesPanel.java b/platform/lang-impl/src/com/intellij/webcore/packaging/InstalledPackagesPanel.java
index eb2936a058d5..649a46a5a6ea 100644
--- a/platform/lang-impl/src/com/intellij/webcore/packaging/InstalledPackagesPanel.java
+++ b/platform/lang-impl/src/com/intellij/webcore/packaging/InstalledPackagesPanel.java
@@ -152,7 +152,7 @@ public class InstalledPackagesPanel extends JPanel {
@Override
public void operationFinished(String packageName,
- @Nullable String errorDescription) {
+ @Nullable PackageManagementService.ErrorDescription errorDescription) {
myNotificationArea.showResult(packageName, errorDescription);
myPackagesTable.clearSelection();
doUpdatePackages(myPackageManagementService);
@@ -230,7 +230,8 @@ public class InstalledPackagesPanel extends JPanel {
}
@Override
- public void operationFinished(final String packageName, @Nullable final String errorDescription) {
+ public void operationFinished(final String packageName,
+ @Nullable final PackageManagementService.ErrorDescription errorDescription) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
@@ -242,9 +243,8 @@ public class InstalledPackagesPanel extends JPanel {
myNotificationArea.showSuccess("Package " + packageName + " successfully upgraded");
}
else {
- myNotificationArea.showError("Upgrade packages failed. Details...",
- "Upgrade Packages Failed",
- "Upgrade packages failed.\n" + errorDescription);
+ myNotificationArea.showError("Upgrade packages failed. Details...", "Upgrade Packages Failed",
+ errorDescription);
}
if (myCurrentlyInstalling.isEmpty() && !myWaitingToUpgrade.isEmpty()) {
@@ -340,7 +340,8 @@ public class InstalledPackagesPanel extends JPanel {
}
@Override
- public void operationFinished(final String packageName, @Nullable final String errorDescription) {
+ public void operationFinished(final String packageName,
+ @Nullable final PackageManagementService.ErrorDescription errorDescription) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
@@ -356,9 +357,8 @@ public class InstalledPackagesPanel extends JPanel {
}
}
else {
- myNotificationArea.showError("Uninstall packages failed. Details...",
- "Uninstall Packages Failed",
- "Uninstall packages failed.\n" + errorDescription);
+ myNotificationArea.showError("Uninstall packages failed. Details...", "Uninstall Packages Failed",
+ errorDescription);
}
}
});
diff --git a/platform/lang-impl/src/com/intellij/webcore/packaging/ManagePackagesDialog.java b/platform/lang-impl/src/com/intellij/webcore/packaging/ManagePackagesDialog.java
index f66982282fa0..3f92bb0d19d0 100644
--- a/platform/lang-impl/src/com/intellij/webcore/packaging/ManagePackagesDialog.java
+++ b/platform/lang-impl/src/com/intellij/webcore/packaging/ManagePackagesDialog.java
@@ -95,7 +95,7 @@ public class ManagePackagesDialog extends DialogWrapper {
init();
setTitle("Available Packages");
myPackages = new JBList();
- myNotificationArea = new PackagesNotificationPanel(project);
+ myNotificationArea = new PackagesNotificationPanel();
myNotificationsAreaPlaceholder.add(myNotificationArea.getComponent(), BorderLayout.CENTER);
final AnActionButton reloadButton = new AnActionButton("Reload List of Packages", AllIcons.Actions.Refresh) {
@@ -247,7 +247,8 @@ public class ManagePackagesDialog extends DialogWrapper {
}
@Override
- public void operationFinished(final String packageName, @Nullable final String errorDescription) {
+ public void operationFinished(final String packageName,
+ @Nullable final PackageManagementService.ErrorDescription errorDescription) {
if (!ApplicationManager.getApplication().isDispatchThread()) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
@@ -277,7 +278,7 @@ public class ManagePackagesDialog extends DialogWrapper {
myPackages.repaint();
}
- private void handleInstallationFinished(String packageName, String errorDescription) {
+ private void handleInstallationFinished(String packageName, PackageManagementService.ErrorDescription errorDescription) {
if (myPackageListener != null) {
myPackageListener.operationFinished(packageName, errorDescription);
}
diff --git a/platform/lang-impl/src/com/intellij/webcore/packaging/PackageManagementService.java b/platform/lang-impl/src/com/intellij/webcore/packaging/PackageManagementService.java
index 2276bb71db31..d1f775023a3e 100644
--- a/platform/lang-impl/src/com/intellij/webcore/packaging/PackageManagementService.java
+++ b/platform/lang-impl/src/com/intellij/webcore/packaging/PackageManagementService.java
@@ -1,6 +1,7 @@
package com.intellij.webcore.packaging;
import com.intellij.util.CatchingConsumer;
+import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
@@ -130,10 +131,60 @@ public abstract class PackageManagementService {
/**
* Fired when the installation of the specified package has been completed (successfully or unsuccessfully).
- *
- * @param packageName the name of the installed package.
+ * @param packageName the name of the installed package.
* @param errorDescription null if the package has been installed successfully, error message otherwise.
*/
- void operationFinished(String packageName, @Nullable String errorDescription);
+ void operationFinished(String packageName, @Nullable ErrorDescription errorDescription);
+ }
+
+ public static class ErrorDescription {
+ @NotNull private final String myMessage;
+ @Nullable private final String myCommand;
+ @Nullable private final String myOutput;
+ @Nullable private final String mySolution;
+
+ @Nullable
+ public static ErrorDescription fromMessage(@Nullable String message) {
+ return message != null ? new ErrorDescription(message, null, null, null) : null;
+ }
+
+ public ErrorDescription(@NotNull String message, @Nullable String command, @Nullable String output, @Nullable String solution) {
+ myMessage = message;
+ myCommand = command;
+ myOutput = output;
+ mySolution = solution;
+ }
+
+ /**
+ * The reason message that explains why the error has occurred.
+ */
+ @NotNull
+ public String getMessage() {
+ return myMessage;
+ }
+
+ /**
+ * The packaging command that has been executed, if it is meaningful to the user.
+ */
+ @Nullable
+ public String getCommand() {
+ return myCommand;
+ }
+
+ /**
+ * The output of the packaging command.
+ */
+ @Nullable
+ public String getOutput() {
+ return myOutput;
+ }
+
+ /**
+ * A possible solution of this packaging problem for the user.
+ */
+ @Nullable
+ public String getSolution() {
+ return mySolution;
+ }
}
}
diff --git a/platform/lang-impl/src/com/intellij/webcore/packaging/PackagesNotificationPanel.java b/platform/lang-impl/src/com/intellij/webcore/packaging/PackagesNotificationPanel.java
index 1513bb451aa2..d5d0e43360ad 100644
--- a/platform/lang-impl/src/com/intellij/webcore/packaging/PackagesNotificationPanel.java
+++ b/platform/lang-impl/src/com/intellij/webcore/packaging/PackagesNotificationPanel.java
@@ -1,13 +1,7 @@
package com.intellij.webcore.packaging;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.ui.DialogBuilder;
import com.intellij.openapi.ui.MessageType;
-import com.intellij.openapi.ui.Messages;
-import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.HyperlinkAdapter;
-import com.intellij.ui.ScrollPaneFactory;
-import com.intellij.ui.components.JBLabel;
import com.intellij.util.ui.SwingHelper;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
@@ -23,14 +17,12 @@ import java.util.Map;
* @author yole
*/
public class PackagesNotificationPanel {
- private final Project myProject;
private final JEditorPane myHtmlViewer;
private final Map myLinkHandlers = new HashMap();
private String myErrorTitle;
- private String myErrorDescription;
+ private PackageManagementService.ErrorDescription myErrorDescription;
- public PackagesNotificationPanel(@NotNull Project project) {
- myProject = project;
+ public PackagesNotificationPanel() {
myHtmlViewer = SwingHelper.createHtmlViewer(true, null, null, null);
myHtmlViewer.setVisible(false);
myHtmlViewer.addHyperlinkListener(new HyperlinkAdapter() {
@@ -41,41 +33,19 @@ public class PackagesNotificationPanel {
handler.run();
}
else if (myErrorTitle != null && myErrorDescription != null) {
- showError(myProject, myErrorTitle, myErrorDescription);
+ showError(myErrorTitle, myErrorDescription);
}
}
});
}
- public static void showError(@NotNull Project project, @NotNull String title, @NotNull String description) {
- doShowError(title, description, new DialogBuilder(project));
+ public static void showError(@NotNull String title, @NotNull PackageManagementService.ErrorDescription description) {
+ final PackagingErrorDialog dialog = new PackagingErrorDialog(title, description);
+ dialog.show();
}
- public static void showError(@NotNull Component owner, @NotNull String title, @NotNull String description) {
- doShowError(title, description, new DialogBuilder(owner));
- }
-
- private static void doShowError(String title, String description, DialogBuilder builder) {
- builder.setTitle(title);
- final JTextArea textArea = new JTextArea();
- textArea.setEditable(false);
- textArea.setText(description);
- textArea.setWrapStyleWord(false);
- textArea.setLineWrap(true);
- final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(textArea);
- scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
- final JPanel panel = new JPanel(new BorderLayout(10, 0));
- panel.setPreferredSize(new Dimension(600, 400));
- panel.add(scrollPane, BorderLayout.CENTER);
- panel.add(new JBLabel("Details:", Messages.getErrorIcon(), SwingConstants.LEFT), BorderLayout.NORTH);
- builder.setCenterPanel(panel);
- builder.setButtonsAlignment(SwingConstants.CENTER);
- builder.addOkAction();
- builder.show();
- }
-
- public void showResult(String packageName, @Nullable String errorDescription) {
- if (StringUtil.isEmpty(errorDescription)) {
+ public void showResult(String packageName, @Nullable PackageManagementService.ErrorDescription errorDescription) {
+ if (errorDescription == null) {
String message = "Package installed successfully";
if (packageName != null) {
message = "Package '" + packageName + "' installed successfully";
@@ -88,9 +58,7 @@ public class PackagesNotificationPanel {
title = "Failed to install package '" + packageName + "'";
}
String firstLine = "Error occurred when installing package '" + packageName + "'. ";
- showError(firstLine + "Details...",
- title,
- firstLine + errorDescription);
+ showError(firstLine + "Details...", title, errorDescription);
}
}
@@ -119,10 +87,10 @@ public class PackagesNotificationPanel {
myErrorDescription = null;
}
- public void showError(String text, final String detailsTitle, final String detailsDescription) {
+ public void showError(String text, final String detailsTitle, final PackageManagementService.ErrorDescription errorDescription) {
showContent(text, MessageType.ERROR.getPopupBackground());
myErrorTitle = detailsTitle;
- myErrorDescription = detailsDescription;
+ myErrorDescription = errorDescription;
}
public void showWarning(String text) {
diff --git a/platform/lang-impl/src/com/intellij/webcore/packaging/PackagingErrorDialog.form b/platform/lang-impl/src/com/intellij/webcore/packaging/PackagingErrorDialog.form
new file mode 100644
index 000000000000..ec21749a4b76
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/webcore/packaging/PackagingErrorDialog.form
@@ -0,0 +1,191 @@
+
+
diff --git a/platform/lang-impl/src/com/intellij/webcore/packaging/PackagingErrorDialog.java b/platform/lang-impl/src/com/intellij/webcore/packaging/PackagingErrorDialog.java
new file mode 100644
index 000000000000..e3f43172a308
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/webcore/packaging/PackagingErrorDialog.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.webcore.packaging;
+
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.ui.components.JBLabel;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+/**
+ * @author vlan
+ */
+public class PackagingErrorDialog extends DialogWrapper {
+ private JPanel myMainPanel;
+ private JTextArea myCommandOutput;
+ private JPanel myCommandOutputPanel;
+ private JPanel myCommandPanel;
+ private JTextPane myCommand;
+ private JPanel mySolutionPanel;
+ private JTextPane mySolution;
+ private JPanel myDetailsPanel;
+ private JTextArea myDetails;
+ private JPanel myMessagePanel;
+ private JBLabel myDetailsLabel;
+ private JTextPane myMessage;
+ private JBLabel myMessageIcon;
+
+ public PackagingErrorDialog(@NotNull String title, @NotNull PackageManagementService.ErrorDescription errorDescription) {
+ super(false);
+ init();
+ setResizable(false);
+ setTitle(title);
+ final String command = errorDescription.getCommand();
+ final String output = errorDescription.getOutput();
+ final String message = errorDescription.getMessage();
+ final String solution = errorDescription.getSolution();
+
+ final boolean extendedInfo = command != null || output != null || solution != null;
+
+ myDetailsPanel.setVisible(!extendedInfo);
+ myMessagePanel.setVisible(extendedInfo);
+ myCommandPanel.setVisible(command != null);
+ myCommandOutputPanel.setVisible(output != null);
+ mySolutionPanel.setVisible(solution != null);
+
+ if (extendedInfo) {
+ myMessage.setText(message);
+ myMessageIcon.setIcon(Messages.getErrorIcon());
+ }
+ else {
+ myDetails.setText(message);
+ myDetailsLabel.setIcon(Messages.getErrorIcon());
+ }
+
+ if (command != null) {
+ myCommand.setText(command);
+ }
+ if (output != null) {
+ myCommandOutput.setText(output);
+ }
+ if (solution != null) {
+ mySolution.setText(solution);
+ }
+ }
+
+ @Nullable
+ @Override
+ protected JComponent createCenterPanel() {
+ return myMainPanel;
+ }
+}
diff --git a/python/helpers/packaging_tool.py b/python/helpers/packaging_tool.py
index b2b2e81a5acd..58b60a342859 100644
--- a/python/helpers/packaging_tool.py
+++ b/python/helpers/packaging_tool.py
@@ -35,7 +35,7 @@ def error(message, retcode):
def error_no_pip():
tb = sys.exc_traceback
if tb is not None and tb.tb_next is None:
- error("Python package management tool 'pip' not found", ERROR_NO_PIP)
+ error("Python packaging tool 'pip' not found", ERROR_NO_PIP)
else:
error(traceback.format_exc(), ERROR_EXCEPTION)
@@ -44,7 +44,7 @@ def do_list():
try:
import pkg_resources
except ImportError:
- error("Python package management tool 'setuptools' or 'distribute' not found", ERROR_NO_SETUPTOOLS)
+ error("Python packaging tool 'setuptools' not found", ERROR_NO_SETUPTOOLS)
for pkg in pkg_resources.working_set:
requires = ':'.join([str(x) for x in pkg.requires()])
sys.stdout.write('\t'.join([pkg.project_name, pkg.version, pkg.location, requires])+chr(10))
diff --git a/python/ide/src/com/jetbrains/python/configuration/PyActiveSdkConfigurable.java b/python/ide/src/com/jetbrains/python/configuration/PyActiveSdkConfigurable.java
index e70fb161a4c8..526dd8a47280 100644
--- a/python/ide/src/com/jetbrains/python/configuration/PyActiveSdkConfigurable.java
+++ b/python/ide/src/com/jetbrains/python/configuration/PyActiveSdkConfigurable.java
@@ -187,7 +187,7 @@ public class PyActiveSdkConfigurable implements UnnamedConfigurable {
mySdkCombo.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
mySdkCombo.setRenderer(new PySdkListCellRenderer(false));
- final PackagesNotificationPanel notificationsArea = new PackagesNotificationPanel(myProject);
+ final PackagesNotificationPanel notificationsArea = new PackagesNotificationPanel();
final JComponent notificationsComponent = notificationsArea.getComponent();
final Dimension preferredSize = mySdkCombo.getPreferredSize();
mySdkCombo.setPreferredSize(preferredSize);
diff --git a/python/ide/src/com/jetbrains/python/newProject/actions/AbstractProjectSettingsStep.java b/python/ide/src/com/jetbrains/python/newProject/actions/AbstractProjectSettingsStep.java
index 6514741fda45..5c440f5fad0d 100644
--- a/python/ide/src/com/jetbrains/python/newProject/actions/AbstractProjectSettingsStep.java
+++ b/python/ide/src/com/jetbrains/python/newProject/actions/AbstractProjectSettingsStep.java
@@ -1,5 +1,6 @@
package com.jetbrains.python.newProject.actions;
+import com.intellij.execution.ExecutionException;
import com.intellij.facet.ui.ValidationResult;
import com.intellij.icons.AllIcons;
import com.intellij.ide.impl.ProjectUtil;
@@ -322,7 +323,13 @@ abstract public class AbstractProjectSettingsStep extends AbstractActionWithPane
String warningText = frameworkName + " will be installed on selected interpreter";
myInstallFramework = true;
final PyPackageManager packageManager = PyPackageManager.getInstance(sdk);
- if (!packageManager.hasManagement(PySdkUtil.isRemote(sdk))) {
+ boolean hasManagement = false;
+ try {
+ hasManagement = packageManager.hasManagement(PySdkUtil.isRemote(sdk));
+ }
+ catch (ExecutionException ignored) {
+ }
+ if (!hasManagement) {
warningText = "Python packaging tools and " + warningText;
}
setWarningText(warningText);
diff --git a/python/openapi/src/com/jetbrains/python/packaging/PyExecutionException.java b/python/openapi/src/com/jetbrains/python/packaging/PyExecutionException.java
new file mode 100644
index 000000000000..e2a7ae1697d1
--- /dev/null
+++ b/python/openapi/src/com/jetbrains/python/packaging/PyExecutionException.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2000-2013 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.packaging;
+
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.process.ProcessOutput;
+import com.intellij.openapi.util.text.StringUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author vlan
+ */
+public class PyExecutionException extends ExecutionException {
+ @NotNull private String myCommand;
+ @NotNull private List myArgs;
+ @NotNull private final String myStdout;
+ @NotNull private final String myStderr;
+ private final int myExitCode;
+ @NotNull private final List extends PyExecutionFix> myFixes;
+
+ public PyExecutionException(@NotNull String message, @NotNull String command, @NotNull List args) {
+ this(message, command, args, "", "", 0, Collections.emptyList());
+ }
+
+ public PyExecutionException(@NotNull String message, @NotNull String command, @NotNull List args, @NotNull ProcessOutput output) {
+ this(message, command, args, output.getStdout(), output.getStderr(), output.getExitCode(), Collections.emptyList());
+ }
+
+ public PyExecutionException(@NotNull String message, @NotNull String command, @NotNull List args,
+ @NotNull String stdout, @NotNull String stderr, int exitCode,
+ @NotNull List extends PyExecutionFix> fixes) {
+ super(message);
+ myCommand = command;
+ myArgs = args;
+ myStdout = stdout;
+ myStderr = stderr;
+ myExitCode = exitCode;
+ myFixes = fixes;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder b = new StringBuilder();
+ b.append("The following command was executed:\n\n");
+ final String command = getCommand() + " " + StringUtil.join(getArgs(), " ");
+ b.append(command);
+ b.append("\n\n");
+ b.append("The error output of the command:\n\n");
+ b.append(myStdout);
+ b.append("\n");
+ b.append(myStderr);
+ return b.toString();
+ }
+
+ @NotNull
+ public String getCommand() {
+ return myCommand;
+ }
+
+ @NotNull
+ public List getArgs() {
+ return myArgs;
+ }
+
+ @NotNull
+ public List extends PyExecutionFix> getFixes() {
+ return myFixes;
+ }
+
+ public int getExitCode() {
+ return myExitCode;
+ }
+
+ @NotNull
+ public String getStdout() {
+ return myStdout;
+ }
+
+ @NotNull
+ public String getStderr() {
+ return myStderr;
+ }
+}
diff --git a/python/openapi/src/com/jetbrains/python/packaging/PyProcessCancelledException.java b/python/openapi/src/com/jetbrains/python/packaging/PyExecutionFix.java
similarity index 74%
rename from python/openapi/src/com/jetbrains/python/packaging/PyProcessCancelledException.java
rename to python/openapi/src/com/jetbrains/python/packaging/PyExecutionFix.java
index 9721bec0bb86..439a3dee62fb 100644
--- a/python/openapi/src/com/jetbrains/python/packaging/PyProcessCancelledException.java
+++ b/python/openapi/src/com/jetbrains/python/packaging/PyExecutionFix.java
@@ -15,15 +15,13 @@
*/
package com.jetbrains.python.packaging;
+import com.intellij.openapi.projectRoots.Sdk;
import org.jetbrains.annotations.NotNull;
-import java.util.List;
-
/**
* @author vlan
*/
-public class PyProcessCancelledException extends PyExternalProcessException {
- public PyProcessCancelledException(@NotNull String name, @NotNull List args) {
- super(255, name, args, "Process cancelled");
- }
+public interface PyExecutionFix {
+ @NotNull String getName();
+ void run(@NotNull Sdk sdk);
}
diff --git a/python/openapi/src/com/jetbrains/python/packaging/PyExternalProcessException.java b/python/openapi/src/com/jetbrains/python/packaging/PyExternalProcessException.java
deleted file mode 100644
index 06e233717fd6..000000000000
--- a/python/openapi/src/com/jetbrains/python/packaging/PyExternalProcessException.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright 2000-2013 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.jetbrains.python.packaging;
-
-import com.intellij.execution.ExecutionException;
-import com.intellij.openapi.util.Pair;
-import com.intellij.openapi.util.text.StringUtil;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Pattern;
-
-/**
- * @author vlan
- */
-public class PyExternalProcessException extends ExecutionException {
- private static final Pattern WITH_CR_DELIMITER_PATTERN = Pattern.compile("(?<=\r|\n|\r\n)");
-
- private final int myRetcode;
- @NotNull private String myName;
- @NotNull private List myArgs;
- @NotNull private String myMessage;
-
- private Pair myHandler = null;
-
- public PyExternalProcessException(int retcode, @NotNull String name, @NotNull List args, @NotNull String message) {
- super(String.format("External process error '%s %s':\n%s", name, StringUtil.join(args, " "), message));
- myRetcode = retcode;
- myName = name;
- myArgs = args;
- myMessage = stripLinesWithoutLineFeeds(message);
- }
-
- public PyExternalProcessException(int retcode, @NotNull String name, @NotNull List args, @NotNull String message, Throwable cause) {
- super(String.format("External process error '%s %s':\n%s", name, StringUtil.join(args, " "), message), cause);
- myRetcode = retcode;
- myName = name;
- myArgs = args;
- myMessage = stripLinesWithoutLineFeeds(message);
- }
-
- @Override
- public String toString() {
- final StringBuilder b = new StringBuilder();
- b.append("The following command was executed:\n\n");
- final String command = getName() + " " + StringUtil.join(getArgs(), " ");
- b.append(command);
- b.append("\n\n");
- b.append("The error output of the command:\n\n");
- b.append(getMessage());
- return b.toString();
- }
-
- public int getRetcode() {
- return myRetcode;
- }
-
- @NotNull
- public String getName() {
- return myName;
- }
-
- @NotNull
- public List getArgs() {
- return myArgs;
- }
-
- @NotNull
- public String getMessage() {
- return myMessage;
- }
-
- @NotNull
- private static String stripLinesWithoutLineFeeds(@NotNull String s) {
- final String[] lines = WITH_CR_DELIMITER_PATTERN.split(s);
- final List result = new ArrayList();
- for (String line : lines) {
- if (!line.endsWith("\r")) {
- result.add(line);
- }
- }
- return StringUtil.join(result, "");
- }
-
- public PyExternalProcessException withHandler(@NotNull String name, @NotNull Runnable handler) {
- myHandler = Pair.create(name, handler);
- return this;
- }
-
-
- public boolean hasHandler() {
- return myHandler != null;
- }
-
- public Pair getHandler() {
- return myHandler;
- }
-}
diff --git a/python/openapi/src/com/jetbrains/python/packaging/PyPackageManager.java b/python/openapi/src/com/jetbrains/python/packaging/PyPackageManager.java
index 82c566fd1249..add5b199016d 100644
--- a/python/openapi/src/com/jetbrains/python/packaging/PyPackageManager.java
+++ b/python/openapi/src/com/jetbrains/python/packaging/PyPackageManager.java
@@ -15,6 +15,7 @@
*/
package com.jetbrains.python.packaging;
+import com.intellij.execution.ExecutionException;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.util.Key;
@@ -40,20 +41,20 @@ public abstract class PyPackageManager {
return PyPackageManagers.getInstance().forSdk(sdk);
}
- public abstract void installManagement() throws PyExternalProcessException;
- public abstract boolean hasManagement(boolean cachedOnly);
- public abstract void install(@NotNull String requirementString) throws PyExternalProcessException;
- public abstract void install(@NotNull List requirements, @NotNull List extraArgs) throws PyExternalProcessException;
- public abstract void uninstall(@NotNull List packages) throws PyExternalProcessException;
+ public abstract void installManagement() throws ExecutionException;
+ public abstract boolean hasManagement(boolean cachedOnly) throws ExecutionException;
+ public abstract void install(@NotNull String requirementString) throws ExecutionException;
+ public abstract void install(@NotNull List requirements, @NotNull List extraArgs) throws ExecutionException;
+ public abstract void uninstall(@NotNull List packages) throws ExecutionException;
public abstract void refresh();
@NotNull
- public abstract String createVirtualEnv(@NotNull String destinationDir, boolean useGlobalSite) throws PyExternalProcessException;
+ public abstract String createVirtualEnv(@NotNull String destinationDir, boolean useGlobalSite) throws ExecutionException;
@Nullable
- public abstract List getPackages(boolean cachedOnly) throws PyExternalProcessException;
+ public abstract List getPackages(boolean cachedOnly) throws ExecutionException;
@Nullable
- public abstract PyPackage findPackage(@NotNull String name, boolean cachedOnly) throws PyExternalProcessException;
+ public abstract PyPackage findPackage(@NotNull String name, boolean cachedOnly) throws ExecutionException;
@Nullable
public abstract List getRequirements(@NotNull Module module);
@Nullable
- public abstract Set getDependents(@NotNull PyPackage pkg) throws PyExternalProcessException;
+ public abstract Set getDependents(@NotNull PyPackage pkg) throws ExecutionException;
}
diff --git a/python/openapi/src/com/jetbrains/python/templateLanguages/PyTemplatesUtil.java b/python/openapi/src/com/jetbrains/python/templateLanguages/PyTemplatesUtil.java
index c37c65ed4144..a770220886e0 100644
--- a/python/openapi/src/com/jetbrains/python/templateLanguages/PyTemplatesUtil.java
+++ b/python/openapi/src/com/jetbrains/python/templateLanguages/PyTemplatesUtil.java
@@ -15,9 +15,9 @@
*/
package com.jetbrains.python.templateLanguages;
+import com.intellij.execution.ExecutionException;
import com.intellij.facet.ui.ValidationResult;
import com.intellij.openapi.projectRoots.Sdk;
-import com.jetbrains.python.packaging.PyExternalProcessException;
import com.jetbrains.python.packaging.PyPackage;
import com.jetbrains.python.packaging.PyPackageManager;
import org.jetbrains.annotations.NonNls;
@@ -44,7 +44,7 @@ public class PyTemplatesUtil {
if (installedPackage == null)
return new ValidationResult(templateBinding + " will be installed on selected interpreter");
}
- catch (PyExternalProcessException ignored) {
+ catch (ExecutionException ignored) {
}
}
}
@@ -55,7 +55,7 @@ public class PyTemplatesUtil {
return new ValidationResult(language + " will be installed on selected interpreter");
}
}
- catch (PyExternalProcessException ignored) {
+ catch (ExecutionException ignored) {
}
}
return null;
diff --git a/python/pluginSrc/com/jetbrains/python/packaging/PyManagePackagesDialog.java b/python/pluginSrc/com/jetbrains/python/packaging/PyManagePackagesDialog.java
index 4a9df08f6eb7..33cf7ccbe9c3 100644
--- a/python/pluginSrc/com/jetbrains/python/packaging/PyManagePackagesDialog.java
+++ b/python/pluginSrc/com/jetbrains/python/packaging/PyManagePackagesDialog.java
@@ -50,7 +50,7 @@ public class PyManagePackagesDialog extends DialogWrapper {
final JComboBox sdkComboBox = new JComboBox(new CollectionComboBoxModel(sdks, sdk));
sdkComboBox.setRenderer(new PySdkListCellRenderer(false));
- PackagesNotificationPanel notificationPanel = new PackagesNotificationPanel(project);
+ PackagesNotificationPanel notificationPanel = new PackagesNotificationPanel();
final PyInstalledPackagesPanel packagesPanel = new PyInstalledPackagesPanel(project, notificationPanel);
packagesPanel.setBorder(BorderFactory.createEmptyBorder(4, 0, 0, 0));
packagesPanel.updatePackages(new PyPackageManagementService(project, sdk));
diff --git a/python/python-rest/src/com/jetbrains/rest/RestPythonUtil.java b/python/python-rest/src/com/jetbrains/rest/RestPythonUtil.java
index e6d379f03676..f7468d82a426 100644
--- a/python/python-rest/src/com/jetbrains/rest/RestPythonUtil.java
+++ b/python/python-rest/src/com/jetbrains/rest/RestPythonUtil.java
@@ -15,6 +15,7 @@
*/
package com.jetbrains.rest;
+import com.intellij.execution.ExecutionException;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.LangDataKeys;
@@ -23,7 +24,6 @@ import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
-import com.jetbrains.python.packaging.PyExternalProcessException;
import com.jetbrains.python.packaging.PyPackage;
import com.jetbrains.python.packaging.PyPackageManager;
import com.jetbrains.python.sdk.PythonSdkType;
@@ -52,7 +52,7 @@ public class RestPythonUtil {
final PyPackage sphinx = manager.findPackage("Sphinx", false);
presentation.setEnabled(sphinx != null);
}
- catch (PyExternalProcessException ignored) {
+ catch (ExecutionException ignored) {
}
}
}
diff --git a/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java b/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java
index e9585a5cd952..3a29f623dae4 100644
--- a/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java
+++ b/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java
@@ -17,6 +17,7 @@ package com.jetbrains.python.configuration;
import com.google.common.collect.Lists;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
+import com.intellij.execution.ExecutionException;
import com.intellij.facet.impl.ui.FacetErrorPanel;
import com.intellij.facet.ui.FacetConfigurationQuickFix;
import com.intellij.facet.ui.FacetEditorValidator;
@@ -161,7 +162,7 @@ public class PyIntegratedToolsConfigurable implements SearchableConfigurable, No
public void started() {}
@Override
- public void finished(List exceptions) {
+ public void finished(List exceptions) {
if (exceptions.isEmpty()) {
VFSTestFrameworkListener.getInstance().testInstalled(true, sdk.getHomePath(), name);
facetErrorPanel.getValidatorsManager().validate();
diff --git a/python/src/com/jetbrains/python/inspections/PyPackageRequirementsInspection.java b/python/src/com/jetbrains/python/inspections/PyPackageRequirementsInspection.java
index 165a3ee04e2c..300b91bf73c1 100644
--- a/python/src/com/jetbrains/python/inspections/PyPackageRequirementsInspection.java
+++ b/python/src/com/jetbrains/python/inspections/PyPackageRequirementsInspection.java
@@ -18,6 +18,7 @@ package com.jetbrains.python.inspections;
import com.google.common.collect.ImmutableSet;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.ui.ListEditForm;
+import com.intellij.execution.ExecutionException;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.Document;
@@ -212,7 +213,7 @@ public class PyPackageRequirementsInspection extends PyInspection {
try {
packages = PyPackageManager.getInstance(sdk).getPackages(PySdkUtil.isRemote(sdk));
}
- catch (PyExternalProcessException e) {
+ catch (ExecutionException e) {
return null;
}
if (packages == null) return null;
@@ -248,7 +249,7 @@ public class PyPackageRequirementsInspection extends PyInspection {
try {
packages = manager.getPackages(PySdkUtil.isRemote(sdk));
}
- catch (PyExternalProcessException e) {
+ catch (ExecutionException e) {
return null;
}
if (packages == null) return null;
@@ -303,7 +304,13 @@ public class PyPackageRequirementsInspection extends PyInspection {
public void applyFix(@NotNull final Project project, @NotNull ProblemDescriptor descriptor) {
boolean installManagement = false;
final PyPackageManager manager = PyPackageManager.getInstance(mySdk);
- if (!manager.hasManagement(false)) {
+ boolean hasManagement = false;
+ try {
+ hasManagement = manager.hasManagement(false);
+ }
+ catch (ExecutionException ignored) {
+ }
+ if (!hasManagement) {
final int result = Messages.showYesNoDialog(project,
"Python packaging tools are required for installing packages. Do you want to " +
"install 'pip' and 'setuptools' for your interpreter?",
@@ -330,7 +337,7 @@ public class PyPackageRequirementsInspection extends PyInspection {
if (installManagement) {
final PyPackageManagerUI ui = new PyPackageManagerUI(project, mySdk, new UIListener(myModule) {
@Override
- public void finished(List exceptions) {
+ public void finished(List exceptions) {
super.finished(exceptions);
if (exceptions.isEmpty()) {
installRequirements(project, chosen);
@@ -363,7 +370,7 @@ public class PyPackageRequirementsInspection extends PyInspection {
}
@Override
- public void finished(List exceptions) {
+ public void finished(List exceptions) {
setRunningPackagingTasks(myModule, false);
}
}
diff --git a/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java b/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java
index 070a89fcb3fe..1c518cf40a58 100644
--- a/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java
+++ b/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java
@@ -18,6 +18,7 @@ package com.jetbrains.python.packaging;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.intellij.execution.ExecutionException;
+import com.intellij.execution.RunCanceledByUserException;
import com.intellij.execution.process.*;
import com.intellij.execution.util.ExecUtil;
import com.intellij.openapi.application.Application;
@@ -69,14 +70,7 @@ public class PyPackageManagerImpl extends PyPackageManager {
public static final String VIRTUALENV_VERSION = "1.11.6";
public static final int OK = 0;
- public static final int ERROR_NO_PIP = 2;
public static final int ERROR_NO_SETUPTOOLS = 3;
- public static final int ERROR_INVALID_SDK = -1;
- public static final int ERROR_TOOL_NOT_FOUND = -2;
- public static final int ERROR_TIMEOUT = -3;
- public static final int ERROR_INVALID_OUTPUT = -4;
- public static final int ERROR_ACCESS_DENIED = -5;
- public static final int ERROR_EXECUTION = -6;
private static final Logger LOG = Logger.getInstance(PyPackageManagerImpl.class);
@@ -91,7 +85,7 @@ public class PyPackageManagerImpl extends PyPackageManager {
private final Object myCacheLock = new Object();
private List myPackagesCache = null;
- private PyExternalProcessException myExceptionCache = null;
+ private ExecutionException myExceptionCache = null;
protected Sdk mySdk;
@@ -117,9 +111,9 @@ public class PyPackageManagerImpl extends PyPackageManager {
}
@Override
- public void installManagement() throws PyExternalProcessException {
+ public void installManagement() throws ExecutionException {
final boolean pre26 = PythonSdkType.getLanguageLevelForSdk(mySdk).isOlderThan(LanguageLevel.PYTHON26);
- if (!hasPackage(SETUPTOOLS, false) && !hasPackage(DISTRIBUTE, false)) {
+ if (!hasSetuptools(false)) {
final String name = SETUPTOOLS + "-" + (pre26 ? SETUPTOOLS_PRE_26_VERSION : SETUPTOOLS_VERSION);
installManagement(name);
}
@@ -130,12 +124,23 @@ public class PyPackageManagerImpl extends PyPackageManager {
}
@Override
- public boolean hasManagement(boolean cachedOnly) {
- return (hasPackage(SETUPTOOLS, cachedOnly) || hasPackage(DISTRIBUTE, cachedOnly)) &&
- hasPackage(PIP, cachedOnly);
+ public boolean hasManagement(boolean cachedOnly) throws ExecutionException {
+ return hasSetuptools(cachedOnly) && hasPackage(PIP, cachedOnly);
}
- protected void installManagement(@NotNull String name) throws PyExternalProcessException {
+ private boolean hasSetuptools(boolean cachedOnly) throws ExecutionException {
+ try {
+ return hasPackage(SETUPTOOLS, cachedOnly) || hasPackage(DISTRIBUTE, cachedOnly);
+ }
+ catch (PyExecutionException e) {
+ if (e.getExitCode() == ERROR_NO_SETUPTOOLS) {
+ return false;
+ }
+ throw e;
+ }
+ }
+
+ protected void installManagement(@NotNull String name) throws ExecutionException {
final String dirName = extractHelper(name + ".tar.gz");
try {
final String fileName = dirName + name + File.separatorChar + "setup.py";
@@ -148,7 +153,7 @@ public class PyPackageManagerImpl extends PyPackageManager {
}
@NotNull
- private String extractHelper(@NotNull String name) throws PyExternalProcessException {
+ private String extractHelper(@NotNull String name) throws ExecutionException {
final String helperPath = getHelperPath(name);
final ArrayList args = Lists.newArrayList(UNTAR, helperPath);
final String result = getHelperResult(PACKAGING_TOOL, args, false, false, null);
@@ -159,13 +164,8 @@ public class PyPackageManagerImpl extends PyPackageManager {
return dirName;
}
- private boolean hasPackage(@NotNull String name, boolean cachedOnly) {
- try {
- return findPackage(name, cachedOnly) != null;
- }
- catch (PyExternalProcessException ignored) {
- return false;
- }
+ private boolean hasPackage(@NotNull String name, boolean cachedOnly) throws ExecutionException {
+ return findPackage(name, cachedOnly) != null;
}
PyPackageManagerImpl(@NotNull Sdk sdk) {
@@ -184,13 +184,13 @@ public class PyPackageManagerImpl extends PyPackageManager {
}
@Override
- public void install(@NotNull String requirementString) throws PyExternalProcessException {
+ public void install(@NotNull String requirementString) throws ExecutionException {
installManagement();
install(Collections.singletonList(PyRequirement.fromString(requirementString)), Collections.emptyList());
}
@Override
- public void install(@NotNull List requirements, @NotNull List extraArgs) throws PyExternalProcessException {
+ public void install(@NotNull List requirements, @NotNull List extraArgs) throws ExecutionException {
final List args = new ArrayList();
args.add(INSTALL);
final File buildDir;
@@ -198,7 +198,7 @@ public class PyPackageManagerImpl extends PyPackageManager {
buildDir = FileUtil.createTempDirectory("pycharm-packaging", null);
}
catch (IOException e) {
- throw new PyExternalProcessException(ERROR_ACCESS_DENIED, PACKAGING_TOOL, args, "Cannot create temporary build directory");
+ throw new ExecutionException("Cannot create temporary build directory");
}
if (!extraArgs.contains(BUILD_DIR_OPTION)) {
args.addAll(Arrays.asList(BUILD_DIR_OPTION, buildDir.getAbsolutePath()));
@@ -218,15 +218,28 @@ public class PyPackageManagerImpl extends PyPackageManager {
try {
getHelperResult(PACKAGING_TOOL, args, !useUserSite, true, null);
}
+ catch (PyExecutionException e) {
+ final List simplifiedArgs = new ArrayList();
+ simplifiedArgs.add("install");
+ if (proxyString != null) {
+ simplifiedArgs.add("--proxy");
+ simplifiedArgs.add(proxyString);
+ }
+ simplifiedArgs.addAll(extraArgs);
+ for (PyRequirement req : requirements) {
+ simplifiedArgs.addAll(req.toOptions());
+ }
+ throw new PyExecutionException(e.getMessage(), "pip", simplifiedArgs, e.getStdout(), e.getStderr(), e.getExitCode(), e.getFixes());
+ }
finally {
clearCaches();
FileUtil.delete(buildDir);
}
}
- public void uninstall(@NotNull List packages) throws PyExternalProcessException {
+ public void uninstall(@NotNull List packages) throws ExecutionException {
+ final List args = new ArrayList();
try {
- final List args = new ArrayList();
args.add(UNINSTALL);
boolean canModify = true;
for (PyPackage pkg : packages) {
@@ -240,16 +253,19 @@ public class PyPackageManagerImpl extends PyPackageManager {
}
getHelperResult(PACKAGING_TOOL, args, !canModify, true, null);
}
+ catch (PyExecutionException e) {
+ throw new PyExecutionException(e.getMessage(), "pip", args, e.getStdout(), e.getStderr(), e.getExitCode(), e.getFixes());
+ }
finally {
clearCaches();
}
}
@Nullable
- public List getPackages(boolean cachedOnly) throws PyExternalProcessException {
+ public List getPackages(boolean cachedOnly) throws ExecutionException {
synchronized (myCacheLock) {
if (myPackagesCache != null) {
- return myPackagesCache;
+ return new ArrayList(myPackagesCache);
}
if (myExceptionCache != null) {
throw myExceptionCache;
@@ -263,10 +279,10 @@ public class PyPackageManagerImpl extends PyPackageManager {
final List packages = parsePackagingToolOutput(output);
synchronized (myCacheLock) {
myPackagesCache = packages;
+ return new ArrayList(myPackagesCache);
}
- return packages;
}
- catch (PyExternalProcessException e) {
+ catch (ExecutionException e) {
synchronized (myCacheLock) {
myExceptionCache = e;
}
@@ -275,7 +291,7 @@ public class PyPackageManagerImpl extends PyPackageManager {
}
@Nullable
- public Set getDependents(@NotNull PyPackage pkg) throws PyExternalProcessException {
+ public Set getDependents(@NotNull PyPackage pkg) throws ExecutionException {
final List packages = getPackages(false);
if (packages != null) {
final Set dependents = new HashSet();
@@ -294,7 +310,7 @@ public class PyPackageManagerImpl extends PyPackageManager {
@Override
@Nullable
- public PyPackage findPackage(@NotNull String name, boolean cachedOnly) throws PyExternalProcessException {
+ public PyPackage findPackage(@NotNull String name, boolean cachedOnly) throws ExecutionException {
final List packages = getPackages(cachedOnly);
if (packages != null) {
for (PyPackage pkg : packages) {
@@ -307,7 +323,7 @@ public class PyPackageManagerImpl extends PyPackageManager {
}
@NotNull
- public String createVirtualEnv(@NotNull String destinationDir, boolean useGlobalSite) throws PyExternalProcessException {
+ public String createVirtualEnv(@NotNull String destinationDir, boolean useGlobalSite) throws ExecutionException {
final List args = new ArrayList();
final LanguageLevel languageLevel = PythonSdkType.getLanguageLevelForSdk(mySdk);
final boolean usePyVenv = languageLevel.isAtLeast(LanguageLevel.PYTHON33);
@@ -404,40 +420,39 @@ public class PyPackageManagerImpl extends PyPackageManager {
@NotNull
private String getHelperResult(@NotNull String helper, @NotNull List args, boolean askForSudo,
- boolean showProgress, @Nullable String parentDir) throws PyExternalProcessException {
+ boolean showProgress, @Nullable String parentDir) throws ExecutionException {
final String helperPath = getHelperPath(helper);
if (helperPath == null) {
- throw new PyExternalProcessException(ERROR_TOOL_NOT_FOUND, helper, args, "Cannot find external tool");
+ throw new ExecutionException("Cannot find external tool: " + helper);
}
return getPythonProcessResult(helperPath, args, askForSudo, showProgress, parentDir);
}
@Nullable
- protected String getHelperPath(String helper) {
+ protected String getHelperPath(String helper) throws ExecutionException {
return PythonHelpersLocator.getHelperPath(helper);
}
@NotNull
private String getPythonProcessResult(@NotNull String path, @NotNull List args, boolean askForSudo,
- boolean showProgress, @Nullable String workingDir) throws PyExternalProcessException {
+ boolean showProgress, @Nullable String workingDir) throws ExecutionException {
final ProcessOutput output = getPythonProcessOutput(path, args, askForSudo, showProgress, workingDir);
final int exitCode = output.getExitCode();
if (output.isTimeout()) {
- throw new PyExternalProcessException(ERROR_TIMEOUT, path, args, "Timed out");
+ throw new PyExecutionException("Timed out", path, args, output);
}
else if (exitCode != 0) {
- final String message = output.getStderr() + "\n" + output.getStdout();
- throw new PyExternalProcessException(exitCode, path, args, message);
+ throw new PyExecutionException("Non-zero exit code", path, args, output);
}
return output.getStdout();
}
@NotNull
protected ProcessOutput getPythonProcessOutput(@NotNull String helperPath, @NotNull List args, boolean askForSudo,
- boolean showProgress, @Nullable String workingDir) throws PyExternalProcessException {
+ boolean showProgress, @Nullable String workingDir) throws ExecutionException {
final String homePath = mySdk.getHomePath();
if (homePath == null) {
- throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, "Cannot find interpreter for SDK");
+ throw new ExecutionException("Cannot find Python interpreter for SDK " + mySdk.getName());
}
if (workingDir == null) {
workingDir = new File(homePath).getParent();
@@ -487,41 +502,29 @@ public class PyPackageManagerImpl extends PyPackageManager {
result = handler.runProcess(TIMEOUT);
}
if (result.isCancelled()) {
- throw new PyProcessCancelledException(helperPath, args);
+ throw new RunCanceledByUserException();
}
- String message = result.getStderr();
- if (result.getExitCode() != 0) {
- final String stdout = result.getStdout();
- if (StringUtil.isEmptyOrSpaces(message)) {
- message = stdout;
- }
- if (StringUtil.isEmptyOrSpaces(message)) {
- message = "Failed to perform action. Permission denied.";
- }
- throw new PyExternalProcessException(result.getExitCode(), helperPath, args, message);
+ final int exitCode = result.getExitCode();
+ if (exitCode != 0) {
+ final String message = StringUtil.isEmptyOrSpaces(result.getStdout()) && StringUtil.isEmptyOrSpaces(result.getStderr()) ?
+ "Permission denied" : "Non-zero exit code";
+ throw new PyExecutionException(message, helperPath, args, result);
}
return result;
}
- catch (PyProcessCancelledException e) {
- throw e;
- }
- catch (ExecutionException e) {
- throw new PyExternalProcessException(ERROR_EXECUTION, helperPath, args, e.getMessage());
- }
catch (IOException e) {
- throw new PyExternalProcessException(ERROR_ACCESS_DENIED, helperPath, args, e.getMessage());
+ throw new PyExecutionException(e.getMessage(), helperPath, args);
}
}
@NotNull
- private static List parsePackagingToolOutput(@NotNull String s) throws PyExternalProcessException {
+ private static List parsePackagingToolOutput(@NotNull String s) throws ExecutionException {
final String[] lines = StringUtil.splitByLines(s);
final List packages = new ArrayList();
for (String line : lines) {
final List fields = StringUtil.split(line, "\t");
if (fields.size() < 3) {
- throw new PyExternalProcessException(ERROR_INVALID_OUTPUT, PACKAGING_TOOL, Collections.emptyList(),
- "Invalid output format");
+ throw new PyExecutionException("Invalid output format", PACKAGING_TOOL, Collections.emptyList());
}
final String name = fields.get(0);
final String version = fields.get(1);
diff --git a/python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java b/python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java
index f504cbae33b9..c894f0fe8087 100644
--- a/python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java
+++ b/python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java
@@ -15,6 +15,8 @@
*/
package com.jetbrains.python.packaging;
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.RunCanceledByUserException;
import com.intellij.icons.AllIcons;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationListener;
@@ -31,7 +33,9 @@ import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.Function;
+import com.intellij.webcore.packaging.PackageManagementService;
import com.intellij.webcore.packaging.PackagesNotificationPanel;
+import com.jetbrains.python.packaging.ui.PyPackageManagementService;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -42,7 +46,8 @@ import java.util.*;
* @author vlan
*/
public class PyPackageManagerUI {
- private static final Logger LOG = Logger.getInstance(PyPackageManagerUI.class);
+ @NotNull private static final Logger LOG = Logger.getInstance(PyPackageManagerUI.class);
+
@Nullable private Listener myListener;
@NotNull private Project myProject;
@NotNull private Sdk mySdk;
@@ -50,7 +55,7 @@ public class PyPackageManagerUI {
public interface Listener {
void started();
- void finished(List exceptions);
+ void finished(List exceptions);
}
public PyPackageManagerUI(@NotNull Project project, @NotNull Sdk sdk, @Nullable Listener listener) {
@@ -113,14 +118,14 @@ public class PyPackageManagerUI {
}
if (warning[0] != Messages.YES) return true;
}
- catch (PyExternalProcessException e) {
+ catch (ExecutionException e) {
LOG.info("Error loading packages dependents: " + e.getMessage(), e);
}
return false;
}
- private static Map> collectDependents(@NotNull final List packages, Sdk sdk)
- throws PyExternalProcessException {
+ private static Map> collectDependents(@NotNull final List packages,
+ Sdk sdk) throws ExecutionException {
Map> dependentPackages = new HashMap>();
for (PyPackage pkg : packages) {
final Set dependents = PyPackageManager.getInstance(sdk).getDependents(pkg);
@@ -138,10 +143,12 @@ public class PyPackageManagerUI {
private abstract static class PackagingTask extends Task.Backgroundable {
private static final String PACKAGING_GROUP_ID = "Packaging";
+ @NotNull protected final Sdk mySdk;
@Nullable protected final Listener myListener;
- public PackagingTask(@Nullable Project project, @NotNull String title, @Nullable Listener listener) {
+ public PackagingTask(@Nullable Project project, @NotNull Sdk sdk, @NotNull String title, @Nullable Listener listener) {
super(project, title);
+ mySdk = sdk;
myListener = listener;
}
@@ -152,7 +159,7 @@ public class PyPackageManagerUI {
}
@NotNull
- protected abstract List runTask(@NotNull ProgressIndicator indicator);
+ protected abstract List runTask(@NotNull ProgressIndicator indicator);
@NotNull
protected abstract String getSuccessTitle();
@@ -175,27 +182,28 @@ public class PyPackageManagerUI {
}
}
- protected void taskFinished(@NotNull final List exceptions) {
+ protected void taskFinished(@NotNull final List exceptions) {
final Ref notificationRef = new Ref(null);
if (exceptions.isEmpty()) {
notificationRef.set(new Notification(PACKAGING_GROUP_ID, getSuccessTitle(), getSuccessDescription(),
NotificationType.INFORMATION));
}
- else if (!isCancelled(exceptions)) {
- final String firstLine = getTitle() + ": error occurred.";
- final String description = createDescription(exceptions, firstLine);
- notificationRef.set(new Notification(PACKAGING_GROUP_ID, getFailureTitle(),
- firstLine + " Details...",
- NotificationType.ERROR,
- new NotificationListener() {
- @Override
- public void hyperlinkUpdate(@NotNull Notification notification,
- @NotNull HyperlinkEvent event) {
- assert myProject != null;
- PackagesNotificationPanel.showError(myProject, getFailureTitle(), description);
- }
- }
- ));
+ else {
+ final PackageManagementService.ErrorDescription description = PyPackageManagementService.toErrorDescription(exceptions, mySdk);
+ if (description != null) {
+ final String firstLine = getTitle() + ": error occurred.";
+ final NotificationListener listener = new NotificationListener() {
+ @Override
+ public void hyperlinkUpdate(@NotNull Notification notification,
+ @NotNull HyperlinkEvent event) {
+ assert myProject != null;
+ final String title = StringUtil.capitalizeWords(getFailureTitle(), true);
+ PackagesNotificationPanel.showError(title, description);
+ }
+ };
+ notificationRef.set(new Notification(PACKAGING_GROUP_ID, getFailureTitle(), firstLine + " Details...",
+ NotificationType.ERROR, listener));
+ }
}
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
@@ -210,19 +218,9 @@ public class PyPackageManagerUI {
}
});
}
-
- private static boolean isCancelled(@NotNull List exceptions) {
- for (PyExternalProcessException e : exceptions) {
- if (e instanceof PyProcessCancelledException) {
- return true;
- }
- }
- return false;
- }
}
private static class InstallTask extends PackagingTask {
- @NotNull protected final Sdk mySdk;
@NotNull private final List myRequirements;
@NotNull private final List myExtraArgs;
@@ -231,16 +229,15 @@ public class PyPackageManagerUI {
@NotNull List requirements,
@NotNull List extraArgs,
@Nullable Listener listener) {
- super(project, "Installing packages", listener);
- mySdk = sdk;
+ super(project, sdk, "Installing packages", listener);
myRequirements = requirements;
myExtraArgs = extraArgs;
}
@NotNull
@Override
- protected List runTask(@NotNull ProgressIndicator indicator) {
- final List exceptions = new ArrayList();
+ protected List runTask(@NotNull ProgressIndicator indicator) {
+ final List exceptions = new ArrayList();
final int size = myRequirements.size();
final PyPackageManager manager = PyPackageManagers.getInstance().forSdk(mySdk);
for (int i = 0; i < size; i++) {
@@ -256,11 +253,11 @@ public class PyPackageManagerUI {
try {
manager.install(Arrays.asList(requirement), myExtraArgs);
}
- catch (PyProcessCancelledException e) {
+ catch (RunCanceledByUserException e) {
exceptions.add(e);
break;
}
- catch (PyExternalProcessException e) {
+ catch (ExecutionException e) {
exceptions.add(e);
}
}
@@ -297,15 +294,15 @@ public class PyPackageManagerUI {
@NotNull
@Override
- protected List runTask(@NotNull ProgressIndicator indicator) {
- final List exceptions = new ArrayList();
+ protected List runTask(@NotNull ProgressIndicator indicator) {
+ final List exceptions = new ArrayList();
final PyPackageManager manager = PyPackageManagers.getInstance().forSdk(mySdk);
indicator.setText("Installing packaging tools...");
indicator.setIndeterminate(true);
try {
manager.installManagement();
}
- catch (PyExternalProcessException e) {
+ catch (ExecutionException e) {
exceptions.add(e);
}
manager.refresh();
@@ -320,28 +317,26 @@ public class PyPackageManagerUI {
}
private static class UninstallTask extends PackagingTask {
- @NotNull private final Sdk mySdk;
@NotNull private final List myPackages;
public UninstallTask(@Nullable Project project,
@NotNull Sdk sdk,
@Nullable Listener listener,
@NotNull List packages) {
- super(project, "Uninstalling packages", listener);
- mySdk = sdk;
+ super(project, sdk, "Uninstalling packages", listener);
myPackages = packages;
}
@NotNull
@Override
- protected List runTask(@NotNull ProgressIndicator indicator) {
+ protected List runTask(@NotNull ProgressIndicator indicator) {
final PyPackageManager manager = PyPackageManagers.getInstance().forSdk(mySdk);
indicator.setIndeterminate(true);
try {
manager.uninstall(myPackages);
return Arrays.asList();
}
- catch (PyExternalProcessException e) {
+ catch (ExecutionException e) {
return Arrays.asList(e);
}
finally {
@@ -374,14 +369,15 @@ public class PyPackageManagerUI {
}
}
- public static String createDescription(List exceptions, String firstLine) {
+ @NotNull
+ public static PackageManagementService.ErrorDescription createDescription(List exceptions, String firstLine) {
final StringBuilder b = new StringBuilder();
b.append(firstLine);
b.append("\n\n");
- for (PyExternalProcessException exception : exceptions) {
+ for (ExecutionException exception : exceptions) {
b.append(exception.toString());
b.append("\n");
}
- return b.toString();
+ return PackageManagementService.ErrorDescription.fromMessage(b.toString());
}
}
diff --git a/python/src/com/jetbrains/python/packaging/PyRemotePackageManagerImpl.java b/python/src/com/jetbrains/python/packaging/PyRemotePackageManagerImpl.java
index bf199d6ded80..22045d1565ea 100644
--- a/python/src/com/jetbrains/python/packaging/PyRemotePackageManagerImpl.java
+++ b/python/src/com/jetbrains/python/packaging/PyRemotePackageManagerImpl.java
@@ -17,6 +17,7 @@ package com.jetbrains.python.packaging;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.process.ProcessOutput;
import com.intellij.openapi.diagnostic.Logger;
@@ -35,16 +36,13 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
* @author vlan
*/
public class PyRemotePackageManagerImpl extends PyPackageManagerImpl {
- private static final String LAUNCH_VAGRANT = "launchVagrant";
- public static final int ERROR_VAGRANT_NOT_LAUNCHED = 101;
- public static final int ERROR_REMOTE_ACCESS = 102;
-
private static final Logger LOG = Logger.getInstance(PyRemotePackageManagerImpl.class);
PyRemotePackageManagerImpl(@NotNull Sdk sdk) {
@@ -53,7 +51,7 @@ public class PyRemotePackageManagerImpl extends PyPackageManagerImpl {
@Nullable
@Override
- protected String getHelperPath(String helper) {
+ protected String getHelperPath(String helper) throws ExecutionException {
final SdkAdditionalData sdkData = mySdk.getSdkAdditionalData();
if (sdkData instanceof PyRemoteSdkAdditionalDataBase) {
final PyRemoteSdkAdditionalDataBase remoteSdkData = (PyRemoteSdkAdditionalDataBase)mySdk.getSdkAdditionalData();
@@ -66,9 +64,12 @@ public class PyRemotePackageManagerImpl extends PyPackageManagerImpl {
return null;
}
}
- catch (Exception e) {
+ catch (InterruptedException e) {
LOG.error(e);
}
+ catch (ExecutionException e) {
+ throw analyzeException(e, helper, Collections.emptyList());
+ }
}
return null;
}
@@ -78,10 +79,10 @@ public class PyRemotePackageManagerImpl extends PyPackageManagerImpl {
protected ProcessOutput getPythonProcessOutput(@NotNull String helperPath,
@NotNull List args,
boolean askForSudo,
- boolean showProgress, @Nullable String workingDir) throws PyExternalProcessException {
+ boolean showProgress, @Nullable String workingDir) throws ExecutionException {
final String homePath = mySdk.getHomePath();
if (homePath == null) {
- throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, "Cannot find interpreter for SDK");
+ throw new ExecutionException("Cannot find Python interpreter for SDK " + mySdk.getName());
}
final SdkAdditionalData sdkData = mySdk.getSdkAdditionalData();
if (sdkData instanceof PyRemoteSdkAdditionalDataBase) { //remote interpreter
@@ -91,33 +92,10 @@ public class PyRemotePackageManagerImpl extends PyPackageManagerImpl {
}
catch (InterruptedException e) {
LOG.error(e);
- remoteSdkCredentials = null;
- }
- catch (final ExecutionException e) {
- if (e.getCause() instanceof VagrantNotStartedException) {
- throw new PyExternalProcessException(ERROR_VAGRANT_NOT_LAUNCHED, helperPath, args, "Vagrant instance is down. Launch vagrant")
- .withHandler(LAUNCH_VAGRANT, new Runnable() {
- @Override
- public void run() {
- final PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
- if (manager != null) {
-
- try {
- manager.runVagrant(((VagrantNotStartedException)e.getCause()).getVagrantFolder());
- clearCaches();
- }
- catch (ExecutionException e1) {
- throw new RuntimeException(e1);
- }
- }
- }
- });
- }
- else {
- throw new PyExternalProcessException(ERROR_REMOTE_ACCESS, helperPath, args, e.getMessage());
+ remoteSdkCredentials = null;
}
+ catch (ExecutionException e) {
+ throw analyzeException(e, helperPath, args);
}
final PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
if (manager != null && remoteSdkCredentials != null) {
@@ -130,44 +108,48 @@ public class PyRemotePackageManagerImpl extends PyPackageManagerImpl {
return quoteIfNeeded(input);
}
}));
- try {
- if (askForSudo) {
- askForSudo = !manager.ensureCanWrite(null, remoteSdkCredentials, remoteSdkCredentials.getInterpreterPath());
- }
- ProcessOutput processOutput;
- do {
- PathMappingSettings mappings = manager.setupMappings(null, (PyRemoteSdkAdditionalDataBase)sdkData, null);
- processOutput =
- manager.runRemoteProcess(null, remoteSdkCredentials, mappings, ArrayUtil.toStringArray(cmdline), workingDir, askForSudo);
- if (askForSudo && processOutput.getStderr().contains("sudo: 3 incorrect password attempts")) {
- continue;
- }
- break;
- }
- while (true);
- return processOutput;
+ if (askForSudo) {
+ askForSudo = !manager.ensureCanWrite(null, remoteSdkCredentials, remoteSdkCredentials.getInterpreterPath());
}
- catch (ExecutionException e) {
- throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, "Error running SDK: " + e.getMessage(), e);
+ ProcessOutput processOutput;
+ do {
+ PathMappingSettings mappings = manager.setupMappings(null, (PyRemoteSdkAdditionalDataBase)sdkData, null);
+ processOutput =
+ manager.runRemoteProcess(null, remoteSdkCredentials, mappings, ArrayUtil.toStringArray(cmdline), workingDir, askForSudo);
+ if (askForSudo && processOutput.getStderr().contains("sudo: 3 incorrect password attempts")) {
+ continue;
+ }
+ break;
}
+ while (true);
+ return processOutput;
}
else {
- throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args,
- PythonRemoteInterpreterManager.WEB_DEPLOYMENT_PLUGIN_IS_DISABLED);
+ throw new PyExecutionException(PythonRemoteInterpreterManager.WEB_DEPLOYMENT_PLUGIN_IS_DISABLED, helperPath, args);
}
}
else {
- throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, "Invalid remote SDK");
+ throw new PyExecutionException("Invalid remote SDK", helperPath, args);
}
}
+ private ExecutionException analyzeException(ExecutionException exception, String command, List args) {
+ final Throwable cause = exception.getCause();
+ if (cause instanceof VagrantNotStartedException) {
+ final String vagrantFolder = ((VagrantNotStartedException)cause).getVagrantFolder();
+ return new PyExecutionException("Vagrant instance is down", command, args, "", "", 0,
+ ImmutableList.of(new LaunchVagrantFix(vagrantFolder)));
+ }
+ return exception;
+ }
+
@Override
protected void subscribeToLocalChanges(Sdk sdk) {
// Local VFS changes aren't needed
}
@Override
- protected void installManagement(@NotNull String name) throws PyExternalProcessException {
+ protected void installManagement(@NotNull String name) throws ExecutionException {
super.installManagement(name);
// TODO: remove temp directory for remote interpreter
}
@@ -175,4 +157,32 @@ public class PyRemotePackageManagerImpl extends PyPackageManagerImpl {
private static String quoteIfNeeded(String arg) {
return arg.replace("<", "\\<").replace(">", "\\>"); //TODO: move this logic to ParametersListUtil.encode
}
+
+ private class LaunchVagrantFix implements PyExecutionFix {
+ @NotNull private final String myVagrantFolder;
+
+ public LaunchVagrantFix(@NotNull String vagrantFolder) {
+ myVagrantFolder = vagrantFolder;
+ }
+
+ @NotNull
+ @Override
+ public String getName() {
+ return "Launch Vagrant";
+ }
+
+ @Override
+ public void run(@NotNull Sdk sdk) {
+ final PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
+ if (manager != null) {
+ try {
+ manager.runVagrant(myVagrantFolder);
+ clearCaches();
+ }
+ catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
}
diff --git a/python/src/com/jetbrains/python/packaging/ui/PyInstalledPackagesPanel.java b/python/src/com/jetbrains/python/packaging/ui/PyInstalledPackagesPanel.java
index 6bb976b6b910..90240955831a 100644
--- a/python/src/com/jetbrains/python/packaging/ui/PyInstalledPackagesPanel.java
+++ b/python/src/com/jetbrains/python/packaging/ui/PyInstalledPackagesPanel.java
@@ -15,7 +15,9 @@
*/
package com.jetbrains.python.packaging.ui;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
+import com.intellij.execution.ExecutionException;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
@@ -24,15 +26,15 @@ import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.util.Consumer;
import com.intellij.webcore.packaging.InstalledPackage;
import com.intellij.webcore.packaging.InstalledPackagesPanel;
+import com.intellij.webcore.packaging.PackageManagementService;
import com.intellij.webcore.packaging.PackagesNotificationPanel;
import com.jetbrains.python.packaging.*;
import com.jetbrains.python.sdk.PySdkUtil;
import com.jetbrains.python.sdk.PythonSdkType;
-import com.jetbrains.python.sdk.flavors.IronPythonSdkFlavor;
-import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -40,9 +42,6 @@ import java.util.Set;
* @author yole
*/
public class PyInstalledPackagesPanel extends InstalledPackagesPanel {
- public static final String INSTALL_MANAGEMENT = "installManagement";
- public static final String CREATE_VENV = "createVEnv";
-
private boolean myHasManagement = false;
public PyInstalledPackagesPanel(Project project, PackagesNotificationPanel area) {
@@ -54,6 +53,41 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel {
return service != null ? service.getSdk() : null;
}
+ class PyInstallPackageManagementFix implements PyExecutionFix {
+ @NotNull
+ @Override
+ public String getName() {
+ return "Install packaging tools";
+ }
+
+ @Override
+ public void run(@NotNull final Sdk sdk) {
+ final PyPackageManagerUI ui = new PyPackageManagerUI(myProject, sdk, new PyPackageManagerUI.Listener() {
+ @Override
+ public void started() {
+ myPackagesTable.setPaintBusy(true);
+ }
+
+ @Override
+ public void finished(List exceptions) {
+ myPackagesTable.setPaintBusy(false);
+ PyPackageManager packageManager = PyPackageManager.getInstance(sdk);
+ final PackageManagementService.ErrorDescription description = PyPackageManagementService.toErrorDescription(exceptions, sdk);
+ if (description != null) {
+ PackagesNotificationPanel.showError("Failed to install Python packaging tools", description);
+ }
+ packageManager.refresh();
+ updatePackages(new PyPackageManagementService(myProject, sdk));
+ for (Consumer listener : myPathChangedListeners) {
+ listener.consume(sdk);
+ }
+ updateNotifications(sdk);
+ }
+ });
+ ui.installManagement();
+ }
+ }
+
public void updateNotifications(@Nullable final Sdk selectedSdk) {
if (selectedSdk == null) {
myNotificationArea.hide();
@@ -63,50 +97,53 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel {
application.executeOnPooledThread(new Runnable() {
@Override
public void run() {
- PyPackageManager packageManager = PyPackageManager.getInstance(selectedSdk);
- myHasManagement = packageManager.hasManagement(false);
+ PyExecutionException exception = null;
+ try {
+ myHasManagement = PyPackageManager.getInstance(selectedSdk).hasManagement(false);
+ if (!myHasManagement) {
+ throw new PyExecutionException("Python packaging tools not found", "pip", Collections.emptyList(), "", "", 0,
+ ImmutableList.of(new PyInstallPackageManagementFix()));
+ }
+ }
+ catch (PyExecutionException e) {
+ exception = e;
+ }
+ catch (ExecutionException e) {
+ return;
+ }
+ final PyExecutionException problem = exception;
application.invokeLater(new Runnable() {
@Override
public void run() {
if (selectedSdk == getSelectedSdk()) {
- final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(selectedSdk);
- final boolean invalid = PythonSdkType.isInvalid(selectedSdk);
- boolean allowCreateVirtualEnv =
- !(PythonSdkType.isRemote(selectedSdk) || flavor instanceof IronPythonSdkFlavor) &&
- !PythonSdkType.isVirtualEnv(selectedSdk) &&
- myNotificationArea.hasLinkHandler(CREATE_VENV);
- final String createVirtualEnvLink = "create new VirtualEnv";
myNotificationArea.hide();
- if (!invalid) {
- String text = null;
- if (!myHasManagement) {
- myNotificationArea.addLinkHandler(INSTALL_MANAGEMENT,
- new Runnable() {
- @Override
- public void run() {
- final Sdk sdk = getSelectedSdk();
- if (sdk != null) {
- installManagementTools(sdk);
- }
- myNotificationArea.removeLinkHandler(INSTALL_MANAGEMENT);
- updateNotifications(selectedSdk);
- }
- }
- );
- }
-
- if (!myHasManagement) {
- text = "Python packaging tools not found. Install packaging tools";
- }
- if (text != null) {
- if (allowCreateVirtualEnv) {
- text += " or " + createVirtualEnvLink;
+ if (problem != null) {
+ final boolean invalid = PythonSdkType.isInvalid(selectedSdk);
+ if (!invalid) {
+ final StringBuilder builder = new StringBuilder(problem.getMessage());
+ builder.append(". ");
+ for (final PyExecutionFix fix : problem.getFixes()) {
+ final String key = "id" + fix.hashCode();
+ final String link = "" + fix.getName() + "";
+ builder.append(link);
+ builder.append(" ");
+ myNotificationArea.addLinkHandler(key, new Runnable() {
+ @Override
+ public void run() {
+ final Sdk sdk = getSelectedSdk();
+ if (sdk != null) {
+ fix.run(sdk);
+ myNotificationArea.removeLinkHandler(key);
+ updatePackages(new PyPackageManagementService(myProject, sdk));
+ updateNotifications(sdk);
+ }
+ }
+ });
}
- myNotificationArea.showWarning(text);
+ myNotificationArea.showWarning(builder.toString());
}
+ myInstallButton.setEnabled(!invalid && myHasManagement);
}
-
- myInstallButton.setEnabled(!invalid && myHasManagement);
}
}
}, ModalityState.any());
@@ -119,33 +156,6 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel {
return Sets.newHashSet("pip", "distutils", "setuptools");
}
- private void installManagementTools(@NotNull final Sdk sdk) {
- final PyPackageManagerUI ui = new PyPackageManagerUI(myProject, sdk, new PyPackageManagerUI.Listener() {
- @Override
- public void started() {
- myPackagesTable.setPaintBusy(true);
- }
-
- @Override
- public void finished(List exceptions) {
- myPackagesTable.setPaintBusy(false);
- PyPackageManager packageManager = PyPackageManager.getInstance(sdk);
- if (!exceptions.isEmpty()) {
- final String firstLine = "Install Python packaging tools failed. ";
- final String description = PyPackageManagerUI.createDescription(exceptions, firstLine);
- PackagesNotificationPanel.showError(myProject, "Failed to install Python packaging tools", description);
- }
- packageManager.refresh();
- updatePackages(new PyPackageManagementService(myProject, sdk));
- for (Consumer listener : myPathChangedListeners) {
- listener.consume(sdk);
- }
- updateNotifications(sdk);
- }
- });
- ui.installManagement();
- }
-
@Override
protected boolean canUninstallPackage(InstalledPackage pkg) {
if (!myHasManagement) return false;
diff --git a/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java b/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java
index 802a5ab612f4..221468ce39a8 100644
--- a/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java
+++ b/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java
@@ -15,14 +15,18 @@
*/
package com.jetbrains.python.packaging.ui;
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.RunCanceledByUserException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.CatchingConsumer;
import com.intellij.webcore.packaging.InstalledPackage;
import com.intellij.webcore.packaging.PackageManagementService;
import com.intellij.webcore.packaging.RepoPackage;
import com.jetbrains.python.packaging.*;
+import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.sdk.PySdkUtil;
import com.jetbrains.python.sdk.PythonSdkType;
import org.apache.xmlrpc.AsyncCallback;
@@ -33,11 +37,15 @@ import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.net.URL;
import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* @author yole
*/
public class PyPackageManagementService extends PackageManagementService {
+ @NotNull private static final Pattern PATTERN_ERROR_LINE = Pattern.compile(".*error:.*", Pattern.CASE_INSENSITIVE);
+
private final Project myProject;
private final Sdk mySdk;
@@ -84,7 +92,7 @@ public class PyPackageManagementService extends PackageManagementService {
private static List versionMapToPackageList(Map packageToVersionMap) {
final boolean customRepoConfigured = !PyPackageService.getInstance().additionalRepositories.isEmpty();
- String url = customRepoConfigured? PyPIPackageUtil.PYPI_URL : "";
+ String url = customRepoConfigured ? PyPIPackageUtil.PYPI_URL : "";
List packages = new ArrayList();
for (Map.Entry entry : packageToVersionMap.entrySet()) {
packages.add(new RepoPackage(entry.getKey(), url, entry.getValue()));
@@ -142,14 +150,14 @@ public class PyPackageManagementService extends PackageManagementService {
});
}
}
- catch (PyExternalProcessException e) {
+ catch (ExecutionException e) {
throw new IOException(e);
}
return packages != null ? new ArrayList(packages) : new ArrayList();
}
@Override
- public void installPackage(final RepoPackage repoPackage,String version, boolean forceUpgrade, String extraOptions,
+ public void installPackage(final RepoPackage repoPackage, String version, boolean forceUpgrade, String extraOptions,
final Listener listener, boolean installToUser) {
final String packageName = repoPackage.getName();
final String repository = PyPIPackageUtil.PYPI_URL.equals(repoPackage.getRepoUrl()) ? null : repoPackage.getRepoUrl();
@@ -183,19 +191,19 @@ public class PyPackageManagementService extends PackageManagementService {
}
@Override
- public void finished(@Nullable List exceptions) {
- listener.operationFinished(packageName, toErrorDescription(exceptions));
+ public void finished(@Nullable List exceptions) {
+ listener.operationFinished(packageName, toErrorDescription(exceptions, mySdk));
}
});
ui.install(Collections.singletonList(req), extraArgs);
}
- private String toErrorDescription(List exceptions) {
- String errorDescription = null;
- if (exceptions != null && exceptions.size() > 0) {
- errorDescription = PyPackageManagerUI.createDescription(exceptions, "");
+ @Nullable
+ public static ErrorDescription toErrorDescription(@Nullable List exceptions, @NotNull Sdk sdk) {
+ if (exceptions != null && !exceptions.isEmpty() && !isCancelled(exceptions)) {
+ return createDescription(exceptions.get(0), sdk);
}
- return errorDescription;
+ return null;
}
@Override
@@ -208,15 +216,15 @@ public class PyPackageManagementService extends PackageManagementService {
}
@Override
- public void finished(final List exceptions) {
- listener.operationFinished(packageName, toErrorDescription(exceptions));
+ public void finished(final List exceptions) {
+ listener.operationFinished(packageName, toErrorDescription(exceptions, mySdk));
}
});
List pyPackages = new ArrayList();
for (InstalledPackage aPackage : installedPackages) {
if (aPackage instanceof PyPackage) {
- pyPackages.add((PyPackage) aPackage);
+ pyPackages.add((PyPackage)aPackage);
}
}
ui.uninstall(pyPackages);
@@ -301,4 +309,65 @@ public class PyPackageManagementService extends PackageManagementService {
private static String composeHref(String vendorUrl) {
return HTML_PREFIX + vendorUrl + "\">" + vendorUrl + HTML_SUFFIX;
}
+
+ private static boolean isCancelled(@NotNull List exceptions) {
+ for (ExecutionException e : exceptions) {
+ if (e instanceof RunCanceledByUserException) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @NotNull
+ private static ErrorDescription createDescription(@NotNull ExecutionException e, @NotNull Sdk sdk) {
+ if (e instanceof PyExecutionException) {
+ final PyExecutionException ee = (PyExecutionException)e;
+ final String stdoutCause = findErrorCause(ee.getStdout());
+ final String stderrCause = findErrorCause(ee.getStderr());
+ final String cause = stdoutCause != null ? stdoutCause : stderrCause;
+ final String message = cause != null ? cause : ee.getMessage();
+ final String command = ee.getCommand() + " " + StringUtil.join(ee.getArgs(), " ");
+ return new ErrorDescription(message, command, ee.getStdout(), findErrorSolution(ee, cause, sdk));
+ }
+ else {
+ return ErrorDescription.fromMessage(e.getMessage());
+ }
+ }
+
+ @Nullable
+ private static String findErrorSolution(@NotNull PyExecutionException e, @Nullable String cause, @NotNull Sdk sdk) {
+ if (cause != null) {
+ if (StringUtil.containsIgnoreCase(cause, "SyntaxError")) {
+ final LanguageLevel languageLevel = PythonSdkType.getLanguageLevelForSdk(sdk);
+ return "Make sure that you use a version of Python supported by this package. Currently you are using Python " +
+ languageLevel + ".";
+ }
+ }
+
+ if (SystemInfo.isLinux && (containsInOutput(e, "pyconfig.h") || containsInOutput(e, "Python.h"))) {
+ return "Make sure that you have installed Python development packages for your operating system.";
+ }
+
+ if ("pip".equals(e.getCommand())) {
+ return "Try to run this command from the system terminal. Make sure that you use the correct version of 'pip' " +
+ "installed for your Python interpreter located at '" + sdk.getHomePath() + "'.";
+ }
+
+ return null;
+ }
+
+ private static boolean containsInOutput(@NotNull PyExecutionException e, @NotNull String text) {
+ return StringUtil.containsIgnoreCase(e.getStdout(), text) || StringUtil.containsIgnoreCase(e.getStderr(), text);
+ }
+
+ @Nullable
+ private static String findErrorCause(@NotNull String output) {
+ final Matcher m = PATTERN_ERROR_LINE.matcher(output);
+ if (m.find()) {
+ final String result = m.group();
+ return result != null ? result.trim() : null;
+ }
+ return null;
+ }
}
diff --git a/python/src/com/jetbrains/python/sdk/CreateVirtualEnvDialog.java b/python/src/com/jetbrains/python/sdk/CreateVirtualEnvDialog.java
index 1d0a4bc3ad54..2d59d1fd4132 100644
--- a/python/src/com/jetbrains/python/sdk/CreateVirtualEnvDialog.java
+++ b/python/src/com/jetbrains/python/sdk/CreateVirtualEnvDialog.java
@@ -17,6 +17,7 @@ package com.jetbrains.python.sdk;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
+import com.intellij.execution.ExecutionException;
import com.intellij.facet.ui.FacetEditorValidator;
import com.intellij.facet.ui.FacetValidatorsManager;
import com.intellij.openapi.application.Application;
@@ -48,10 +49,11 @@ import com.intellij.ui.components.JBLabel;
import com.intellij.util.NullableConsumer;
import com.intellij.util.PathUtil;
import com.intellij.util.PlatformUtils;
+import com.intellij.webcore.packaging.PackageManagementService;
import com.intellij.webcore.packaging.PackagesNotificationPanel;
-import com.jetbrains.python.packaging.PyExternalProcessException;
import com.jetbrains.python.packaging.PyPackageManager;
import com.jetbrains.python.packaging.PyPackageService;
+import com.jetbrains.python.packaging.ui.PyPackageManagementService;
import com.jetbrains.python.sdk.flavors.VirtualEnvSdkFlavor;
import com.jetbrains.python.ui.IdeaDialog;
import org.jetbrains.annotations.NotNull;
@@ -421,11 +423,15 @@ public class CreateVirtualEnvDialog extends IdeaDialog {
indicator.setText("Creating virtual environment for " + basicSdk.getName());
myPath = packageManager.createVirtualEnv(getDestination(), useGlobalSitePackages());
}
- catch (final PyExternalProcessException e) {
+ catch (final ExecutionException e) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
- PackagesNotificationPanel.showError(getOwner(), "Failed to Create Virtual Environment", e.toString());
+ final PackageManagementService.ErrorDescription description =
+ PyPackageManagementService.toErrorDescription(Collections.singletonList(e), basicSdk);
+ if (description != null) {
+ PackagesNotificationPanel.showError("Failed to Create Virtual Environment", description);
+ }
}
}, ModalityState.any());
}
diff --git a/python/src/com/jetbrains/python/sdk/skeletons/PySkeletonRefresher.java b/python/src/com/jetbrains/python/sdk/skeletons/PySkeletonRefresher.java
index b1e8ca19acf8..0ce96cf42ea6 100644
--- a/python/src/com/jetbrains/python/sdk/skeletons/PySkeletonRefresher.java
+++ b/python/src/com/jetbrains/python/sdk/skeletons/PySkeletonRefresher.java
@@ -42,7 +42,6 @@ import com.intellij.util.io.ZipUtil;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.codeInsight.userSkeletons.PyUserSkeletonsUtil;
-import com.jetbrains.python.packaging.PyExternalProcessException;
import com.jetbrains.python.packaging.PyPackageManager;
import com.jetbrains.python.psi.resolve.PythonSdkPathCache;
import com.jetbrains.python.remote.PythonRemoteInterpreterManager;
@@ -351,7 +350,7 @@ public class PySkeletonRefresher {
// Force loading packages
PyPackageManager.getInstance(mySdk).getPackages(false);
}
- catch (PyExternalProcessException e) {
+ catch (ExecutionException e) {
// ignore - already logged
}
}
diff --git a/python/src/com/jetbrains/python/testing/VFSTestFrameworkListener.java b/python/src/com/jetbrains/python/testing/VFSTestFrameworkListener.java
index 64adf2093028..c121dacdeaf8 100644
--- a/python/src/com/jetbrains/python/testing/VFSTestFrameworkListener.java
+++ b/python/src/com/jetbrains/python/testing/VFSTestFrameworkListener.java
@@ -15,6 +15,7 @@
*/
package com.jetbrains.python.testing;
+import com.intellij.execution.ExecutionException;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ApplicationComponent;
import com.intellij.openapi.components.ServiceManager;
@@ -33,7 +34,6 @@ import com.intellij.util.messages.MessageBus;
import com.intellij.util.ui.update.MergingUpdateQueue;
import com.intellij.util.ui.update.Update;
import com.jetbrains.python.PyNames;
-import com.jetbrains.python.packaging.PyExternalProcessException;
import com.jetbrains.python.packaging.PyPackageManager;
import com.jetbrains.python.sdk.PySdkUtil;
import com.jetbrains.python.sdk.PythonSdkType;
@@ -134,7 +134,7 @@ public class VFSTestFrameworkListener implements ApplicationComponent {
try {
return packageManager.findPackage(testPackageName, false) != null;
}
- catch (PyExternalProcessException e) {
+ catch (ExecutionException e) {
LOG.info("Can't load package list " + e.getMessage());
}
return null;
diff --git a/python/src/com/jetbrains/python/testing/pytest/PyTestConfigurationProducer.java b/python/src/com/jetbrains/python/testing/pytest/PyTestConfigurationProducer.java
index 8dc27d4fd995..575690c52dae 100644
--- a/python/src/com/jetbrains/python/testing/pytest/PyTestConfigurationProducer.java
+++ b/python/src/com/jetbrains/python/testing/pytest/PyTestConfigurationProducer.java
@@ -15,6 +15,7 @@
*/
package com.jetbrains.python.testing.pytest;
+import com.intellij.execution.ExecutionException;
import com.intellij.execution.Location;
import com.intellij.execution.actions.ConfigurationContext;
import com.intellij.openapi.module.Module;
@@ -28,7 +29,6 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFileSystemItem;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.webcore.packaging.PackageVersionComparator;
-import com.jetbrains.python.packaging.PyExternalProcessException;
import com.jetbrains.python.packaging.PyPackage;
import com.jetbrains.python.packaging.PyPackageManager;
import com.jetbrains.python.psi.PyClass;
@@ -106,7 +106,7 @@ public class PyTestConfigurationProducer extends PythonTestConfigurationProducer
keywords = pyClass.getName() + "." + keywords;
}
}
- catch (PyExternalProcessException e) {
+ catch (ExecutionException e) {
keywords = pyClass.getName() + "." + keywords;
}
}
diff --git a/python/testSrc/com/jetbrains/env/python/PyPackagingTest.java b/python/testSrc/com/jetbrains/env/python/PyPackagingTest.java
index c4616e5e3add..c15d0dcb871d 100644
--- a/python/testSrc/com/jetbrains/env/python/PyPackagingTest.java
+++ b/python/testSrc/com/jetbrains/env/python/PyPackagingTest.java
@@ -1,6 +1,7 @@
package com.jetbrains.env.python;
import com.google.common.collect.Sets;
+import com.intellij.execution.ExecutionException;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
@@ -44,11 +45,7 @@ public class PyPackagingTest extends PyEnvTestCase {
try {
packages = PyPackageManager.getInstance(sdk).getPackages(false);
}
- catch (PyExternalProcessException e) {
- final int retcode = e.getRetcode();
- if (retcode != PyPackageManagerImpl.ERROR_NO_PIP && retcode != PyPackageManagerImpl.ERROR_NO_SETUPTOOLS) {
- fail(String.format("Error for interpreter '%s': %s", sdk.getHomePath(), e.getMessage()));
- }
+ catch (ExecutionException ignored) {
}
if (packages != null) {
assertTrue(packages.size() > 0);
@@ -87,7 +84,7 @@ public class PyPackagingTest extends PyEnvTestCase {
catch (IOException e) {
throw new RuntimeException(e);
}
- catch (PyExternalProcessException e) {
+ catch (ExecutionException e) {
throw new RuntimeException(String.format("Error for interpreter '%s': %s", sdk.getHomePath(), e.getMessage()), e);
}
}
@@ -123,7 +120,7 @@ public class PyPackagingTest extends PyEnvTestCase {
final PyPackage pip2 = findPackage("pip", packages3);
assertNull(pip2);
}
- catch (PyExternalProcessException e) {
+ catch (ExecutionException e) {
new RuntimeException(String.format("Error for interpreter '%s': %s", sdk.getHomePath(), e.getMessage()), e);
}
catch (IOException e) {