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