From dac6068d9f44c5ce32a2569b772011e5033c836b Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Tue, 27 Jul 2021 12:20:28 +0800 Subject: [PATCH] Post a deprecation warning for distutils configs Since we can't do anything about them in the transition (CPython is dropping support for those entirely), there's nothing we can do but to tell users to not use them. This also accounts for Homebrew and Linuxbrew for now. Hopefully they will come up with better solutions that don't trigger the location mismatch warning. --- src/pip/_internal/locations/__init__.py | 50 +++++++++++++++++++---- src/pip/_internal/locations/_distutils.py | 25 +++++++----- 2 files changed, 57 insertions(+), 18 deletions(-) diff --git a/src/pip/_internal/locations/__init__.py b/src/pip/_internal/locations/__init__.py index ce94c160971..0edde4277b2 100644 --- a/src/pip/_internal/locations/__init__.py +++ b/src/pip/_internal/locations/__init__.py @@ -7,6 +7,7 @@ from typing import List, Optional from pip._internal.models.scheme import SCHEME_KEYS, Scheme +from pip._internal.utils.deprecation import deprecated from . import _distutils, _sysconfig from .base import ( @@ -51,9 +52,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>" @@ -61,6 +60,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 @@ -109,12 +114,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 @@ -143,10 +151,38 @@ def get_scheme( if skip_osx_framework_user_special_case: 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"],