Thanks to visit codestin.com
Credit goes to github.com

Skip to content

gh-75229: ensurepip does not honour the value of $(prefix) #17634

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions Doc/library/ensurepip.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ 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:

* :samp:`--prefix {dir}`: Installs ``pip`` using the given directory prefix.
* :samp:`--root {dir}`: Installs ``pip`` relative to the given root directory
rather than the root of the currently active virtual environment (if any)
or the default root for the current Python installation.
Expand Down Expand Up @@ -94,7 +95,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.

Expand Down Expand Up @@ -122,6 +123,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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should explain the difference between root and prefix better. Per now, it is not obvious how they differ.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it's not obvious. And that's speaking as a pip maintainer... It's all shrouded in mystery and legacy behaviour dating back to the origins of distutils, I think. And it's very much to do with Unix - on Windows, those options are essentially never used (which is mostly why I don't understand them...)

But I think it's fair to assume that anyone using these options is doing so because they know of the same options in pip, and so they understand what they are doing (or at least their confusion isn't our problem). It's not ideal, but OTOH, this isn't the place for a tutorial on packaging concepts, either.

Copy link
Member

@merwok merwok May 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

root vs prefix goes back to the base unix model I think, and may be explained in make documentation or something similarly old.

Something along the lines: the prefix (for installation) can be /usr or /usr/local or /opt/my-program but the root directory (for building) is /tmp/something, so the actual part where files are created is /tmp/something/usr/local/bin/python but strings embedded in the programs/libraries/docs look like /usr/local/bin/python so that they’re good at runtime.

(don’t ask me which of the three values corresponds to DESTDIR)

