_sysconfig.py 7.7 KB

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