_sysconfig.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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
  11. logger = logging.getLogger(__name__)
  12. # Notes on _infer_* functions.
  13. # Unfortunately ``_get_default_scheme()`` is private, so there's no way to
  14. # 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. def _infer_prefix():
  21. # type: () -> str
  22. """Try to find a prefix scheme for the current platform.
  23. This tries:
  24. * Implementation + OS, used by PyPy on Windows (``pypy_nt``).
  25. * Implementation without OS, used by PyPy on POSIX (``pypy``).
  26. * OS + "prefix", used by CPython on POSIX (``posix_prefix``).
  27. * Just the OS name, used by CPython on Windows (``nt``).
  28. If none of the above works, fall back to ``posix_prefix``.
  29. """
  30. implementation_suffixed = f"{sys.implementation.name}_{os.name}"
  31. if implementation_suffixed in _AVAILABLE_SCHEMES:
  32. return implementation_suffixed
  33. if sys.implementation.name in _AVAILABLE_SCHEMES:
  34. return sys.implementation.name
  35. suffixed = f"{os.name}_prefix"
  36. if suffixed in _AVAILABLE_SCHEMES:
  37. return suffixed
  38. if os.name in _AVAILABLE_SCHEMES: # On Windows, prefx is just called "nt".
  39. return os.name
  40. return "posix_prefix"
  41. def _infer_user():
  42. # type: () -> str
  43. """Try to find a user scheme for the current platform."""
  44. suffixed = f"{os.name}_user"
  45. if suffixed in _AVAILABLE_SCHEMES:
  46. return suffixed
  47. if "posix_user" not in _AVAILABLE_SCHEMES: # User scheme unavailable.
  48. raise UserInstallationInvalid()
  49. return "posix_user"
  50. def _infer_home():
  51. # type: () -> str
  52. """Try to find a home for the current platform."""
  53. suffixed = f"{os.name}_home"
  54. if suffixed in _AVAILABLE_SCHEMES:
  55. return suffixed
  56. return "posix_home"
  57. # Update these keys if the user sets a custom home.
  58. _HOME_KEYS = [
  59. "installed_base",
  60. "base",
  61. "installed_platbase",
  62. "platbase",
  63. "prefix",
  64. "exec_prefix",
  65. ]
  66. if sysconfig.get_config_var("userbase") is not None:
  67. _HOME_KEYS.append("userbase")
  68. def get_scheme(
  69. dist_name, # type: str
  70. user=False, # type: bool
  71. home=None, # type: typing.Optional[str]
  72. root=None, # type: typing.Optional[str]
  73. isolated=False, # type: bool
  74. prefix=None, # type: typing.Optional[str]
  75. ):
  76. # type: (...) -> Scheme
  77. """
  78. Get the "scheme" corresponding to the input parameters.
  79. :param dist_name: the name of the package to retrieve the scheme for, used
  80. in the headers scheme path
  81. :param user: indicates to use the "user" scheme
  82. :param home: indicates to use the "home" scheme
  83. :param root: root under which other directories are re-based
  84. :param isolated: ignored, but kept for distutils compatibility (where
  85. this controls whether the user-site pydistutils.cfg is honored)
  86. :param prefix: indicates to use the "prefix" scheme and provides the
  87. base directory for the same
  88. """
  89. if user and prefix:
  90. raise InvalidSchemeCombination("--user", "--prefix")
  91. if home and prefix:
  92. raise InvalidSchemeCombination("--home", "--prefix")
  93. if home is not None:
  94. scheme_name = _infer_home()
  95. elif user:
  96. scheme_name = _infer_user()
  97. else:
  98. scheme_name = _infer_prefix()
  99. if home is not None:
  100. variables = {k: home for k in _HOME_KEYS}
  101. elif prefix is not None:
  102. variables = {k: prefix for k in _HOME_KEYS}
  103. else:
  104. variables = {}
  105. paths = sysconfig.get_paths(scheme=scheme_name, vars=variables)
  106. # Logic here is very arbitrary, we're doing it for compatibility, don't ask.
  107. # 1. Pip historically uses a special header path in virtual environments.
  108. # 2. If the distribution name is not known, distutils uses 'UNKNOWN'. We
  109. # only do the same when not running in a virtual environment because
  110. # pip's historical header path logic (see point 1) did not do this.
  111. if running_under_virtualenv():
  112. if user:
  113. base = variables.get("userbase", sys.prefix)
  114. else:
  115. base = variables.get("base", sys.prefix)
  116. python_xy = f"python{get_major_minor_version()}"
  117. paths["include"] = os.path.join(base, "include", "site", python_xy)
  118. elif not dist_name:
  119. dist_name = "UNKNOWN"
  120. scheme = Scheme(
  121. platlib=paths["platlib"],
  122. purelib=paths["purelib"],
  123. headers=os.path.join(paths["include"], dist_name),
  124. scripts=paths["scripts"],
  125. data=paths["data"],
  126. )
  127. if root is not None:
  128. for key in SCHEME_KEYS:
  129. value = distutils.util.change_root(root, getattr(scheme, key))
  130. setattr(scheme, key, value)
  131. return scheme
  132. def get_bin_prefix():
  133. # type: () -> str
  134. # Forcing to use /usr/local/bin for standard macOS framework installs.
  135. if sys.platform[:6] == "darwin" and sys.prefix[:16] == "/System/Library/":
  136. return "/usr/local/bin"
  137. return sysconfig.get_paths()["scripts"]
  138. def get_purelib():
  139. # type: () -> str
  140. return sysconfig.get_paths()["purelib"]
  141. def get_platlib():
  142. # type: () -> str
  143. return sysconfig.get_paths()["platlib"]
  144. def get_prefixed_libs(prefix):
  145. # type: (str) -> typing.Tuple[str, str]
  146. paths = sysconfig.get_paths(vars={"base": prefix, "platbase": prefix})
  147. return (paths["purelib"], paths["platlib"])