1# Copyright (c) 2009, Willow Garage, Inc. 2# All rights reserved. 3# 4# Redistribution and use in source and binary forms, with or without 5# modification, are permitted provided that the following conditions are met: 6# 7# * Redistributions of source code must retain the above copyright 8# notice, this list of conditions and the following disclaimer. 9# * Redistributions in binary form must reproduce the above copyright 10# notice, this list of conditions and the following disclaimer in the 11# documentation and/or other materials provided with the distribution. 12# * Neither the name of the Willow Garage, Inc. nor the names of its 13# contributors may be used to endorse or promote products derived from 14# this software without specific prior written permission. 15# 16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 20# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26# POSSIBILITY OF SUCH DAMAGE. 27 28# Author Tully Foote/tfoote@willowgarage.com 29 30from __future__ import print_function 31 32import os 33import pkg_resources 34import subprocess 35import sys 36 37from ..core import InstallFailed 38from ..installers import PackageManagerInstaller 39from ..shell_utils import read_stdout 40 41# pip package manager key 42PIP_INSTALLER = 'pip' 43 44 45def register_installers(context): 46 context.set_installer(PIP_INSTALLER, PipInstaller()) 47 48 49def get_pip_command(): 50 # First try pip2 or pip3 51 cmd = ['pip' + os.environ['ROS_PYTHON_VERSION']] 52 if is_cmd_available(cmd): 53 return cmd 54 55 # Second, try using the same python executable since we know that exists 56 if os.environ['ROS_PYTHON_VERSION'] == sys.version[0]: 57 try: 58 import pip 59 except ImportError: 60 pass 61 else: 62 return [sys.executable, '-m', 'pip'] 63 64 # Finally, try python2 or python3 commands 65 cmd = ['python' + os.environ['ROS_PYTHON_VERSION'], '-m', 'pip'] 66 if is_cmd_available(cmd): 67 return cmd 68 return None 69 70 71def is_cmd_available(cmd): 72 try: 73 subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() 74 return True 75 except OSError: 76 return False 77 78 79def pip_detect(pkgs, exec_fn=None): 80 """ 81 Given a list of package, return the list of installed packages. 82 83 :param exec_fn: function to execute Popen and read stdout (for testing) 84 """ 85 pip_cmd = get_pip_command() 86 if not pip_cmd: 87 return [] 88 89 fallback_to_pip_show = False 90 if exec_fn is None: 91 exec_fn = read_stdout 92 fallback_to_pip_show = True 93 pkg_list = exec_fn(pip_cmd + ['freeze']).split('\n') 94 95 ret_list = [] 96 for pkg in pkg_list: 97 pkg_row = pkg.split('==') 98 if pkg_row[0] in pkgs: 99 ret_list.append(pkg_row[0]) 100 101 # Try to detect with the return code of `pip show`. 102 # This can show the existance of things like `argparse` which 103 # otherwise do not show up. 104 # See: 105 # https://github.com/pypa/pip/issues/1570#issuecomment-71111030 106 if fallback_to_pip_show: 107 for pkg in [p for p in pkgs if p not in ret_list]: 108 # does not see retcode but stdout for old pip to check if installed 109 proc = subprocess.Popen( 110 pip_cmd + ['show', pkg], 111 stdout=subprocess.PIPE, 112 stderr=subprocess.STDOUT 113 ) 114 output, _ = proc.communicate() 115 output = output.strip() 116 if proc.returncode == 0 and output: 117 # `pip show` detected it, add it to the list. 118 ret_list.append(pkg) 119 120 return ret_list 121 122 123class PipInstaller(PackageManagerInstaller): 124 """ 125 :class:`Installer` support for pip. 126 """ 127 128 def __init__(self): 129 super(PipInstaller, self).__init__(pip_detect, supports_depends=True) 130 131 def get_version_strings(self): 132 pip_version = pkg_resources.get_distribution('pip').version 133 setuptools_version = pkg_resources.get_distribution('setuptools').version 134 version_strings = [ 135 'pip {}'.format(pip_version), 136 'setuptools {}'.format(setuptools_version), 137 ] 138 return version_strings 139 140 def get_install_command(self, resolved, interactive=True, reinstall=False, quiet=False): 141 pip_cmd = get_pip_command() 142 if not pip_cmd: 143 raise InstallFailed((PIP_INSTALLER, 'pip is not installed')) 144 packages = self.get_packages_to_install(resolved, reinstall=reinstall) 145 if not packages: 146 return [] 147 cmd = pip_cmd + ['install', '-U'] 148 if quiet: 149 cmd.append('-q') 150 if reinstall: 151 cmd.append('-I') 152 return [self.elevate_priv(cmd + [p]) for p in packages] 153