| 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"] |
| |
| |
| def get_prefixed_libs(prefix: str) -> typing.Tuple[str, str]: |
| paths = sysconfig.get_paths(vars={"base": prefix, "platbase": prefix}) |
| return (paths["purelib"], paths["platlib"]) |