(edit: better replies at #17634 (comment))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DESTDIR is the make equivalent of specifying --root


.. versionadded:: 3.13

The *prefix* parameter.

.. audit-event:: ensurepip.bootstrap root ensurepip.bootstrap

.. note::
Expand Down
20 changes: 15 additions & 5 deletions Lib/ensurepip/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,28 +122,30 @@ 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.
"""
if root is not None and prefix is not None:
raise ValueError("Cannot use 'root' and 'prefix' together")
if altinstall and default_pip:
raise ValueError("Cannot use altinstall and default_pip together")

Expand Down Expand Up @@ -190,6 +192,8 @@ def _bootstrap(*, root=None, upgrade=False, user=False,
args = ["install", "--no-cache-dir", "--no-index", "--find-links", tmpdir]
if root:
args += ["--root", root]
if prefix:
args += ["--prefix", prefix]
if upgrade:
args += ["--upgrade"]
if user:
Expand Down Expand Up @@ -264,6 +268,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",
Expand All @@ -283,6 +292,7 @@ def _main(argv=None):

return _bootstrap(
root=args.root,
prefix=args.prefix,
upgrade=args.upgrade,
user=args.user,
verbosity=args.verbosity,
Expand Down
15 changes: 15 additions & 0 deletions Lib/test/test_ensurepip.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,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)

Expand Down
4 changes: 2 additions & 2 deletions Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -1981,7 +1981,7 @@ install: @FRAMEWORKINSTALLFIRST@ commoninstall bininstall maninstall @FRAMEWORKI
install|*) ensurepip="" ;; \
esac; \
$(RUNSHARED) $(PYTHON_FOR_BUILD) -m ensurepip \
$$ensurepip --root=$(DESTDIR)/ ; \
$$ensurepip --prefix=$(prefix) ; \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know enough about the Unix build process to know if using --prefix instead of --root is the right thing to do here. I'm going to mark my review as resolved (and approve the PR) but I'd recommend getting someone who knows how Unix/MacOS installs work to check this part of the change before merging (unless, of course, you are such a person and you're happy it's right 🙂)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can test it on *nix and macOS, but I know very little about pip/ensurepip :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Looking at the pip source, --prefix corresponds roughly to the sysconfig scheme posix_prefix. And --root can be used with --prefix, it basically rebases the installation directories on the root directory.

So if you have prefix=/foo/bar and root=/bax/quux, then you'd get purelib pointing to /baz/quux/foo/bar/lib/pythonX.Y/site-packages. With just prefix you'd get /foo/bar/lib/pythonX.Y/site-packages. And with just root you'd get /baz/quux/lib/pythonX.Y/site-packages.

There's all sorts of qualifications and special cases (because this is packaging 🙁) but that's the basic idea.

Given that the original issue was around cross-compilation for Android, I have zero idea how any of this would impact that actual scenario, though...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would guess you'd use --root for the directory where your target rootfs is being populated, and then you'd use --prefix for the path within the target rootfs.

cc. @gpshead, since you dabble with embedded systems IIRC.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not the right person to answer this either, i've poked #build on discord to see if anyone else is.

Copy link

@JasonGantner JasonGantner May 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can confirm that using --prefix for the expected location in the destination filesystem and --root(=$DESTDIR) for the current location of the virtual filesystem where the package is being prepared makes total sense. (and is how setup.py works )

Having only one or the other leads to headaches;

TL;DR: We expect --prefix to change the install location of files and the path they use to reference one another while --root should change the location we put files without changing how they refer to one another

With a basic build process and environment as such :

SOURCE_DIR=/tmp/py3.12.3
TARGET_ROOT=/tmp/build.out
TARGET_PREFIX=/opt/python/py3.12.3
cd ${SOURCE_DIR}
configure --prefix=${TARGET_PREFIX} --with-ensurepip=no [...]
make
make DESTDIR=${TARGET_ROOT} install altinstall

We get these results :

${TARGET_ROOT}${TARGET_PREFIX}/bin/python3 -m ensurepip
head -n 1 ${TARGET_ROOT}${TARGET_PREFIX}/bin/pip
#!/tmp/build.out/opt/python/py3.12.3/bin/python3
${TARGET_ROOT}${TARGET_PREFIX}/bin/python -m ensurepip --root ${TARGET_ROOT}
head -n 1 ${TARGET_ROOT}${TARGET_ROOT}${TARGET_PREFIX}/bin/pip
#!/tmp/build.out/tmp/build.out/opt/python/py3.12.3/bin/python3

In both cases the script installed contains the ${TARGET_ROOT} in the shebang when it shouldn't, but specificaly using --root behaves the way I would expect --prefix to work if no prefix had been set in the configure script.

The expected install locations for pip3 should be:

  • --prefix : ${PREFIX}/bin/pip3 with shebang #!${PREFIX}/bin/python3
  • --root (assuming no prefix) : ${ROOT}/bin/pip3 with shebang #!/bin/python3
  • --prefix and --root : ${ROOT}${PREFIX}/bin/pip3 with shebang #!${PREFIX}/bin/python3

The behaviour becomes even weirder when using --with-ensurepip=(install|upgrade) in configure.
It triggers a call to something equivalent to :

LD_LIBRARY_PATH=${SOURCE_DIR} ${SOURCE_DIR}/python -E -m ensurepip [--upgrade] --root=${DESTDIR}

But this call ignores the --root parameter completely and just acts on the host system if an installation using the same prefix exists. The -E parameter makes setting PYTHONHOME irrelevant but omitting it doesn't resolve the issue.

With thing the way they are currently, with-ensurepip=no is the only option making sense in configure. After that, I see two quick and dirty solutions :

  • calling ${TARGET_ROOT}${TARGET_PREFIX}/bin/python3 -m ensurepip on the build host, and fixing the shebang in the scripts afterwards
  • adding ${TARGET_PREFIX}/bin/python3 -m ensurepip as a post-install action on the destination host

I agree that the proper solution would be to have the makefile call something along the lines of:

Suggested change
$$ensurepip --prefix=$(prefix) ; \
$$ensurepip --root=$(DESTDIR)/ --prefix=$(prefix) ; \

I'm not sure the trailing / is necessary.

One issue remains when cross-compiling (eg. AARCH64 binaries on a X86_64 host), the build host (x86_64) must be able to execute the target's binaries (AARCH64) for the actual call to work. Assuming ensurepip is pure python, the call should use the host's python3 binary (x86_64) with --prefix and --root rather than the one being built (AARCH64).

I don't see it as a blocking issue since the person building the package should be able to tell if they can use --with-ensurepip or if they should rather call ensurepip from the build host's python rather than letting the Makefile guess/try.

EDIT:
I forgot to mention that the actual module installation path follows the behaviour shown with the scripts and shebang. I just used them to illustrate the expected and actual behaviours

EDIT2: Added TL;DR an some clarifications

fi

.PHONY: altinstall
Expand All @@ -1992,7 +1992,7 @@ altinstall: commoninstall
install|*) ensurepip="--altinstall" ;; \
esac; \
$(RUNSHARED) $(PYTHON_FOR_BUILD) -m ensurepip \
$$ensurepip --root=$(DESTDIR)/ ; \
$$ensurepip --prefix=$(prefix) ; \
fi

.PHONY: commoninstall
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A directory prefix can now be specified when using :mod:`ensurepip`.