181 lines
5.8 KiB
Python
181 lines
5.8 KiB
Python
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"])
|