diff --git a/Doc/library/ensurepip.rst b/Doc/library/ensurepip.rst
index e0d77229b11802..8d3b2021d83d75 100644
--- a/Doc/library/ensurepip.rst
+++ b/Doc/library/ensurepip.rst
@@ -65,7 +65,11 @@ is at least as recent as the one available in ``ensurepip``, pass the
By default, ``pip`` is installed into the current virtual environment
(if one is active) or into the system site packages (if there is no
active virtual environment). The installation location can be controlled
-through two additional command line options:
+through some additional command line options:
+
+.. option:: --prefix
+
+ Installs ``pip`` using the given directory prefix.
.. option:: --root
@@ -108,7 +112,7 @@ Module API
.. function:: bootstrap(root=None, upgrade=False, user=False, \
altinstall=False, default_pip=False, \
- verbosity=0)
+ verbosity=0, prefix=None)
Bootstraps ``pip`` into the current or designated environment.
@@ -136,6 +140,12 @@ Module API
*verbosity* controls the level of output to :data:`sys.stdout` from the
bootstrapping operation.
+ *prefix* specifies the directory prefix to use when installing.
+
+ .. versionadded:: 3.14
+
+ The *prefix* parameter.
+
.. audit-event:: ensurepip.bootstrap root ensurepip.bootstrap
.. note::
diff --git a/Lib/ensurepip/__init__.py b/Lib/ensurepip/__init__.py
index 5a55525d6bd235..27a44d32b5cf51 100644
--- a/Lib/ensurepip/__init__.py
+++ b/Lib/ensurepip/__init__.py
@@ -109,25 +109,25 @@ def _disable_pip_configuration_settings():
def bootstrap(*, root=None, upgrade=False, user=False,
altinstall=False, default_pip=False,
- verbosity=0):
+ verbosity=0, prefix=None):
"""
Bootstrap pip into the current Python installation (or the given root
- directory).
+ and directory prefix).
Note that calling this function will alter both sys.path and os.environ.
"""
# Discard the return value
_bootstrap(root=root, upgrade=upgrade, user=user,
altinstall=altinstall, default_pip=default_pip,
- verbosity=verbosity)
+ verbosity=verbosity, prefix=prefix)
def _bootstrap(*, root=None, upgrade=False, user=False,
altinstall=False, default_pip=False,
- verbosity=0):
+ verbosity=0, prefix=None):
"""
Bootstrap pip into the current Python installation (or the given root
- directory). Returns pip command status code.
+ and directory prefix). Returns pip command status code.
Note that calling this function will alter both sys.path and os.environ.
"""
@@ -170,17 +170,28 @@ def _bootstrap(*, root=None, upgrade=False, user=False,
# Construct the arguments to be passed to the pip command
args = ["install", "--no-cache-dir", "--no-index", "--find-links", tmpdir]
- if root:
- args += ["--root", root]
if upgrade:
args += ["--upgrade"]
- if user:
- args += ["--user"]
if verbosity:
args += ["-" + "v" * verbosity]
if sys.implementation.cache_tag is None:
args += ["--no-compile"]
+ if root:
+ args += ["--root", root]
+
+ if user:
+ # --user is mutually exclusive with --root/--prefix,
+ # pip will enforce this.
+ args += ["--user"]
+ elif prefix:
+ args += ["--prefix", prefix]
+
+ # Force the script shebang to point to the correct, final
+ # executable path. This is necessary when --root is used.
+ executable_path = Path(prefix) / "bin" / Path(sys.executable).name
+ args += ["--executable", os.fsdecode(executable_path)]
+
return _run_pip([*args, "pip"], [os.fsdecode(tmp_wheel_path)])
@@ -249,6 +260,11 @@ def _main(argv=None):
default=None,
help="Install everything relative to this alternate root directory.",
)
+ parser.add_argument(
+ "--prefix",
+ default=None,
+ help="Install everything using this prefix.",
+ )
parser.add_argument(
"--altinstall",
action="store_true",
@@ -268,6 +284,7 @@ def _main(argv=None):
return _bootstrap(
root=args.root,
+ prefix=args.prefix,
upgrade=args.upgrade,
user=args.user,
verbosity=args.verbosity,
diff --git a/Lib/test/test_ensurepip.py b/Lib/test/test_ensurepip.py
index 20a56ed715d8ab..e19a118d45e046 100644
--- a/Lib/test/test_ensurepip.py
+++ b/Lib/test/test_ensurepip.py
@@ -115,8 +115,30 @@ def test_bootstrapping_with_root(self):
self.run_pip.assert_called_once_with(
[
"install", "--no-cache-dir", "--no-index", "--find-links",
- unittest.mock.ANY, "--root", "/foo/bar/", *COMPILE_OPT,
- "pip",
+ unittest.mock.ANY, "--root", "/foo/bar/", "pip", *COMPILE_OPT,
+ ],
+ unittest.mock.ANY,
+ )
+
+ def test_bootstrapping_with_prefix(self):
+ ensurepip.bootstrap(prefix="/foo/bar/")
+ self.run_pip.assert_called_once_with(
+ [
+ "install", "--no-cache-dir", "--no-index", "--find-links",
+ unittest.mock.ANY, "--prefix", "/foo/bar/",
+ "--executable", unittest.mock.ANY, "pip",
+ ],
+ unittest.mock.ANY,
+ )
+
+ def test_bootstrapping_with_root_and_prefix(self):
+ ensurepip.bootstrap(root="/foo/root/", prefix="/foo/prefix/")
+ self.run_pip.assert_called_once_with(
+ [
+ "install", "--no-cache-dir", "--no-index", "--find-links",
+ unittest.mock.ANY, "--root", "/foo/root/",
+ "--prefix", "/foo/prefix/", "--executable",
+ unittest.mock.ANY, "pip",
],
unittest.mock.ANY,
)
diff --git a/Makefile.pre.in b/Makefile.pre.in
index 2ce53c6a816212..2c12f7dcd44f4b 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -2443,7 +2443,7 @@ install: @FRAMEWORKINSTALLFIRST@ @INSTALLTARGETS@ @FRAMEWORKINSTALLLAST@
install|*) ensurepip="" ;; \
esac; \
$(RUNSHARED) $(PYTHON_FOR_BUILD) -m ensurepip \
- $$ensurepip --root=$(DESTDIR)/ ; \
+ $$ensurepip --root=$(DESTDIR)/ --prefix=$(prefix) ; \
fi
.PHONY: altinstall
@@ -2454,7 +2454,7 @@ altinstall: commoninstall
install|*) ensurepip="--altinstall" ;; \
esac; \
$(RUNSHARED) $(PYTHON_FOR_BUILD) -m ensurepip \
- $$ensurepip --root=$(DESTDIR)/ ; \
+ $$ensurepip --root=$(DESTDIR)/ --prefix=$(prefix) ; \
fi
.PHONY: commoninstall
diff --git a/Misc/NEWS.d/next/Library/2019-12-16-17-50-42.bpo-31046.XA-Qfr.rst b/Misc/NEWS.d/next/Library/2019-12-16-17-50-42.bpo-31046.XA-Qfr.rst
new file mode 100644
index 00000000000000..07eb89d4d23e50
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-12-16-17-50-42.bpo-31046.XA-Qfr.rst
@@ -0,0 +1 @@
+A directory prefix can now be specified when using :mod:`ensurepip`.