Description
Description
pip
will fail to build a project in the following circumstances:
- A build-backend is installed using a
MetaPathFinder
(e.g. as if it was installed in editable mode) that is inserted beforeimportlib.machinery.PathFinder
. - The same virtual environment is used to perform an installation from a VCS URL of a project which matches the name of the build-backend previously installed (to a
target
directory).
Real-world scenario/motivation:
I run into this problem when trying to address pypa/setuptools#3806 and pypa/setuptools#3828. In those issues, users describe problems with the precedence of editable installs with MetaPathFinder
. My initial approach was to "bump" the precedence by changing the insert position in sys.meta_path
and sys.path_hook
.
However, when running the tests with pytest-perf
, I received an error message from pip
:
pip._vendor.pyproject_hooks._impl.BackendInvalid: Backend was not loaded from backend-path
Note that pytest-perf
will install the project from the tracking VCS URL to use as a baseline for the performance comparison.
References
Implementation: pypa/setuptools#3829
Failed tests: https://github.com/pypa/setuptools/actions/runs/4187531113/jobs/7257489290#step:7:646
Expected behavior
pip
would build the project in an isolated environment that is not contaminated by the virtual environment where pip is installed. This should be inline with PEP 517 description:
A build frontend SHOULD, by default, create an isolated environment for each build, containing only the standard library and any explicitly requested build-dependencies.
(or at least use importlib.util.spec_from_file_location
to ensure the build-backend
is loaded from the place specified in pyproject.toml > [build-system] > backend-path
)
pip version
23.0.1
Python version
Tested in 3.10.6 and 3.10.9
OS
Tested in Ubuntu 20.04.5 LTS and also python:3.10
container (Debian GNU/Linux 11 - bullseye)
How to Reproduce
- First we create a dummy backend that uses
backend-path
> docker run --rm -it python:3.10 /bin/bash rm -rf /tmp/my_backend mkdir -p /tmp/my_backend cd /tmp/my_backend cat <<EOF > pyproject.toml [build-system] requires = ["flit_core >=3.8.0,<4"] backend-path = ["."] build-backend = "my_backend" [project] name = "my_backend" version = "0.0.0" description = "foobar" EOF # Let's simulate a custom build backend ... # Assume it has a `build_editable` that uses a MetaPathFinder # inserted at position 0 (omitted for the sake of brevity). echo 'from flit_core.buildapi import *' > my_backend.py
- Then we simulate the backend installation in "editable mode" using a
MetaPathFinder
:rm -rf /tmp/.venv python3.10 -m venv /tmp/.venv /tmp/.venv/bin/python -m pip install -U 'pip==23.0.1' /tmp/.venv/bin/python -m pip install 'flit_core >=3.8.0,<4' # Let's simulate the custom build backend was previously installed in editable mode cat <<EOF > /tmp/.venv/lib/python3.10/site-packages/_editable_impl_my_backend.py import sys from importlib.util import spec_from_file_location MAPPING = {"my_backend": "/tmp/my_backend/my_backend.py"} class _EditableFinder: # MetaPathFinder @classmethod def find_spec(cls, fullname, path=None, target=None): if fullname in MAPPING: return spec_from_file_location(fullname, MAPPING[fullname]) def install(): if not any(finder == _EditableFinder for finder in sys.meta_path): sys.meta_path.insert(0, _EditableFinder) EOF echo 'import _editable_impl_my_backend; _editable_impl_my_backend.install()' > /tmp/.venv/lib/python3.10/site-packages/my_backend.pth mkdir /tmp/other cd /tmp/other /tmp/.venv/bin/python -c 'import my_backend; print(my_backend)' # => <module 'my_backend' from '/tmp/my_backend/my_backend.py'>
- Finally, we try to install a different version of the same backend from a VCS URL - which will result in an error:
rm -rf /tmp/install_target /tmp/.venv/bin/python -m pip -vvv install -t /tmp/install_target "my_backend @ git+https://gist.github.com/4f1f86d879179e2d185b6e82e4f50d17.git" # ... # pip._vendor.pyproject_hooks._impl.BackendInvalid: Backend was not loaded from backend-path
Please note that the backend is not really invalid in this case... What happens is that the build frontend is loading it from a different location instead of loading it from backend-path
.
Output
# docker run --rm -it python:3.10 /bin/bash
$ rm -rf /tmp/my_backend
$ mkdir -p /tmp/my_backend
$ cd /tmp/my_backend
$ cat <<EOF > pyproject.toml
[build-system]
requires = ["flit_core >=3.8.0,<4"]
backend-path = ["."]
build-backend = "my_backend"
[project]
name = "my_backend"
version = "0.0.0"
description = "foobar"
EOF
$ echo 'from flit_core.buildapi import *' > my_backend.py
$ rm -rf /tmp/.venv
$ python3.10 -m venv /tmp/.venv
$ /tmp/.venv/bin/python -m pip install -U 'pip==23.0.1'
Collecting pip==23.0.1
Downloading pip-23.0.1-py3-none-any.whl (2.1 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.1/2.1 MB 1.2 MB/s eta 0:00:00
Installing collected packages: pip
Attempting uninstall: pip
Found existing installation: pip 22.2.1
Uninstalling pip-22.2.1:
Successfully uninstalled pip-22.2.1
Successfully installed pip-23.0.1
$ /tmp/.venv/bin/python -m pip install 'flit_core >=3.8.0,<4'
Collecting flit_core<4,>=3.8.0
Downloading flit_core-3.8.0-py3-none-any.whl (62 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 62.6/62.6 kB 1.1 MB/s eta 0:00:00
Installing collected packages: flit_core
Successfully installed flit_core-3.8.0
$ cat <<EOF > /tmp/.venv/lib/python3.10/site-packages/_editable_impl_my_backend.py
import sys
from importlib.util import spec_from_file_location
MAPPING = {"my_backend": "/tmp/my_backend/my_backend.py"}
class _EditableFinder: # MetaPathFinder
@classmethod
def find_spec(cls, fullname, path=None, target=None):
if fullname in MAPPING:
return spec_from_file_location(fullname, MAPPING[fullname])
def install():
if not any(finder == _EditableFinder for finder in sys.meta_path):
sys.meta_path.insert(0, _EditableFinder)
EOF
$ echo 'import _editable_impl_my_backend; _editable_impl_my_backend.install()' > /tmp/.venv/lib/python3.10/site-packages/my_backend.pth
$ mkdir /tmp/other
$ cd /tmp/other
$ /tmp/.venv/bin/python -c 'import my_backend; print(my_backend)'
<module 'my_backend' from '/tmp/my_backend/my_backend.py'>
$ rm -rf /tmp/install_target
$ /tmp/.venv/bin/python -m pip -vvv install -t /tmp/install_target "my_backend @ git+https://gist.github.com/4f1f86d879179e2d185b6e82e4f50d17.git"
Using pip 23.0.1 from /tmp/.venv/lib/python3.10/site-packages/pip (python 3.10)
Non-user install due to --prefix or --target option
Created temporary directory: /tmp/pip-target-rb_hedkt
Created temporary directory: /tmp/pip-build-tracker-vpe84uob
Initialized build tracking at /tmp/pip-build-tracker-vpe84uob
Created build tracker: /tmp/pip-build-tracker-vpe84uob
Entered build tracker: /tmp/pip-build-tracker-vpe84uob
Created temporary directory: /tmp/pip-install-rjiqcf3e
Created temporary directory: /tmp/pip-ephem-wheel-cache-thvjcdal
Collecting my_backend@ git+https://gist.github.com/4f1f86d879179e2d185b6e82e4f50d17.git
Cloning https://gist.github.com/4f1f86d879179e2d185b6e82e4f50d17.git to /tmp/pip-install-rjiqcf3e/my-backend_90e570b192394d7287e6c771158ffeb3
Running command git version
git version 2.30.2
Running command git clone --filter=blob:none --verbose --progress https://gist.github.com/4f1f86d879179e2d185b6e82e4f50d17.git /tmp/pip-install-rjiqcf3e/my-backend_90e570b192394d7287e6c771158ffeb3
Cloning into '/tmp/pip-install-rjiqcf3e/my-backend_90e570b192394d7287e6c771158ffeb3'...
POST git-upload-pack (164 bytes)
POST git-upload-pack (272 bytes)
remote: Enumerating objects: 2, done.
remote: Counting objects: 50% (1/2)
remote: Counting objects: 100% (2/2)
remote: Counting objects: 100% (2/2), done.
remote: Compressing objects: 50% (1/2)
remote: Compressing objects: 100% (2/2)
remote: Compressing objects: 100% (2/2), done.
remote: Total 2 (delta 0), reused 2 (delta 0), pack-reused 0
Receiving objects: 50% (1/2)
Receiving objects: 100% (2/2)
Receiving objects: 100% (2/2), done.
Running command git rev-parse HEAD
1577d5b524e74766a2b9b8cf6a7cc73e630794e7
Resolved https://gist.github.com/4f1f86d879179e2d185b6e82e4f50d17.git to commit 1577d5b524e74766a2b9b8cf6a7cc73e630794e7
Running command git rev-parse HEAD
1577d5b524e74766a2b9b8cf6a7cc73e630794e7
Added my_backend@ git+https://gist.github.com/4f1f86d879179e2d185b6e82e4f50d17.git from git+https://gist.github.com/4f1f86d879179e2d185b6e82e4f50d17.git to build tracker '/tmp/pip-build-tracker-vpe84uob'
Created temporary directory: /tmp/pip-build-env-wbowle8_
Running command pip subprocess to install build dependencies
Using pip 23.0.1 from /tmp/.venv/lib/python3.10/site-packages/pip (python 3.10)
Collecting flit_core<4,>=3.8.0
Using cached flit_core-3.8.0-py3-none-any.whl (62 kB)
Installing collected packages: flit_core
Successfully installed flit_core-3.8.0
Installing build dependencies ... done
Running command Getting requirements to build wheel
Getting requirements to build wheel ... done
ERROR: Exception:
Traceback (most recent call last):
File "/tmp/.venv/lib/python3.10/site-packages/pip/_internal/cli/base_command.py", line 160, in exc_logging_wrapper
status = run_func(*args)
File "/tmp/.venv/lib/python3.10/site-packages/pip/_internal/cli/req_command.py", line 247, in wrapper
return func(self, options, args)
File "/tmp/.venv/lib/python3.10/site-packages/pip/_internal/commands/install.py", line 419, in run
requirement_set = resolver.resolve(
File "/tmp/.venv/lib/python3.10/site-packages/pip/_internal/resolution/resolvelib/resolver.py", line 73, in resolve
collected = self.factory.collect_root_requirements(root_reqs)
File "/tmp/.venv/lib/python3.10/site-packages/pip/_internal/resolution/resolvelib/factory.py", line 491, in collect_root_requirements
req = self._make_requirement_from_install_req(
File "/tmp/.venv/lib/python3.10/site-packages/pip/_internal/resolution/resolvelib/factory.py", line 453, in _make_requirement_from_install_req
cand = self._make_candidate_from_link(
File "/tmp/.venv/lib/python3.10/site-packages/pip/_internal/resolution/resolvelib/factory.py", line 206, in _make_candidate_from_link
self._link_candidate_cache[link] = LinkCandidate(
File "/tmp/.venv/lib/python3.10/site-packages/pip/_internal/resolution/resolvelib/candidates.py", line 297, in __init__
super().__init__(
File "/tmp/.venv/lib/python3.10/site-packages/pip/_internal/resolution/resolvelib/candidates.py", line 162, in __init__
self.dist = self._prepare()
File "/tmp/.venv/lib/python3.10/site-packages/pip/_internal/resolution/resolvelib/candidates.py", line 231, in _prepare
dist = self._prepare_distribution()
File "/tmp/.venv/lib/python3.10/site-packages/pip/_internal/resolution/resolvelib/candidates.py", line 308, in _prepare_distribution
return preparer.prepare_linked_requirement(self._ireq, parallel_builds=True)
File "/tmp/.venv/lib/python3.10/site-packages/pip/_internal/operations/prepare.py", line 491, in prepare_linked_requirement
return self._prepare_linked_requirement(req, parallel_builds)
File "/tmp/.venv/lib/python3.10/site-packages/pip/_internal/operations/prepare.py", line 577, in _prepare_linked_requirement
dist = _get_prepared_distribution(
File "/tmp/.venv/lib/python3.10/site-packages/pip/_internal/operations/prepare.py", line 69, in _get_prepared_distribution
abstract_dist.prepare_distribution_metadata(
File "/tmp/.venv/lib/python3.10/site-packages/pip/_internal/distributions/sdist.py", line 48, in prepare_distribution_metadata
self._install_build_reqs(finder)
File "/tmp/.venv/lib/python3.10/site-packages/pip/_internal/distributions/sdist.py", line 118, in _install_build_reqs
build_reqs = self._get_build_requires_wheel()
File "/tmp/.venv/lib/python3.10/site-packages/pip/_internal/distributions/sdist.py", line 95, in _get_build_requires_wheel
return backend.get_requires_for_build_wheel()
File "/tmp/.venv/lib/python3.10/site-packages/pip/_internal/utils/misc.py", line 701, in get_requires_for_build_wheel
return super().get_requires_for_build_wheel(config_settings=cs)
File "/tmp/.venv/lib/python3.10/site-packages/pip/_vendor/pyproject_hooks/_impl.py", line 166, in get_requires_for_build_wheel
return self._call_hook('get_requires_for_build_wheel', {
File "/tmp/.venv/lib/python3.10/site-packages/pip/_vendor/pyproject_hooks/_impl.py", line 323, in _call_hook
raise BackendInvalid(
pip._vendor.pyproject_hooks._impl.BackendInvalid: Backend was not loaded from backend-path
Remote version of pip: 23.0.1
Local version of pip: 23.0.1
Was pip installed by pip? True
Removed my_backend@ git+https://gist.github.com/4f1f86d879179e2d185b6e82e4f50d17.git from git+https://gist.github.com/4f1f86d879179e2d185b6e82e4f50d17.git from build tracker '/tmp/pip-build-tracker-vpe84uob'
Removed build tracker: '/tmp/pip-build-tracker-vpe84uob'
Code of Conduct
- I agree to follow the PSF Code of Conduct.