diff --git a/news/5760.bugfix.rst b/news/5760.bugfix.rst new file mode 100644 index 00000000..816fa5b1 --- /dev/null +++ b/news/5760.bugfix.rst @@ -0,0 +1 @@ +Fix ``error: invalid command 'egg_info'`` edge case with requirementslib 3.0.0. It exposed pipenv resolver sometimes was using a different python than expected. diff --git a/pipenv/project.py b/pipenv/project.py index 1303152d..35cc7f2f 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -1155,6 +1155,13 @@ class Project: result = str(result.path) return result + @property + def python(self) -> str: + """Path to the project python""" + from pipenv.utils.shell import project_python + + return project_python(self) + def _which(self, command, location=None, allow_global=False): if not allow_global and location is None: if self.virtualenv_exists: diff --git a/pipenv/resolver.py b/pipenv/resolver.py index 87e76564..eb24669a 100644 --- a/pipenv/resolver.py +++ b/pipenv/resolver.py @@ -795,10 +795,6 @@ def _main( parse_only=False, category=None, ): - os.environ["PIPENV_REQUESTED_PYTHON_VERSION"] = ".".join( - [str(s) for s in sys.version_info[:3]] - ) - os.environ["PIP_PYTHON_PATH"] = str(sys.executable) if parse_only: parse_packages( packages, diff --git a/pipenv/utils/dependencies.py b/pipenv/utils/dependencies.py index 4295c11a..0248271c 100644 --- a/pipenv/utils/dependencies.py +++ b/pipenv/utils/dependencies.py @@ -53,25 +53,17 @@ def get_pipfile_category_using_lockfile_section(category): class HackedPythonVersion: - """A Beautiful hack, which allows us to tell pip which version of Python we're using.""" + """A hack, which allows us to tell resolver which version of Python we're using.""" - def __init__(self, python_version, python_path): - self.python_version = python_version + def __init__(self, python_path): self.python_path = python_path def __enter__(self): - # Only inject when the value is valid - if self.python_version: - os.environ["PIPENV_REQUESTED_PYTHON_VERSION"] = str(self.python_version) if self.python_path: os.environ["PIP_PYTHON_PATH"] = str(self.python_path) def __exit__(self, *args): - # Restore original Python version information. - try: - del os.environ["PIPENV_REQUESTED_PYTHON_VERSION"] - except KeyError: - pass + pass def get_canonical_names(packages): diff --git a/pipenv/utils/project.py b/pipenv/utils/project.py index f7791ed9..d95ba5cb 100644 --- a/pipenv/utils/project.py +++ b/pipenv/utils/project.py @@ -1,3 +1,5 @@ +import os + from pipenv import exceptions from pipenv.utils.dependencies import python_version from pipenv.utils.pipfile import ensure_pipfile @@ -77,3 +79,4 @@ def ensure_project( skip_requirements=skip_requirements, system=system, ) + os.environ["PIP_PYTHON_PATH"] = project.python diff --git a/pipenv/utils/resolver.py b/pipenv/utils/resolver.py index d79e08e1..2bff3f55 100644 --- a/pipenv/utils/resolver.py +++ b/pipenv/utils/resolver.py @@ -1156,10 +1156,8 @@ def resolve_deps( """ index_lookup = {} markers_lookup = {} - python_path = which("python", allow_global=allow_global) if not os.environ.get("PIP_SRC"): os.environ["PIP_SRC"] = project.virtualenv_src_location - backup_python_path = sys.executable results = [] resolver = None if not deps: @@ -1168,7 +1166,7 @@ def resolve_deps( req_dir = req_dir if req_dir else os.environ.get("req_dir", None) if not req_dir: req_dir = create_tracked_tempdir(prefix="pipenv-", suffix="-requirements") - with HackedPythonVersion(python_version=python, python_path=python_path): + with HackedPythonVersion(python_path=project.python): try: results, hashes, markers_lookup, resolver, skipped = actually_resolve_deps( deps, @@ -1187,8 +1185,7 @@ def resolve_deps( # Second (last-resort) attempt: if results is None: with HackedPythonVersion( - python_version=".".join([str(s) for s in sys.version_info[:3]]), - python_path=backup_python_path, + python_path=project.python, ): try: # Attempt to resolve again, with different Python version information, diff --git a/pipenv/utils/virtualenv.py b/pipenv/utils/virtualenv.py index ad9aa54f..a8e90394 100644 --- a/pipenv/utils/virtualenv.py +++ b/pipenv/utils/virtualenv.py @@ -217,7 +217,7 @@ def cleanup_virtualenv(project, bare=True): def ensure_python(project, python=None): # Runtime import is necessary due to the possibility that the environments module may have been reloaded. - if project.s.PIPENV_PYTHON and python is False: + if project.s.PIPENV_PYTHON and not python: python = project.s.PIPENV_PYTHON def abort(msg=""): diff --git a/pipenv/vendor/requirementslib/models/setup_info.py b/pipenv/vendor/requirementslib/models/setup_info.py index d6106655..58978b71 100644 --- a/pipenv/vendor/requirementslib/models/setup_info.py +++ b/pipenv/vendor/requirementslib/models/setup_info.py @@ -58,11 +58,6 @@ from .utils import ( CACHE_DIR = os.environ.get("PIPENV_CACHE_DIR", user_cache_dir("pipenv")) -# The following are necessary for people who like to use "if __name__" conditionals -# in their setup.py scripts -_setup_stop_after = None -_setup_distribution = None - def pep517_subprocess_runner(cmd, cwd=None, extra_environ=None) -> None: """The default method of calling the wrapper subprocess.""" @@ -75,8 +70,9 @@ def pep517_subprocess_runner(cmd, cwd=None, extra_environ=None) -> None: class BuildEnv(envbuild.BuildEnvironment): def pip_install(self, reqs): + python = os.environ.get("PIP_PYTHON_PATH", sys.executable) cmd = [ - sys.executable, + python, "-m", "pip", "install", @@ -1123,6 +1119,7 @@ def run_setup(script_path, egg_base=None): :return: The metadata dictionary :rtype: Dict[Any, Any] """ + from pathlib import Path if not os.path.exists(script_path): raise FileNotFoundError(script_path) @@ -1130,39 +1127,16 @@ def run_setup(script_path, egg_base=None): if egg_base is None: egg_base = os.path.join(target_cwd, "reqlib-metadata") with temp_path(), cd(target_cwd): - # This is for you, Hynek - # see https://github.com/hynek/environ_config/blob/69b1c8a/setup.py args = ["egg_info"] if egg_base: args += ["--egg-base", egg_base] - script_name = os.path.basename(script_path) - g = {"__file__": script_name, "__name__": "__main__"} - sys.path.insert(0, target_cwd) - save_argv = sys.argv.copy() - try: - global _setup_distribution, _setup_stop_after - _setup_stop_after = "run" - sys.argv[0] = script_name - sys.argv[1:] = args - with open(script_name, "rb") as f: - contents = f.read().replace(rb"\r\n", rb"\n") - exec(contents, g) - # We couldn't import everything needed to run setup - except Exception: - python = os.environ.get("PIP_PYTHON_PATH", sys.executable) - - sp.run( - [python, "setup.py"] + args, - cwd=target_cwd, - stdout=sp.PIPE, - stderr=sp.PIPE, - ) - finally: - _setup_stop_after = None - sys.argv = save_argv - _setup_distribution = get_metadata(egg_base, metadata_type="egg") - dist = _setup_distribution + python = os.environ.get("PIP_PYTHON_PATH", sys.executable) + sp.run( + [python, "setup.py"] + args, + capture_output=True, + ) + dist = get_metadata(egg_base, metadata_type="egg") return dist