1import os 2import os.path 3import sys 4import runpy 5import tempfile 6import subprocess 7from importlib import resources 8 9from . import _bundled 10 11 12 13__all__ = ["version", "bootstrap"] 14 15 16_SETUPTOOLS_VERSION = "49.2.1" 17 18_PIP_VERSION = "20.2.3" 19 20_PROJECTS = [ 21 ("setuptools", _SETUPTOOLS_VERSION, "py3"), 22 ("pip", _PIP_VERSION, "py2.py3"), 23] 24 25 26def _run_pip(args, additional_paths=None): 27 # Run the bootstraping in a subprocess to avoid leaking any state that happens 28 # after pip has executed. Particulary, this avoids the case when pip holds onto 29 # the files in *additional_paths*, preventing us to remove them at the end of the 30 # invocation. 31 code = f""" 32import runpy 33import sys 34sys.path = {additional_paths or []} + sys.path 35sys.argv[1:] = {args} 36runpy.run_module("pip", run_name="__main__", alter_sys=True) 37""" 38 return subprocess.run([sys.executable, "-c", code], check=True).returncode 39 40 41def version(): 42 """ 43 Returns a string specifying the bundled version of pip. 44 """ 45 return _PIP_VERSION 46 47def _disable_pip_configuration_settings(): 48 # We deliberately ignore all pip environment variables 49 # when invoking pip 50 # See http://bugs.python.org/issue19734 for details 51 keys_to_remove = [k for k in os.environ if k.startswith("PIP_")] 52 for k in keys_to_remove: 53 del os.environ[k] 54 # We also ignore the settings in the default pip configuration file 55 # See http://bugs.python.org/issue20053 for details 56 os.environ['PIP_CONFIG_FILE'] = os.devnull 57 58 59def bootstrap(*, root=None, upgrade=False, user=False, 60 altinstall=False, default_pip=False, 61 verbosity=0): 62 """ 63 Bootstrap pip into the current Python installation (or the given root 64 directory). 65 66 Note that calling this function will alter both sys.path and os.environ. 67 """ 68 # Discard the return value 69 _bootstrap(root=root, upgrade=upgrade, user=user, 70 altinstall=altinstall, default_pip=default_pip, 71 verbosity=verbosity) 72 73 74def _bootstrap(*, root=None, upgrade=False, user=False, 75 altinstall=False, default_pip=False, 76 verbosity=0): 77 """ 78 Bootstrap pip into the current Python installation (or the given root 79 directory). Returns pip command status code. 80 81 Note that calling this function will alter both sys.path and os.environ. 82 """ 83 if altinstall and default_pip: 84 raise ValueError("Cannot use altinstall and default_pip together") 85 86 sys.audit("ensurepip.bootstrap", root) 87 88 _disable_pip_configuration_settings() 89 90 # By default, installing pip and setuptools installs all of the 91 # following scripts (X.Y == running Python version): 92 # 93 # pip, pipX, pipX.Y, easy_install, easy_install-X.Y 94 # 95 # pip 1.5+ allows ensurepip to request that some of those be left out 96 if altinstall: 97 # omit pip, pipX and easy_install 98 os.environ["ENSUREPIP_OPTIONS"] = "altinstall" 99 elif not default_pip: 100 # omit pip and easy_install 101 os.environ["ENSUREPIP_OPTIONS"] = "install" 102 103 with tempfile.TemporaryDirectory() as tmpdir: 104 # Put our bundled wheels into a temporary directory and construct the 105 # additional paths that need added to sys.path 106 additional_paths = [] 107 for project, version, py_tag in _PROJECTS: 108 wheel_name = "{}-{}-{}-none-any.whl".format(project, version, py_tag) 109 whl = resources.read_binary( 110 _bundled, 111 wheel_name, 112 ) 113 with open(os.path.join(tmpdir, wheel_name), "wb") as fp: 114 fp.write(whl) 115 116 additional_paths.append(os.path.join(tmpdir, wheel_name)) 117 118 # Construct the arguments to be passed to the pip command 119 args = ["install", "--no-cache-dir", "--no-index", "--find-links", tmpdir] 120 if root: 121 args += ["--root", root] 122 if upgrade: 123 args += ["--upgrade"] 124 if user: 125 args += ["--user"] 126 if verbosity: 127 args += ["-" + "v" * verbosity] 128 129 return _run_pip(args + [p[0] for p in _PROJECTS], additional_paths) 130 131def _uninstall_helper(*, verbosity=0): 132 """Helper to support a clean default uninstall process on Windows 133 134 Note that calling this function may alter os.environ. 135 """ 136 # Nothing to do if pip was never installed, or has been removed 137 try: 138 import pip 139 except ImportError: 140 return 141 142 # If the pip version doesn't match the bundled one, leave it alone 143 if pip.__version__ != _PIP_VERSION: 144 msg = ("ensurepip will only uninstall a matching version " 145 "({!r} installed, {!r} bundled)") 146 print(msg.format(pip.__version__, _PIP_VERSION), file=sys.stderr) 147 return 148 149 _disable_pip_configuration_settings() 150 151 # Construct the arguments to be passed to the pip command 152 args = ["uninstall", "-y", "--disable-pip-version-check"] 153 if verbosity: 154 args += ["-" + "v" * verbosity] 155 156 return _run_pip(args + [p[0] for p in reversed(_PROJECTS)]) 157 158 159def _main(argv=None): 160 import argparse 161 parser = argparse.ArgumentParser(prog="python -m ensurepip") 162 parser.add_argument( 163 "--version", 164 action="version", 165 version="pip {}".format(version()), 166 help="Show the version of pip that is bundled with this Python.", 167 ) 168 parser.add_argument( 169 "-v", "--verbose", 170 action="count", 171 default=0, 172 dest="verbosity", 173 help=("Give more output. Option is additive, and can be used up to 3 " 174 "times."), 175 ) 176 parser.add_argument( 177 "-U", "--upgrade", 178 action="store_true", 179 default=False, 180 help="Upgrade pip and dependencies, even if already installed.", 181 ) 182 parser.add_argument( 183 "--user", 184 action="store_true", 185 default=False, 186 help="Install using the user scheme.", 187 ) 188 parser.add_argument( 189 "--root", 190 default=None, 191 help="Install everything relative to this alternate root directory.", 192 ) 193 parser.add_argument( 194 "--altinstall", 195 action="store_true", 196 default=False, 197 help=("Make an alternate install, installing only the X.Y versioned " 198 "scripts (Default: pipX, pipX.Y, easy_install-X.Y)."), 199 ) 200 parser.add_argument( 201 "--default-pip", 202 action="store_true", 203 default=False, 204 help=("Make a default pip install, installing the unqualified pip " 205 "and easy_install in addition to the versioned scripts."), 206 ) 207 208 args = parser.parse_args(argv) 209 210 return _bootstrap( 211 root=args.root, 212 upgrade=args.upgrade, 213 user=args.user, 214 verbosity=args.verbosity, 215 altinstall=args.altinstall, 216 default_pip=args.default_pip, 217 ) 218