summaryrefslogtreecommitdiff
path: root/_distutils_hack
diff options
context:
space:
mode:
Diffstat (limited to '_distutils_hack')
-rw-r--r--_distutils_hack/__init__.py91
1 files changed, 73 insertions, 18 deletions
diff --git a/_distutils_hack/__init__.py b/_distutils_hack/__init__.py
index f7074162..605a6edc 100644
--- a/_distutils_hack/__init__.py
+++ b/_distutils_hack/__init__.py
@@ -1,18 +1,11 @@
+# don't import any costly modules
import sys
import os
-import re
-import importlib
-import warnings
is_pypy = '__pypy__' in sys.builtin_module_names
-warnings.filterwarnings('ignore',
- r'.+ distutils\b.+ deprecated',
- DeprecationWarning)
-
-
def warn_distutils_present():
if 'distutils' not in sys.modules:
return
@@ -20,6 +13,7 @@ def warn_distutils_present():
# PyPy for 3.6 unconditionally imports distutils, so bypass the warning
# https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250
return
+ import warnings
warnings.warn(
"Distutils was imported before Setuptools, but importing Setuptools "
"also replaces the `distutils` module in `sys.modules`. This may lead "
@@ -32,8 +26,12 @@ def warn_distutils_present():
def clear_distutils():
if 'distutils' not in sys.modules:
return
+ import warnings
warnings.warn("Setuptools is replacing distutils.")
- mods = [name for name in sys.modules if re.match(r'distutils\b', name)]
+ mods = [
+ name for name in sys.modules
+ if name == "distutils" or name.startswith("distutils.")
+ ]
for name in mods:
del sys.modules[name]
@@ -42,23 +40,24 @@ def enabled():
"""
Allow selection of distutils by environment variable.
"""
- which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'stdlib')
+ which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'local')
return which == 'local'
def ensure_local_distutils():
+ import importlib
clear_distutils()
# With the DistutilsMetaFinder in place,
# perform an import to cause distutils to be
# loaded from setuptools._distutils. Ref #2906.
- add_shim()
- importlib.import_module('distutils')
- remove_shim()
+ with shim():
+ importlib.import_module('distutils')
# check that submodules load as expected
core = importlib.import_module('distutils.core')
assert '_distutils' in core.__file__, core.__file__
+ assert 'setuptools._distutils.log' not in sys.modules
def do_override():
@@ -73,6 +72,14 @@ def do_override():
ensure_local_distutils()
+class _TrivialRe:
+ def __init__(self, *patterns):
+ self._patterns = patterns
+
+ def match(self, string):
+ return all(pat in string for pat in self._patterns)
+
+
class DistutilsMetaFinder:
def find_spec(self, fullname, path, target=None):
if path is not None:
@@ -83,18 +90,46 @@ class DistutilsMetaFinder:
return method()
def spec_for_distutils(self):
+ if self.is_cpython():
+ return
+
+ import importlib
import importlib.abc
import importlib.util
+ try:
+ mod = importlib.import_module('setuptools._distutils')
+ except Exception:
+ # There are a couple of cases where setuptools._distutils
+ # may not be present:
+ # - An older Setuptools without a local distutils is
+ # taking precedence. Ref #2957.
+ # - Path manipulation during sitecustomize removes
+ # setuptools from the path but only after the hook
+ # has been loaded. Ref #2980.
+ # In either case, fall back to stdlib behavior.
+ return
+
class DistutilsLoader(importlib.abc.Loader):
def create_module(self, spec):
- return importlib.import_module('setuptools._distutils')
+ mod.__name__ = 'distutils'
+ return mod
def exec_module(self, module):
pass
- return importlib.util.spec_from_loader('distutils', DistutilsLoader())
+ return importlib.util.spec_from_loader(
+ 'distutils', DistutilsLoader(), origin=mod.__file__
+ )
+
+ @staticmethod
+ def is_cpython():
+ """
+ Suppress supplying distutils for CPython (build and tests).
+ Ref #2965 and #3007.
+ """
+ return os.path.isfile('pybuilddir.txt')
def spec_for_pip(self):
"""
@@ -106,22 +141,42 @@ class DistutilsMetaFinder:
clear_distutils()
self.spec_for_distutils = lambda: None
- @staticmethod
- def pip_imported_during_build():
+ @classmethod
+ def pip_imported_during_build(cls):
"""
Detect if pip is being imported in a build script. Ref #2355.
"""
import traceback
return any(
- frame.f_globals['__file__'].endswith('setup.py')
+ cls.frame_file_is_setup(frame)
for frame, line in traceback.walk_stack(None)
)
+ @staticmethod
+ def frame_file_is_setup(frame):
+ """
+ Return True if the indicated frame suggests a setup.py file.
+ """
+ # some frames may not have __file__ (#2940)
+ return frame.f_globals.get('__file__', '').endswith('setup.py')
+
DISTUTILS_FINDER = DistutilsMetaFinder()
def add_shim():
+ DISTUTILS_FINDER in sys.meta_path or insert_shim()
+
+
+class shim:
+ def __enter__(self):
+ insert_shim()
+
+ def __exit__(self, exc, value, tb):
+ remove_shim()
+
+
+def insert_shim():
sys.meta_path.insert(0, DISTUTILS_FINDER)