[bumpversion]
-current_version = 44.0.0
+current_version = 44.1.0
commit = True
tag = True
[bumpversion:file:setup.cfg]
-
if: tag IS present
script: tox -e release
after_success: skip
+ allow_failures:
+ # suppress failures due to pypa/setuptools#2000
+ - python: pypy3
cache: pip
- pip freeze --all
- env
-# update egg_info based on setup.py in checkout
-- python bootstrap.py
- "! grep pyc setuptools.egg-info/SOURCES.txt"
script:
+v44.1.0
+-------
+
+* #1704: Set sys.argv[0] in setup script run by build_meta.__legacy__
+* #1959: Fix for Python 4: replace unsafe six.PY3 with six.PY2
+* #1994: Fixed a bug in the "setuptools.finalize_distribution_options" hook that lead to ignoring the order attribute of entry points managed by this hook.
+
+
v44.0.0
-------
- python -m pip install --disable-pip-version-check --upgrade pip setuptools wheel
- pip install --upgrade tox tox-venv virtualenv
- pip freeze --all
- - python bootstrap.py
- tox -- --cov
after_test:
--- /dev/null
+# Create the project in Azure with:
+# az devops project create --name $name --organization https://dev.azure.com/$org/ --visibility public
+# then configure the pipelines (through web UI)
+
+trigger:
+ branches:
+ include:
+ - '*'
+ tags:
+ include:
+ - '*'
+
+pool:
+ vmimage: 'Ubuntu-18.04'
+
+variables:
+- group: Azure secrets
+
+stages:
+- stage: Test
+ jobs:
+
+ - job: 'Test'
+ strategy:
+ matrix:
+ Python36:
+ python.version: '3.6'
+ Python38:
+ python.version: '3.8'
+ maxParallel: 4
+
+ steps:
+ - task: UsePythonVersion@0
+ inputs:
+ versionSpec: '$(python.version)'
+ architecture: 'x64'
+
+ - script: python -m pip install tox
+ displayName: 'Install tox'
+
+ - script: |
+ tox -- --junit-xml=test-results.xml
+ displayName: 'run tests'
+
+ - task: PublishTestResults@2
+ inputs:
+ testResultsFiles: '**/test-results.xml'
+ testRunTitle: 'Python $(python.version)'
+ condition: succeededOrFailed()
+
+- stage: Publish
+ dependsOn: Test
+ jobs:
+ - job: 'Publish'
+
+ steps:
+ - task: UsePythonVersion@0
+ inputs:
+ versionSpec: '3.8'
+ architecture: 'x64'
+
+ - script: python -m pip install tox
+ displayName: 'Install tox'
+
+ - script: |
+ tox -e release
+ env:
+ TWINE_PASSWORD: $(PyPI-token)
+ displayName: 'publish to PyPI'
+
+ condition: contains(variables['Build.SourceBranch'], 'tags')
# hack to run the bootstrap script so that jaraco.packaging.sphinx
# can invoke setup.py
'READTHEDOCS' in os.environ and subprocess.check_call(
- [sys.executable, 'bootstrap.py'],
+ [sys.executable, '-m', 'bootstrap'],
cwd=os.path.join(os.path.dirname(__file__), os.path.pardir),
)
Testing
-------
-The primary tests are run using tox. To run the tests, first create the metadata
-needed to run the tests::
-
- $ python bootstrap.py
-
-Then make sure you have tox installed, and invoke it::
+The primary tests are run using tox. Make sure you have tox installed,
+and invoke it::
$ tox
===============
In order to allow for rapid, predictable releases, Setuptools uses a
-mechanical technique for releases, enacted by Travis following a
-successful build of a tagged release per
-`PyPI deployment <https://docs.travis-ci.com/user/deployment/pypi>`_.
-
-Prior to cutting a release, please use `towncrier`_ to update
-``CHANGES.rst`` to summarize the changes since the last release.
-To update the changelog:
-
-1. Install towncrier via ``pip install towncrier`` if not already installed.
-2. Preview the new ``CHANGES.rst`` entry by running
- ``towncrier --draft --version {new.version.number}`` (enter the desired
- version number for the next release). If any changes are needed, make
- them and generate a new preview until the output is acceptable. Run
- ``git add`` for any modified files.
-3. Run ``towncrier --version {new.version.number}`` to stage the changelog
- updates in git.
-4. Verify that there are no remaining ``changelog.d/*.rst`` files. If a
- file was named incorrectly, it may be ignored by towncrier.
-5. Review the updated ``CHANGES.rst`` file. If any changes are needed,
- make the edits and stage them via ``git add CHANGES.rst``.
-
-Once the changelog edits are staged and ready to commit, cut a release by
-installing and running ``bump2version --allow-dirty {part}`` where ``part``
-is major, minor, or patch based on the scope of the changes in the
-release. Then, push the commits to the master branch::
-
- $ git push origin master
- $ git push --tags
-
-If tests pass, the release will be uploaded to PyPI (from the Python 3.6
-tests).
-
-.. _towncrier: https://pypi.org/project/towncrier/
+mechanical technique for releases, enacted on tagged commits by
+continuous integration.
+
+To finalize a release, run ``tox -e finalize``, review, then push
+the changes.
+
+If tests pass, the release will be uploaded to PyPI.
Release Frequency
-----------------
[metadata]
name = setuptools
-version = 44.0.0
+version = 44.1.0
description = Easily download, build, install, upgrade, and uninstall Python packages
author = Python Packaging Authority
author_email = distutils-sig@python.org
if script_dir not in sys.path:
sys.path.insert(0, script_dir)
+ # Some setup.py scripts (e.g. in pygame and numpy) use sys.argv[0] to
+ # get the directory of the source code. They expect it to refer to the
+ # setup.py script.
+ sys_argv_0 = sys.argv[0]
+ sys.argv[0] = setup_script
+
try:
super(_BuildMetaLegacyBackend,
self).run_setup(setup_script=setup_script)
# the original path so that the path manipulation does not persist
# within the hook after run_setup is called.
sys.path[:] = sys_path
+ sys.argv[0] = sys_argv_0
# The primary backend
_BACKEND = _BuildMetaBackend()
if fullname in self.ext_map:
ext = self.ext_map[fullname]
use_abi3 = (
- six.PY3
+ not six.PY2
and getattr(ext, 'py_limited_api')
and get_abi3_suffix()
)
return path_to_setup
def install_for_development(self):
- if six.PY3 and getattr(self.distribution, 'use_2to3', False):
+ if not six.PY2 and getattr(self.distribution, 'use_2to3', False):
# If we run 2to3 we can not do this inplace:
# Ensure metadata is up-to-date
continue
if parts[0].upper() in ('PURELIB', 'PLATLIB'):
contents = z.read(name)
- if six.PY3:
+ if not six.PY2:
contents = contents.decode()
for pth in yield_lines(contents):
pth = pth.strip().replace('\\', '/')
to the file.
"""
log.info("writing %s to %s", what, filename)
- if six.PY3:
+ if not six.PY2:
data = data.encode("utf-8")
if not self.dry_run:
f = open(filename, 'wb')
manifest = open(self.manifest, 'rb')
for line in manifest:
# The manifest must contain UTF-8. See #303.
- if six.PY3:
+ if not six.PY2:
try:
line = line.decode('UTF-8')
except UnicodeDecodeError:
@contextlib.contextmanager
def project_on_sys_path(self, include_dists=[]):
- with_2to3 = six.PY3 and getattr(self.distribution, 'use_2to3', False)
+ with_2to3 = not six.PY2 and getattr(self.distribution, 'use_2to3', False)
if with_2to3:
# If we run 2to3 we can not do this inplace:
# Purge modules under test from sys.modules. The test loader will
# re-import them from the build location. Required when 2to3 is used
# with namespace packages.
- if six.PY3 and getattr(self.distribution, 'use_2to3', False):
+ if not six.PY2 and getattr(self.distribution, 'use_2to3', False):
module = self.test_suite.split('.')[0]
if module in _namespace_packages:
del_modules = []
def _encode(s):
- errors = 'surrogateescape' if six.PY3 else 'strict'
+ errors = 'strict' if six.PY2 else 'surrogateescape'
return s.encode('utf-8', errors)
# set up the authentication
credentials = _encode(self.username + ':' + self.password)
credentials = standard_b64encode(credentials)
- if six.PY3:
+ if not six.PY2:
credentials = credentials.decode('ascii')
auth = "Basic " + credentials
from setuptools.extern.six.moves.configparser import ConfigParser
# Ignore install directory options if we have a venv
- if six.PY3 and sys.prefix != sys.base_prefix:
+ if not six.PY2 and sys.prefix != sys.base_prefix:
ignore_options = [
'install-base', 'install-platbase', 'install-lib',
'install-platlib', 'install-purelib', 'install-headers',
with io.open(filename, encoding='utf-8') as reader:
if DEBUG:
self.announce(" reading {filename}".format(**locals()))
- (parser.read_file if six.PY3 else parser.readfp)(reader)
+ (parser.readfp if six.PY2 else parser.read_file)(reader)
for section in parser.sections():
options = parser.options(section)
opt_dict = self.get_option_dict(section)
Ref #1653
"""
- if six.PY3:
+ if not six.PY2:
return val
try:
return val.encode()
to influence the order of execution. Smaller numbers
go first and the default is 0.
"""
- hook_key = 'setuptools.finalize_distribution_options'
+ group = 'setuptools.finalize_distribution_options'
def by_order(hook):
return getattr(hook, 'order', 0)
- eps = pkg_resources.iter_entry_points(hook_key)
+ eps = map(lambda e: e.load(), pkg_resources.iter_entry_points(group))
for ep in sorted(eps, key=by_order):
- ep.load()(self)
+ ep(self)
def _finalize_setup_keywords(self):
for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'):
assert expected == sorted(actual)
+ _sys_argv_0_passthrough = {
+ 'setup.py': DALS("""
+ import os
+ import sys
+
+ __import__('setuptools').setup(
+ name='foo',
+ version='0.0.0',
+ )
+
+ sys_argv = os.path.abspath(sys.argv[0])
+ file_path = os.path.abspath('setup.py')
+ assert sys_argv == file_path
+ """)
+ }
+
+ def test_sys_argv_passthrough(self, tmpdir_cwd):
+ build_files(self._sys_argv_0_passthrough)
+ build_backend = self.get_build_backend()
+ with pytest.raises(AssertionError):
+ build_backend.build_sdist("temp")
+
class TestBuildMetaLegacyBackend(TestBuildMetaBackend):
backend_name = 'setuptools.build_meta:__legacy__'
build_backend = self.get_build_backend()
build_backend.build_sdist("temp")
+
+ def test_sys_argv_passthrough(self, tmpdir_cwd):
+ build_files(self._sys_argv_0_passthrough)
+
+ build_backend = self.get_build_backend()
+ build_backend.build_sdist("temp")
with io.open(fn) as init_file:
init = init_file.read().strip()
- expected = 'print("foo")' if six.PY3 else 'print "foo"'
+ expected = 'print "foo"' if six.PY2 else 'print("foo")'
assert init == expected
def test_console_scripts(self, tmpdir):
reason="https://github.com/pypa/setuptools/issues/851",
)
@pytest.mark.skipif(
- platform.python_implementation() == 'PyPy' and six.PY3,
+ platform.python_implementation() == 'PyPy' and not six.PY2,
reason="https://github.com/pypa/setuptools/issues/1202",
)
def test_namespace_package_importable(self, tmpdir):
# Convert to POSIX path
def posix(path):
- if six.PY3 and not isinstance(path, str):
+ if not six.PY2 and not isinstance(path, str):
return path.replace(os.sep.encode('ascii'), b'/')
else:
return path.replace(os.sep, '/')
cmd.read_manifest()
# The filelist should contain the UTF-8 filename
- if six.PY3:
+ if not six.PY2:
filename = filename.decode('utf-8')
assert filename in cmd.filelist.files
if sys.platform == 'darwin':
filename = decompose(filename)
- if six.PY3:
+ if not six.PY2:
fs_enc = sys.getfilesystemencoding()
if sys.platform == 'win32':
with quiet():
cmd.run()
- if six.PY3:
+ if six.PY2:
+ # Under Python 2 there seems to be no decoded string in the
+ # filelist. However, due to decode and encoding of the
+ # file name to get utf-8 Manifest the latin1 maybe excluded
+ try:
+ # fs_enc should match how one is expect the decoding to
+ # be proformed for the manifest output.
+ fs_enc = sys.getfilesystemencoding()
+ filename.decode(fs_enc)
+ assert filename in cmd.filelist.files
+ except UnicodeDecodeError:
+ filename not in cmd.filelist.files
+ else:
# not all windows systems have a default FS encoding of cp1252
if sys.platform == 'win32':
# Latin-1 is similar to Windows-1252 however
# The Latin-1 filename should have been skipped
filename = filename.decode('latin-1')
filename not in cmd.filelist.files
- else:
- # Under Python 2 there seems to be no decoded string in the
- # filelist. However, due to decode and encoding of the
- # file name to get utf-8 Manifest the latin1 maybe excluded
- try:
- # fs_enc should match how one is expect the decoding to
- # be proformed for the manifest output.
- fs_enc = sys.getfilesystemencoding()
- filename.decode(fs_enc)
- assert filename in cmd.filelist.files
- except UnicodeDecodeError:
- filename not in cmd.filelist.files
def test_pyproject_toml_in_sdist(self, tmpdir):
"""
def parse_config(filename):
parser = configparser.ConfigParser()
with io.open(filename, encoding='utf-8') as reader:
- (parser.read_file if six.PY3 else parser.readfp)(reader)
+ (parser.readfp if six.PY2 else parser.read_file)(reader)
return parser
@staticmethod
--- /dev/null
+"""
+Finalize the repo for a release. Invokes towncrier and bumpversion.
+"""
+
+__requires__ = ['bump2version', 'towncrier']
+
+
+import subprocess
+import pathlib
+import re
+import sys
+
+
+def release_kind():
+ """
+ Determine which release to make based on the files in the
+ changelog.
+ """
+ # use min here as 'major' < 'minor' < 'patch'
+ return min(
+ 'major' if 'breaking' in file.name else
+ 'minor' if 'change' in file.name else
+ 'patch'
+ for file in pathlib.Path('changelog.d').iterdir()
+ )
+
+
+bump_version_command = [
+ sys.executable,
+ '-m', 'bumpversion',
+ release_kind(),
+]
+
+
+def get_version():
+ cmd = bump_version_command + ['--dry-run', '--verbose']
+ out = subprocess.check_output(cmd, text=True)
+ return re.search('^new_version=(.*)', out, re.MULTILINE).group(1)
+
+
+def update_changelog():
+ cmd = [
+ sys.executable, '-m',
+ 'towncrier',
+ '--version', get_version(),
+ '--yes',
+ ]
+ subprocess.check_call(cmd)
+
+
+def bump_version():
+ cmd = bump_version_command + ['--allow-dirty']
+ subprocess.check_call(cmd)
+
+
+if __name__ == '__main__':
+ print("Cutting release at", get_version())
+ update_changelog()
+ bump_version()
-import os
-import shutil
import subprocess
import sys
-from glob import glob
-VIRTUAL_ENV = os.environ['VIRTUAL_ENV']
-TOX_PIP_DIR = os.path.join(VIRTUAL_ENV, 'pip')
+
+def remove_setuptools():
+ """
+ Remove setuptools from the current environment.
+ """
+ print("Removing setuptools")
+ cmd = [sys.executable, '-m', 'pip', 'uninstall', '-y', 'setuptools']
+ # set cwd to something other than '.' to avoid detecting
+ # '.' as the installed package.
+ subprocess.check_call(cmd, cwd='.tox')
+
+
+def bootstrap():
+ print("Running bootstrap")
+ cmd = [sys.executable, '-m', 'bootstrap']
+ subprocess.check_call(cmd)
def pip(args):
- # First things first, get a recent (stable) version of pip.
- if not os.path.exists(TOX_PIP_DIR):
- subprocess.check_call([sys.executable, '-m', 'pip',
- '--disable-pip-version-check',
- 'install', '-t', TOX_PIP_DIR,
- 'pip'])
- shutil.rmtree(glob(os.path.join(TOX_PIP_DIR, 'pip-*.dist-info'))[0])
- # And use that version.
- pypath = os.environ.get('PYTHONPATH')
- pypath = pypath.split(os.pathsep) if pypath is not None else []
- pypath.insert(0, TOX_PIP_DIR)
- os.environ['PYTHONPATH'] = os.pathsep.join(pypath)
- # Fix call for setuptools editable install.
- for n, a in enumerate(args):
- if a == '.':
- args[n] = os.getcwd()
- subprocess.check_call([sys.executable, '-m', 'pip'] + args, cwd=TOX_PIP_DIR)
+ if '.' in args:
+ remove_setuptools()
+ bootstrap()
+
+ cmd = [sys.executable, '-m', 'pip'] + args
+ subprocess.check_call(cmd)
if __name__ == '__main__':
-# Note: Run "python bootstrap.py" before running Tox, to generate metadata.
-#
# To run Tox against all supported Python interpreters, you can set:
#
# export TOXENV='py27,py3{5,6,7,8},pypy,pypy3'
[tox]
envlist=python
+minversion = 3.2
+requires =
+ tox-pip-version >= 0.0.6
+ # workaround for #1998
+ virtualenv < 20
[helpers]
-# Wrapper for calls to pip that make sure the version being used is a
-# up-to-date, and to prevent the current working directory from being
-# added to `sys.path`.
+# Custom pip behavior
pip = python {toxinidir}/tools/tox_pip.py
[testenv]
deps=-r{toxinidir}/tests/requirements.txt
+pip_version = pip
install_command = {[helpers]pip} install {opts} {packages}
list_dependencies_command = {[helpers]pip} freeze --all
setenv=COVERAGE_FILE={toxworkdir}/.coverage.{envname}
deps = -r{toxinidir}/docs/requirements.txt
skip_install=True
commands =
- python {toxinidir}/bootstrap.py
+ python -m bootstrap
sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/build/html
sphinx-build -W -b man -d {envtmpdir}/doctrees docs docs/build/man
omit=
*/_vendor/*
+[testenv:finalize]
+skip_install = True
+deps =
+ towncrier
+ bump2version
+commands =
+ python tools/finalize.py
+
[testenv:release]
skip_install = True
deps =