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