• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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