first commit
This commit is contained in:
		| @@ -0,0 +1,467 @@ | ||||
| import functools | ||||
| import logging | ||||
| import os | ||||
| import pathlib | ||||
| import sys | ||||
| import sysconfig | ||||
| from typing import Any, Dict, Generator, 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 _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_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() | ||||
|  | ||||
| if not _USE_SYSCONFIG: | ||||
|     # Import distutils lazily to avoid deprecation warnings, | ||||
|     # but import it soon enough that it is in memory and available during | ||||
|     # a pip reinstall. | ||||
|     from . import _distutils | ||||
|  | ||||
| # 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 | ||||
|  | ||||
|     from . import _distutils | ||||
|  | ||||
|     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 | ||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -0,0 +1,173 @@ | ||||
| """Locations where we look for configs, install stuff, etc""" | ||||
|  | ||||
| # The following comment should be removed at some point in the future. | ||||
| # mypy: strict-optional=False | ||||
|  | ||||
| # If pip's going to use distutils, it should not be using the copy that setuptools | ||||
| # might have injected into the environment. This is done by removing the injected | ||||
| # shim, if it's injected. | ||||
| # | ||||
| # See https://github.com/pypa/pip/issues/8761 for the original discussion and | ||||
| # rationale for why this is done within pip. | ||||
| try: | ||||
|     __import__("_distutils_hack").remove_shim() | ||||
| except (ImportError, AttributeError): | ||||
|     pass | ||||
|  | ||||
| import logging | ||||
| import os | ||||
| import sys | ||||
| from distutils.cmd import Command as DistutilsCommand | ||||
| from distutils.command.install import SCHEME_KEYS | ||||
| from distutils.command.install import install as distutils_install_command | ||||
| from distutils.sysconfig import get_python_lib | ||||
| from typing import Dict, List, Optional, Union, cast | ||||
|  | ||||
| from pip._internal.models.scheme import Scheme | ||||
| from pip._internal.utils.compat import WINDOWS | ||||
| from pip._internal.utils.virtualenv import running_under_virtualenv | ||||
|  | ||||
| from .base import get_major_minor_version | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| def distutils_scheme( | ||||
|     dist_name: str, | ||||
|     user: bool = False, | ||||
|     home: Optional[str] = None, | ||||
|     root: Optional[str] = None, | ||||
|     isolated: bool = False, | ||||
|     prefix: Optional[str] = None, | ||||
|     *, | ||||
|     ignore_config_files: bool = False, | ||||
| ) -> Dict[str, str]: | ||||
|     """ | ||||
|     Return a distutils install scheme | ||||
|     """ | ||||
|     from distutils.dist import Distribution | ||||
|  | ||||
|     dist_args: Dict[str, Union[str, List[str]]] = {"name": dist_name} | ||||
|     if isolated: | ||||
|         dist_args["script_args"] = ["--no-user-cfg"] | ||||
|  | ||||
|     d = Distribution(dist_args) | ||||
|     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 | ||||
|     i = cast(distutils_install_command, obj) | ||||
|     # NOTE: setting user or home has the side-effect of creating the home dir | ||||
|     # or user base for installations during finalize_options() | ||||
|     # ideally, we'd prefer a scheme class that has no side-effects. | ||||
|     assert not (user and prefix), f"user={user} prefix={prefix}" | ||||
|     assert not (home and prefix), f"home={home} prefix={prefix}" | ||||
|     i.user = user or i.user | ||||
|     if user or home: | ||||
|         i.prefix = "" | ||||
|     i.prefix = prefix or i.prefix | ||||
|     i.home = home or i.home | ||||
|     i.root = root or i.root | ||||
|     i.finalize_options() | ||||
|  | ||||
|     scheme = {} | ||||
|     for key in SCHEME_KEYS: | ||||
|         scheme[key] = getattr(i, "install_" + key) | ||||
|  | ||||
|     # install_lib specified in setup.cfg should install *everything* | ||||
|     # into there (i.e. it takes precedence over both purelib and | ||||
|     # platlib).  Note, i.install_lib is *always* set after | ||||
|     # finalize_options(); we only want to override here if the user | ||||
|     # has explicitly requested it hence going back to the config | ||||
|     if "install_lib" in d.get_option_dict("install"): | ||||
|         scheme.update(dict(purelib=i.install_lib, platlib=i.install_lib)) | ||||
|  | ||||
|     if running_under_virtualenv(): | ||||
|         if home: | ||||
|             prefix = home | ||||
|         elif user: | ||||
|             prefix = i.install_userbase | ||||
|         else: | ||||
|             prefix = i.prefix | ||||
|         scheme["headers"] = os.path.join( | ||||
|             prefix, | ||||
|             "include", | ||||
|             "site", | ||||
|             f"python{get_major_minor_version()}", | ||||
|             dist_name, | ||||
|         ) | ||||
|  | ||||
|         if root is not None: | ||||
|             path_no_drive = os.path.splitdrive(os.path.abspath(scheme["headers"]))[1] | ||||
|             scheme["headers"] = os.path.join(root, path_no_drive[1:]) | ||||
|  | ||||
|     return scheme | ||||
|  | ||||
|  | ||||
| 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: | ||||
|     """ | ||||
|     Get the "scheme" corresponding to the input parameters. The distutils | ||||
|     documentation provides the context for the available schemes: | ||||
|     https://docs.python.org/3/install/index.html#alternate-installation | ||||
|  | ||||
|     :param dist_name: the name of the package to retrieve the scheme for, used | ||||
|         in the headers scheme path | ||||
|     :param user: indicates to use the "user" scheme | ||||
|     :param home: indicates to use the "home" scheme and provides the base | ||||
|         directory for the same | ||||
|     :param root: root under which other directories are re-based | ||||
|     :param isolated: equivalent to --no-user-cfg, i.e. do not consider | ||||
|         ~/.pydistutils.cfg (posix) or ~/pydistutils.cfg (non-posix) for | ||||
|         scheme paths | ||||
|     :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) | ||||
|     return Scheme( | ||||
|         platlib=scheme["platlib"], | ||||
|         purelib=scheme["purelib"], | ||||
|         headers=scheme["headers"], | ||||
|         scripts=scheme["scripts"], | ||||
|         data=scheme["data"], | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def get_bin_prefix() -> str: | ||||
|     # XXX: In old virtualenv versions, sys.prefix can contain '..' components, | ||||
|     # so we need to call normpath to eliminate them. | ||||
|     prefix = os.path.normpath(sys.prefix) | ||||
|     if WINDOWS: | ||||
|         bin_py = os.path.join(prefix, "Scripts") | ||||
|         # buildout uses 'bin' on Windows too? | ||||
|         if not os.path.exists(bin_py): | ||||
|             bin_py = os.path.join(prefix, "bin") | ||||
|         return bin_py | ||||
|     # Forcing to use /usr/local/bin for standard macOS framework installs | ||||
|     # Also log to ~/Library/Logs/ for use with the Console.app log viewer | ||||
|     if sys.platform[:6] == "darwin" and prefix[:16] == "/System/Library/": | ||||
|         return "/usr/local/bin" | ||||
|     return os.path.join(prefix, "bin") | ||||
|  | ||||
|  | ||||
| def get_purelib() -> str: | ||||
|     return get_python_lib(plat_specific=False) | ||||
|  | ||||
|  | ||||
| def get_platlib() -> str: | ||||
|     return get_python_lib(plat_specific=True) | ||||
| @@ -0,0 +1,213 @@ | ||||
| import logging | ||||
| import os | ||||
| import sys | ||||
| import sysconfig | ||||
| import typing | ||||
|  | ||||
| from pip._internal.exceptions import InvalidSchemeCombination, UserInstallationInvalid | ||||
| from pip._internal.models.scheme import SCHEME_KEYS, Scheme | ||||
| from pip._internal.utils.virtualenv import running_under_virtualenv | ||||
|  | ||||
| from .base import change_root, get_major_minor_version, is_osx_framework | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| # Notes on _infer_* functions. | ||||
| # Unfortunately ``get_default_scheme()`` didn't exist before 3.10, so there's no | ||||
| # way to ask things like "what is the '_prefix' scheme on this platform". These | ||||
| # functions try to answer that with some heuristics while accounting for ad-hoc | ||||
| # platforms not covered by CPython's default sysconfig implementation. If the | ||||
| # ad-hoc implementation does not fully implement sysconfig, we'll fall back to | ||||
| # a POSIX scheme. | ||||
|  | ||||
| _AVAILABLE_SCHEMES = set(sysconfig.get_scheme_names()) | ||||
|  | ||||
| _PREFERRED_SCHEME_API = getattr(sysconfig, "get_preferred_scheme", None) | ||||
|  | ||||
|  | ||||
| def _should_use_osx_framework_prefix() -> bool: | ||||
|     """Check for Apple's ``osx_framework_library`` scheme. | ||||
|  | ||||
|     Python distributed by Apple's Command Line Tools has this special scheme | ||||
|     that's used when: | ||||
|  | ||||
|     * This is a framework build. | ||||
|     * We are installing into the system prefix. | ||||
|  | ||||
|     This does not account for ``pip install --prefix`` (also means we're not | ||||
|     installing to the system prefix), which should use ``posix_prefix``, but | ||||
|     logic here means ``_infer_prefix()`` outputs ``osx_framework_library``. But | ||||
|     since ``prefix`` is not available for ``sysconfig.get_default_scheme()``, | ||||
|     which is the stdlib replacement for ``_infer_prefix()``, presumably Apple | ||||
|     wouldn't be able to magically switch between ``osx_framework_library`` and | ||||
|     ``posix_prefix``. ``_infer_prefix()`` returning ``osx_framework_library`` | ||||
|     means its behavior is consistent whether we use the stdlib implementation | ||||
|     or our own, and we deal with this special case in ``get_scheme()`` instead. | ||||
|     """ | ||||
|     return ( | ||||
|         "osx_framework_library" in _AVAILABLE_SCHEMES | ||||
|         and not running_under_virtualenv() | ||||
|         and is_osx_framework() | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def _infer_prefix() -> str: | ||||
|     """Try to find a prefix scheme for the current platform. | ||||
|  | ||||
|     This tries: | ||||
|  | ||||
|     * A special ``osx_framework_library`` for Python distributed by Apple's | ||||
|       Command Line Tools, when not running in a virtual environment. | ||||
|     * Implementation + OS, used by PyPy on Windows (``pypy_nt``). | ||||
|     * Implementation without OS, used by PyPy on POSIX (``pypy``). | ||||
|     * OS + "prefix", used by CPython on POSIX (``posix_prefix``). | ||||
|     * Just the OS name, used by CPython on Windows (``nt``). | ||||
|  | ||||
|     If none of the above works, fall back to ``posix_prefix``. | ||||
|     """ | ||||
|     if _PREFERRED_SCHEME_API: | ||||
|         return _PREFERRED_SCHEME_API("prefix") | ||||
|     if _should_use_osx_framework_prefix(): | ||||
|         return "osx_framework_library" | ||||
|     implementation_suffixed = f"{sys.implementation.name}_{os.name}" | ||||
|     if implementation_suffixed in _AVAILABLE_SCHEMES: | ||||
|         return implementation_suffixed | ||||
|     if sys.implementation.name in _AVAILABLE_SCHEMES: | ||||
|         return sys.implementation.name | ||||
|     suffixed = f"{os.name}_prefix" | ||||
|     if suffixed in _AVAILABLE_SCHEMES: | ||||
|         return suffixed | ||||
|     if os.name in _AVAILABLE_SCHEMES:  # On Windows, prefx is just called "nt". | ||||
|         return os.name | ||||
|     return "posix_prefix" | ||||
|  | ||||
|  | ||||
| def _infer_user() -> str: | ||||
|     """Try to find a user scheme for the current platform.""" | ||||
|     if _PREFERRED_SCHEME_API: | ||||
|         return _PREFERRED_SCHEME_API("user") | ||||
|     if is_osx_framework() and not running_under_virtualenv(): | ||||
|         suffixed = "osx_framework_user" | ||||
|     else: | ||||
|         suffixed = f"{os.name}_user" | ||||
|     if suffixed in _AVAILABLE_SCHEMES: | ||||
|         return suffixed | ||||
|     if "posix_user" not in _AVAILABLE_SCHEMES:  # User scheme unavailable. | ||||
|         raise UserInstallationInvalid() | ||||
|     return "posix_user" | ||||
|  | ||||
|  | ||||
| def _infer_home() -> str: | ||||
|     """Try to find a home for the current platform.""" | ||||
|     if _PREFERRED_SCHEME_API: | ||||
|         return _PREFERRED_SCHEME_API("home") | ||||
|     suffixed = f"{os.name}_home" | ||||
|     if suffixed in _AVAILABLE_SCHEMES: | ||||
|         return suffixed | ||||
|     return "posix_home" | ||||
|  | ||||
|  | ||||
| # Update these keys if the user sets a custom home. | ||||
| _HOME_KEYS = [ | ||||
|     "installed_base", | ||||
|     "base", | ||||
|     "installed_platbase", | ||||
|     "platbase", | ||||
|     "prefix", | ||||
|     "exec_prefix", | ||||
| ] | ||||
| if sysconfig.get_config_var("userbase") is not None: | ||||
|     _HOME_KEYS.append("userbase") | ||||
|  | ||||
|  | ||||
| def get_scheme( | ||||
|     dist_name: str, | ||||
|     user: bool = False, | ||||
|     home: typing.Optional[str] = None, | ||||
|     root: typing.Optional[str] = None, | ||||
|     isolated: bool = False, | ||||
|     prefix: typing.Optional[str] = None, | ||||
| ) -> Scheme: | ||||
|     """ | ||||
|     Get the "scheme" corresponding to the input parameters. | ||||
|  | ||||
|     :param dist_name: the name of the package to retrieve the scheme for, used | ||||
|         in the headers scheme path | ||||
|     :param user: indicates to use the "user" scheme | ||||
|     :param home: indicates to use the "home" scheme | ||||
|     :param root: root under which other directories are re-based | ||||
|     :param isolated: ignored, but kept for distutils compatibility (where | ||||
|         this controls whether the user-site pydistutils.cfg is honored) | ||||
|     :param prefix: indicates to use the "prefix" scheme and provides the | ||||
|         base directory for the same | ||||
|     """ | ||||
|     if user and prefix: | ||||
|         raise InvalidSchemeCombination("--user", "--prefix") | ||||
|     if home and prefix: | ||||
|         raise InvalidSchemeCombination("--home", "--prefix") | ||||
|  | ||||
|     if home is not None: | ||||
|         scheme_name = _infer_home() | ||||
|     elif user: | ||||
|         scheme_name = _infer_user() | ||||
|     else: | ||||
|         scheme_name = _infer_prefix() | ||||
|  | ||||
|     # Special case: When installing into a custom prefix, use posix_prefix | ||||
|     # instead of osx_framework_library. See _should_use_osx_framework_prefix() | ||||
|     # docstring for details. | ||||
|     if prefix is not None and scheme_name == "osx_framework_library": | ||||
|         scheme_name = "posix_prefix" | ||||
|  | ||||
|     if home is not None: | ||||
|         variables = {k: home for k in _HOME_KEYS} | ||||
|     elif prefix is not None: | ||||
|         variables = {k: prefix for k in _HOME_KEYS} | ||||
|     else: | ||||
|         variables = {} | ||||
|  | ||||
|     paths = sysconfig.get_paths(scheme=scheme_name, vars=variables) | ||||
|  | ||||
|     # Logic here is very arbitrary, we're doing it for compatibility, don't ask. | ||||
|     # 1. Pip historically uses a special header path in virtual environments. | ||||
|     # 2. If the distribution name is not known, distutils uses 'UNKNOWN'. We | ||||
|     #    only do the same when not running in a virtual environment because | ||||
|     #    pip's historical header path logic (see point 1) did not do this. | ||||
|     if running_under_virtualenv(): | ||||
|         if user: | ||||
|             base = variables.get("userbase", sys.prefix) | ||||
|         else: | ||||
|             base = variables.get("base", sys.prefix) | ||||
|         python_xy = f"python{get_major_minor_version()}" | ||||
|         paths["include"] = os.path.join(base, "include", "site", python_xy) | ||||
|     elif not dist_name: | ||||
|         dist_name = "UNKNOWN" | ||||
|  | ||||
|     scheme = Scheme( | ||||
|         platlib=paths["platlib"], | ||||
|         purelib=paths["purelib"], | ||||
|         headers=os.path.join(paths["include"], dist_name), | ||||
|         scripts=paths["scripts"], | ||||
|         data=paths["data"], | ||||
|     ) | ||||
|     if root is not None: | ||||
|         for key in SCHEME_KEYS: | ||||
|             value = change_root(root, getattr(scheme, key)) | ||||
|             setattr(scheme, key, value) | ||||
|     return scheme | ||||
|  | ||||
|  | ||||
| def get_bin_prefix() -> str: | ||||
|     # Forcing to use /usr/local/bin for standard macOS framework installs. | ||||
|     if sys.platform[:6] == "darwin" and sys.prefix[:16] == "/System/Library/": | ||||
|         return "/usr/local/bin" | ||||
|     return sysconfig.get_paths()["scripts"] | ||||
|  | ||||
|  | ||||
| def get_purelib() -> str: | ||||
|     return sysconfig.get_paths()["purelib"] | ||||
|  | ||||
|  | ||||
| def get_platlib() -> str: | ||||
|     return sysconfig.get_paths()["platlib"] | ||||
| @@ -0,0 +1,81 @@ | ||||
| import functools | ||||
| import os | ||||
| import site | ||||
| import sys | ||||
| import sysconfig | ||||
| import typing | ||||
|  | ||||
| from pip._internal.exceptions import InstallationError | ||||
| from pip._internal.utils import appdirs | ||||
| from pip._internal.utils.virtualenv import running_under_virtualenv | ||||
|  | ||||
| # Application Directories | ||||
| USER_CACHE_DIR = appdirs.user_cache_dir("pip") | ||||
|  | ||||
| # FIXME doesn't account for venv linked to global site-packages | ||||
| site_packages: str = sysconfig.get_path("purelib") | ||||
|  | ||||
|  | ||||
| def get_major_minor_version() -> str: | ||||
|     """ | ||||
|     Return the major-minor version of the current Python as a string, e.g. | ||||
|     "3.7" or "3.10". | ||||
|     """ | ||||
|     return "{}.{}".format(*sys.version_info) | ||||
|  | ||||
|  | ||||
| def change_root(new_root: str, pathname: str) -> str: | ||||
|     """Return 'pathname' with 'new_root' prepended. | ||||
|  | ||||
|     If 'pathname' is relative, this is equivalent to os.path.join(new_root, pathname). | ||||
|     Otherwise, it requires making 'pathname' relative and then joining the | ||||
|     two, which is tricky on DOS/Windows and Mac OS. | ||||
|  | ||||
|     This is borrowed from Python's standard library's distutils module. | ||||
|     """ | ||||
|     if os.name == "posix": | ||||
|         if not os.path.isabs(pathname): | ||||
|             return os.path.join(new_root, pathname) | ||||
|         else: | ||||
|             return os.path.join(new_root, pathname[1:]) | ||||
|  | ||||
|     elif os.name == "nt": | ||||
|         (drive, path) = os.path.splitdrive(pathname) | ||||
|         if path[0] == "\\": | ||||
|             path = path[1:] | ||||
|         return os.path.join(new_root, path) | ||||
|  | ||||
|     else: | ||||
|         raise InstallationError( | ||||
|             f"Unknown platform: {os.name}\n" | ||||
|             "Can not change root path prefix on unknown platform." | ||||
|         ) | ||||
|  | ||||
|  | ||||
| def get_src_prefix() -> str: | ||||
|     if running_under_virtualenv(): | ||||
|         src_prefix = os.path.join(sys.prefix, "src") | ||||
|     else: | ||||
|         # FIXME: keep src in cwd for now (it is not a temporary folder) | ||||
|         try: | ||||
|             src_prefix = os.path.join(os.getcwd(), "src") | ||||
|         except OSError: | ||||
|             # In case the current working directory has been renamed or deleted | ||||
|             sys.exit("The folder you are executing pip from can no longer be found.") | ||||
|  | ||||
|     # under macOS + virtualenv sys.prefix is not properly resolved | ||||
|     # it is something like /path/to/python/bin/.. | ||||
|     return os.path.abspath(src_prefix) | ||||
|  | ||||
|  | ||||
| try: | ||||
|     # Use getusersitepackages if this is present, as it ensures that the | ||||
|     # value is initialised properly. | ||||
|     user_site: typing.Optional[str] = site.getusersitepackages() | ||||
| except AttributeError: | ||||
|     user_site = site.USER_SITE | ||||
|  | ||||
|  | ||||
| @functools.lru_cache(maxsize=None) | ||||
| def is_osx_framework() -> bool: | ||||
|     return bool(sysconfig.get_config_var("PYTHONFRAMEWORK")) | ||||
		Reference in New Issue
	
	Block a user