[bumpversion]
-current_version = 49.2.1
+current_version = 49.3.0
commit = True
tag = True
- 3.6
- 3.5
os:
- - ubuntu-latest
+ - ubuntu-18.04
- ubuntu-16.04
- macOS-latest
# - windows-2019
# - windows-2016
include:
- # Dev versions
- - { python-version: 3.9-dev, os: ubuntu-20.04 }
+ # Pre-release versions (GH-shipped)
+ - os: ubuntu-20.04
+ python-version: 3.9.0-beta.4 - 3.9.0
+ # Pre-release versions (deadsnakes)
+ - os: ubuntu-20.04
+ python-version: 3.9-beta
+ # Dev versions (deadsnakes)
+ - os: ubuntu-20.04
+ python-version: 3.9-dev
+ - os: ubuntu-20.04
+ python-version: 3.8-dev
env:
NETWORK_REQUIRED: 1
+ PYTHON_VERSION: ${{ matrix.python-version }}
TOX_PARALLEL_NO_SPINNER: 1
TOXENV: python
+ USE_DEADSNAKES: false
steps:
- uses: actions/checkout@master
- - name: Set up Python ${{ matrix.python-version }} (deadsnakes)
+ - name: Set flag to use deadsnakes
+ if: >-
+ endsWith(env.PYTHON_VERSION, '-beta') ||
+ endsWith(env.PYTHON_VERSION, '-dev')
+ run: |
+ from __future__ import print_function
+ python_version = '${{ env.PYTHON_VERSION }}'.replace('-beta', '')
+ print('::set-env name=PYTHON_VERSION::{ver}'.format(ver=python_version))
+ print('::set-env name=USE_DEADSNAKES::true')
+ shell: python
+ - name: Set up Python ${{ env.PYTHON_VERSION }} (deadsnakes)
uses: deadsnakes/action@v1.0.0
- if: endsWith(matrix.python-version, '-dev')
+ if: fromJSON(env.USE_DEADSNAKES) && true || false
with:
- python-version: ${{ matrix.python-version }}
- - name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v1.1.1
- if: "!endsWith(matrix.python-version, '-dev')"
+ python-version: ${{ env.PYTHON_VERSION }}
+ - name: Set up Python ${{ env.PYTHON_VERSION }}
+ uses: actions/setup-python@v2.1.1
+ if: >-
+ !fromJSON(env.USE_DEADSNAKES) && true || false
with:
- python-version: ${{ matrix.python-version }}
+ python-version: ${{ env.PYTHON_VERSION }}
- name: Log Python version
run: >-
python --version
run: >-
python -m pip freeze --all
- name: Adjust TOXENV for PyPy
- if: startsWith(matrix.python-version, 'pypy')
+ if: startsWith(env.PYTHON_VERSION, 'pypy')
run: >-
- echo "::set-env name=TOXENV::${{ matrix.python-version }}"
+ echo "::set-env name=TOXENV::${{ env.PYTHON_VERSION }}"
- name: Log env vars
run: >-
env
python -m
tox
--parallel auto
+ --parallel-live
--notest
--skip-missing-interpreters false
- name: Test with tox
python -m
tox
--parallel auto
+ --parallel-live
+ --
+ -vvvvv
+v49.3.0
+-------
+
+* #2259: Setuptools now provides a .pth file (except for editable installs of setuptools) to the target environment to ensure that when enabled, the setuptools-provided distutils is preferred before setuptools has been imported (and even if setuptools is never imported). Honors the SETUPTOOLS_USE_DISTUTILS environment variable.
+
+
v49.2.1
-------
--- /dev/null
+import sys
+import os
+import re
+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__
+
+
+def do_override():
+ """
+ Ensure that the local copy of distutils is preferred over stdlib.
+
+ See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401
+ for more motivation.
+ """
+ warn_distutils_present()
+ if enabled():
+ ensure_local_distutils()
+
+
+class DistutilsMetaFinder:
+ def find_spec(self, fullname, path, target=None):
+ if path is not None or fullname != "distutils":
+ return None
+
+ return self.get_distutils_spec()
+
+ def get_distutils_spec(self):
+ import importlib.util
+
+ class DistutilsLoader(importlib.util.abc.Loader):
+
+ def create_module(self, spec):
+ return importlib.import_module('._distutils', 'setuptools')
+
+ def exec_module(self, module):
+ pass
+
+ return importlib.util.spec_from_loader('distutils', DistutilsLoader())
+
+
+DISTUTILS_FINDER = DistutilsMetaFinder()
+
+
+def add_shim():
+ sys.meta_path.insert(0, DISTUTILS_FINDER)
+
+
+def remove_shim():
+ try:
+ sys.meta_path.remove(DISTUTILS_FINDER)
+ except ValueError:
+ pass
--- /dev/null
+__import__('_distutils_hack').do_override()
'tests/manual_test.py',
'setuptools/tests/mod_with_constant.py',
'setuptools/_distutils',
- 'setuptools/distutils_patch.py',
+ '_distutils_hack',
]
[metadata]
name = setuptools
-version = 49.2.1
+version = 49.3.0
description = Easily download, build, install, upgrade, and uninstall Python packages
author = Python Packaging Authority
author_email = distutils-sig@python.org
paver; python_version>="3.6"
futures; python_version=="2.7"
pip>=19.1 # For proper file:// URLs support.
+ jaraco.envs
docs =
# Keep these in sync with docs/requirements.txt
import os
import sys
+import textwrap
import setuptools
+from setuptools.command.install import install
here = os.path.dirname(__file__)
return '/'.join(parts)
+class install_with_pth(install):
+ """
+ Custom install command to install a .pth file for distutils patching.
+
+ This hack is necessary because there's no standard way to install behavior
+ on startup (and it's debatable if there should be one). This hack (ab)uses
+ the `extra_path` behavior in Setuptools to install a `.pth` file with
+ implicit behavior on startup to give higher precedence to the local version
+ of `distutils` over the version from the standard library.
+
+ Please do not replicate this behavior.
+ """
+
+ _pth_name = 'distutils-precedence'
+ _pth_contents = textwrap.dedent("""
+ import os
+ enabled = os.environ.get('SETUPTOOLS_USE_DISTUTILS') == 'local'
+ enabled and __import__('_distutils_hack').add_shim()
+ """).lstrip().replace('\n', '; ')
+
+ def initialize_options(self):
+ install.initialize_options(self)
+ self.extra_path = self._pth_name, self._pth_contents
+
+ def finalize_options(self):
+ install.finalize_options(self)
+ self._restore_install_lib()
+
+ def _restore_install_lib(self):
+ """
+ Undo secondary effect of `extra_path` adding to `install_lib`
+ """
+ suffix = os.path.relpath(self.install_lib, self.install_libbase)
+
+ if suffix.strip() == self._pth_contents.strip():
+ self.install_lib = self.install_libbase
+
+
setup_params = dict(
src_root=None,
+ cmdclass={'install': install_with_pth},
package_data=package_data,
entry_points={
"distutils.commands": [
"""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_hack.override # 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
+++ /dev/null
-"""
-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()
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):
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):
yield
+_MODULES_TO_HIDE = {
+ 'setuptools',
+ 'distutils',
+ 'pkg_resources',
+ 'Cython',
+ '_distutils_hack',
+}
+
+
def _needs_hiding(mod_name):
"""
>>> _needs_hiding('setuptools')
>>> _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():
necessary to avoid issues such as #315 where setuptools upgrading itself
would fail to find a function declared in the metadata.
"""
+ _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)
paver; python_version>="3.6"
futures; python_version=="2.7"
pip>=19.1 # For proper file:// URLs support.
+jaraco.envs
--- /dev/null
+import os
+import sys
+import functools
+import subprocess
+import platform
+
+import pytest
+import jaraco.envs
+import path
+
+
+IS_PYPY = '__pypy__' in sys.builtin_module_names
+
+
+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):
+ """
+ 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)
+
+
+def find_distutils(venv, imports='distutils', env=None, **kwargs):
+ py_cmd = 'import {imports}; print(distutils.__file__)'.format(**locals())
+ cmd = ['python', '-c', py_cmd]
+ if platform.system() == 'Windows':
+ env['SYSTEMROOT'] = os.environ['SYSTEMROOT']
+ return popen_text(venv.run)(cmd, env=env, **kwargs)
+
+
+def test_distutils_stdlib(venv):
+ """
+ Ensure stdlib distutils is used when appropriate.
+ """
+ assert venv.name not in find_distutils(venv, env=dict()).split(os.sep)
+
+
+def test_distutils_local_with_setuptools(venv):
+ """
+ Ensure local distutils is used when appropriate.
+ """
+ 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('IS_PYPY', reason='pypy imports distutils on startup')
+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)