220 lines
7.7 KiB
Python
220 lines
7.7 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, 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 = distutils.util.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"])
|