__init__.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. # don't import any costly modules
  2. import sys
  3. import os
  4. def warn_distutils_present():
  5. if 'distutils' not in sys.modules:
  6. return
  7. import warnings
  8. warnings.warn(
  9. "Distutils was imported before Setuptools, but importing Setuptools "
  10. "also replaces the `distutils` module in `sys.modules`. This may lead "
  11. "to undesirable behaviors or errors. To avoid these issues, avoid "
  12. "using distutils directly, ensure that setuptools is installed in the "
  13. "traditional way (e.g. not an editable install), and/or make sure "
  14. "that setuptools is always imported before distutils."
  15. )
  16. def clear_distutils():
  17. if 'distutils' not in sys.modules:
  18. return
  19. import warnings
  20. warnings.warn("Setuptools is replacing distutils.")
  21. mods = [
  22. name
  23. for name in sys.modules
  24. if name == "distutils" or name.startswith("distutils.")
  25. ]
  26. for name in mods:
  27. del sys.modules[name]
  28. def enabled():
  29. """
  30. Allow selection of distutils by environment variable.
  31. """
  32. which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'local')
  33. return which == 'local'
  34. def ensure_local_distutils():
  35. import importlib
  36. clear_distutils()
  37. # With the DistutilsMetaFinder in place,
  38. # perform an import to cause distutils to be
  39. # loaded from setuptools._distutils. Ref #2906.
  40. with shim():
  41. importlib.import_module('distutils')
  42. # check that submodules load as expected
  43. core = importlib.import_module('distutils.core')
  44. assert '_distutils' in core.__file__, core.__file__
  45. assert 'setuptools._distutils.log' not in sys.modules
  46. def do_override():
  47. """
  48. Ensure that the local copy of distutils is preferred over stdlib.
  49. See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401
  50. for more motivation.
  51. """
  52. if enabled():
  53. warn_distutils_present()
  54. ensure_local_distutils()
  55. class _TrivialRe:
  56. def __init__(self, *patterns):
  57. self._patterns = patterns
  58. def match(self, string):
  59. return all(pat in string for pat in self._patterns)
  60. class DistutilsMetaFinder:
  61. def find_spec(self, fullname, path, target=None):
  62. # optimization: only consider top level modules and those
  63. # found in the CPython test suite.
  64. if path is not None and not fullname.startswith('test.'):
  65. return None
  66. method_name = 'spec_for_{fullname}'.format(**locals())
  67. method = getattr(self, method_name, lambda: None)
  68. return method()
  69. def spec_for_distutils(self):
  70. if self.is_cpython():
  71. return None
  72. import importlib
  73. import importlib.abc
  74. import importlib.util
  75. try:
  76. mod = importlib.import_module('setuptools._distutils')
  77. except Exception:
  78. # There are a couple of cases where setuptools._distutils
  79. # may not be present:
  80. # - An older Setuptools without a local distutils is
  81. # taking precedence. Ref #2957.
  82. # - Path manipulation during sitecustomize removes
  83. # setuptools from the path but only after the hook
  84. # has been loaded. Ref #2980.
  85. # In either case, fall back to stdlib behavior.
  86. return None
  87. class DistutilsLoader(importlib.abc.Loader):
  88. def create_module(self, spec):
  89. mod.__name__ = 'distutils'
  90. return mod
  91. def exec_module(self, module):
  92. pass
  93. return importlib.util.spec_from_loader(
  94. 'distutils', DistutilsLoader(), origin=mod.__file__
  95. )
  96. @staticmethod
  97. def is_cpython():
  98. """
  99. Suppress supplying distutils for CPython (build and tests).
  100. Ref #2965 and #3007.
  101. """
  102. return os.path.isfile('pybuilddir.txt')
  103. def spec_for_pip(self):
  104. """
  105. Ensure stdlib distutils when running under pip.
  106. See pypa/pip#8761 for rationale.
  107. """
  108. if sys.version_info >= (3, 12) or self.pip_imported_during_build():
  109. return
  110. clear_distutils()
  111. self.spec_for_distutils = lambda: None
  112. @classmethod
  113. def pip_imported_during_build(cls):
  114. """
  115. Detect if pip is being imported in a build script. Ref #2355.
  116. """
  117. import traceback
  118. return any(
  119. cls.frame_file_is_setup(frame) for frame, line in traceback.walk_stack(None)
  120. )
  121. @staticmethod
  122. def frame_file_is_setup(frame):
  123. """
  124. Return True if the indicated frame suggests a setup.py file.
  125. """
  126. # some frames may not have __file__ (#2940)
  127. return frame.f_globals.get('__file__', '').endswith('setup.py')
  128. def spec_for_sensitive_tests(self):
  129. """
  130. Ensure stdlib distutils when running select tests under CPython.
  131. python/cpython#91169
  132. """
  133. clear_distutils()
  134. self.spec_for_distutils = lambda: None
  135. sensitive_tests = (
  136. [
  137. 'test.test_distutils',
  138. 'test.test_peg_generator',
  139. 'test.test_importlib',
  140. ]
  141. if sys.version_info < (3, 10)
  142. else [
  143. 'test.test_distutils',
  144. ]
  145. )
  146. for name in DistutilsMetaFinder.sensitive_tests:
  147. setattr(
  148. DistutilsMetaFinder,
  149. f'spec_for_{name}',
  150. DistutilsMetaFinder.spec_for_sensitive_tests,
  151. )
  152. DISTUTILS_FINDER = DistutilsMetaFinder()
  153. def add_shim():
  154. DISTUTILS_FINDER in sys.meta_path or insert_shim()
  155. class shim:
  156. def __enter__(self):
  157. insert_shim()
  158. def __exit__(self, exc, value, tb):
  159. _remove_shim()
  160. def insert_shim():
  161. sys.meta_path.insert(0, DISTUTILS_FINDER)
  162. def _remove_shim():
  163. try:
  164. sys.meta_path.remove(DISTUTILS_FINDER)
  165. except ValueError:
  166. pass
  167. if sys.version_info < (3, 12):
  168. # DistutilsMetaFinder can only be disabled in Python < 3.12 (PEP 632)
  169. remove_shim = _remove_shim