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`.