You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

129 lines
3.6KB

  1. import sys
  2. import os
  3. import re
  4. import importlib
  5. import warnings
  6. is_pypy = '__pypy__' in sys.builtin_module_names
  7. warnings.filterwarnings('ignore',
  8. r'.+ distutils\b.+ deprecated',
  9. DeprecationWarning)
  10. def warn_distutils_present():
  11. if 'distutils' not in sys.modules:
  12. return
  13. if is_pypy and sys.version_info < (3, 7):
  14. # PyPy for 3.6 unconditionally imports distutils, so bypass the warning
  15. # https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250
  16. return
  17. warnings.warn(
  18. "Distutils was imported before Setuptools, but importing Setuptools "
  19. "also replaces the `distutils` module in `sys.modules`. This may lead "
  20. "to undesirable behaviors or errors. To avoid these issues, avoid "
  21. "using distutils directly, ensure that setuptools is installed in the "
  22. "traditional way (e.g. not an editable install), and/or make sure "
  23. "that setuptools is always imported before distutils.")
  24. def clear_distutils():
  25. if 'distutils' not in sys.modules:
  26. return
  27. warnings.warn("Setuptools is replacing distutils.")
  28. mods = [name for name in sys.modules if re.match(r'distutils\b', name)]
  29. for name in mods:
  30. del sys.modules[name]
  31. def enabled():
  32. """
  33. Allow selection of distutils by environment variable.
  34. """
  35. which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'stdlib')
  36. return which == 'local'
  37. def ensure_local_distutils():
  38. clear_distutils()
  39. distutils = importlib.import_module('setuptools._distutils')
  40. distutils.__name__ = 'distutils'
  41. sys.modules['distutils'] = distutils
  42. # sanity check that submodules load as expected
  43. core = importlib.import_module('distutils.core')
  44. assert '_distutils' in core.__file__, core.__file__
  45. def do_override():
  46. """
  47. Ensure that the local copy of distutils is preferred over stdlib.
  48. See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401
  49. for more motivation.
  50. """
  51. if enabled():
  52. warn_distutils_present()
  53. ensure_local_distutils()
  54. class DistutilsMetaFinder:
  55. def find_spec(self, fullname, path, target=None):
  56. if path is not None:
  57. return
  58. method_name = 'spec_for_{fullname}'.format(**locals())
  59. method = getattr(self, method_name, lambda: None)
  60. return method()
  61. def spec_for_distutils(self):
  62. import importlib.abc
  63. import importlib.util
  64. class DistutilsLoader(importlib.abc.Loader):
  65. def create_module(self, spec):
  66. return importlib.import_module('setuptools._distutils')
  67. def exec_module(self, module):
  68. pass
  69. return importlib.util.spec_from_loader('distutils', DistutilsLoader())
  70. def spec_for_pip(self):
  71. """
  72. Ensure stdlib distutils when running under pip.
  73. See pypa/pip#8761 for rationale.
  74. """
  75. if self.pip_imported_during_build():
  76. return
  77. clear_distutils()
  78. self.spec_for_distutils = lambda: None
  79. @staticmethod
  80. def pip_imported_during_build():
  81. """
  82. Detect if pip is being imported in a build script. Ref #2355.
  83. """
  84. import traceback
  85. return any(
  86. frame.f_globals['__file__'].endswith('setup.py')
  87. for frame, line in traceback.walk_stack(None)
  88. )
  89. DISTUTILS_FINDER = DistutilsMetaFinder()
  90. def add_shim():
  91. sys.meta_path.insert(0, DISTUTILS_FINDER)
  92. def remove_shim():
  93. try:
  94. sys.meta_path.remove(DISTUTILS_FINDER)
  95. except ValueError:
  96. pass