[bumpversion]
-current_version = 51.3.3
+current_version = 52.0.0
commit = True
tag = True
+v52.0.0
+-------
+
+
+Breaking Changes
+^^^^^^^^^^^^^^^^
+* #2537: Remove fallback support for fetch_build_eggs using easy_install. Now pip is required for setup_requires to succeed.
+* #2544: Removed 'easy_install' top-level model (runpy entry point) and 'easy_install' console script.
+* #2545: Removed support for eggsecutables.
+
+Changes
+^^^^^^^
+* #2459: Tests now run in parallel via pytest-xdist, completing in about half the time. Special thanks to :user:`webknjaz` for hard work implementing test isolation. To run without parallelization, disable the plugin with ``tox -- -p no:xdist``.
+
+
v51.3.3
-------
+++ /dev/null
-"""Run the EasyInstall command"""
-
-if __name__ == '__main__':
- from setuptools.command.easy_install import main
- main()
[pytest.enabler.cov]
addopts = "--cov"
+[pytest.enabler.xdist]
+addopts = "-n auto"
+
[tool.towncrier]
package = "setuptools"
package_dir = "setuptools"
license_files =
LICENSE
name = setuptools
-version = 51.3.3
+version = 52.0.0
author = Python Packaging Authority
author_email = distutils-sig@python.org
description = Easily download, build, install, upgrade, and uninstall Python packages
[options]
packages = find_namespace:
-py_modules = easy_install
# disabled as it causes tests to be included #2505
# include_package_data = true
python_requires = >=3.6
pytest-black >= 0.3.7; python_implementation != "PyPy"
pytest-cov
pytest-mypy; python_implementation != "PyPy"
- pytest-enabler
+ pytest-enabler >= 1.0.1
# local
mock
paver
pip>=19.1 # For proper file:// URLs support.
jaraco.envs
+ pytest-xdist
docs =
# Keep these in sync with docs/requirements.txt
return command_ns['__all__']
-def _gen_console_scripts():
- yield "easy_install = setuptools.command.easy_install:main"
-
- # Gentoo distributions manage the python-version-specific scripts
- # themselves, so those platforms define an environment variable to
- # suppress the creation of the version-specific scripts.
- var_names = (
- 'SETUPTOOLS_DISABLE_VERSIONED_EASY_INSTALL_SCRIPT',
- 'DISTRIBUTE_DISABLE_VERSIONED_EASY_INSTALL_SCRIPT',
- )
- if any(os.environ.get(var) not in (None, "", "0") for var in var_names):
- return
- tmpl = "easy_install-{shortver} = setuptools.command.easy_install:main"
- yield tmpl.format(shortver='{}.{}'.format(*sys.version_info))
-
-
package_data = dict(
setuptools=['script (dev).tmpl', 'script.tmpl', 'site-patch.py'],
)
"depends.txt = setuptools.command.egg_info:warn_depends_obsolete",
"dependency_links.txt = setuptools.command.egg_info:overwrite_arg",
],
- "console_scripts": list(_gen_console_scripts()),
- "setuptools.installation":
- ['eggsecutable = setuptools.command.easy_install:bootstrap'],
},
dependency_links=[
pypi_link(
Build .egg distributions"""
-from distutils.errors import DistutilsSetupError
from distutils.dir_util import remove_tree, mkpath
from distutils import log
from types import CodeType
import re
import textwrap
import marshal
-import warnings
from pkg_resources import get_build_platform, Distribution, ensure_directory
-from pkg_resources import EntryPoint
from setuptools.extension import Library
-from setuptools import Command, SetuptoolsDeprecationWarning
+from setuptools import Command
from sysconfig import get_path, get_python_version
return analyze_egg(self.bdist_dir, self.stubs)
def gen_header(self):
- epm = EntryPoint.parse_map(self.distribution.entry_points or '')
- ep = epm.get('setuptools.installation', {}).get('eggsecutable')
- if ep is None:
- return 'w' # not an eggsecutable, do it the usual way.
-
- warnings.warn(
- "Eggsecutables are deprecated and will be removed in a future "
- "version.",
- SetuptoolsDeprecationWarning
- )
-
- if not ep.attrs or ep.extras:
- raise DistutilsSetupError(
- "eggsecutable entry point (%r) cannot have 'extras' "
- "or refer to a module" % (ep,)
- )
-
- pyver = '{}.{}'.format(*sys.version_info)
- pkg = ep.module_name
- full = '.'.join(ep.attrs)
- base = ep.attrs[0]
- basename = os.path.basename(self.egg_output)
-
- header = (
- "#!/bin/sh\n"
- 'if [ `basename $0` = "%(basename)s" ]\n'
- 'then exec python%(pyver)s -c "'
- "import sys, os; sys.path.insert(0, os.path.abspath('$0')); "
- "from %(pkg)s import %(base)s; sys.exit(%(full)s())"
- '" "$@"\n'
- 'else\n'
- ' echo $0 is not the correct name for this egg file.\n'
- ' echo Please rename it back to %(basename)s and try again.\n'
- ' exec false\n'
- 'fi\n'
- ) % locals()
-
- if not self.dry_run:
- mkpath(os.path.dirname(self.egg_output), dry_run=self.dry_run)
- f = open(self.egg_output, 'w')
- f.write(header)
- f.close()
- return 'a'
+ return 'w'
def copy_metadata_to(self, target_dir):
"Copy metadata (egg info) to the target_dir"
__all__ = [
'samefile', 'easy_install', 'PthDistributions', 'extract_wininst_cfg',
- 'main', 'get_exe_prefixes',
+ 'get_exe_prefixes',
]
return tmp
-def bootstrap():
- # This function is called when setuptools*.egg is run using /bin/sh
- import setuptools
-
- argv0 = os.path.dirname(setuptools.__path__[0])
- sys.argv[0] = argv0
- sys.argv.append(argv0)
- main()
-
-
-def main(argv=None, **kw):
- from setuptools import setup
- from setuptools.dist import Distribution
-
- class DistributionWithoutHelpCommands(Distribution):
- common_usage = ""
-
- def _show_help(self, *args, **kw):
- with _patch_usage():
- Distribution._show_help(self, *args, **kw)
-
- if argv is None:
- argv = sys.argv[1:]
-
- with _patch_usage():
- setup(
- script_args=['-q', 'easy_install', '-v'] + argv,
- script_name=sys.argv[0] or 'easy_install',
- distclass=DistributionWithoutHelpCommands,
- **kw
- )
-
-
-@contextlib.contextmanager
-def _patch_usage():
- import distutils.core
- USAGE = textwrap.dedent("""
- usage: %(script)s [options] requirement_or_url ...
- or: %(script)s --help
- """).lstrip()
-
- def gen_usage(script_name):
- return USAGE % dict(
- script=os.path.basename(script_name),
- )
-
- saved = distutils.core.gen_usage
- distutils.core.gen_usage = gen_usage
- try:
- yield
- finally:
- distutils.core.gen_usage = saved
-
-
class EasyInstallDeprecationWarning(SetuptoolsDeprecationWarning):
"""
Warning for EasyInstall deprecations, bypassing suppression.
from distutils.errors import DistutilsError
import pkg_resources
-from setuptools.command.easy_install import easy_install
from setuptools.wheel import Wheel
return find_links
-def _legacy_fetch_build_egg(dist, req):
- """Fetch an egg needed for building.
-
- Legacy path using EasyInstall.
- """
- tmp_dist = dist.__class__({'script_args': ['easy_install']})
- opts = tmp_dist.get_option_dict('easy_install')
- opts.clear()
- opts.update(
- (k, v)
- for k, v in dist.get_option_dict('easy_install').items()
- if k in (
- # don't use any other settings
- 'find_links', 'site_dirs', 'index_url',
- 'optimize', 'site_dirs', 'allow_hosts',
- ))
- if dist.dependency_links:
- links = dist.dependency_links[:]
- if 'find_links' in opts:
- links = _fixup_find_links(opts['find_links'][1]) + links
- opts['find_links'] = ('setup', links)
- install_dir = dist.get_egg_cache_dir()
- cmd = easy_install(
- tmp_dist, args=["x"], install_dir=install_dir,
- exclude_scripts=True,
- always_copy=False, build_directory=None, editable=False,
- upgrade=False, multi_version=True, no_report=True, user=False
- )
- cmd.ensure_finalized()
- return cmd.easy_install(req)
-
-
def fetch_build_egg(dist, req): # noqa: C901 # is too complex (16) # FIXME
"""Fetch an egg needed for building.
Use pip/wheel to fetch/build a wheel."""
- # Check pip is available.
- try:
- pkg_resources.get_distribution('pip')
- except pkg_resources.DistributionNotFound:
- dist.announce(
- 'WARNING: The pip package is not available, falling back '
- 'to EasyInstall for handling setup_requires/test_requires; '
- 'this is deprecated and will be removed in a future version.',
- log.WARN
- )
- return _legacy_fetch_build_egg(dist, req)
- # Warn if wheel is not.
+ # Warn if wheel is not available
try:
pkg_resources.get_distribution('wheel')
except pkg_resources.DistributionNotFound:
+import contextlib
+import sys
+import shutil
+
import pytest
from . import contexts
def tmpdir_cwd(tmpdir):
with tmpdir.as_cwd() as orig:
yield orig
+
+
+@pytest.fixture
+def tmp_src(request, tmp_path):
+ """Make a copy of the source dir under `$tmp/src`.
+
+ This fixture is useful whenever it's necessary to run `setup.py`
+ or `pip install` against the source directory when there's no
+ control over the number of simultaneous invocations. Such
+ concurrent runs create and delete directories with the same names
+ under the target directory and so they influence each other's runs
+ when they are not being executed sequentially.
+ """
+ tmp_src_path = tmp_path / 'src'
+ shutil.copytree(request.config.rootdir, tmp_src_path)
+ return tmp_src_path
+
+
+@pytest.fixture(autouse=True, scope="session")
+def workaround_xdist_376(request):
+ """
+ Workaround pytest-dev/pytest-xdist#376
+
+ ``pytest-xdist`` tends to inject '' into ``sys.path``,
+ which may break certain isolation expectations.
+ Remove the entry so the import
+ machinery behaves the same irrespective of xdist.
+ """
+ if not request.config.pluginmanager.has_plugin('xdist'):
+ return
+
+ with contextlib.suppress(ValueError):
+ sys.path.remove('')
import pytest
from setuptools.dist import Distribution
-from setuptools import SetuptoolsDeprecationWarning
from . import contexts
names = list(zi.filename for zi in zip.filelist)
assert 'hi.pyc' in names
assert 'hi.py' not in names
-
- def test_eggsecutable_warning(self, setup_context, user_override):
- dist = Distribution(dict(
- script_name='setup.py',
- script_args=['bdist_egg'],
- name='foo',
- py_modules=['hi'],
- entry_points={
- 'setuptools.installation':
- ['eggsecutable = my_package.some_module:main_func']},
- ))
- dist.parse_command_line()
- with pytest.warns(SetuptoolsDeprecationWarning):
- dist.run_commands()
class BuildBackendBase:
- def __init__(self, cwd=None, env={}, backend_name='setuptools.build_meta'):
+ def __init__(self, cwd='.', env={}, backend_name='setuptools.build_meta'):
self.cwd = cwd
self.env = env
self.backend_name = backend_name
backend_name = 'setuptools.build_meta'
def get_build_backend(self):
- return BuildBackend(cwd='.', backend_name=self.backend_name)
+ return BuildBackend(backend_name=self.backend_name)
@pytest.fixture(params=defns)
def build_backend(self, tmpdir, request):
def test_build_sdist_relative_path_import(self, tmpdir_cwd):
build_files(self._relative_path_import_files)
build_backend = self.get_build_backend()
- with pytest.raises(ImportError):
+ with pytest.raises(ImportError, match="^No module named 'hello'$"):
build_backend.build_sdist("temp")
@pytest.mark.parametrize('setup_literal, requirements', [
@pytest.fixture
-def venv(tmpdir):
+def venv(tmp_path, tmp_src):
env = VirtualEnv()
- env.root = path.Path(tmpdir)
- env.req = os.getcwd()
+ env.root = path.Path(tmp_path / 'venv')
+ env.req = str(tmp_src)
return env.create()
import mock
import time
import re
+import subprocess
import pytest
EasyInstallDeprecationWarning, ScriptWriter, PthDistributions,
WindowsScriptWriter,
)
-from setuptools.command import easy_install as easy_install_pkg
from setuptools.dist import Distribution
from pkg_resources import normalize_path, working_set
from pkg_resources import Distribution as PRDistribution
with TestSetupRequires.create_sdist() as dist_file:
with contexts.tempdir() as temp_install_dir:
with contexts.environment(PYTHONPATH=temp_install_dir):
- ei_params = [
+ cmd = [
+ sys.executable,
+ '-m', 'setup',
+ 'easy_install',
'--index-url', mock_index.url,
'--exclude-scripts',
'--install-dir', temp_install_dir,
dist_file,
]
- with sandbox.save_argv(['easy_install']):
- # attempt to install the dist. It should
- # fail because it doesn't exist.
- with pytest.raises(SystemExit):
- easy_install_pkg.main(ei_params)
+ subprocess.Popen(cmd).wait()
# there should have been one requests to the server
assert [r.path for r in mock_index.requests] == ['/does-not-exist/']
assert eggs == ['dep 1.0']
@pytest.mark.parametrize(
- 'use_legacy_installer,with_dependency_links_in_setup_py',
- itertools.product((False, True), (False, True)))
+ 'with_dependency_links_in_setup_py',
+ (False, True))
def test_setup_requires_with_find_links_in_setup_cfg(
- self, monkeypatch, use_legacy_installer,
+ self, monkeypatch,
with_dependency_links_in_setup_py):
monkeypatch.setenv(str('PIP_RETRIES'), str('0'))
monkeypatch.setenv(str('PIP_TIMEOUT'), str('0'))
fp.write(DALS(
'''
from setuptools import installer, setup
- if {use_legacy_installer}:
- installer.fetch_build_egg = installer._legacy_fetch_build_egg
setup(setup_requires='python-xlib==42',
dependency_links={dependency_links!r})
- ''').format(use_legacy_installer=use_legacy_installer, # noqa
+ ''').format(
dependency_links=dependency_links))
with open(test_setup_cfg, 'w') as fp:
fp.write(DALS(
target.mkdir()
install_cmd = [
sys.executable,
- '-m', 'easy_install',
- '-d', str(target),
+ '-m', 'pip',
+ 'install',
+ '-t', str(target),
str(pkg),
]
with test.test.paths_on_pythonpath([str(target)]):
yield venv
-SOURCE_DIR = os.path.join(os.path.dirname(__file__), '../..')
-
-
-def test_clean_env_install(bare_virtualenv):
+def test_clean_env_install(bare_virtualenv, tmp_src):
"""
Check setuptools can be installed in a clean environment.
"""
- bare_virtualenv.run(['python', 'setup.py', 'install'], cd=SOURCE_DIR)
+ bare_virtualenv.run(['python', 'setup.py', 'install'], cd=tmp_src)
def _get_pip_versions():
@pytest.mark.parametrize('pip_version', _get_pip_versions())
-def test_pip_upgrade_from_source(pip_version, virtualenv):
+def test_pip_upgrade_from_source(pip_version, tmp_src, virtualenv):
"""
Check pip can upgrade setuptools from source.
"""
virtualenv.run(' && '.join((
'python setup.py -q sdist -d {dist}',
'python setup.py -q bdist_wheel -d {dist}',
- )).format(dist=dist_dir), cd=SOURCE_DIR)
+ )).format(dist=dist_dir), cd=tmp_src)
sdist = glob.glob(os.path.join(dist_dir, '*.zip'))[0]
wheel = glob.glob(os.path.join(dist_dir, '*.whl'))[0]
# Then update from wheel.
virtualenv.run('pip install --no-cache-dir --upgrade ' + sdist)
-def _check_test_command_install_requirements(virtualenv, tmpdir):
+def _check_test_command_install_requirements(virtualenv, tmpdir, cwd):
"""
Check the test command will install all required dependencies.
"""
# Install setuptools.
- virtualenv.run('python setup.py develop', cd=SOURCE_DIR)
+ virtualenv.run('python setup.py develop', cd=cwd)
def sdist(distname, version):
dist_path = tmpdir.join('%s-%s.tar.gz' % (distname, version))
assert tmpdir.join('success').check()
-def test_test_command_install_requirements(virtualenv, tmpdir):
+def test_test_command_install_requirements(virtualenv, tmpdir, request):
# Ensure pip/wheel packages are installed.
virtualenv.run(
"python -c \"__import__('pkg_resources').require(['pip', 'wheel'])\"")
virtualenv.run("python -m pip uninstall -y setuptools")
# disable index URL so bits and bobs aren't requested from PyPI
virtualenv.env['PIP_NO_INDEX'] = '1'
- _check_test_command_install_requirements(virtualenv, tmpdir)
-
-
-def test_test_command_install_requirements_when_using_easy_install(
- bare_virtualenv, tmpdir):
- _check_test_command_install_requirements(bare_virtualenv, tmpdir)
+ _check_test_command_install_requirements(virtualenv, tmpdir, request.config.rootdir)
-def test_no_missing_dependencies(bare_virtualenv):
+def test_no_missing_dependencies(bare_virtualenv, request):
"""
Quick and dirty test to ensure all external dependencies are vendored.
"""
for command in ('upload',): # sorted(distutils.command.__all__):
- bare_virtualenv.run(
- ['python', 'setup.py', command, '-h'], cd=SOURCE_DIR)
+ cmd = ['python', 'setup.py', command, '-h']
+ bare_virtualenv.run(cmd, cd=request.config.rootdir)