diff --git a/Doc/library/ensurepip.rst b/Doc/library/ensurepip.rst
index fa102c4a080103..26cf1715f5d74b 100644
--- a/Doc/library/ensurepip.rst
+++ b/Doc/library/ensurepip.rst
@@ -61,7 +61,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
@@ -104,7 +108,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.
@@ -132,6 +136,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 aa641e94a8b336..2d41dd09d9d9da 100644
--- a/Lib/ensurepip/__init__.py
+++ b/Lib/ensurepip/__init__.py
@@ -108,25 +108,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.
"""
@@ -160,15 +160,31 @@ 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 user:
+ # --user is mutually exclusive with --root/--prefix,
+ # pip will enforce this.
+ args += ["--user"]
+ else:
+ # Handle installation paths.
+ # If --root is given but not --prefix, we default to a prefix of "/"
+ # so that the install happens at the root of the --root directory.
+ # Otherwise, pip would use the configured sys.prefix, e.g.
+ # /usr/local, and install into ${root}/usr/local/.
+ effective_prefix = prefix
+ if root and not prefix:
+ effective_prefix = "/"
+
+ if root:
+ args += ["--root", root]
+
+ if effective_prefix:
+ args += ["--prefix", effective_prefix]
+
return _run_pip([*args, "pip"], [os.fsdecode(tmp_wheel_path)])
@@ -237,6 +253,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",
@@ -256,6 +277,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 6d3c91b0b6d9f9..cd858ff09f3e4d 100644
--- a/Lib/test/test_ensurepip.py
+++ b/Lib/test/test_ensurepip.py
@@ -100,6 +100,21 @@ def test_bootstrapping_with_root(self):
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/", "pip",
+ ],
+ unittest.mock.ANY,
+ )
+
+ def test_root_and_prefix_mutual_exclusive(self):
+ with self.assertRaises(ValueError):
+ ensurepip.bootstrap(root="", prefix="")
+ self.assertFalse(self.run_pip.called)
+
def test_bootstrapping_with_user(self):
ensurepip.bootstrap(user=True)
diff --git a/Makefile.pre.in b/Makefile.pre.in
index 66b34b779f27cb..07bbd6c4dde729 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -2336,7 +2336,7 @@ install: @FRAMEWORKINSTALLFIRST@ @INSTALLTARGETS@ @FRAMEWORKINSTALLLAST@
install|*) ensurepip="" ;; \
esac; \
$(RUNSHARED) $(PYTHON_FOR_BUILD) -m ensurepip \
- $$ensurepip --root=$(DESTDIR)/ ; \
+ $$ensurepip --prefix=$(prefix) ; \
fi
.PHONY: altinstall
@@ -2347,7 +2347,7 @@ altinstall: commoninstall
install|*) ensurepip="--altinstall" ;; \
esac; \
$(RUNSHARED) $(PYTHON_FOR_BUILD) -m ensurepip \
- $$ensurepip --root=$(DESTDIR)/ ; \
+ $$ensurepip --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`.