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 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 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 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) {