codekingpro/portable-devtools
115k
1# don't import any costly modules2import os3import sys4 5report_url = (6 "https://github.com/pypa/setuptools/issues/new?template=distutils-deprecation.yml"7)8 9 10def warn_distutils_present():11 if 'distutils' not in sys.modules:12 return13 import warnings14 15 warnings.warn(16 "Distutils was imported before Setuptools, but importing Setuptools "17 "also replaces the `distutils` module in `sys.modules`. This may lead "18 "to undesirable behaviors or errors. To avoid these issues, avoid "19 "using distutils directly, ensure that setuptools is installed in the "20 "traditional way (e.g. not an editable install), and/or make sure "21 "that setuptools is always imported before distutils."22 )23 24 25def clear_distutils():26 if 'distutils' not in sys.modules:27 return28 import warnings29 30 warnings.warn(31 "Setuptools is replacing distutils. Support for replacing "32 "an already imported distutils is deprecated. In the future, "33 "this condition will fail. "34 f"Register concerns at {report_url}"35 )36 mods = [37 name38 for name in sys.modules39 if name == "distutils" or name.startswith("distutils.")40 ]41 for name in mods:42 del sys.modules[name]43 44 45def enabled():46 """47 Allow selection of distutils by environment variable.48 """49 which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'local')50 if which == 'stdlib':51 import warnings52 53 warnings.warn(54 "Reliance on distutils from stdlib is deprecated. Users "55 "must rely on setuptools to provide the distutils module. "56 "Avoid importing distutils or import setuptools first, "57 "and avoid setting SETUPTOOLS_USE_DISTUTILS=stdlib. "58 f"Register concerns at {report_url}"59 )60 return which == 'local'61 62 63def ensure_local_distutils():64 import importlib65 66 clear_distutils()67 68 # With the DistutilsMetaFinder in place,69 # perform an import to cause distutils to be70 # loaded from setuptools._distutils. Ref #2906.71 with shim():72 importlib.import_module('distutils')73 74 # check that submodules load as expected75 core = importlib.import_module('distutils.core')76 assert '_distutils' in core.__file__, core.__file__77 assert 'setuptools._distutils.log' not in sys.modules78 79 80def do_override():81 """82 Ensure that the local copy of distutils is preferred over stdlib.83 84 See https://github.com/pypa/setuptools/issues/417#issuecomment-39229840185 for more motivation.86 """87 if enabled():88 warn_distutils_present()89 ensure_local_distutils()90 91 92class _TrivialRe:93 def __init__(self, *patterns) -> None:94 self._patterns = patterns95 96 def match(self, string):97 return all(pat in string for pat in self._patterns)98 99 100class DistutilsMetaFinder:101 def find_spec(self, fullname, path, target=None):102 # optimization: only consider top level modules and those103 # found in the CPython test suite.104 if path is not None and not fullname.startswith('test.'):105 return None106 107 method_name = 'spec_for_{fullname}'.format(**locals())108 method = getattr(self, method_name, lambda: None)109 return method()110 111 def spec_for_distutils(self):112 if self.is_cpython():113 return None114 115 import importlib116 import importlib.abc117 import importlib.util118 119 try:120 mod = importlib.import_module('setuptools._distutils')121 except Exception:122 # There are a couple of cases where setuptools._distutils123 # may not be present:124 # - An older Setuptools without a local distutils is125 # taking precedence. Ref #2957.126 # - Path manipulation during sitecustomize removes127 # setuptools from the path but only after the hook128 # has been loaded. Ref #2980.129 # In either case, fall back to stdlib behavior.130 return None131 132 class DistutilsLoader(importlib.abc.Loader):133 def create_module(self, spec):134 mod.__name__ = 'distutils'135 return mod136 137 def exec_module(self, module):138 pass139 140 return importlib.util.spec_from_loader(141 'distutils', DistutilsLoader(), origin=mod.__file__142 )143 144 @staticmethod145 def is_cpython():146 """147 Suppress supplying distutils for CPython (build and tests).148 Ref #2965 and #3007.149 """150 return os.path.isfile('pybuilddir.txt')151 152 def spec_for_pip(self):153 """154 Ensure stdlib distutils when running under pip.155 See pypa/pip#8761 for rationale.156 """157 if sys.version_info >= (3, 12) or self.pip_imported_during_build():158 return159 clear_distutils()160 self.spec_for_distutils = lambda: None161 162 @classmethod163 def pip_imported_during_build(cls):164 """165 Detect if pip is being imported in a build script. Ref #2355.166 """167 import traceback168 169 return any(170 cls.frame_file_is_setup(frame) for frame, line in traceback.walk_stack(None)171 )172 173 @staticmethod174 def frame_file_is_setup(frame):175 """176 Return True if the indicated frame suggests a setup.py file.177 """178 # some frames may not have __file__ (#2940)179 return frame.f_globals.get('__file__', '').endswith('setup.py')180 181 def spec_for_sensitive_tests(self):182 """183 Ensure stdlib distutils when running select tests under CPython.184 185 python/cpython#91169186 """187 clear_distutils()188 self.spec_for_distutils = lambda: None189 190 sensitive_tests = (191 [192 'test.test_distutils',193 'test.test_peg_generator',194 'test.test_importlib',195 ]196 if sys.version_info < (3, 10)197 else [198 'test.test_distutils',199 ]200 )201 202 203for name in DistutilsMetaFinder.sensitive_tests:204 setattr(205 DistutilsMetaFinder,206 f'spec_for_{name}',207 DistutilsMetaFinder.spec_for_sensitive_tests,208 )209 210 211DISTUTILS_FINDER = DistutilsMetaFinder()212 213 214def add_shim():215 DISTUTILS_FINDER in sys.meta_path or insert_shim()216 217 218class shim:219 def __enter__(self) -> None:220 insert_shim()221 222 def __exit__(self, exc: object, value: object, tb: object) -> None:223 _remove_shim()224 225 226def insert_shim():227 sys.meta_path.insert(0, DISTUTILS_FINDER)228 229 230def _remove_shim():231 try:232 sys.meta_path.remove(DISTUTILS_FINDER)233 except ValueError:234 pass235 236 237if sys.version_info < (3, 12):238 # DistutilsMetaFinder can only be disabled in Python < 3.12 (PEP 632)239 remove_shim = _remove_shim240 