__init__.py 6.0 KB

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