Improve build isolation in PythonPipBuilder#40224
Conversation
df3eece to
16130f3
Compare
|
Dunno if somebody can suggest a better solution for ensuring |
4eb3976 to
9a184a1
Compare
|
@adamjstewart thoughts? This works in Gitlab. The GHA failure is also correct (setuptools is missing as a dependency in py-isort). Fixed in #40234. The fact that the GHA failed with this PR only shows that we need this for better isolation from system python packages :) |
Detected in spack#40224. In the past, system setuptools could be picked up when using an external python, so py-isort@4 would install fine. With the linked PR, pip can only consider packages that Spack controls from PYTHONPATH, so the issue of missing py-setuptools showed up.
|
Alternative / compromise:
|
Would using or extending EnvBuilder work for this case? At a glance it looks like this method could be leveraged to do what you want. |
b3eb42e to
ebf9358
Compare
Not immediately sure if that can be used. Bit more context, here's an example Here's where pip figures out the shebang line: The The only way to override this, is if you can somehow pass a custom executable here or here, but that looks impossible. (especially since all we have is the pip command line...) |
PythonPipBuilder
ebf9358 to
16c7c03
Compare
|
@pradyunsg, do you have opinions about this? The top level comment was updated to describe the issue and solution, the rest of the comments is not relevant. |
Hmm... we could make pip respect this in a future release, I think. File an issue over at pypa/pip about this? This might also get resolved once we get to the possible-but-needs-work improvement to actually avoid pip-subprocesses inside pip. :) This seems reasonable to me, with the caveat that it's... a few layers of detail-dependent bodges/behaviours (which tends toward some fragility), but they're reasonable to solve the problem as long as that fragility isn't a huge concern. |
|
Fantastic work, completely agree with all of the decisions reached here. I like the path forward of:
The fact that this PR discovered bugs in our recipes is all the proof I need that this is useful. I'll review any separate PRs for bugs found in packages and then we can merge this. Thanks for reviewing @pradyunsg. TIL about footnotes!1 Footnotes |
|
I still have to check if I need to add more patching: pypa/pip#12309 (comment) Edit: ok, everything seems to be about shebangs really, and I'm recursing the full install prefix, so should be fine. |
* py-isort: needs setuptools build dep before v5 Detected in #40224. In the past, system setuptools could be picked up when using an external python, so py-isort@4 would install fine. With the linked PR, pip can only consider packages that Spack controls from PYTHONPATH, so the issue of missing py-setuptools showed up. * py-importlib-metadata: fix lowerbounds on python * review * py-isort unconditionally add optional setuptools dep to prevent picking up user package at runtime * style * drop optional py-setuptools run dep
45b28ba to
103afea
Compare
103afea to
537a109
Compare
|
@adamjstewart should be ready for review now |
537a109 to
2432879
Compare
We run pip with `--no-build-isolation` because we don't wanna let pip install build deps. As a consequence, when pip runs hooks, it runs hooks of *any* package it can find in `sys.path`. For Spack-built Python this includes user site packages -- there shouldn't be any system site packages. So in this case it suffices to set the environment variable PYTHONNOUSERSITE=1. For external Python, more needs to be done, cause there is no env variable that disables both system and user site packages; setting the `python -S` flag doesn't work because pip runs subprocesses that don't inherit this flag (and there is no API to know if -S was passed) So, for external Python, an empty venv is created before invoking pip in Spack's build env ensures that pip can no longer see anything but standard libraries and `PYTHONPATH`. The downside of this is that pip will generate shebangs that point to the python executable from the venv. So, for external python an extra step is necessary where we fix up shebangs post install.
2432879 to
58a340d
Compare
|
And by now I mean now. Coverage is low, but it's tested in bootstrapping gha w/o coverage anyways. |
|
Thanks for the review all :) |
* py-isort: needs setuptools build dep before v5 Detected in spack#40224. In the past, system setuptools could be picked up when using an external python, so py-isort@4 would install fine. With the linked PR, pip can only consider packages that Spack controls from PYTHONPATH, so the issue of missing py-setuptools showed up. * py-importlib-metadata: fix lowerbounds on python * review * py-isort unconditionally add optional setuptools dep to prevent picking up user package at runtime * style * drop optional py-setuptools run dep
We run pip with `--no-build-isolation` because we don't wanna let pip install build deps. As a consequence, when pip runs hooks, it runs hooks of *any* package it can find in `sys.path`. For Spack-built Python this includes user site packages -- there shouldn't be any system site packages. So in this case it suffices to set the environment variable PYTHONNOUSERSITE=1. For external Python, more needs to be done, cause there is no env variable that disables both system and user site packages; setting the `python -S` flag doesn't work because pip runs subprocesses that don't inherit this flag (and there is no API to know if -S was passed) So, for external Python, an empty venv is created before invoking pip in Spack's build env ensures that pip can no longer see anything but standard libraries and `PYTHONPATH`. The downside of this is that pip will generate shebangs that point to the python executable from the venv. So, for external python an extra step is necessary where we fix up shebangs post install.
This reverts commit 0f43074.
* py-isort: needs setuptools build dep before v5 Detected in spack#40224. In the past, system setuptools could be picked up when using an external python, so py-isort@4 would install fine. With the linked PR, pip can only consider packages that Spack controls from PYTHONPATH, so the issue of missing py-setuptools showed up. * py-importlib-metadata: fix lowerbounds on python * review * py-isort unconditionally add optional setuptools dep to prevent picking up user package at runtime * style * drop optional py-setuptools run dep
We run pip with `--no-build-isolation` because we don't wanna let pip install build deps. As a consequence, when pip runs hooks, it runs hooks of *any* package it can find in `sys.path`. For Spack-built Python this includes user site packages -- there shouldn't be any system site packages. So in this case it suffices to set the environment variable PYTHONNOUSERSITE=1. For external Python, more needs to be done, cause there is no env variable that disables both system and user site packages; setting the `python -S` flag doesn't work because pip runs subprocesses that don't inherit this flag (and there is no API to know if -S was passed) So, for external Python, an empty venv is created before invoking pip in Spack's build env ensures that pip can no longer see anything but standard libraries and `PYTHONPATH`. The downside of this is that pip will generate shebangs that point to the python executable from the venv. So, for external python an extra step is necessary where we fix up shebangs post install.
* Revert "Improve build isolation in PythonPipBuilder (spack#40224)" This reverts commit 0f43074. * Revert "py-setuptools: sdist + rpath patch backport (spack#40205)" This reverts commit 512e41a.
* Revert "Improve build isolation in PythonPipBuilder (spack#40224)" This reverts commit 0f43074. * Revert "py-setuptools: sdist + rpath patch backport (spack#40205)" This reverts commit 512e41a.
We run pip with `--no-build-isolation` because we don't wanna let pip install build deps. As a consequence, when pip runs hooks, it runs hooks of *any* package it can find in `sys.path`. For Spack-built Python this includes user site packages -- there shouldn't be any system site packages. So in this case it suffices to set the environment variable PYTHONNOUSERSITE=1. For external Python, more needs to be done, cause there is no env variable that disables both system and user site packages; setting the `python -S` flag doesn't work because pip runs subprocesses that don't inherit this flag (and there is no API to know if -S was passed) So, for external Python, an empty venv is created before invoking pip in Spack's build env ensures that pip can no longer see anything but standard libraries and `PYTHONPATH`. The downside of this is that pip will generate shebangs that point to the python executable from the venv. So, for external python an extra step is necessary where we fix up shebangs post install.
We run pip with `--no-build-isolation` because we don't wanna let pip install build deps. As a consequence, when pip runs hooks, it runs hooks of *any* package it can find in `sys.path`. For Spack-built Python this includes user site packages -- there shouldn't be any system site packages. So in this case it suffices to set the environment variable PYTHONNOUSERSITE=1. For external Python, more needs to be done, cause there is no env variable that disables both system and user site packages; setting the `python -S` flag doesn't work because pip runs subprocesses that don't inherit this flag (and there is no API to know if -S was passed) So, for external Python, an empty venv is created before invoking pip in Spack's build env ensures that pip can no longer see anything but standard libraries and `PYTHONPATH`. The downside of this is that pip will generate shebangs that point to the python executable from the venv. So, for external python an extra step is necessary where we fix up shebangs post install.
This PR supersedes and is the more general / proper fix for #31131
Needs #40234 (currently rebased on it).
We run pip with
--no-build-isolationbecause we don't wanna let pip installbuild deps. As a consequence, when pip runs hooks, it runs hooks of any
package it can find in
sys.path1This is problematic both for external and non-external python.
site-packages dirs
2, or at least that's empty, but user site-packages are still picked up.
An example of the problem is when you e.g.
spack install py-setuptools@63 ^[email protected]when you have e.g.
setuptools==44.0.0installed as a user package in~/.local.
During the build,
pipexecutes the hooksetuptools.finalize_distribution_options,and looks for entry points through (vendored)
importlib.metadata.entry_points(...).This finds
~/.local/.../setuptools.egg-info/entry_points.txtand executes itshooks, and that fails badly, cause
setuptoolsthat's being built is alreadyimported, and the old hooks of user setuptools refer to functions that no
longer exist in newer setuptools.
Solution for external Python
Creating an empty venv before invoking pip in Spack's build env ensures that
pip can no longer see user/system site-package, but is limited to standard
libraries and packages in
PYTHONPATH.The downside of this approach is that shebangs generated by pip now will point
to the temporary virtual environment path, instead of the underlying python.
The current workaround for that is to fixup shebangs post install.
Solution for Spack managed Python
We only have to drop user site-packages from
sys.path, and for that anactual environment variable exists:
PYTHONNOUSERSITE=1, so pip is invokedwith that.
Alternative solutions
Alternative solution that didn't work well was to try and run pip like
python -S -m pip,which also drops the system / user site packages paths -- problem is that pip
spawns subprocesses and those flags don't propagate.
Using environment variables for external Python is also not an option, because
there's no corresponding env variable to
-S, only to-s.TL;DR: use venv so pip cannot run hooks from system / user packages, only from
stuff in PYTHONPATH, aka our "build environment".
Footnotes
This is through
importlib.metadata.entry_points(...); for older Pythonand older setuptools things are even worse, cause it has a buggy old vendored
importlib. ↩
although users may install py-pip ^python, and then "accidentally"
pollute the original spack python prefix with pip install system packages. ↩