From 36df1d74933ef18a4099f4e82dbe55c00e45709e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 12 Jul 2020 11:39:51 -0400 Subject: Warn the user when distutils is present to discourage this usage and direct users to the recommended usage. Closes #2230. --- setuptools/distutils_patch.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'setuptools') diff --git a/setuptools/distutils_patch.py b/setuptools/distutils_patch.py index c5f273dd..33f1e7f9 100644 --- a/setuptools/distutils_patch.py +++ b/setuptools/distutils_patch.py @@ -12,10 +12,26 @@ import importlib import warnings +is_pypy = '__pypy__' in sys.builtin_module_names + + +def warn_distutils_present(): + if 'distutils' not in sys.modules: + return + if is_pypy and sys.version_info < (3, 7): + # 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 + warnings.warn( + "Distutils was imported before Setuptools. This usage is discouraged " + "and may exhibit undesirable behaviors or errors. Please use " + "Setuptools' objects directly or at least import Setuptools first.") + + def clear_distutils(): if 'distutils' not in sys.modules: return - warnings.warn("Setuptools is replacing distutils") + warnings.warn("Setuptools is replacing distutils.") mods = [name for name in sys.modules if re.match(r'distutils\b', name)] for name in mods: del sys.modules[name] @@ -40,5 +56,6 @@ def ensure_local_distutils(): assert '_distutils' in core.__file__, core.__file__ +warn_distutils_present() if enabled(): ensure_local_distutils() -- cgit v1.2.1 From 03b36b5dc594bbe239d0ad66dc43ea7d1832072c Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Mon, 13 Jul 2020 12:02:41 -0400 Subject: Remove warnings With the new `.pth` file, these warnings are no longer necessary. --- setuptools/distutils_patch.py | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) (limited to 'setuptools') diff --git a/setuptools/distutils_patch.py b/setuptools/distutils_patch.py index 33f1e7f9..d01a1a1b 100644 --- a/setuptools/distutils_patch.py +++ b/setuptools/distutils_patch.py @@ -12,26 +12,10 @@ import importlib import warnings -is_pypy = '__pypy__' in sys.builtin_module_names - - -def warn_distutils_present(): - if 'distutils' not in sys.modules: - return - if is_pypy and sys.version_info < (3, 7): - # 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 - warnings.warn( - "Distutils was imported before Setuptools. This usage is discouraged " - "and may exhibit undesirable behaviors or errors. Please use " - "Setuptools' objects directly or at least import Setuptools first.") - - def clear_distutils(): if 'distutils' not in sys.modules: return - warnings.warn("Setuptools is replacing distutils.") + mods = [name for name in sys.modules if re.match(r'distutils\b', name)] for name in mods: del sys.modules[name] @@ -56,6 +40,5 @@ def ensure_local_distutils(): assert '_distutils' in core.__file__, core.__file__ -warn_distutils_present() if enabled(): ensure_local_distutils() -- cgit v1.2.1 From 89e9d3c8910c3f419eb9f1c2758a748c6938655b Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Mon, 13 Jul 2020 14:43:23 -0400 Subject: Adjust distutils shim when removing _distutils_importer --- setuptools/sandbox.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) (limited to 'setuptools') diff --git a/setuptools/sandbox.py b/setuptools/sandbox.py index 93ae8eb4..342a713f 100644 --- a/setuptools/sandbox.py +++ b/setuptools/sandbox.py @@ -185,8 +185,8 @@ def setup_context(setup_dir): temp_dir = os.path.join(setup_dir, 'temp') with save_pkg_resources_state(): with save_modules(): - hide_setuptools() with save_path(): + hide_setuptools() with save_argv(): with override_temp(temp_dir): with pushd(setup_dir): @@ -195,6 +195,15 @@ def setup_context(setup_dir): yield +_MODULES_TO_HIDE = { + 'setuptools', + 'distutils', + 'pkg_resources', + 'Cython', + '_distutils_importer', +} + + def _needs_hiding(mod_name): """ >>> _needs_hiding('setuptools') @@ -212,8 +221,8 @@ def _needs_hiding(mod_name): >>> _needs_hiding('Cython') True """ - pattern = re.compile(r'(setuptools|pkg_resources|distutils|Cython)(\.|$)') - return bool(pattern.match(mod_name)) + base_module = mod_name.split('.', 1)[0] + return base_module in _MODULES_TO_HIDE def hide_setuptools(): @@ -223,6 +232,10 @@ def hide_setuptools(): necessary to avoid issues such as #315 where setuptools upgrading itself would fail to find a function declared in the metadata. """ + _distutils_importer = sys.modules.get('_distutils_importer', None) + if _distutils_importer is not None: + _distutils_importer.remove_shim() + modules = filter(_needs_hiding, sys.modules) _clear_modules(modules) -- cgit v1.2.1 From 642604f82c01175f2ad285800d969ff521495af0 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Mon, 13 Jul 2020 14:44:12 -0400 Subject: Clean up setuptools/__init__.py imports This puts non-distutils imports first and removes one unused import. --- setuptools/__init__.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'setuptools') diff --git a/setuptools/__init__.py b/setuptools/__init__.py index 83882511..d9740403 100644 --- a/setuptools/__init__.py +++ b/setuptools/__init__.py @@ -1,17 +1,16 @@ """Extensions to the 'distutils' for large or complex distributions""" -import os +from fnmatch import fnmatchcase import functools +import os +import re # Disabled for now due to: #2228, #2230 import setuptools.distutils_patch # noqa: F401 import distutils.core -import distutils.filelist -import re from distutils.errors import DistutilsOptionError from distutils.util import convert_path -from fnmatch import fnmatchcase from ._deprecation_warning import SetuptoolsDeprecationWarning -- cgit v1.2.1 From 85a0a9026d1b40448d1757ca6cd75e5cc2c50fc6 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Mon, 13 Jul 2020 15:36:39 -0400 Subject: Revert "Remove warnings" This reverts commit 30b883f0b8071a3b1472c884574f38ce0128e457. --- setuptools/distutils_patch.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'setuptools') diff --git a/setuptools/distutils_patch.py b/setuptools/distutils_patch.py index d01a1a1b..33f1e7f9 100644 --- a/setuptools/distutils_patch.py +++ b/setuptools/distutils_patch.py @@ -12,10 +12,26 @@ import importlib import warnings -def clear_distutils(): +is_pypy = '__pypy__' in sys.builtin_module_names + + +def warn_distutils_present(): if 'distutils' not in sys.modules: return + if is_pypy and sys.version_info < (3, 7): + # 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 + warnings.warn( + "Distutils was imported before Setuptools. This usage is discouraged " + "and may exhibit undesirable behaviors or errors. Please use " + "Setuptools' objects directly or at least import Setuptools first.") + +def clear_distutils(): + if 'distutils' not in sys.modules: + return + warnings.warn("Setuptools is replacing distutils.") mods = [name for name in sys.modules if re.match(r'distutils\b', name)] for name in mods: del sys.modules[name] @@ -40,5 +56,6 @@ def ensure_local_distutils(): assert '_distutils' in core.__file__, core.__file__ +warn_distutils_present() if enabled(): ensure_local_distutils() -- cgit v1.2.1 From e371422476f51a83d27d70dc45bbfba1544aad55 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 19 Jul 2020 21:36:33 -0400 Subject: Consolidate distutils importing hacks into _distutils_importer package. Generate distutils-precedence.pth inline. --- setuptools/__init__.py | 3 +-- setuptools/distutils_patch.py | 61 ------------------------------------------- 2 files changed, 1 insertion(+), 63 deletions(-) delete mode 100644 setuptools/distutils_patch.py (limited to 'setuptools') diff --git a/setuptools/__init__.py b/setuptools/__init__.py index d9740403..80b287b4 100644 --- a/setuptools/__init__.py +++ b/setuptools/__init__.py @@ -5,8 +5,7 @@ import functools import os import re -# Disabled for now due to: #2228, #2230 -import setuptools.distutils_patch # noqa: F401 +import _distutils_importer.override # noqa: F401 import distutils.core from distutils.errors import DistutilsOptionError diff --git a/setuptools/distutils_patch.py b/setuptools/distutils_patch.py deleted file mode 100644 index 33f1e7f9..00000000 --- a/setuptools/distutils_patch.py +++ /dev/null @@ -1,61 +0,0 @@ -""" -Ensure that the local copy of distutils is preferred over stdlib. - -See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401 -for more motivation. -""" - -import sys -import re -import os -import importlib -import warnings - - -is_pypy = '__pypy__' in sys.builtin_module_names - - -def warn_distutils_present(): - if 'distutils' not in sys.modules: - return - if is_pypy and sys.version_info < (3, 7): - # 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 - warnings.warn( - "Distutils was imported before Setuptools. This usage is discouraged " - "and may exhibit undesirable behaviors or errors. Please use " - "Setuptools' objects directly or at least import Setuptools first.") - - -def clear_distutils(): - if 'distutils' not in sys.modules: - return - warnings.warn("Setuptools is replacing distutils.") - mods = [name for name in sys.modules if re.match(r'distutils\b', name)] - for name in mods: - del sys.modules[name] - - -def enabled(): - """ - Allow selection of distutils by environment variable. - """ - which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'stdlib') - return which == 'local' - - -def ensure_local_distutils(): - clear_distutils() - distutils = importlib.import_module('setuptools._distutils') - distutils.__name__ = 'distutils' - sys.modules['distutils'] = distutils - - # sanity check that submodules load as expected - core = importlib.import_module('distutils.core') - assert '_distutils' in core.__file__, core.__file__ - - -warn_distutils_present() -if enabled(): - ensure_local_distutils() -- cgit v1.2.1 From dcc71f773576c19a3658735879893515b056ece5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 26 Jul 2020 10:35:02 -0400 Subject: Rename _distutils_importer to _distutils_hack, as it supplies more than just an importer. --- setuptools/__init__.py | 2 +- setuptools/sandbox.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'setuptools') diff --git a/setuptools/__init__.py b/setuptools/__init__.py index 80b287b4..99094230 100644 --- a/setuptools/__init__.py +++ b/setuptools/__init__.py @@ -5,7 +5,7 @@ import functools import os import re -import _distutils_importer.override # noqa: F401 +import _distutils_hack.override # noqa: F401 import distutils.core from distutils.errors import DistutilsOptionError diff --git a/setuptools/sandbox.py b/setuptools/sandbox.py index 342a713f..24a36080 100644 --- a/setuptools/sandbox.py +++ b/setuptools/sandbox.py @@ -200,7 +200,7 @@ _MODULES_TO_HIDE = { 'distutils', 'pkg_resources', 'Cython', - '_distutils_importer', + '_distutils_hack', } @@ -232,9 +232,9 @@ def hide_setuptools(): necessary to avoid issues such as #315 where setuptools upgrading itself would fail to find a function declared in the metadata. """ - _distutils_importer = sys.modules.get('_distutils_importer', None) - if _distutils_importer is not None: - _distutils_importer.remove_shim() + _distutils_hack = sys.modules.get('_distutils_hack', None) + if _distutils_hack is not None: + _distutils_hack.remove_shim() modules = filter(_needs_hiding, sys.modules) _clear_modules(modules) -- cgit v1.2.1 From 6af765910c71ead64b0093ac51db7592d1ad238c Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Sat, 1 Aug 2020 12:20:35 +1000 Subject: Fixed typo --- setuptools/monkey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'setuptools') diff --git a/setuptools/monkey.py b/setuptools/monkey.py index 3c77f8cf..e5f1377b 100644 --- a/setuptools/monkey.py +++ b/setuptools/monkey.py @@ -138,7 +138,7 @@ def patch_for_msvc_specialized_compiler(): msvc = import_module('setuptools.msvc') if platform.system() != 'Windows': - # Compilers only availables on Microsoft Windows + # Compilers only available on Microsoft Windows return def patch_params(mod_name, func_name): -- cgit v1.2.1 From 7c3a3817923de67956656ea058e80669b77bed1f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 8 Aug 2020 11:44:12 -0400 Subject: Add tests capturing expected distutils behavior. Ref #417. --- setuptools/tests/test_distutils_adoption.py | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 setuptools/tests/test_distutils_adoption.py (limited to 'setuptools') diff --git a/setuptools/tests/test_distutils_adoption.py b/setuptools/tests/test_distutils_adoption.py new file mode 100644 index 00000000..f4c7072d --- /dev/null +++ b/setuptools/tests/test_distutils_adoption.py @@ -0,0 +1,37 @@ +import os +import pytest + + +@pytest.fixture +def env(virtualenv): + virtualenv.run(['pip', 'uninstall', '-y', 'setuptools']) + virtualenv.run(['pip', 'install', os.getcwd()]) + return virtualenv + + +def find_distutils(env, imports='distutils'): + py_cmd = 'import {imports}; print(distutils.__file__)'.format(**locals()) + cmd = ['python', '-c', py_cmd] + return env.run(cmd, capture=True, text=True) + + +def test_distutils_stdlib(env): + """ + Ensure stdlib distutils is used when appropriate. + """ + assert '.env' not in find_distutils(env).split(os.sep) + + +def test_distutils_local_with_setuptools(env): + """ + Ensure local distutils is used when appropriate. + """ + env.env.update(SETUPTOOLS_USE_DISTUTILS='local') + loc = find_distutils(env, imports='setuptools, distutils') + assert '.env' in loc.split(os.sep) + + +@pytest.mark.xfail(reason="#2259") +def test_distutils_local(env): + env.env.update(SETUPTOOLS_USE_DISTUTILS='local') + assert '.env' in find_distutils(env).split(os.sep) -- cgit v1.2.1 From 6281bd0917882b930d5923d76cc72ff4ee5a7695 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 8 Aug 2020 12:15:13 -0400 Subject: Support Python 3.5 and 3.6 in the tests. --- setuptools/tests/test_distutils_adoption.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'setuptools') diff --git a/setuptools/tests/test_distutils_adoption.py b/setuptools/tests/test_distutils_adoption.py index f4c7072d..a945a69f 100644 --- a/setuptools/tests/test_distutils_adoption.py +++ b/setuptools/tests/test_distutils_adoption.py @@ -1,7 +1,18 @@ import os +import sys +import functools + import pytest +def popen_text(call): + """ + Augment the Popen call with the parameters to ensure unicode text. + """ + return functools.partial(call, universal_newlines=True) \ + if sys.version_info < (3, 7) else functools.partial(call, text=True) + + @pytest.fixture def env(virtualenv): virtualenv.run(['pip', 'uninstall', '-y', 'setuptools']) @@ -12,7 +23,7 @@ def env(virtualenv): def find_distutils(env, imports='distutils'): py_cmd = 'import {imports}; print(distutils.__file__)'.format(**locals()) cmd = ['python', '-c', py_cmd] - return env.run(cmd, capture=True, text=True) + return popen_text(env.run)(cmd, capture=True) def test_distutils_stdlib(env): -- cgit v1.2.1 From 41deeb76fc88382f27b3d37cce75eda2c54cbc2a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 8 Aug 2020 15:53:41 -0400 Subject: Bypass pytest-virtualenv due to bugs in 'run' command. Instead, use jaraco.envs.VirtualEnv and build a bespoke fixture with a run command. --- setuptools/tests/requirements.txt | 1 + setuptools/tests/test_distutils_adoption.py | 48 ++++++++++++++++++----------- 2 files changed, 31 insertions(+), 18 deletions(-) (limited to 'setuptools') diff --git a/setuptools/tests/requirements.txt b/setuptools/tests/requirements.txt index 19bf5aef..d0d07f70 100644 --- a/setuptools/tests/requirements.txt +++ b/setuptools/tests/requirements.txt @@ -10,3 +10,4 @@ pytest-cov>=2.5.1 paver; python_version>="3.6" futures; python_version=="2.7" pip>=19.1 # For proper file:// URLs support. +jaraco.envs diff --git a/setuptools/tests/test_distutils_adoption.py b/setuptools/tests/test_distutils_adoption.py index a945a69f..476b0a9e 100644 --- a/setuptools/tests/test_distutils_adoption.py +++ b/setuptools/tests/test_distutils_adoption.py @@ -1,8 +1,27 @@ import os import sys import functools +import subprocess import pytest +import jaraco.envs +import path + + +class VirtualEnv(jaraco.envs.VirtualEnv): + name = '.env' + + def run(self, cmd, *args, **kwargs): + cmd = [self.exe(cmd[0])] + cmd[1:] + return subprocess.check_output(cmd, *args, cwd=self.root, **kwargs) + + +@pytest.fixture +def venv(tmpdir): + env = VirtualEnv() + env.root = path.Path(tmpdir) + env.req = os.getcwd() + return env.create() def popen_text(call): @@ -13,36 +32,29 @@ def popen_text(call): if sys.version_info < (3, 7) else functools.partial(call, text=True) -@pytest.fixture -def env(virtualenv): - virtualenv.run(['pip', 'uninstall', '-y', 'setuptools']) - virtualenv.run(['pip', 'install', os.getcwd()]) - return virtualenv - - -def find_distutils(env, imports='distutils'): +def find_distutils(venv, imports='distutils', **kwargs): py_cmd = 'import {imports}; print(distutils.__file__)'.format(**locals()) cmd = ['python', '-c', py_cmd] - return popen_text(env.run)(cmd, capture=True) + return popen_text(venv.run)(cmd, **kwargs) -def test_distutils_stdlib(env): +def test_distutils_stdlib(venv): """ Ensure stdlib distutils is used when appropriate. """ - assert '.env' not in find_distutils(env).split(os.sep) + assert venv.name not in find_distutils(venv, env=dict()).split(os.sep) -def test_distutils_local_with_setuptools(env): +def test_distutils_local_with_setuptools(venv): """ Ensure local distutils is used when appropriate. """ - env.env.update(SETUPTOOLS_USE_DISTUTILS='local') - loc = find_distutils(env, imports='setuptools, distutils') - assert '.env' in loc.split(os.sep) + env = dict(SETUPTOOLS_USE_DISTUTILS='local') + loc = find_distutils(venv, imports='setuptools, distutils', env=env) + assert venv.name in loc.split(os.sep) @pytest.mark.xfail(reason="#2259") -def test_distutils_local(env): - env.env.update(SETUPTOOLS_USE_DISTUTILS='local') - assert '.env' in find_distutils(env).split(os.sep) +def test_distutils_local(venv): + env = dict(SETUPTOOLS_USE_DISTUTILS='local') + assert venv.name in find_distutils(venv, env=env).split(os.sep) -- cgit v1.2.1 From 47ae38fd6f233e6423404bfebfd24d3b98fb897c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 8 Aug 2020 17:01:41 -0400 Subject: On Windows, SYSTEMROOT must be supplied. --- setuptools/tests/test_distutils_adoption.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'setuptools') diff --git a/setuptools/tests/test_distutils_adoption.py b/setuptools/tests/test_distutils_adoption.py index 476b0a9e..7f28a217 100644 --- a/setuptools/tests/test_distutils_adoption.py +++ b/setuptools/tests/test_distutils_adoption.py @@ -2,6 +2,7 @@ import os import sys import functools import subprocess +import platform import pytest import jaraco.envs @@ -32,10 +33,12 @@ def popen_text(call): if sys.version_info < (3, 7) else functools.partial(call, text=True) -def find_distutils(venv, imports='distutils', **kwargs): +def find_distutils(venv, imports='distutils', env=None, **kwargs): py_cmd = 'import {imports}; print(distutils.__file__)'.format(**locals()) cmd = ['python', '-c', py_cmd] - return popen_text(venv.run)(cmd, **kwargs) + if platform.system() == 'Windows': + env['SYSTEMROOT'] = os.environ['SYSTEMROOT'] + return popen_text(venv.run)(cmd, env=env, **kwargs) def test_distutils_stdlib(venv): -- cgit v1.2.1 From 521987da809e63ee51a63aa45dbe372d40deb8f7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 8 Aug 2020 19:50:24 -0400 Subject: Remove expected fail. --- setuptools/tests/test_distutils_adoption.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'setuptools') diff --git a/setuptools/tests/test_distutils_adoption.py b/setuptools/tests/test_distutils_adoption.py index 7f28a217..bb8e34d5 100644 --- a/setuptools/tests/test_distutils_adoption.py +++ b/setuptools/tests/test_distutils_adoption.py @@ -57,7 +57,10 @@ def test_distutils_local_with_setuptools(venv): assert venv.name in loc.split(os.sep) -@pytest.mark.xfail(reason="#2259") def test_distutils_local(venv): + """ + Even without importing, the setuptools-local copy of distutils is + preferred. + """ env = dict(SETUPTOOLS_USE_DISTUTILS='local') assert venv.name in find_distutils(venv, env=env).split(os.sep) -- cgit v1.2.1 From 7cf009a7e39270e1e1d13d913e0c352fb00534c0 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 8 Aug 2020 20:58:57 -0400 Subject: Expect test to fail on PyPy due to implicit import during startup. --- setuptools/tests/test_distutils_adoption.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'setuptools') diff --git a/setuptools/tests/test_distutils_adoption.py b/setuptools/tests/test_distutils_adoption.py index bb8e34d5..daccc473 100644 --- a/setuptools/tests/test_distutils_adoption.py +++ b/setuptools/tests/test_distutils_adoption.py @@ -9,6 +9,9 @@ import jaraco.envs import path +IS_PYPY = '__pypy__' in sys.builtin_module_names + + class VirtualEnv(jaraco.envs.VirtualEnv): name = '.env' @@ -57,6 +60,7 @@ def test_distutils_local_with_setuptools(venv): assert venv.name in loc.split(os.sep) +@pytest.mark.xfail('IS_PYPY', reason='pypy imports distutils on startup') def test_distutils_local(venv): """ Even without importing, the setuptools-local copy of distutils is -- cgit v1.2.1