diff --git a/src/pip/_internal/locations/__init__.py b/src/pip/_internal/locations/__init__.py index aee98ca51d0..be18f21bb58 100644 --- a/src/pip/_internal/locations/__init__.py +++ b/src/pip/_internal/locations/__init__.py @@ -8,6 +8,7 @@ from pip._internal.models.scheme import SCHEME_KEYS, Scheme from pip._internal.utils.compat import WINDOWS +from pip._internal.utils.deprecation import deprecated from . import _distutils, _sysconfig from .base import ( @@ -99,9 +100,7 @@ def _default_base(*, user: bool) -> str: @functools.lru_cache(maxsize=None) -def _warn_if_mismatch(old: pathlib.Path, new: pathlib.Path, *, key: str) -> bool: - if old == new: - return False +def _warn_mismatched(old: pathlib.Path, new: pathlib.Path, *, key: str) -> None: issue_url = "https://github.com/pypa/pip/issues/10151" message = ( "Value for %s does not match. Please report this to <%s>" @@ -109,6 +108,12 @@ def _warn_if_mismatch(old: pathlib.Path, new: pathlib.Path, *, key: str) -> bool "\nsysconfig: %s" ) logger.log(_MISMATCH_LEVEL, message, key, issue_url, old, new) + + +def _warn_if_mismatch(old: pathlib.Path, new: pathlib.Path, *, key: str) -> bool: + if old == new: + return False + _warn_mismatched(old, new, key=key) return True @@ -157,12 +162,15 @@ def get_scheme( ) base = prefix or home or _default_base(user=user) - warned = [] + warning_contexts = [] for k in SCHEME_KEYS: # Extra join because distutils can return relative paths. old_v = pathlib.Path(base, getattr(old, k)) new_v = pathlib.Path(getattr(new, k)) + if old_v == new_v: + continue + # distutils incorrectly put PyPy packages under ``site-packages/python`` # in the ``posix_home`` scheme, but PyPy devs said they expect the # directory name to be ``pypy`` instead. So we treat this as a bug fix @@ -221,10 +229,38 @@ def get_scheme( if skip_sysconfig_abiflag_bug: continue - warned.append(_warn_if_mismatch(old_v, new_v, key=f"scheme.{k}")) + warning_contexts.append((old_v, new_v, f"scheme.{k}")) - if any(warned): - _log_context(user=user, home=home, root=root, prefix=prefix) + if not warning_contexts: + return old + + # Check if this path mismatch is caused by distutils config files. Those + # files will no longer work once we switch to sysconfig, so this raises a + # deprecation message for them. + default_old = _distutils.distutils_scheme( + dist_name, + user, + home, + root, + isolated, + prefix, + ignore_config_files=True, + ) + if any(default_old[k] != getattr(old, k) for k in SCHEME_KEYS): + deprecated( + "Configuring installation scheme with distutils config files " + "is deprecated and will no longer work in the near future. If you " + "are using a Homebrew or Linuxbrew Python, please see discussion " + "at https://github.com/Homebrew/homebrew-core/issues/76621", + replacement=None, + gone_in=None, + ) + return old + + # Post warnings about this mismatch so user can report them back. + for old_v, new_v, key in warning_contexts: + _warn_mismatched(old_v, new_v, key=key) + _log_context(user=user, home=home, root=root, prefix=prefix) return old diff --git a/src/pip/_internal/locations/_distutils.py b/src/pip/_internal/locations/_distutils.py index 38742d1ddb6..6d7c09dd0fb 100644 --- a/src/pip/_internal/locations/_distutils.py +++ b/src/pip/_internal/locations/_distutils.py @@ -21,13 +21,15 @@ logger = logging.getLogger(__name__) -def _distutils_scheme( +def distutils_scheme( dist_name: str, user: bool = False, home: str = None, root: str = None, isolated: bool = False, prefix: str = None, + *, + ignore_config_files: bool = False, ) -> Dict[str, str]: """ Return a distutils install scheme @@ -39,15 +41,16 @@ def _distutils_scheme( dist_args["script_args"] = ["--no-user-cfg"] d = Distribution(dist_args) - try: - d.parse_config_files() - except UnicodeDecodeError: - # Typeshed does not include find_config_files() for some reason. - paths = d.find_config_files() # type: ignore - logger.warning( - "Ignore distutils configs in %s due to encoding errors.", - ", ".join(os.path.basename(p) for p in paths), - ) + if not ignore_config_files: + try: + d.parse_config_files() + except UnicodeDecodeError: + # Typeshed does not include find_config_files() for some reason. + paths = d.find_config_files() # type: ignore + logger.warning( + "Ignore distutils configs in %s due to encoding errors.", + ", ".join(os.path.basename(p) for p in paths), + ) obj: Optional[DistutilsCommand] = None obj = d.get_command_obj("install", create=True) assert obj is not None @@ -121,7 +124,7 @@ def get_scheme( :param prefix: indicates to use the "prefix" scheme and provides the base directory for the same """ - scheme = _distutils_scheme(dist_name, user, home, root, isolated, prefix) + scheme = distutils_scheme(dist_name, user, home, root, isolated, prefix) return Scheme( platlib=scheme["platlib"], purelib=scheme["purelib"],