codekingpro/portable-devtools
114k
1# don't import any costly modules2import sys3import os4 5 6def warn_distutils_present():7 if 'distutils' not in sys.modules:8 return9 import warnings10 11 warnings.warn(12 "Distutils was imported before Setuptools, but importing Setuptools "13 "also replaces the `distutils` module in `sys.modules`. This may lead "14 "to undesirable behaviors or errors. To avoid these issues, avoid "15 "using distutils directly, ensure that setuptools is installed in the "16 "traditional way (e.g. not an editable install), and/or make sure "17 "that setuptools is always imported before distutils."18 )19 20 21def clear_distutils():22 if 'distutils' not in sys.modules:23 return24 import warnings25 26 warnings.warn("Setuptools is replacing distutils.")27 mods = [28 name29 for name in sys.modules30 if name == "distutils" or name.startswith("distutils.")31 ]32 for name in mods:33 del sys.modules[name]34 35 36def enabled():37 """38 Allow selection of distutils by environment variable.39 """40 which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'local')41 return which == 'local'42 43 44def ensure_local_distutils():45 import importlib46 47 clear_distutils()48 49 # With the DistutilsMetaFinder in place,50 # perform an import to cause distutils to be51 # loaded from setuptools._distutils. Ref #2906.52 with shim():53 importlib.import_module('distutils')54 55 # check that submodules load as expected56 core = importlib.import_module('distutils.core')57 assert '_distutils' in core.__file__, core.__file__58 assert 'setuptools._distutils.log' not in sys.modules59 60 61def do_override():62 """63 Ensure that the local copy of distutils is preferred over stdlib.64 65 See https://github.com/pypa/setuptools/issues/417#issuecomment-39229840166 for more motivation.67 """68 if enabled():69 warn_distutils_present()70 ensure_local_distutils()71 72 73class _TrivialRe:74 def __init__(self, *patterns):75 self._patterns = patterns76 77 def match(self, string):78 return all(pat in string for pat in self._patterns)79 80 81class DistutilsMetaFinder:82 def find_spec(self, fullname, path, target=None):83 # optimization: only consider top level modules and those84 # found in the CPython test suite.85 if path is not None and not fullname.startswith('test.'):86 return None87 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 None95 96 import importlib97 import importlib.abc98 import importlib.util99 100 try:101 mod = importlib.import_module('setuptools._distutils')102 except Exception:103 # There are a couple of cases where setuptools._distutils104 # may not be present:105 # - An older Setuptools without a local distutils is106 # taking precedence. Ref #2957.107 # - Path manipulation during sitecustomize removes108 # setuptools from the path but only after the hook109 # has been loaded. Ref #2980.110 # In either case, fall back to stdlib behavior.111 return None112 113 class DistutilsLoader(importlib.abc.Loader):114 def create_module(self, spec):115 mod.__name__ = 'distutils'116 return mod117 118 def exec_module(self, module):119 pass120 121 return importlib.util.spec_from_loader(122 'distutils', DistutilsLoader(), origin=mod.__file__123 )124 125 @staticmethod126 def is_cpython():127 """128 Suppress supplying distutils for CPython (build and tests).129 Ref #2965 and #3007.130 """131 return os.path.isfile('pybuilddir.txt')132 133 def spec_for_pip(self):134 """135 Ensure stdlib distutils when running under pip.136 See pypa/pip#8761 for rationale.137 """138 if sys.version_info >= (3, 12) or self.pip_imported_during_build():139 return140 clear_distutils()141 self.spec_for_distutils = lambda: None142 143 @classmethod144 def pip_imported_during_build(cls):145 """146 Detect if pip is being imported in a build script. Ref #2355.147 """148 import traceback149 150 return any(151 cls.frame_file_is_setup(frame) for frame, line in traceback.walk_stack(None)152 )153 154 @staticmethod155 def frame_file_is_setup(frame):156 """157 Return True if the indicated frame suggests a setup.py file.158 """159 # some frames may not have __file__ (#2940)160 return frame.f_globals.get('__file__', '').endswith('setup.py')161 162 def spec_for_sensitive_tests(self):163 """164 Ensure stdlib distutils when running select tests under CPython.165 166 python/cpython#91169167 """168 clear_distutils()169 self.spec_for_distutils = lambda: None170 171 sensitive_tests = (172 [173 'test.test_distutils',174 'test.test_peg_generator',175 'test.test_importlib',176 ]177 if sys.version_info < (3, 10)178 else [179 'test.test_distutils',180 ]181 )182 183 184for name in DistutilsMetaFinder.sensitive_tests:185 setattr(186 DistutilsMetaFinder,187 f'spec_for_{name}',188 DistutilsMetaFinder.spec_for_sensitive_tests,189 )190 191 192DISTUTILS_FINDER = DistutilsMetaFinder()193 194 195def add_shim():196 DISTUTILS_FINDER in sys.meta_path or insert_shim()197 198 199class shim:200 def __enter__(self):201 insert_shim()202 203 def __exit__(self, exc, value, tb):204 _remove_shim()205 206 207def insert_shim():208 sys.meta_path.insert(0, DISTUTILS_FINDER)209 210 211def _remove_shim():212 try:213 sys.meta_path.remove(DISTUTILS_FINDER)214 except ValueError:215 pass216 217 218if sys.version_info < (3, 12):219 # DistutilsMetaFinder can only be disabled in Python < 3.12 (PEP 632)220 remove_shim = _remove_shim221 