_sysconfig.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. from __future__ import annotations
  2. import logging
  3. import os
  4. import sys
  5. import sysconfig
  6. from typing import Callable
  7. from pip._internal.exceptions import InvalidSchemeCombination, UserInstallationInvalid
  8. from pip._internal.models.scheme import SCHEME_KEYS, Scheme
  9. from pip._internal.utils.virtualenv import running_under_virtualenv
  10. from .base import change_root, get_major_minor_version, is_osx_framework
  11. logger = logging.getLogger(__name__)
  12. # Notes on _infer_* functions.
  13. # Unfortunately ``get_default_scheme()`` didn't exist before 3.10, so there's no
  14. # way to ask things like "what is the '_prefix' scheme on this platform". These
  15. # functions try to answer that with some heuristics while accounting for ad-hoc
  16. # platforms not covered by CPython's default sysconfig implementation. If the
  17. # ad-hoc implementation does not fully implement sysconfig, we'll fall back to
  18. # a POSIX scheme.
  19. _AVAILABLE_SCHEMES = set(sysconfig.get_scheme_names())
  20. _PREFERRED_SCHEME_API: Callable[[str], str] | None = getattr(
  21. sysconfig, "get_preferred_scheme", None
  22. )
  23. def _should_use_osx_framework_prefix() -> bool:
  24. """Check for Apple's ``osx_framework_library`` scheme.
  25. Python distributed by Apple's Command Line Tools has this special scheme
  26. that's used when:
  27. * This is a framework build.
  28. * We are installing into the system prefix.
  29. This does not account for ``pip install --prefix`` (also means we're not
  30. installing to the system prefix), which should use ``posix_prefix``, but
  31. logic here means ``_infer_prefix()`` outputs ``osx_framework_library``. But
  32. since ``prefix`` is not available for ``sysconfig.get_default_scheme()``,
  33. which is the stdlib replacement for ``_infer_prefix()``, presumably Apple
  34. wouldn't be able to magically switch between ``osx_framework_library`` and
  35. ``posix_prefix``. ``_infer_prefix()`` returning ``osx_framework_library``
  36. means its behavior is consistent whether we use the stdlib implementation
  37. or our own, and we deal with this special case in ``get_scheme()`` instead.
  38. """
  39. return (
  40. "osx_framework_library" in _AVAILABLE_SCHEMES
  41. and not running_under_virtualenv()
  42. and is_osx_framework()
  43. )
  44. def _infer_prefix() -> str:
  45. """Try to find a prefix scheme for the current platform.
  46. This tries:
  47. * A special ``osx_framework_library`` for Python distributed by Apple's
  48. Command Line Tools, when not running in a virtual environment.
  49. * Implementation + OS, used by PyPy on Windows (``pypy_nt``).
  50. * Implementation without OS, used by PyPy on POSIX (``pypy``).
  51. * OS + "prefix", used by CPython on POSIX (``posix_prefix``).
  52. * Just the OS name, used by CPython on Windows (``nt``).
  53. If none of the above works, fall back to ``posix_prefix``.
  54. """
  55. if _PREFERRED_SCHEME_API:
  56. return _PREFERRED_SCHEME_API("prefix")
  57. if _should_use_osx_framework_prefix():
  58. return "osx_framework_library"
  59. implementation_suffixed = f"{sys.implementation.name}_{os.name}"
  60. if implementation_suffixed in _AVAILABLE_SCHEMES:
  61. return implementation_suffixed
  62. if sys.implementation.name in _AVAILABLE_SCHEMES:
  63. return sys.implementation.name
  64. suffixed = f"{os.name}_prefix"
  65. if suffixed in _AVAILABLE_SCHEMES:
  66. return suffixed
  67. if os.name in _AVAILABLE_SCHEMES: # On Windows, prefx is just called "nt".
  68. return os.name
  69. return "posix_prefix"
  70. def _infer_user() -> str:
  71. """Try to find a user scheme for the current platform."""
  72. if _PREFERRED_SCHEME_API:
  73. return _PREFERRED_SCHEME_API("user")
  74. if is_osx_framework() and not running_under_virtualenv():
  75. suffixed = "osx_framework_user"
  76. else:
  77. suffixed = f"{os.name}_user"
  78. if suffixed in _AVAILABLE_SCHEMES:
  79. return suffixed
  80. if "posix_user" not in _AVAILABLE_SCHEMES: # User scheme unavailable.
  81. raise UserInstallationInvalid()
  82. return "posix_user"
  83. def _infer_home() -> str:
  84. """Try to find a home for the current platform."""
  85. if _PREFERRED_SCHEME_API:
  86. return _PREFERRED_SCHEME_API("home")
  87. suffixed = f"{os.name}_home"
  88. if suffixed in _AVAILABLE_SCHEMES:
  89. return suffixed
  90. return "posix_home"
  91. # Update these keys if the user sets a custom home.
  92. _HOME_KEYS = [
  93. "installed_base",
  94. "base",
  95. "installed_platbase",
  96. "platbase",
  97. "prefix",
  98. "exec_prefix",
  99. ]
  100. if sysconfig.get_config_var("userbase") is not None:
  101. _HOME_KEYS.append("userbase")
  102. def get_scheme(
  103. dist_name: str,
  104. user: bool = False,
  105. home: str | None = None,
  106. root: str | None = None,
  107. isolated: bool = False,
  108. prefix: str | None = None,
  109. ) -> Scheme:
  110. """
  111. Get the "scheme" corresponding to the input parameters.
  112. :param dist_name: the name of the package to retrieve the scheme for, used
  113. in the headers scheme path
  114. :param user: indicates to use the "user" scheme
  115. :param home: indicates to use the "home" scheme
  116. :param root: root under which other directories are re-based
  117. :param isolated: ignored, but kept for distutils compatibility (where
  118. this controls whether the user-site pydistutils.cfg is honored)
  119. :param prefix: indicates to use the "prefix" scheme and provides the
  120. base directory for the same
  121. """
  122. if user and prefix:
  123. raise InvalidSchemeCombination("--user", "--prefix")
  124. if home and prefix:
  125. raise InvalidSchemeCombination("--home", "--prefix")
  126. if home is not None:
  127. scheme_name = _infer_home()
  128. elif user:
  129. scheme_name = _infer_user()
  130. else:
  131. scheme_name = _infer_prefix()
  132. # Special case: When installing into a custom prefix, use posix_prefix
  133. # instead of osx_framework_library. See _should_use_osx_framework_prefix()
  134. # docstring for details.
  135. if prefix is not None and scheme_name == "osx_framework_library":
  136. scheme_name = "posix_prefix"
  137. if home is not None:
  138. variables = {k: home for k in _HOME_KEYS}
  139. elif prefix is not None:
  140. variables = {k: prefix for k in _HOME_KEYS}
  141. else:
  142. variables = {}
  143. paths = sysconfig.get_paths(scheme=scheme_name, vars=variables)
  144. # Logic here is very arbitrary, we're doing it for compatibility, don't ask.
  145. # 1. Pip historically uses a special header path in virtual environments.
  146. # 2. If the distribution name is not known, distutils uses 'UNKNOWN'. We
  147. # only do the same when not running in a virtual environment because
  148. # pip's historical header path logic (see point 1) did not do this.
  149. if running_under_virtualenv():
  150. if user:
  151. base = variables.get("userbase", sys.prefix)
  152. else:
  153. base = variables.get("base", sys.prefix)
  154. python_xy = f"python{get_major_minor_version()}"
  155. paths["include"] = os.path.join(base, "include", "site", python_xy)
  156. elif not dist_name:
  157. dist_name = "UNKNOWN"
  158. scheme = Scheme(
  159. platlib=paths["platlib"],
  160. purelib=paths["purelib"],
  161. headers=os.path.join(paths["include"], dist_name),
  162. scripts=paths["scripts"],
  163. data=paths["data"],
  164. )
  165. if root is not None:
  166. converted_keys = {}
  167. for key in SCHEME_KEYS:
  168. converted_keys[key] = change_root(root, getattr(scheme, key))
  169. scheme = Scheme(**converted_keys)
  170. return scheme
  171. def get_bin_prefix() -> str:
  172. # Forcing to use /usr/local/bin for standard macOS framework installs.
  173. if sys.platform[:6] == "darwin" and sys.prefix[:16] == "/System/Library/":
  174. return "/usr/local/bin"
  175. return sysconfig.get_paths()["scripts"]
  176. def get_purelib() -> str:
  177. return sysconfig.get_paths()["purelib"]
  178. def get_platlib() -> str:
  179. return sysconfig.get_paths()["platlib"]