From 24773bc4f5ab49835294abfa2cb479e9733a4f66 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 23 Sep 2022 14:32:26 +0200 Subject: [PATCH] Fix build isolation on system Pythons use site.getsitepackages() where available instead of just purelib/platlib, which is often insufficient on e.g. System Pythons for Debian/macOS handle virtualenv < 20 overwriting site.py without getsitepackages() by preserving current behavior. --- news/6264.bugfix.rst | 1 + src/pip/_internal/build_env.py | 26 +++++++++++++++++++++++--- tests/functional/test_build_env.py | 15 ++++++++++++++- 3 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 news/6264.bugfix.rst diff --git a/news/6264.bugfix.rst b/news/6264.bugfix.rst new file mode 100644 index 00000000000..66554a473f5 --- /dev/null +++ b/news/6264.bugfix.rst @@ -0,0 +1 @@ +Fix build environment isolation on some system Pythons. diff --git a/src/pip/_internal/build_env.py b/src/pip/_internal/build_env.py index 6213eedd14a..cc2b38bab79 100644 --- a/src/pip/_internal/build_env.py +++ b/src/pip/_internal/build_env.py @@ -4,6 +4,7 @@ import logging import os import pathlib +import site import sys import textwrap from collections import OrderedDict @@ -55,6 +56,26 @@ def get_runnable_pip() -> str: return os.fsdecode(source / "__pip-runner__.py") +def _get_system_sitepackages() -> Set[str]: + """Get system site packages + + Usually from site.getsitepackages, + but fallback on `get_purelib()/get_platlib()` if unavailable + (e.g. in a virtualenv created by virtualenv<20) + + Returns normalized set of strings. + """ + if hasattr(site, "getsitepackages"): + system_sites = site.getsitepackages() + else: + # virtualenv < 20 overwrites site.py without getsitepackages + # fallback on get_purelib/get_platlib. + # this is known to miss things, but shouldn't in the cases + # where getsitepackages() has been removed (inside a virtualenv) + system_sites = [get_purelib(), get_platlib()] + return {os.path.normcase(path) for path in system_sites} + + class BuildEnvironment: """Creates and manages an isolated environment to install build deps""" @@ -75,9 +96,8 @@ def __init__(self) -> None: # Customize site to: # - ensure .pth files are honored # - prevent access to system site packages - system_sites = { - os.path.normcase(site) for site in (get_purelib(), get_platlib()) - } + system_sites = _get_system_sitepackages() + self._site_dir = os.path.join(temp_dir.path, "site") if not os.path.exists(self._site_dir): os.mkdir(self._site_dir) diff --git a/tests/functional/test_build_env.py b/tests/functional/test_build_env.py index 437adb99570..869e8ad921d 100644 --- a/tests/functional/test_build_env.py +++ b/tests/functional/test_build_env.py @@ -4,7 +4,7 @@ import pytest -from pip._internal.build_env import BuildEnvironment +from pip._internal.build_env import BuildEnvironment, _get_system_sitepackages from tests.lib import ( PipTestEnvironment, TestPipResult, @@ -226,6 +226,10 @@ def test_build_env_isolation(script: PipTestEnvironment) -> None: script.pip_install_local("-t", target, pkg_whl) script.environ["PYTHONPATH"] = target + system_sites = _get_system_sitepackages() + # there should always be something to exclude + assert system_sites + run_with_build_env( script, "", @@ -247,5 +251,14 @@ def test_build_env_isolation(script: PipTestEnvironment) -> None: })), file=sys.stderr) print('sys.path:\n ' + '\n '.join(sys.path), file=sys.stderr) sys.exit(1) + """ + f""" + # second check: direct check of exclusion of system site packages + import os + + normalized_path = [os.path.normcase(path) for path in sys.path] + for system_path in {system_sites!r}: + assert system_path not in normalized_path, \ + f"{{system_path}} found in {{normalized_path}}" """, )