text: import functools
  import logging
  import os
  import pathlib
  import sys
  import sysconfig
  from typing import Any, Dict, Generator, List, Optional, Tuple
  
  from pip._internal.models.scheme import SCHEME_KEYS, Scheme
  from pip._internal.utils.compat import WINDOWS
  from pip._internal.utils.deprecation import deprecated
  from pip._internal.utils.virtualenv import running_under_virtualenv
  
  from . import _distutils, _sysconfig
  from .base import (
      USER_CACHE_DIR,
      get_major_minor_version,
      get_src_prefix,
      is_osx_framework,
      site_packages,
      user_site,
  )
  
  __all__ = [
      "USER_CACHE_DIR",
      "get_bin_prefix",
      "get_bin_user",
      "get_major_minor_version",
      "get_platlib",
      "get_prefixed_libs",
      "get_purelib",
      "get_scheme",
      "get_src_prefix",
      "site_packages",
      "user_site",
  ]
  
  
  logger = logging.getLogger(__name__)
  
  
  _PLATLIBDIR: str = getattr(sys, "platlibdir", "lib")
  
  _USE_SYSCONFIG_DEFAULT = sys.version_info >= (3, 10)
  
  
  def _should_use_sysconfig() -> bool:
      """This function determines the value of _USE_SYSCONFIG.
  
      By default, pip uses sysconfig on Python 3.10+.
      But Python distributors can override this decision by setting:
          sysconfig._PIP_USE_SYSCONFIG = True / False
      Rationale in https://github.com/pypa/pip/issues/10647
  
      This is a function for testability, but should be constant during any one
      run.
      """
      return bool(getattr(sysconfig, "_PIP_USE_SYSCONFIG", _USE_SYSCONFIG_DEFAULT))
  
  
  _USE_SYSCONFIG = _should_use_sysconfig()
  
  # Be noisy about incompatibilities if this platforms "should" be using
  # sysconfig, but is explicitly opting out and using distutils instead.
  if _USE_SYSCONFIG_DEFAULT and not _USE_SYSCONFIG:
      _MISMATCH_LEVEL = logging.WARNING
  else:
      _MISMATCH_LEVEL = logging.DEBUG
  
  
  def _looks_like_bpo_44860() -> bool:
      """The resolution to bpo-44860 will change this incorrect platlib.
  
      See <https://bugs.python.org/issue44860>.
      """
      from distutils.command.install import INSTALL_SCHEMES
  
      try:
          unix_user_platlib = INSTALL_SCHEMES["unix_user"]["platlib"]
      except KeyError:
          return False
      return unix_user_platlib == "$usersite"
  
  
  def _looks_like_red_hat_patched_platlib_purelib(scheme: Dict[str, str]) -> bool:
      platlib = scheme["platlib"]
      if "/$platlibdir/" in platlib:
          platlib = platlib.replace("/$platlibdir/", f"/{_PLATLIBDIR}/")
      if "/lib64/" not in platlib:
          return False
      unpatched = platlib.replace("/lib64/", "/lib/")
      return unpatched.replace("$platbase/", "$base/") == scheme["purelib"]
  
  
  @functools.lru_cache(maxsize=None)
  def _looks_like_red_hat_lib() -> bool:
      """Red Hat patches platlib in unix_prefix and unix_home, but not purelib.
  
      This is the only way I can see to tell a Red Hat-patched Python.
      """
      from distutils.command.install import INSTALL_SCHEMES
  
      return all(
          k in INSTALL_SCHEMES
          and _looks_like_red_hat_patched_platlib_purelib(INSTALL_SCHEMES[k])
          for k in ("unix_prefix", "unix_home")
      )
  
  
  @functools.lru_cache(maxsize=None)
  def _looks_like_debian_scheme() -> bool:
      """Debian adds two additional schemes."""
      from distutils.command.install import INSTALL_SCHEMES
  
      return "deb_system" in INSTALL_SCHEMES and "unix_local" in INSTALL_SCHEMES
  
  
  @functools.lru_cache(maxsize=None)
  def _looks_like_red_hat_scheme() -> bool:
      """Red Hat patches ``sys.prefix`` and ``sys.exec_prefix``.
  
      Red Hat's ``00251-change-user-install-location.patch`` changes the install
      command's ``prefix`` and ``exec_prefix`` to append ``"/local"``. This is
      (fortunately?) done quite unconditionally, so we create a default command
      object without any configuration to detect this.
      """
      from distutils.command.install import install
      from distutils.dist import Distribution
  
      cmd: Any = install(Distribution())
      cmd.finalize_options()
      return (
          cmd.exec_prefix == f"{os.path.normpath(sys.exec_prefix)}/local"
          and cmd.prefix == f"{os.path.normpath(sys.prefix)}/local"
      )
  
  
  @functools.lru_cache(maxsize=None)
  def _looks_like_slackware_scheme() -> bool:
      """Slackware patches sysconfig but fails to patch distutils and site.
  
      Slackware changes sysconfig's user scheme to use ``"lib64"`` for the lib
      path, but does not do the same to the site module.
      """
      if user_site is None:  # User-site not available.
          return False
      try:
          paths = sysconfig.get_paths(scheme="posix_user", expand=False)
      except KeyError:  # User-site not available.
          return False
      return "/lib64/" in paths["purelib"] and "/lib64/" not in user_site
  
  
  @functools.lru_cache(maxsize=None)
  def _looks_like_msys2_mingw_scheme() -> bool:
      """MSYS2 patches distutils and sysconfig to use a UNIX-like scheme.
  
      However, MSYS2 incorrectly patches sysconfig ``nt`` scheme. The fix is
      likely going to be included in their 3.10 release, so we ignore the warning.
      See msys2/MINGW-packages#9319.
  
      MSYS2 MINGW's patch uses lowercase ``"lib"`` instead of the usual uppercase,
      and is missing the final ``"site-packages"``.
      """
      paths = sysconfig.get_paths("nt", expand=False)
      return all(
          "Lib" not in p and "lib" in p and not p.endswith("site-packages")
          for p in (paths[key] for key in ("platlib", "purelib"))
      )
  
  
  def _fix_abiflags(parts: Tuple[str]) -> Generator[str, None, None]:
      ldversion = sysconfig.get_config_var("LDVERSION")
      abiflags = getattr(sys, "abiflags", None)
  
      # LDVERSION does not end with sys.abiflags. Just return the path unchanged.
      if not ldversion or not abiflags or not ldversion.endswith(abiflags):
          yield from parts
          return
  
      # Strip sys.abiflags from LDVERSION-based path components.
      for part in parts:
          if part.endswith(ldversion):
              part = part[: (0 - len(abiflags))]
          yield part
  
  
  @functools.lru_cache(maxsize=None)
  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>"
          "\ndistutils: %s"
          "\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
  
  
  @functools.lru_cache(maxsize=None)
  def _log_context(
      *,
      user: bool = False,
      home: Optional[str] = None,
      root: Optional[str] = None,
      prefix: Optional[str] = None,
  ) -> None:
      parts = [
          "Additional context:",
          "user = %r",
          "home = %r",
          "root = %r",
          "prefix = %r",
      ]
  
      logger.log(_MISMATCH_LEVEL, "\n".join(parts), user, home, root, prefix)
  
  
  def get_scheme(
      dist_name: str,
      user: bool = False,
      home: Optional[str] = None,
      root: Optional[str] = None,
      isolated: bool = False,
      prefix: Optional[str] = None,
  ) -> Scheme:
      new = _sysconfig.get_scheme(
          dist_name,
          user=user,
          home=home,
          root=root,
          isolated=isolated,
          prefix=prefix,
      )
      if _USE_SYSCONFIG:
          return new
  
      old = _distutils.get_scheme(
          dist_name,
          user=user,
          home=home,
          root=root,
          isolated=isolated,
          prefix=prefix,
      )
  
      warning_contexts = []
      for k in SCHEME_KEYS:
          old_v = pathlib.Path(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
          # and not warn about it. See bpo-43307 and python/cpython#24628.
          skip_pypy_special_case = (
              sys.implementation.name == "pypy"
              and home is not None
              and k in ("platlib", "purelib")
              and old_v.parent == new_v.parent
              and old_v.name.startswith("python")
              and new_v.name.startswith("pypy")
          )
          if skip_pypy_special_case:
              continue
  
          # sysconfig's ``osx_framework_user`` does not include ``pythonX.Y`` in
          # the ``include`` value, but distutils's ``headers`` does. We'll let
          # CPython decide whether this is a bug or feature. See bpo-43948.
          skip_osx_framework_user_special_case = (
              user
              and is_osx_framework()
              and k == "headers"
              and old_v.parent.parent == new_v.parent
              and old_v.parent.name.startswith("python")
          )
          if skip_osx_framework_user_special_case:
              continue
  
          # On Red Hat and derived Linux distributions, distutils is patched to
          # use "lib64" instead of "lib" for platlib.
          if k == "platlib" and _looks_like_red_hat_lib():
              continue
  
          # On Python 3.9+, sysconfig's posix_user scheme sets platlib against
          # sys.platlibdir, but distutils's unix_user incorrectly coninutes
          # using the same $usersite for both platlib and purelib. This creates a
          # mismatch when sys.platlibdir is not "lib".
          skip_bpo_44860 = (
              user
              and k == "platlib"
              and not WINDOWS
              and sys.version_info >= (3, 9)
              and _PLATLIBDIR != "lib"
              and _looks_like_bpo_44860()
          )
          if skip_bpo_44860:
              continue
  
          # Slackware incorrectly patches posix_user to use lib64 instead of lib,
          # but not usersite to match the location.
          skip_slackware_user_scheme = (
              user
              and k in ("platlib", "purelib")
              and not WINDOWS
              and _looks_like_slackware_scheme()
          )
          if skip_slackware_user_scheme:
              continue
  
          # Both Debian and Red Hat patch Python to place the system site under
          # /usr/local instead of /usr. Debian also places lib in dist-packages
          # instead of site-packages, but the /usr/local check should cover it.
          skip_linux_system_special_case = (
              not (user or home or prefix or running_under_virtualenv())
              and old_v.parts[1:3] == ("usr", "local")
              and len(new_v.parts) > 1
              and new_v.parts[1] == "usr"
              and (len(new_v.parts) < 3 or new_v.parts[2] != "local")
              and (_looks_like_red_hat_scheme() or _looks_like_debian_scheme())
          )
          if skip_linux_system_special_case:
              continue
  
          # On Python 3.7 and earlier, sysconfig does not include sys.abiflags in
          # the "pythonX.Y" part of the path, but distutils does.
          skip_sysconfig_abiflag_bug = (
              sys.version_info < (3, 8)
              and not WINDOWS
              and k in ("headers", "platlib", "purelib")
              and tuple(_fix_abiflags(old_v.parts)) == new_v.parts
          )
          if skip_sysconfig_abiflag_bug:
              continue
  
          # MSYS2 MINGW's sysconfig patch does not include the "site-packages"
          # part of the path. This is incorrect and will be fixed in MSYS.
          skip_msys2_mingw_bug = (
              WINDOWS and k in ("platlib", "purelib") and _looks_like_msys2_mingw_scheme()
          )
          if skip_msys2_mingw_bug:
              continue
  
          # CPython's POSIX install script invokes pip (via ensurepip) against the
          # interpreter located in the source tree, not the install site. This
          # triggers special logic in sysconfig that's not present in distutils.
          # https://github.com/python/cpython/blob/8c21941ddaf/Lib/sysconfig.py#L178-L194
          skip_cpython_build = (
              sysconfig.is_python_build(check_home=True)
              and not WINDOWS
              and k in ("headers", "include", "platinclude")
          )
          if skip_cpython_build:
              continue
  
          warning_contexts.append((old_v, new_v, f"scheme.{k}"))
  
      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(
              reason=(
                  "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
  
  
  def get_bin_prefix() -> str:
      new = _sysconfig.get_bin_prefix()
      if _USE_SYSCONFIG:
          return new
  
      old = _distutils.get_bin_prefix()
      if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="bin_prefix"):
          _log_context()
      return old
  
  
  def get_bin_user() -> str:
      return _sysconfig.get_scheme("", user=True).scripts
  
  
  def _looks_like_deb_system_dist_packages(value: str) -> bool:
      """Check if the value is Debian's APT-controlled dist-packages.
  
      Debian's ``distutils.sysconfig.get_python_lib()`` implementation returns the
      default package path controlled by APT, but does not patch ``sysconfig`` to
      do the same. This is similar to the bug worked around in ``get_scheme()``,
      but here the default is ``deb_system`` instead of ``unix_local``. Ultimately
      we can't do anything about this Debian bug, and this detection allows us to
      skip the warning when needed.
      """
      if not _looks_like_debian_scheme():
          return False
      if value == "/usr/lib/python3/dist-packages":
          return True
      return False
  
  
  def get_purelib() -> str:
      """Return the default pure-Python lib location."""
      new = _sysconfig.get_purelib()
      if _USE_SYSCONFIG:
          return new
  
      old = _distutils.get_purelib()
      if _looks_like_deb_system_dist_packages(old):
          return old
      if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="purelib"):
          _log_context()
      return old
  
  
  def get_platlib() -> str:
      """Return the default platform-shared lib location."""
      new = _sysconfig.get_platlib()
      if _USE_SYSCONFIG:
          return new
  
      old = _distutils.get_platlib()
      if _looks_like_deb_system_dist_packages(old):
          return old
      if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="platlib"):
          _log_context()
      return old
  
  
  def _deduplicated(v1: str, v2: str) -> List[str]:
      """Deduplicate values from a list."""
      if v1 == v2:
          return [v1]
      return [v1, v2]
  
  
  def _looks_like_apple_library(path: str) -> bool:
      """Apple patches sysconfig to *always* look under */Library/Python*."""
      if sys.platform[:6] != "darwin":
          return False
      return path == f"/Library/Python/{get_major_minor_version()}/site-packages"
  
  
  def get_prefixed_libs(prefix: str) -> List[str]:
      """Return the lib locations under ``prefix``."""
      new_pure, new_plat = _sysconfig.get_prefixed_libs(prefix)
      if _USE_SYSCONFIG:
          return _deduplicated(new_pure, new_plat)
  
      old_pure, old_plat = _distutils.get_prefixed_libs(prefix)
      old_lib_paths = _deduplicated(old_pure, old_plat)
  
      # Apple's Python (shipped with Xcode and Command Line Tools) hard-code
      # platlib and purelib to '/Library/Python/X.Y/site-packages'. This will
      # cause serious build isolation bugs when Apple starts shipping 3.10 because
      # pip will install build backends to the wrong location. This tells users
      # who is at fault so Apple may notice it and fix the issue in time.
      if all(_looks_like_apple_library(p) for p in old_lib_paths):
          deprecated(
              reason=(
                  "Python distributed by Apple's Command Line Tools incorrectly "
                  "patches sysconfig to always point to '/Library/Python'. This "
                  "will cause build isolation to operate incorrectly on Python "
                  "3.10 or later. Please help report this to Apple so they can "
                  "fix this. https://developer.apple.com/bug-reporting/"
              ),
              replacement=None,
              gone_in=None,
          )
          return old_lib_paths
  
      warned = [
          _warn_if_mismatch(
              pathlib.Path(old_pure),
              pathlib.Path(new_pure),
              key="prefixed-purelib",
          ),
          _warn_if_mismatch(
              pathlib.Path(old_plat),
              pathlib.Path(new_plat),
              key="prefixed-platlib",
          ),
      ]
      if any(warned):
          _log_context(prefix=prefix)
  
      return old_lib_paths
  