123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- import distutils.util # FIXME: For change_root.
- 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 get_major_minor_version
- logger = logging.getLogger(__name__)
- # Notes on _infer_* functions.
- # Unfortunately ``_get_default_scheme()`` is private, 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())
- def _infer_prefix():
- # type: () -> str
- """Try to find a prefix scheme for the current platform.
- This tries:
- * 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``.
- """
- 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():
- # type: () -> str
- """Try to find a user scheme for the current platform."""
- 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():
- # type: () -> str
- """Try to find a home for the current platform."""
- 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, # type: str
- user=False, # type: bool
- home=None, # type: typing.Optional[str]
- root=None, # type: typing.Optional[str]
- isolated=False, # type: bool
- prefix=None, # type: typing.Optional[str]
- ):
- # type: (...) -> 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()
- 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 = distutils.util.change_root(root, getattr(scheme, key))
- setattr(scheme, key, value)
- return scheme
- def get_bin_prefix():
- # type: () -> 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():
- # type: () -> str
- return sysconfig.get_paths()["purelib"]
- def get_platlib():
- # type: () -> str
- return sysconfig.get_paths()["platlib"]
- def get_prefixed_libs(prefix):
- # type: (str) -> typing.Tuple[str, str]
- paths = sysconfig.get_paths(vars={"base": prefix, "platbase": prefix})
- return (paths["purelib"], paths["platlib"])
|