diff options
author | Tor Norbye <tnorbye@google.com> | 2014-09-18 20:40:22 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2014-09-18 20:40:22 +0000 |
commit | 07d35c37ce79a64bdd905b394d40fc9bbb18fa60 (patch) | |
tree | e8787c45e494dfcc558faf0f75956f8785c39b94 /python/src/com/jetbrains/python/packaging | |
parent | e222a9e1e66670a56e926a6b0f3e10231eeeb1fb (diff) | |
parent | b5fb31ef6a38f19404859755dbd2e345215b97bf (diff) | |
download | idea-07d35c37ce79a64bdd905b394d40fc9bbb18fa60.tar.gz |
Merge "Merge remote-tracking branch 'aosp/upstream-master' into merge"
Diffstat (limited to 'python/src/com/jetbrains/python/packaging')
6 files changed, 696 insertions, 671 deletions
diff --git a/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java b/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java index 65d95eb62713..9b5f669168f7 100644 --- a/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java +++ b/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java @@ -15,31 +15,17 @@ */ package com.jetbrains.python.packaging; -import com.google.common.collect.Collections2; import com.google.common.collect.Lists; import com.intellij.execution.ExecutionException; import com.intellij.execution.process.ProcessOutput; import com.intellij.execution.util.ExecUtil; -import com.intellij.icons.AllIcons; -import com.intellij.notification.Notification; -import com.intellij.notification.NotificationListener; -import com.intellij.notification.NotificationType; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.Task; -import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.projectRoots.SdkAdditionalData; import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl; import com.intellij.openapi.roots.OrderRootType; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; @@ -49,46 +35,36 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.openapi.vfs.newvfs.BulkFileListener; import com.intellij.openapi.vfs.newvfs.events.VFileEvent; -import com.intellij.remote.RemoteFile; -import com.intellij.remote.RemoteSdkAdditionalData; -import com.intellij.remote.RemoteSdkCredentials; -import com.intellij.remote.VagrantNotStartedException; import com.intellij.util.ArrayUtil; -import com.intellij.util.Function; -import com.intellij.util.PathMappingSettings; -import com.intellij.util.SystemProperties; import com.intellij.util.containers.HashSet; import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.net.HttpConfigurable; -import com.intellij.webcore.packaging.PackagesNotificationPanel; import com.jetbrains.python.PythonHelpersLocator; import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.psi.PyExpression; import com.jetbrains.python.psi.PyListLiteralExpression; import com.jetbrains.python.psi.PyStringLiteralExpression; -import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase; -import com.jetbrains.python.remote.PythonRemoteInterpreterManager; import com.jetbrains.python.sdk.PySdkUtil; import com.jetbrains.python.sdk.PythonSdkType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.swing.event.HyperlinkEvent; -import java.awt.*; import java.io.File; import java.io.IOException; import java.util.*; -import java.util.List; /** * @author vlan */ -@SuppressWarnings({"UnusedDeclaration", "FieldAccessedSynchronizedAndUnsynchronized"}) public class PyPackageManagerImpl extends PyPackageManager { - private static final Logger LOG = Logger.getInstance(PyPackageManagerImpl.class); + // Bundled versions of package management tools + public static final String SETUPTOOLS_VERSION = "1.1.5"; + public static final String PIP_VERSION = "1.4.1"; + + public static final String SETUPTOOLS = PACKAGE_SETUPTOOLS + "-" + SETUPTOOLS_VERSION; + public static final String PIP = PACKAGE_PIP + "-" + PIP_VERSION; public static final int OK = 0; - public static final int ERROR_WRONG_USAGE = 1; public static final int ERROR_NO_PIP = 2; public static final int ERROR_NO_SETUPTOOLS = 3; public static final int ERROR_INVALID_SDK = -1; @@ -97,260 +73,24 @@ public class PyPackageManagerImpl extends PyPackageManager { public static final int ERROR_INVALID_OUTPUT = -4; public static final int ERROR_ACCESS_DENIED = -5; public static final int ERROR_EXECUTION = -6; - public static final int ERROR_INTERRUPTED = -7; - public static final int ERROR_VAGRANT_NOT_LAUNCHED = 101; - public static final int ERROR_REMOTE_ACCESS = 102; - - public static final String PACKAGE_PIP = "pip"; - public static final String PACKAGE_DISTRIBUTE = "distribute"; - public static final String PACKAGE_SETUPTOOLS = "setuptools"; - - public static final Key<Boolean> RUNNING_PACKAGING_TASKS = Key.create("PyPackageRequirementsInspection.RunningPackagingTasks"); + private static final Logger LOG = Logger.getInstance(PyPackageManagerImpl.class); private static final String PACKAGING_TOOL = "packaging_tool.py"; private static final String VIRTUALENV = "virtualenv.py"; private static final int TIMEOUT = 10 * 60 * 1000; private static final String BUILD_DIR_OPTION = "--build-dir"; - public static final String USE_USER_SITE = "--user"; public static final String INSTALL = "install"; public static final String UNINSTALL = "uninstall"; public static final String UNTAR = "untar"; - // Bundled versions of package management tools - public static final String SETUPTOOLS_VERSION = "1.1.5"; - public static final String PIP_VERSION = "1.4.1"; - - public static final String SETUPTOOLS = PACKAGE_SETUPTOOLS + "-" + SETUPTOOLS_VERSION; - public static final String PIP = PACKAGE_PIP + "-" + PIP_VERSION; - private static final String LAUNCH_VAGRANT = "launchVagrant"; - private List<PyPackage> myPackagesCache = null; private Map<String, Set<PyPackage>> myDependenciesCache = null; private PyExternalProcessException myExceptionCache = null; - private Sdk mySdk; - - public static class UI { - @Nullable private Listener myListener; - @NotNull private Project myProject; - @NotNull private Sdk mySdk; - - public interface Listener { - void started(); - - void finished(List<PyExternalProcessException> exceptions); - } - - public UI(@NotNull Project project, @NotNull Sdk sdk, @Nullable Listener listener) { - myProject = project; - mySdk = sdk; - myListener = listener; - } - - public void installManagement(@NotNull final String name) { - final String progressTitle; - final String successTitle; - progressTitle = "Installing package " + name; - successTitle = "Packages installed successfully"; - run(new MultiExternalRunnable() { - @Override - public List<PyExternalProcessException> run(@NotNull ProgressIndicator indicator) { - final List<PyExternalProcessException> exceptions = new ArrayList<PyExternalProcessException>(); - indicator.setText(String.format("Installing package '%s'...", name)); - final PyPackageManagerImpl manager = (PyPackageManagerImpl)PyPackageManagers.getInstance().forSdk(mySdk); - try { - manager.installManagement(name); - } - catch (PyExternalProcessException e) { - exceptions.add(e); - } - return exceptions; - } - }, progressTitle, successTitle, "Installed package " + name, - "Install package failed" - ); - } - - public void install(@NotNull final List<PyRequirement> requirements, @NotNull final List<String> extraArgs) { - final String progressTitle; - final String successTitle; - progressTitle = "Installing packages"; - successTitle = "Packages installed successfully"; - run(new MultiExternalRunnable() { - @Override - public List<PyExternalProcessException> run(@NotNull ProgressIndicator indicator) { - final int size = requirements.size(); - final List<PyExternalProcessException> exceptions = new ArrayList<PyExternalProcessException>(); - final PyPackageManagerImpl manager = (PyPackageManagerImpl)PyPackageManagers.getInstance().forSdk(mySdk); - for (int i = 0; i < size; i++) { - final PyRequirement requirement = requirements.get(i); - if (myListener != null) { - indicator.setText(String.format("Installing package '%s'...", requirement)); - indicator.setFraction((double)i / size); - } - try { - manager.install(list(requirement), extraArgs); - } - catch (PyExternalProcessException e) { - exceptions.add(e); - } - } - manager.refresh(); - return exceptions; - } - }, progressTitle, successTitle, "Installed packages: " + PyPackageUtil.requirementsToString(requirements), - "Install packages failed" - ); - } - - public void uninstall(@NotNull final List<PyPackage> packages) { - final String packagesString = StringUtil.join(packages, new Function<PyPackage, String>() { - @Override - public String fun(PyPackage pkg) { - return "'" + pkg.getName() + "'"; - } - }, ", "); - if (checkDependents(packages)) return; - - run(new MultiExternalRunnable() { - @Override - public List<PyExternalProcessException> run(@NotNull ProgressIndicator indicator) { - final PyPackageManagerImpl manager = (PyPackageManagerImpl)PyPackageManagers.getInstance().forSdk(mySdk); - try { - manager.uninstall(packages); - return list(); - } - catch (PyExternalProcessException e) { - return list(e); - } - finally { - manager.refresh(); - } - } - }, "Uninstalling packages", "Packages uninstalled successfully", "Uninstalled packages: " + packagesString, - "Uninstall packages failed" - ); - } - - private boolean checkDependents(@NotNull final List<PyPackage> packages) { - try { - final Map<String, Set<PyPackage>> dependentPackages = collectDependents(packages, mySdk); - final int[] warning = {0}; - if (!dependentPackages.isEmpty()) { - ApplicationManager.getApplication().invokeAndWait(new Runnable() { - @Override - public void run() { - if (dependentPackages.size() == 1) { - String message = "You are attempting to uninstall "; - List<String> dep = new ArrayList<String>(); - int size = 1; - for (Map.Entry<String, Set<PyPackage>> entry : dependentPackages.entrySet()) { - final Set<PyPackage> value = entry.getValue(); - size = value.size(); - dep.add(entry.getKey() + " package which is required for " + StringUtil.join(value, ", ")); - } - message += StringUtil.join(dep, "\n"); - message += size == 1 ? " package" : " packages"; - message += "\n\nDo you want to proceed?"; - warning[0] = Messages.showYesNoDialog(message, "Warning", - AllIcons.General.BalloonWarning); - } - else { - String message = "You are attempting to uninstall packages which are required for another packages.\n\n"; - List<String> dep = new ArrayList<String>(); - for (Map.Entry<String, Set<PyPackage>> entry : dependentPackages.entrySet()) { - dep.add(entry.getKey() + " -> " + StringUtil.join(entry.getValue(), ", ")); - } - message += StringUtil.join(dep, "\n"); - message += "\n\nDo you want to proceed?"; - warning[0] = Messages.showYesNoDialog(message, "Warning", - AllIcons.General.BalloonWarning); - } - } - }, ModalityState.current()); - } - if (warning[0] != Messages.YES) return true; - } - catch (PyExternalProcessException e) { - LOG.info("Error loading packages dependents: " + e.getMessage(), e); - } - return false; - } - - private interface MultiExternalRunnable { - List<PyExternalProcessException> run(@NotNull ProgressIndicator indicator); - } - - private void run(@NotNull final MultiExternalRunnable runnable, @NotNull final String progressTitle, - @NotNull final String successTitle, @NotNull final String successDescription, @NotNull final String failureTitle) { - ProgressManager.getInstance().run(new Task.Backgroundable(myProject, progressTitle, false) { - @Override - public void run(@NotNull ProgressIndicator indicator) { - indicator.setText(progressTitle + "..."); - final Ref<Notification> notificationRef = new Ref<Notification>(null); - final String PACKAGING_GROUP_ID = "Packaging"; - final Application application = ApplicationManager.getApplication(); - if (myListener != null) { - application.invokeLater(new Runnable() { - @Override - public void run() { - myListener.started(); - } - }); - } - - final List<PyExternalProcessException> exceptions = runnable.run(indicator); - if (exceptions.isEmpty()) { - notificationRef.set(new Notification(PACKAGING_GROUP_ID, successTitle, successDescription, NotificationType.INFORMATION)); - } - else { - final String progressLower = progressTitle.toLowerCase(); - final String firstLine = String.format("Error%s occurred when %s.", exceptions.size() > 1 ? "s" : "", progressLower); - - final String description = createDescription(exceptions, firstLine); - notificationRef.set(new Notification(PACKAGING_GROUP_ID, failureTitle, - firstLine + " <a href=\"xxx\">Details...</a>", - NotificationType.ERROR, - new NotificationListener() { - @Override - public void hyperlinkUpdate(@NotNull Notification notification, - @NotNull HyperlinkEvent event) { - assert myProject != null; - PackagesNotificationPanel.showError(myProject, failureTitle, description); - } - } - )); - } - application.invokeLater(new Runnable() { - @Override - public void run() { - if (myListener != null) { - myListener.finished(exceptions); - } - final Notification notification = notificationRef.get(); - if (notification != null) { - notification.notify(myProject); - } - } - }); - } - }); - } - - public static String createDescription(List<PyExternalProcessException> exceptions, String firstLine) { - final StringBuilder b = new StringBuilder(); - b.append(firstLine); - b.append("\n\n"); - for (PyExternalProcessException exception : exceptions) { - b.append(exception.toString()); - b.append("\n"); - } - return b.toString(); - } - } + protected Sdk mySdk; @Override public void refresh() { @@ -373,7 +113,23 @@ public class PyPackageManagerImpl extends PyPackageManager { }); } - private void installManagement(String name) throws PyExternalProcessException { + @Override + public void installManagement() throws PyExternalProcessException { + if (!hasPackage(PACKAGE_SETUPTOOLS, false) && !hasPackage(PACKAGE_DISTRIBUTE, false)) { + installManagement(SETUPTOOLS); + } + if (!hasPackage(PACKAGE_PIP, false)) { + installManagement(PIP); + } + } + + @Override + public boolean hasManagement(boolean cachedOnly) { + return (hasPackage(PACKAGE_SETUPTOOLS, cachedOnly) || hasPackage(PACKAGE_DISTRIBUTE, cachedOnly)) && + hasPackage(PACKAGE_PIP, cachedOnly); + } + + protected void installManagement(@NotNull String name) throws PyExternalProcessException { final String helperPath = getHelperPath(name); ArrayList<String> args = Lists.newArrayList(UNTAR, helperPath); @@ -390,7 +146,7 @@ public class PyPackageManagerImpl extends PyPackageManager { } final String fileName = dirName + name + File.separatorChar + "setup.py"; try { - output = getProcessOutput(fileName, Collections.<String>singletonList(INSTALL), true, dirName + name); + output = getProcessOutput(fileName, Collections.singletonList(INSTALL), true, dirName + name); final int retcode = output.getExitCode(); if (output.isTimeout()) { throw new PyExternalProcessException(ERROR_TIMEOUT, fileName, Lists.newArrayList(INSTALL), "Timed out"); @@ -406,17 +162,28 @@ public class PyPackageManagerImpl extends PyPackageManager { } finally { clearCaches(); - FileUtil.delete(new File(dirName)); //TODO: remove temp directory for remote interpreter + FileUtil.delete(new File(dirName)); + } + } + + private boolean hasPackage(@NotNull String name, boolean cachedOnly) { + try { + return findPackage(name, cachedOnly) != null; + } + catch (PyExternalProcessException ignored) { + return false; } } PyPackageManagerImpl(@NotNull Sdk sdk) { mySdk = sdk; + subscribeToLocalChanges(sdk); + } + + protected void subscribeToLocalChanges(Sdk sdk) { final Application app = ApplicationManager.getApplication(); final MessageBusConnection connection = app.getMessageBus().connect(); - if (!PySdkUtil.isRemote(sdk)) { - connection.subscribe(VirtualFileManager.VFS_CHANGES, new MySdkRootWatcher()); - } + connection.subscribe(VirtualFileManager.VFS_CHANGES, new MySdkRootWatcher()); } public Sdk getSdk() { @@ -424,27 +191,13 @@ public class PyPackageManagerImpl extends PyPackageManager { } @Override - public void install(String requirementString) throws PyExternalProcessException { - boolean hasSetuptools = false; - boolean hasPip = false; - try { - hasSetuptools = findInstalledPackage(SETUPTOOLS) != null; - } - catch (PyExternalProcessException ignored) { - } - try { - hasPip = findInstalledPackage(PIP) != null; - } - catch (PyExternalProcessException ignored) { - } - - if (!hasSetuptools) installManagement(SETUPTOOLS); - if (!hasPip) installManagement(PIP); + public void install(@NotNull String requirementString) throws PyExternalProcessException { + installManagement(); install(Collections.singletonList(PyRequirement.fromString(requirementString)), Collections.<String>emptyList()); } - public void install(@NotNull List<PyRequirement> requirements, @NotNull List<String> extraArgs) - throws PyExternalProcessException { + @Override + public void install(@NotNull List<PyRequirement> requirements, @NotNull List<String> extraArgs) throws PyExternalProcessException { final List<String> args = new ArrayList<String>(); args.add(INSTALL); final File buildDir; @@ -455,7 +208,7 @@ public class PyPackageManagerImpl extends PyPackageManager { throw new PyExternalProcessException(ERROR_ACCESS_DENIED, PACKAGING_TOOL, args, "Cannot create temporary build directory"); } if (!extraArgs.contains(BUILD_DIR_OPTION)) { - args.addAll(list(BUILD_DIR_OPTION, buildDir.getAbsolutePath())); + args.addAll(Arrays.asList(BUILD_DIR_OPTION, buildDir.getAbsolutePath())); } boolean useUserSite = extraArgs.contains(USE_USER_SITE); @@ -499,69 +252,22 @@ public class PyPackageManagerImpl extends PyPackageManager { } } - private static Map<String, Set<PyPackage>> collectDependents(@NotNull final List<PyPackage> packages, Sdk sdk) - throws PyExternalProcessException { - Map<String, Set<PyPackage>> dependentPackages = new HashMap<String, Set<PyPackage>>(); - for (PyPackage pkg : packages) { - final Set<PyPackage> dependents = - ((PyPackageManagerImpl)PyPackageManager.getInstance(sdk)).getDependents(pkg.getName()); - if (dependents != null && !dependents.isEmpty()) { - for (PyPackage dependent : dependents) { - if (!packages.contains(dependent)) { - dependentPackages.put(pkg.getName(), dependents); - } - } - } - } - return dependentPackages; - } - - public static String getUserSite() { - if (SystemInfo.isWindows) { - final String appdata = System.getenv("APPDATA"); - return appdata + File.separator + "Python"; - } - else { - final String userHome = SystemProperties.getUserHome(); - return userHome + File.separator + ".local"; - } - } - - - public boolean cacheIsNotNull() { - return myPackagesCache != null; - } - - /** - * Returns the list of packages for the SDK without initiating a remote connection. Returns null - * for a remote interpreter if the list of packages was not loaded. - * - * @return the list of packages or null - */ @Nullable - public synchronized List<PyPackage> getPackagesFast() throws PyExternalProcessException { - if (myPackagesCache != null) { - return myPackagesCache; - } - if (PySdkUtil.isRemote(mySdk)) { - return null; - } - return getPackages(); - } - - @NotNull - public synchronized List<PyPackage> getPackages() throws PyExternalProcessException { + public synchronized List<PyPackage> getPackages(boolean cachedOnly) throws PyExternalProcessException { if (myPackagesCache == null) { if (myExceptionCache != null) { throw myExceptionCache; } - + if (cachedOnly) { + return null; + } loadPackages(); } return myPackagesCache; } - public synchronized Set<PyPackage> getDependents(String pkg) throws PyExternalProcessException { + @Nullable + public synchronized Set<PyPackage> getDependents(@NotNull PyPackage pkg) throws PyExternalProcessException { if (myDependenciesCache == null) { if (myExceptionCache != null) { throw myExceptionCache; @@ -569,12 +275,12 @@ public class PyPackageManagerImpl extends PyPackageManager { loadPackages(); } - return myDependenciesCache.get(pkg); + return myDependenciesCache.get(pkg.getName()); } public synchronized void loadPackages() throws PyExternalProcessException { try { - final String output = runPythonHelper(PACKAGING_TOOL, list("list")); + final String output = runPythonHelper(PACKAGING_TOOL, Arrays.asList("list")); myPackagesCache = parsePackagingToolOutput(output); Collections.sort(myPackagesCache, new Comparator<PyPackage>() { @Override @@ -592,7 +298,7 @@ public class PyPackageManagerImpl extends PyPackageManager { } } - private void calculateDependents() { + private synchronized void calculateDependents() { myDependenciesCache = new HashMap<String, Set<PyPackage>>(); for (PyPackage p : myPackagesCache) { final List<PyRequirement> requirements = p.getRequirements(); @@ -608,47 +314,18 @@ public class PyPackageManagerImpl extends PyPackageManager { @Override @Nullable - public PyPackage findInstalledPackage(String name) throws PyExternalProcessException { - return findPackageByName(name, getPackages()); - } - - @Override - public boolean findPackage(@NotNull final String name) { - try { - final String output = runPythonHelper(PACKAGING_TOOL, list("search", name)); - return StringUtil.containsIgnoreCase(output, name + " "); - } - catch (PyExternalProcessException e) { - LOG.error(e.getMessage()); - return false; - } - } - - @Nullable - public PyPackage findPackageFast(String name) throws PyExternalProcessException { - final List<PyPackage> packages = getPackagesFast(); - return packages != null ? findPackageByName(name, packages) : null; - } - - @Nullable - private static PyPackage findPackageByName(String name, List<PyPackage> packages) { - for (PyPackage pkg : packages) { - if (name.equalsIgnoreCase(pkg.getName())) { - return pkg; + public PyPackage findPackage(@NotNull String name, boolean cachedOnly) throws PyExternalProcessException { + final List<PyPackage> packages = getPackages(cachedOnly); + if (packages != null) { + for (PyPackage pkg : packages) { + if (name.equalsIgnoreCase(pkg.getName())) { + return pkg; + } } } return null; } - public boolean hasPip() { - try { - return findPackageFast(PACKAGE_PIP) != null; - } - catch (PyExternalProcessException e) { - return false; - } - } - @NotNull public String createVirtualEnv(@NotNull String destinationDir, boolean useGlobalSite) throws PyExternalProcessException { final List<String> args = new ArrayList<String>(); @@ -674,30 +351,21 @@ public class PyPackageManagerImpl extends PyPackageManager { final String path = (binary != null) ? binary : binaryFallback; if (usePyVenv) { - // TODO: Still no 'packaging' and 'pysetup3' for Python 3.3rc1, see PEP 405 + // Still no 'packaging' and 'pysetup3' for Python 3.3rc1, see PEP 405 final VirtualFile binaryFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(path); if (binaryFile != null) { final ProjectJdkImpl tmpSdk = new ProjectJdkImpl("", PythonSdkType.getInstance()); tmpSdk.setHomePath(path); - final PyPackageManagerImpl manager = new PyPackageManagerImpl(tmpSdk); - manager.installManagement(SETUPTOOLS); - manager.installManagement(PIP); + final PyPackageManager manager = PyPackageManager.getInstance(tmpSdk); + manager.installManagement(); } } return path; } - public static void deleteVirtualEnv(@NotNull String sdkHome) throws PyExternalProcessException { - final File root = PythonSdkType.getVirtualEnvRoot(sdkHome); - if (root != null) { - FileUtil.delete(root); - } - } - @Nullable - public static List<PyRequirement> getRequirements(@NotNull Module module) { - // TODO: Cache requirements, clear cache on requirements.txt or setup.py updates - List<PyRequirement> requirements = getRequirementsFromTxt(module); + public List<PyRequirement> getRequirements(@NotNull Module module) { + List<PyRequirement> requirements = PySdkUtil.getRequirementsFromTxt(module); if (requirements != null) { return requirements; } @@ -721,25 +389,12 @@ public class PyPackageManagerImpl extends PyPackageManager { return null; } - @Nullable - public static List<PyRequirement> getRequirementsFromTxt(Module module) { - final VirtualFile requirementsTxt = PyPackageUtil.findRequirementsTxt(module); - if (requirementsTxt != null) { - return PyRequirement.parse(requirementsTxt); - } - return null; - } - - private void clearCaches() { + protected synchronized void clearCaches() { myPackagesCache = null; myDependenciesCache = null; myExceptionCache = null; } - private static <T> List<T> list(T... xs) { - return Arrays.asList(xs); - } - @Nullable private static String getProxyString() { final HttpConfigurable settings = HttpConfigurable.getInstance(); @@ -792,164 +447,61 @@ public class PyPackageManagerImpl extends PyPackageManager { } @Nullable - private String getHelperPath(String helper) { - String helperPath; - final SdkAdditionalData sdkData = mySdk.getSdkAdditionalData(); - if (sdkData instanceof PyRemoteSdkAdditionalDataBase) { - PyRemoteSdkAdditionalDataBase remoteSdkData = (PyRemoteSdkAdditionalDataBase)mySdk.getSdkAdditionalData(); - - try { - final RemoteSdkCredentials remoteSdkCredentials = remoteSdkData.getRemoteSdkCredentials(false); - if (!StringUtil.isEmpty(remoteSdkCredentials.getHelpersPath())) { - helperPath = new RemoteFile(remoteSdkCredentials.getHelpersPath(), - helper).getPath(); - } - else { - helperPath = null; - } - } - catch (Exception e) { - helperPath = null; - LOG.error(e); - } - } - else { - helperPath = PythonHelpersLocator.getHelperPath(helper); - } - return helperPath; + protected String getHelperPath(String helper) { + return PythonHelpersLocator.getHelperPath(helper); } - private ProcessOutput getProcessOutput(@NotNull String helperPath, + protected ProcessOutput getProcessOutput(@NotNull String helperPath, @NotNull List<String> args, boolean askForSudo, - @Nullable String workingDir) - throws PyExternalProcessException { - final SdkAdditionalData sdkData = mySdk.getSdkAdditionalData(); + @Nullable String workingDir) throws PyExternalProcessException { final String homePath = mySdk.getHomePath(); if (homePath == null) { throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, "Cannot find interpreter for SDK"); } - if (sdkData instanceof PyRemoteSdkAdditionalDataBase) { //remote interpreter - RemoteSdkCredentials remoteSdkCredentials; + if (workingDir == null) { + workingDir = new File(homePath).getParent(); + } + final List<String> cmdline = new ArrayList<String>(); + cmdline.add(homePath); + cmdline.add(helperPath); + cmdline.addAll(args); + LOG.info("Running packaging tool: " + StringUtil.join(cmdline, " ")); + + final boolean canCreate = FileUtil.ensureCanCreateFile(new File(homePath)); + if (!canCreate && !SystemInfo.isWindows && askForSudo) { //is system site interpreter --> we need sudo privileges try { - remoteSdkCredentials = ((RemoteSdkAdditionalData)sdkData).getRemoteSdkCredentials(false); - } - 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. <a href=\"" + - LAUNCH_VAGRANT + - "\">Launch vagrant</a>") - .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()); - } - } - final PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance(); - if (manager != null && remoteSdkCredentials != null) { - final List<String> cmdline = new ArrayList<String>(); - cmdline.add(homePath); - cmdline.add(RemoteFile.detectSystemByPath(homePath).createRemoteFile(helperPath).getPath()); - cmdline.addAll(Collections2.transform(args, new com.google.common.base.Function<String, String>() { - @Override - public String apply(@Nullable String input) { - return quoteIfNeeded(input); + final ProcessOutput result = ExecUtil.sudoAndGetOutput(cmdline, + "Please enter your password to make changes in system packages: ", + workingDir); + String message = result.getStderr(); + if (result.getExitCode() != 0) { + final String stdout = result.getStdout(); + if (StringUtil.isEmptyOrSpaces(message)) { + message = stdout; } - })); - try { - if (askForSudo) { - askForSudo = !manager.ensureCanWrite(null, remoteSdkCredentials, remoteSdkCredentials.getInterpreterPath()); + if (StringUtil.isEmptyOrSpaces(message)) { + message = "Failed to perform action. Permission denied."; } - 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; + throw new PyExternalProcessException(result.getExitCode(), helperPath, args, message); } - catch (ExecutionException e) { - throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, "Error running SDK: " + e.getMessage(), e); + if (SystemInfo.isMac && !StringUtil.isEmptyOrSpaces(message)) { + throw new PyExternalProcessException(result.getExitCode(), helperPath, args, message); } + return result; } - else { - throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, - PythonRemoteInterpreterManager.WEB_DEPLOYMENT_PLUGIN_IS_DISABLED); + 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()); } } else { - if (workingDir == null) { - workingDir = new File(homePath).getParent(); - } - final List<String> cmdline = new ArrayList<String>(); - cmdline.add(homePath); - cmdline.add(helperPath); - cmdline.addAll(args); - LOG.info("Running packaging tool: " + StringUtil.join(cmdline, " ")); - - final boolean canCreate = FileUtil.ensureCanCreateFile(new File(homePath)); - if (!canCreate && !SystemInfo.isWindows && askForSudo) { //is system site interpreter --> we need sudo privileges - try { - final ProcessOutput result = ExecUtil.sudoAndGetOutput(cmdline, - "Please enter your password to make changes in system packages: ", - workingDir); - 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); - } - if (SystemInfo.isMac && !StringUtil.isEmptyOrSpaces(message)) { - throw new PyExternalProcessException(result.getExitCode(), helperPath, args, message); - } - return result; - } - 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()); - } - } - else { - return PySdkUtil.getProcessOutput(workingDir, ArrayUtil.toStringArray(cmdline), TIMEOUT); - } + return PySdkUtil.getProcessOutput(workingDir, ArrayUtil.toStringArray(cmdline), TIMEOUT); } } - private static String quoteIfNeeded(String arg) { - return arg.replace("<", "\\<").replace(">", "\\>"); //TODO: move this logic to ParametersListUtil.encode - } - @NotNull private static List<PyPackage> parsePackagingToolOutput(@NotNull String s) throws PyExternalProcessException { final String[] lines = StringUtil.splitByLines(s); @@ -976,17 +528,6 @@ public class PyPackageManagerImpl extends PyPackageManager { return packages; } - - @Override - public void showInstallationError(Project project, String title, String description) { - PackagesNotificationPanel.showError(project, title, description); - } - - @Override - public void showInstallationError(Component owner, String title, String description) { - PackagesNotificationPanel.showError(owner, title, description); - } - private class MySdkRootWatcher extends BulkFileListener.Adapter { @Override public void after(@NotNull List<? extends VFileEvent> events) { diff --git a/python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java b/python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java new file mode 100644 index 000000000000..28dfa6c834a4 --- /dev/null +++ b/python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java @@ -0,0 +1,367 @@ +/* + * 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.jetbrains.python.packaging; + +import com.intellij.icons.AllIcons; +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationListener; +import com.intellij.notification.NotificationType; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; +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.PackagesNotificationPanel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.event.HyperlinkEvent; +import java.util.*; + +/** +* @author vlan +*/ +public class PyPackageManagerUI { + private static final Logger LOG = Logger.getInstance(PyPackageManagerUI.class); + @Nullable private Listener myListener; + @NotNull private Project myProject; + @NotNull private Sdk mySdk; + + public interface Listener { + void started(); + + void finished(List<PyExternalProcessException> exceptions); + } + + public PyPackageManagerUI(@NotNull Project project, @NotNull Sdk sdk, @Nullable Listener listener) { + myProject = project; + mySdk = sdk; + myListener = listener; + } + + public void installManagement() { + ProgressManager.getInstance().run(new InstallManagementTask(myProject, mySdk, myListener)); + } + + public void install(@NotNull final List<PyRequirement> requirements, @NotNull final List<String> extraArgs) { + ProgressManager.getInstance().run(new InstallTask(myProject, mySdk, requirements, extraArgs, myListener)); + } + + public void uninstall(@NotNull final List<PyPackage> packages) { + if (checkDependents(packages)) { + return; + } + ProgressManager.getInstance().run(new UninstallTask(myProject, mySdk, myListener, packages)); + } + + private boolean checkDependents(@NotNull final List<PyPackage> packages) { + try { + final Map<String, Set<PyPackage>> dependentPackages = collectDependents(packages, mySdk); + final int[] warning = {0}; + if (!dependentPackages.isEmpty()) { + ApplicationManager.getApplication().invokeAndWait(new Runnable() { + @Override + public void run() { + if (dependentPackages.size() == 1) { + String message = "You are attempting to uninstall "; + List<String> dep = new ArrayList<String>(); + int size = 1; + for (Map.Entry<String, Set<PyPackage>> entry : dependentPackages.entrySet()) { + final Set<PyPackage> value = entry.getValue(); + size = value.size(); + dep.add(entry.getKey() + " package which is required for " + StringUtil.join(value, ", ")); + } + message += StringUtil.join(dep, "\n"); + message += size == 1 ? " package" : " packages"; + message += "\n\nDo you want to proceed?"; + warning[0] = Messages.showYesNoDialog(message, "Warning", + AllIcons.General.BalloonWarning); + } + else { + String message = "You are attempting to uninstall packages which are required for another packages.\n\n"; + List<String> dep = new ArrayList<String>(); + for (Map.Entry<String, Set<PyPackage>> entry : dependentPackages.entrySet()) { + dep.add(entry.getKey() + " -> " + StringUtil.join(entry.getValue(), ", ")); + } + message += StringUtil.join(dep, "\n"); + message += "\n\nDo you want to proceed?"; + warning[0] = Messages.showYesNoDialog(message, "Warning", + AllIcons.General.BalloonWarning); + } + } + }, ModalityState.current()); + } + if (warning[0] != Messages.YES) return true; + } + catch (PyExternalProcessException e) { + LOG.info("Error loading packages dependents: " + e.getMessage(), e); + } + return false; + } + + private static Map<String, Set<PyPackage>> collectDependents(@NotNull final List<PyPackage> packages, Sdk sdk) + throws PyExternalProcessException { + Map<String, Set<PyPackage>> dependentPackages = new HashMap<String, Set<PyPackage>>(); + for (PyPackage pkg : packages) { + final Set<PyPackage> dependents = PyPackageManager.getInstance(sdk).getDependents(pkg); + if (dependents != null && !dependents.isEmpty()) { + for (PyPackage dependent : dependents) { + if (!packages.contains(dependent)) { + dependentPackages.put(pkg.getName(), dependents); + } + } + } + } + return dependentPackages; + } + + private abstract static class PackagingTask extends Task.Backgroundable { + private static final String PACKAGING_GROUP_ID = "Packaging"; + + @Nullable protected final Listener myListener; + + public PackagingTask(@Nullable Project project, @NotNull String title, @Nullable Listener listener) { + super(project, title); + myListener = listener; + } + + @Override + public void run(@NotNull ProgressIndicator indicator) { + taskStarted(indicator); + taskFinished(runTask(indicator)); + } + + @NotNull + protected abstract List<PyExternalProcessException> runTask(@NotNull ProgressIndicator indicator); + + @NotNull + protected abstract String getSuccessTitle(); + + @NotNull + protected abstract String getSuccessDescription(); + + @NotNull + protected abstract String getFailureTitle(); + + protected void taskStarted(@NotNull ProgressIndicator indicator) { + indicator.setText(getTitle() + "..."); + if (myListener != null) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + myListener.started(); + } + }); + } + } + + protected void taskFinished(@NotNull final List<PyExternalProcessException> exceptions) { + final Ref<Notification> notificationRef = new Ref<Notification>(null); + if (exceptions.isEmpty()) { + notificationRef.set(new Notification(PACKAGING_GROUP_ID, getSuccessTitle(), getSuccessDescription(), + NotificationType.INFORMATION)); + } + else { + final String firstLine = getTitle() + ": error occurred."; + final String description = createDescription(exceptions, firstLine); + notificationRef.set(new Notification(PACKAGING_GROUP_ID, getFailureTitle(), + firstLine + " <a href=\"xxx\">Details...</a>", + NotificationType.ERROR, + new NotificationListener() { + @Override + public void hyperlinkUpdate(@NotNull Notification notification, + @NotNull HyperlinkEvent event) { + assert myProject != null; + PackagesNotificationPanel.showError(myProject, getFailureTitle(), description); + } + } + )); + } + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + if (myListener != null) { + myListener.finished(exceptions); + } + final Notification notification = notificationRef.get(); + if (notification != null) { + notification.notify(myProject); + } + } + }); + } + } + + private static class InstallTask extends PackagingTask { + @NotNull protected final Sdk mySdk; + @NotNull private final List<PyRequirement> myRequirements; + @NotNull private final List<String> myExtraArgs; + + public InstallTask(@Nullable Project project, + @NotNull Sdk sdk, + @NotNull List<PyRequirement> requirements, + @NotNull List<String> extraArgs, + @Nullable Listener listener) { + super(project, "Installing packages", listener); + mySdk = sdk; + myRequirements = requirements; + myExtraArgs = extraArgs; + } + + @NotNull + @Override + protected List<PyExternalProcessException> runTask(@NotNull ProgressIndicator indicator) { + final List<PyExternalProcessException> exceptions = new ArrayList<PyExternalProcessException>(); + final int size = myRequirements.size(); + final PyPackageManager manager = PyPackageManagers.getInstance().forSdk(mySdk); + for (int i = 0; i < size; i++) { + final PyRequirement requirement = myRequirements.get(i); + indicator.setText(String.format("Installing package '%s'...", requirement)); + indicator.setFraction((double)i / size); + try { + manager.install(Arrays.asList(requirement), myExtraArgs); + } + catch (PyExternalProcessException e) { + exceptions.add(e); + } + } + manager.refresh(); + return exceptions; + } + + @NotNull + @Override + protected String getSuccessTitle() { + return "Packages installed successfully"; + } + + @NotNull + @Override + protected String getSuccessDescription() { + return "Installed packages: " + PyPackageUtil.requirementsToString(myRequirements); + } + + @NotNull + @Override + protected String getFailureTitle() { + return "Install packages failed"; + } + } + + private static class InstallManagementTask extends InstallTask { + + public InstallManagementTask(@Nullable Project project, + @NotNull Sdk sdk, + @Nullable Listener listener) { + super(project, sdk, Collections.<PyRequirement>emptyList(), Collections.<String>emptyList(), listener); + } + + @NotNull + @Override + protected List<PyExternalProcessException> runTask(@NotNull ProgressIndicator indicator) { + final List<PyExternalProcessException> exceptions = new ArrayList<PyExternalProcessException>(); + final PyPackageManager manager = PyPackageManagers.getInstance().forSdk(mySdk); + indicator.setText("Installing packaging tools..."); + indicator.setIndeterminate(true); + try { + manager.installManagement(); + } + catch (PyExternalProcessException e) { + exceptions.add(e); + } + manager.refresh(); + return exceptions; + } + + @NotNull + @Override + protected String getSuccessDescription() { + return "Installed Python packaging tools"; + } + } + + private static class UninstallTask extends PackagingTask { + @NotNull private final Sdk mySdk; + @NotNull private final List<PyPackage> myPackages; + + public UninstallTask(@Nullable Project project, + @NotNull Sdk sdk, + @Nullable Listener listener, + @NotNull List<PyPackage> packages) { + super(project, "Uninstalling packages", listener); + mySdk = sdk; + myPackages = packages; + } + + @NotNull + @Override + protected List<PyExternalProcessException> runTask(@NotNull ProgressIndicator indicator) { + final PyPackageManager manager = PyPackageManagers.getInstance().forSdk(mySdk); + try { + manager.uninstall(myPackages); + return Arrays.asList(); + } + catch (PyExternalProcessException e) { + return Arrays.asList(e); + } + finally { + manager.refresh(); + } + } + + @NotNull + @Override + protected String getSuccessTitle() { + return "Packages uninstalled successfully"; + } + + @NotNull + @Override + protected String getSuccessDescription() { + final String packagesString = StringUtil.join(myPackages, new Function<PyPackage, String>() { + @Override + public String fun(PyPackage pkg) { + return "'" + pkg.getName() + "'"; + } + }, ", "); + return "Uninstalled packages: " + packagesString; + } + + @NotNull + @Override + protected String getFailureTitle() { + return "Uninstall packages failed"; + } + } + + public static String createDescription(List<PyExternalProcessException> exceptions, String firstLine) { + final StringBuilder b = new StringBuilder(); + b.append(firstLine); + b.append("\n\n"); + for (PyExternalProcessException exception : exceptions) { + b.append(exception.toString()); + b.append("\n"); + } + return b.toString(); + } +} diff --git a/python/src/com/jetbrains/python/packaging/PyPackageManagersImpl.java b/python/src/com/jetbrains/python/packaging/PyPackageManagersImpl.java index 054e9e01d23f..e68cede3f799 100644 --- a/python/src/com/jetbrains/python/packaging/PyPackageManagersImpl.java +++ b/python/src/com/jetbrains/python/packaging/PyPackageManagersImpl.java @@ -15,13 +15,11 @@ */ package com.jetbrains.python.packaging; -import com.intellij.openapi.module.Module; import com.intellij.openapi.projectRoots.Sdk; +import com.jetbrains.python.sdk.PythonSdkType; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.HashMap; -import java.util.List; import java.util.Map; /** @@ -36,22 +34,14 @@ public class PyPackageManagersImpl extends PyPackageManagers { final String name = sdk.getName(); PyPackageManagerImpl manager = myInstances.get(name); if (manager == null) { - manager = new PyPackageManagerImpl(sdk); + if (PythonSdkType.isRemote(sdk)) { + manager = new PyRemotePackageManagerImpl(sdk); + } + else { + manager = new PyPackageManagerImpl(sdk); + } myInstances.put(name, manager); } return manager; } - - @Nullable - @Override - public List<PyRequirement> getRequirements(Module module) { - return PyPackageManagerImpl.getRequirements(module); - } - - - @Nullable - @Override - public List<PyRequirement> getRequirementsFromTxt(Module module) { - return PyPackageManagerImpl.getRequirementsFromTxt(module); - } } diff --git a/python/src/com/jetbrains/python/packaging/PyRemotePackageManagerImpl.java b/python/src/com/jetbrains/python/packaging/PyRemotePackageManagerImpl.java new file mode 100644 index 000000000000..6c6de77d9454 --- /dev/null +++ b/python/src/com/jetbrains/python/packaging/PyRemotePackageManagerImpl.java @@ -0,0 +1,177 @@ +/* + * 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.jetbrains.python.packaging; + +import com.google.common.base.Function; +import com.google.common.collect.Collections2; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.process.ProcessOutput; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.projectRoots.SdkAdditionalData; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.remote.RemoteFile; +import com.intellij.remote.RemoteSdkAdditionalData; +import com.intellij.remote.RemoteSdkCredentials; +import com.intellij.remote.VagrantNotStartedException; +import com.intellij.util.ArrayUtil; +import com.intellij.util.PathMappingSettings; +import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase; +import com.jetbrains.python.remote.PythonRemoteInterpreterManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +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) { + super(sdk); + } + + @Nullable + @Override + protected String getHelperPath(String helper) { + final SdkAdditionalData sdkData = mySdk.getSdkAdditionalData(); + if (sdkData instanceof PyRemoteSdkAdditionalDataBase) { + final PyRemoteSdkAdditionalDataBase remoteSdkData = (PyRemoteSdkAdditionalDataBase)mySdk.getSdkAdditionalData(); + try { + final RemoteSdkCredentials remoteSdkCredentials = remoteSdkData.getRemoteSdkCredentials(false); + if (!StringUtil.isEmpty(remoteSdkCredentials.getHelpersPath())) { + return new RemoteFile(remoteSdkCredentials.getHelpersPath(), helper).getPath(); + } + else { + return null; + } + } + catch (Exception e) { + LOG.error(e); + } + } + return null; + } + + @Override + protected ProcessOutput getProcessOutput(@NotNull String helperPath, + @NotNull List<String> args, + boolean askForSudo, + @Nullable String workingDir) throws PyExternalProcessException { + final String homePath = mySdk.getHomePath(); + if (homePath == null) { + throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, "Cannot find interpreter for SDK"); + } + final SdkAdditionalData sdkData = mySdk.getSdkAdditionalData(); + if (sdkData instanceof PyRemoteSdkAdditionalDataBase) { //remote interpreter + RemoteSdkCredentials remoteSdkCredentials; + try { + remoteSdkCredentials = ((RemoteSdkAdditionalData)sdkData).getRemoteSdkCredentials(false); + } + 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. <a href=\"" + + LAUNCH_VAGRANT + + "\">Launch vagrant</a>") + .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()); + } + } + final PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance(); + if (manager != null && remoteSdkCredentials != null) { + final List<String> cmdline = new ArrayList<String>(); + cmdline.add(homePath); + cmdline.add(RemoteFile.detectSystemByPath(homePath).createRemoteFile(helperPath).getPath()); + cmdline.addAll(Collections2.transform(args, new Function<String, String>() { + @Override + public String apply(@Nullable String input) { + 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; + } + catch (ExecutionException e) { + throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, "Error running SDK: " + e.getMessage(), e); + } + } + else { + throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, + PythonRemoteInterpreterManager.WEB_DEPLOYMENT_PLUGIN_IS_DISABLED); + } + } + else { + throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, "Invalid remote SDK"); + } + } + + @Override + protected void subscribeToLocalChanges(Sdk sdk) { + // Local VFS changes aren't needed + } + + @Override + protected void installManagement(@NotNull String name) throws PyExternalProcessException { + super.installManagement(name); + // TODO: remove temp directory for remote interpreter + } + + private static String quoteIfNeeded(String arg) { + return arg.replace("<", "\\<").replace(">", "\\>"); //TODO: move this logic to ParametersListUtil.encode + } +} diff --git a/python/src/com/jetbrains/python/packaging/ui/PyInstalledPackagesPanel.java b/python/src/com/jetbrains/python/packaging/ui/PyInstalledPackagesPanel.java index 348d2817ec75..094149336d5d 100644 --- a/python/src/com/jetbrains/python/packaging/ui/PyInstalledPackagesPanel.java +++ b/python/src/com/jetbrains/python/packaging/ui/PyInstalledPackagesPanel.java @@ -25,10 +25,8 @@ import com.intellij.util.Consumer; import com.intellij.webcore.packaging.InstalledPackage; import com.intellij.webcore.packaging.InstalledPackagesPanel; import com.intellij.webcore.packaging.PackagesNotificationPanel; -import com.jetbrains.python.packaging.PyExternalProcessException; -import com.jetbrains.python.packaging.PyPackage; -import com.jetbrains.python.packaging.PyPackageManager; -import com.jetbrains.python.packaging.PyPackageManagerImpl; +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; @@ -42,33 +40,13 @@ import java.util.Set; * @author yole */ public class PyInstalledPackagesPanel extends InstalledPackagesPanel { - public static final String INSTALL_SETUPTOOLS = "installSetuptools"; - public static final String INSTALL_PIP = "installPip"; + public static final String INSTALL_MANAGEMENT = "installManagement"; public static final String CREATE_VENV = "createVEnv"; - private boolean myHasSetuptools; - private boolean myHasPip = true; + private boolean myHasManagement = false; public PyInstalledPackagesPanel(Project project, PackagesNotificationPanel area) { super(project, area); - myNotificationArea.addLinkHandler(INSTALL_SETUPTOOLS, new Runnable() { - @Override - public void run() { - final Sdk sdk = getSelectedSdk(); - if (sdk != null) { - installManagementTool(sdk, PyPackageManagerImpl.SETUPTOOLS); - } - } - }); - myNotificationArea.addLinkHandler(INSTALL_PIP, new Runnable() { - @Override - public void run() { - final Sdk sdk = getSelectedSdk(); - if (sdk != null) { - installManagementTool(sdk, PyPackageManagerImpl.PIP); - } - } - }); } private Sdk getSelectedSdk() { @@ -85,19 +63,8 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel { application.executeOnPooledThread(new Runnable() { @Override public void run() { - PyExternalProcessException exc = null; - try { - PyPackageManagerImpl packageManager = (PyPackageManagerImpl)PyPackageManager.getInstance(selectedSdk); - myHasSetuptools = packageManager.findInstalledPackage(PyPackageManagerImpl.PACKAGE_SETUPTOOLS) != null; - if (!myHasSetuptools) { - myHasSetuptools = packageManager.findInstalledPackage(PyPackageManagerImpl.PACKAGE_DISTRIBUTE) != null; - } - myHasPip = packageManager.findInstalledPackage(PyPackageManagerImpl.PACKAGE_PIP) != null; - } - catch (PyExternalProcessException e) { - exc = e; - } - final PyExternalProcessException externalProcessException = exc; + PyPackageManager packageManager = PyPackageManager.getInstance(selectedSdk); + myHasManagement = packageManager.hasManagement(false); application.invokeLater(new Runnable() { @Override public void run() { @@ -112,42 +79,24 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel { myNotificationArea.hide(); if (!invalid) { String text = null; - if (externalProcessException != null) { - final int retCode = externalProcessException.getRetcode(); - if (retCode == PyPackageManagerImpl.ERROR_NO_PIP) { - myHasPip = false; - } - else if (retCode == PyPackageManagerImpl.ERROR_NO_SETUPTOOLS) { - myHasSetuptools = false; - } - else { - text = externalProcessException.getMessage(); - } - final boolean hasPackagingTools = myHasPip && myHasSetuptools; - allowCreateVirtualEnv &= !hasPackagingTools; - - if (externalProcessException.hasHandler()) { - final String key = externalProcessException.getHandler().first; - myNotificationArea.addLinkHandler(key, - new Runnable() { - @Override - public void run() { - externalProcessException.getHandler().second.run(); - myNotificationArea.removeLinkHandler(key); - updateNotifications(selectedSdk); + 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 (text == null) { - if (!myHasSetuptools) { - text = "Python package management tools not found. <a href=\"" + INSTALL_SETUPTOOLS + "\">Install 'setuptools'</a>"; - } - else if (!myHasPip) { - text = "Python packaging tool 'pip' not found. <a href=\"" + INSTALL_PIP + "\">Install 'pip'</a>"; - } + if (!myHasManagement) { + text = "Python packaging tools not found. <a href=\"" + INSTALL_MANAGEMENT + "\">Install packaging tools</a>"; } if (text != null) { if (allowCreateVirtualEnv) { @@ -157,7 +106,7 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel { } } - myInstallButton.setEnabled(!invalid && externalProcessException == null && myHasPip); + myInstallButton.setEnabled(!invalid && myHasManagement); } } }, ModalityState.any()); @@ -170,8 +119,8 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel { return Sets.newHashSet("pip", "distutils", "setuptools"); } - private void installManagementTool(@NotNull final Sdk sdk, final String name) { - final PyPackageManagerImpl.UI ui = new PyPackageManagerImpl.UI(myProject, sdk, new PyPackageManagerImpl.UI.Listener() { + private void installManagementTools(@NotNull final Sdk sdk) { + final PyPackageManagerUI ui = new PyPackageManagerUI(myProject, sdk, new PyPackageManagerUI.Listener() { @Override public void started() { myPackagesTable.setPaintBusy(true); @@ -180,11 +129,11 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel { @Override public void finished(List<PyExternalProcessException> exceptions) { myPackagesTable.setPaintBusy(false); - PyPackageManagerImpl packageManager = (PyPackageManagerImpl)PyPackageManager.getInstance(sdk); + PyPackageManager packageManager = PyPackageManager.getInstance(sdk); if (!exceptions.isEmpty()) { - final String firstLine = "Install package failed. "; - final String description = PyPackageManagerImpl.UI.createDescription(exceptions, firstLine); - packageManager.showInstallationError(myProject, "Failed to install " + name, description); + 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)); @@ -194,22 +143,22 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel { updateNotifications(sdk); } }); - ui.installManagement(name); + ui.installManagement(); } @Override protected boolean canUninstallPackage(InstalledPackage pkg) { - if (!myHasPip) return false; + if (!myHasManagement) return false; if (PythonSdkType.isVirtualEnv(getSelectedSdk()) && pkg instanceof PyPackage) { final String location = ((PyPackage)pkg).getLocation(); - if (location != null && location.startsWith(PyPackageManagerImpl.getUserSite())) { + if (location != null && location.startsWith(PySdkUtil.getUserSite())) { return false; } } final String name = pkg.getName(); - if (PyPackageManagerImpl.PACKAGE_PIP.equals(name) || - PyPackageManagerImpl.PACKAGE_SETUPTOOLS.equals(name) || - PyPackageManagerImpl.PACKAGE_DISTRIBUTE.equals(name)) { + if (PyPackageManager.PACKAGE_PIP.equals(name) || + PyPackageManager.PACKAGE_SETUPTOOLS.equals(name) || + PyPackageManager.PACKAGE_DISTRIBUTE.equals(name)) { return false; } return true; @@ -217,6 +166,6 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel { @Override protected boolean canUpgradePackage(InstalledPackage pyPackage) { - return myHasPip; + return myHasManagement; } } diff --git a/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java b/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java index ffd291a995b1..95330b361dfb 100644 --- a/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java +++ b/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java @@ -23,6 +23,7 @@ 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.sdk.PySdkUtil; import com.jetbrains.python.sdk.PythonSdkType; import org.apache.xmlrpc.AsyncCallback; import org.jetbrains.annotations.NonNls; @@ -112,7 +113,7 @@ public class PyPackageManagementService extends PackageManagementService { public String getInstallToUserText() { String userSiteText = "Install to user's site packages directory"; if (!PythonSdkType.isRemote(mySdk)) - userSiteText += " (" + PyPackageManagerImpl.getUserSite() + ")"; + userSiteText += " (" + PySdkUtil.getUserSite() + ")"; return userSiteText; } @@ -130,12 +131,12 @@ public class PyPackageManagementService extends PackageManagementService { public Collection<InstalledPackage> getInstalledPackages() throws IOException { List<PyPackage> packages; try { - packages = ((PyPackageManagerImpl)PyPackageManager.getInstance(mySdk)).getPackages(); + packages = PyPackageManager.getInstance(mySdk).getPackages(false); } catch (PyExternalProcessException e) { throw new IOException(e); } - return new ArrayList<InstalledPackage>(packages); + return packages != null ? new ArrayList<InstalledPackage>(packages) : new ArrayList<InstalledPackage>(); } @Override @@ -145,7 +146,7 @@ public class PyPackageManagementService extends PackageManagementService { final String repository = PyPIPackageUtil.PYPI_URL.equals(repoPackage.getRepoUrl()) ? null : repoPackage.getRepoUrl(); final List<String> extraArgs = new ArrayList<String>(); if (installToUser) { - extraArgs.add(PyPackageManagerImpl.USE_USER_SITE); + extraArgs.add(PyPackageManager.USE_USER_SITE); } if (extraOptions != null) { // TODO: Respect arguments quotation @@ -166,7 +167,7 @@ public class PyPackageManagementService extends PackageManagementService { req = new PyRequirement(packageName); } - final PyPackageManagerImpl.UI ui = new PyPackageManagerImpl.UI(myProject, mySdk, new PyPackageManagerImpl.UI.Listener() { + final PyPackageManagerUI ui = new PyPackageManagerUI(myProject, mySdk, new PyPackageManagerUI.Listener() { @Override public void started() { listener.operationStarted(packageName); @@ -183,7 +184,7 @@ public class PyPackageManagementService extends PackageManagementService { private String toErrorDescription(List<PyExternalProcessException> exceptions) { String errorDescription = null; if (exceptions != null && exceptions.size() > 0) { - errorDescription = PyPackageManagerImpl.UI.createDescription(exceptions, ""); + errorDescription = PyPackageManagerUI.createDescription(exceptions, ""); } return errorDescription; } @@ -191,7 +192,7 @@ public class PyPackageManagementService extends PackageManagementService { @Override public void uninstallPackages(List<InstalledPackage> installedPackages, final Listener listener) { final String packageName = installedPackages.size() == 1 ? installedPackages.get(0).getName() : null; - PyPackageManagerImpl.UI ui = new PyPackageManagerImpl.UI(myProject, mySdk, new PyPackageManagerImpl.UI.Listener() { + PyPackageManagerUI ui = new PyPackageManagerUI(myProject, mySdk, new PyPackageManagerUI.Listener() { @Override public void started() { listener.operationStarted(packageName); |