-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
[BUG] Isolated build env breaks console scripts installed in other venv
(e.g., cmake
, ninja
)
#13222
Comments
This is the point of build isolation, the build process shouldn't depend on any Python-level dependencies that are not declared in the You need to use a pyproject.toml so pip will install everything your package needs to build: [build-system]
requires = ["setuptools", "cmake"]
build-backend = "setuptools.build_meta" You already know that though, so I'm not really sure what the problem is here. |
Apparently scikit-build disagrees and believes this is a pip bug. I'll talk with them and return when I understand what's going on here. |
The See also: |
The user's setup script is not trying to import the |
I've read through all the links, but I'm still confused as to what the ask is? If a build time dependency is a Python package it should be in the build-system.requires. If a build time dependency is an external dependency it should be part of the prerequisite install instructions, pip is bound to Python dependencies, the conda environment has attempted to solve the problem of making all 3rd party dependencies available, but that requires using conda. If there is some awkward in between where you have to support third party code that's importing things that aren't always there then you're going to have to write some infrastructure to handle that, e.g. a build time dependency that accepts the import but doesn't do anything with it if the external dependency isn't available. It’s messy, but that’s sometimes the problem with supporting arbitrary third party code running in an environment it wasn’t designed for. Do you have some specific thing pip could do here to alleviate the problem that fits within the spec? |
Ah, I think you can ignore my comment, I see @ichard26 has been more deeply investigating the issue. |
I talked over this issue with @henryiii on the PyPA Discord because I realized that I was wholly uninformed about the complexity involved here. My understanding is summarized here: The problem is that the isolated build logic is messing with the import path of Python subprocesses invoked by the build process that are using the Python from the parent environment (venv A).
The problem is that even under normal execution, pip's own calls in the build environment (and not whatever the build process may be doing) are also running off the parent Python. In other words, I'm not aware of an easy way to isolate pip's own subprocesses vs other Python subprocesses the build environment may create since the same python executable is being used ( Anyway, as evident by my original response, I am not an expert in build isolation or PEP 517. I apologise for my confusion @XuehaiPan. Thank you for the high-quality bug report even if it flew over my head. |
I'm curious to how uv handles this (since apparently an external cmake does work under uv), but that is far outside my expertise. |
I should clarify that pip doesn't really create a true virtual environment (à la virtualenv/venv). It is indeed an isolated environment, but the isolation is achieved by the site/sys.path manipulations that are also the reason why cmake is broken here. Otherwise, it's really the same Python pip is running off. |
Oh, this clarifies a lot for me. That is messy... I wonder why pip isn't using venv? Performance issue? Availability? uv can make a real venv at almost no cost, so maybe they're doing that? |
Taking a brief look on the tracker for related reading nets these issues:
TL;DR, it's complicated. I don't plan on investigating this issue further. |
IMO "use a proper venv" is the right solution here1. Especially if it turns out that it's what Footnotes
|
I'm not sure we should allow users to run arbitrary external scripts in the build environment with shared The issue I'm facing is a narrower one that is limited to console scripts in UPDATE: This might not work because the console script could be installed by other installers rather than For example, inject the #!${PROJECT}/venv/bin/python3.13
# -*- coding: utf-8 -*-
import re
import sys
+ sys.path.insert(0, '${PROJECT}/venv/lib/python3.13/site-packages')
from cmake import cmake
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(cmake()) Related source: pip/src/pip/_vendor/distlib/scripts.py Lines 41 to 49 in 93d43c9
Note that we should handle the site-packages path carefully because there could be non-standard paths (e.g., |
Yeah, whatever is changed here is only valid for things pip installed, and it would affect all things pip installed. I'd be worried this would break some other use case, and there would need to be convincing that it doesn't. There's been significant discussion about making a much safer change to this entry-point script template (#13165) and it's currently undecided. |
FYI, after some debugging effort, I find: $ source venv/bin/activate
$ pip3 install cmake
$ pip3 install . # fail (use cmake console script in venv/bin/camke) $ source venv/bin/activate
$ pip3 install cmake
$ deactivate # install without the venv activated
$ venv/bin/pip3 install . # success (use system cmake binary) Removing the # setup.py
import os
import shutil
from pathlib import Path
from setuptools import Extension, setup
from setuptools.command.build_ext import build_ext
HERE = Path(__file__).absolute().parent
class CMakeExtension(Extension):
def __init__(self, name, source_dir=".", target=None, **kwargs):
super().__init__(name, sources=[], **kwargs)
self.source_dir = Path(source_dir).absolute()
self.target = target if target is not None else name.rpartition(".")[-1]
@classmethod
def cmake_executable(cls):
cmake = os.getenv("CMAKE_EXECUTABLE", "")
if not cmake:
cmake = shutil.which("cmake")
return cmake
class cmake_build_ext(build_ext):
def build_extension(self, ext):
if not isinstance(ext, CMakeExtension):
super().build_extension(ext)
return
cmake = ext.cmake_executable()
if cmake is None:
raise RuntimeError("Cannot find CMake executable.")
+ os.environ.pop('PYTHONPATH', None)
self.spawn([cmake, "--version"])
setup(
name="cmake-venv-test",
version="0.0.1",
cmdclass={"build_ext": cmake_build_ext},
ext_modules=[CMakeExtension("cmake_venv_test._C", source_dir=HERE)],
) $ source venv/bin/activate
$ pip3 install cmake
$ pip3 install . # success Here is a temporary workaround that works for me: - self.spawn([cmake, '-S', str(ext.source_dir), '-B', str(build_temp), *cmake_args])
- if not self.dry_run:
- self.spawn([cmake, '--build', str(build_temp), *build_args])
+ python_path = None
+ try:
+ # pip's build environment pseudo-isolation sets `PYTHONPATH` and may break console scripts
+ python_path = os.environ.pop('PYTHONPATH', None) # unset `PYTHONPATH`
+ self.spawn([cmake, '-S', str(ext.source_dir), '-B', str(build_temp), *cmake_args])
+ if not self.dry_run:
+ self.spawn([cmake, '--build', str(build_temp), *build_args])
+ finally:
+ if python_path is not None:
+ os.environ['PYTHONPATH'] = python_path |
Description
For packages that contain extension modules, they may need to use build tools such as
cmake
/ninja
. There are PyPI packages that ship those tools with console scripts. E.g.,pip3 install cmake
will create an executable${PROJECT}/venv/bin/cmake
.When installing a user package from the source,
pip
will create an isolated build environment (venv
(B)). During installing the user package, the setup script may invoke thecmake
executable in thePATH
mentioned above (installed invenv
(A)). However, in the build environment (venv
(B)), thecmake
script (interpreted by Python invenv
(A)) failed to find thecmake
module installed invenv
(A).See issue scikit-build/cmake-python-distributions#586 for more information.
cmake
module in a wheel build venv when thecmake
executable is installed in another venv scikit-build/cmake-python-distributions#586I think this issue is caused by the suspicious
sitecustomize.py
in the build environment:pip/src/pip/_internal/build_env.py
Lines 96 to 134 in 93d43c9
Expected behavior
The console script (e.g.,
cmake
) should always be runnable if invoked in a subprocess with an absolute path.pip version
25.0.1
Python version
3.13.2
OS
macOS
How to Reproduce
setup.py
with content:Output
The error raises when installing with and without the
--editable
flag.There will be no error if do either of the following:
cmake
tobuild-system.requires
inpyproject.toml
.--no-build-isolation
.Code of Conduct
The text was updated successfully, but these errors were encountered: