From: DongHun Kwak Date: Tue, 29 Dec 2020 22:05:11 +0000 (+0900) Subject: Imported Upstream version 46.1.0 X-Git-Tag: upstream/46.1.0^0 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=8bace73ac321728c4cb41f8e36d3071b83de91a4;p=platform%2Fupstream%2Fpython-setuptools.git Imported Upstream version 46.1.0 --- diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 5fabdff..28a327b 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,7 +1,6 @@ [bumpversion] -current_version = 46.0.0 +current_version = 46.1.0 commit = True tag = True [bumpversion:file:setup.cfg] - diff --git a/CHANGES.rst b/CHANGES.rst index 93c1f89..12680a4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,26 @@ +v46.1.0 +------- + +* #308: Allow version number normalization to be bypassed by wrapping in a 'setuptools.sic()' call. +* #1424: Prevent keeping files mode for package_data build. It may break a build if user's package data has read only flag. +* #1431: In ``easy_install.check_site_dir``, ensure the installation directory exists. +* #1563: In ``pkg_resources`` prefer ``find_spec`` (PEP 451) to ``find_module``. + +Incorporate changes from 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.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. + + v46.0.0 ------- diff --git a/docs/releases.txt b/docs/releases.txt index 98ba39e..35b415c 100644 --- a/docs/releases.txt +++ b/docs/releases.txt @@ -3,39 +3,13 @@ Release Process =============== 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 `_. - -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 ----------------- diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index 88d4bdc..15a4401 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -2197,10 +2197,14 @@ def _handle_ns(packageName, path_item): if importer is None: return None - # capture warnings due to #1111 - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - loader = importer.find_module(packageName) + # use find_spec (PEP 451) and fall-back to find_module (PEP 302) + try: + loader = importer.find_spec(packageName).loader + except AttributeError: + # capture warnings due to #1111 + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + loader = importer.find_module(packageName) if loader is None: return None diff --git a/pytest.ini b/pytest.ini index af61043..b13b7f3 100644 --- a/pytest.ini +++ b/pytest.ini @@ -18,3 +18,5 @@ filterwarnings = # Suppress other Python 2 UnicodeWarnings ignore:Unicode equal comparison failed to convert:UnicodeWarning ignore:Unicode unequal comparison failed to convert:UnicodeWarning + # https://github.com/pypa/setuptools/issues/2025 + ignore:direct construction of .*Item has been deprecated:DeprecationWarning diff --git a/setup.cfg b/setup.cfg index d231a3e..0e9a05e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,7 +16,7 @@ formats = zip [metadata] name = setuptools -version = 46.0.0 +version = 46.1.0 description = Easily download, build, install, upgrade, and uninstall Python packages author = Python Packaging Authority author_email = distutils-sig@python.org diff --git a/setuptools/__init__.py b/setuptools/__init__.py index 4485852..811f3fd 100644 --- a/setuptools/__init__.py +++ b/setuptools/__init__.py @@ -224,5 +224,9 @@ def findall(dir=os.curdir): return list(files) +class sic(str): + """Treat this string as-is (https://en.wikipedia.org/wiki/Sic)""" + + # Apply monkey patches monkey.patch_all() diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py index b0314fd..6fc0a4e 100644 --- a/setuptools/command/build_py.py +++ b/setuptools/command/build_py.py @@ -120,7 +120,7 @@ class build_py(orig.build_py, Mixin2to3): target = os.path.join(build_dir, filename) self.mkpath(os.path.dirname(target)) srcfile = os.path.join(src_dir, filename) - outf, copied = self.copy_file(srcfile, target) + outf, copied = self.copy_file(srcfile, target, preserve_mode=False) srcfile = os.path.abspath(srcfile) if (copied and srcfile in self.distribution.convert_2to3_doctests): diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index abca1ae..64ff045 100644 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -460,6 +460,12 @@ class easy_install(Command): instdir = normalize_path(self.install_dir) pth_file = os.path.join(instdir, 'easy-install.pth') + if not os.path.exists(instdir): + try: + os.makedirs(instdir) + except (OSError, IOError): + self.cant_write_to_target() + # Is it a configured, PYTHONPATH, implicit, or explicit site dir? is_site_dir = instdir in self.all_site_dirs diff --git a/setuptools/dist.py b/setuptools/dist.py index 7ffe0ba..fe64afa 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -30,6 +30,7 @@ from setuptools.extern.six.moves import map, filter, filterfalse from . import SetuptoolsDeprecationWarning +import setuptools from setuptools import windows_support from setuptools.monkey import get_unpatched from setuptools.config import parse_configuration @@ -438,30 +439,40 @@ class Distribution(_Distribution): value = default() if default else None setattr(self.metadata, option, value) - if isinstance(self.metadata.version, numbers.Number): + self.metadata.version = self._normalize_version( + self._validate_version(self.metadata.version)) + self._finalize_requires() + + @staticmethod + def _normalize_version(version): + if isinstance(version, setuptools.sic) or version is None: + return version + + normalized = str(packaging.version.Version(version)) + if version != normalized: + tmpl = "Normalizing '{version}' to '{normalized}'" + warnings.warn(tmpl.format(**locals())) + return normalized + return version + + @staticmethod + def _validate_version(version): + if isinstance(version, numbers.Number): # Some people apparently take "version number" too literally :) - self.metadata.version = str(self.metadata.version) + version = str(version) - if self.metadata.version is not None: + if version is not None: try: - ver = packaging.version.Version(self.metadata.version) - normalized_version = str(ver) - if self.metadata.version != normalized_version: - warnings.warn( - "Normalizing '%s' to '%s'" % ( - self.metadata.version, - normalized_version, - ) - ) - self.metadata.version = normalized_version + packaging.version.Version(version) except (packaging.version.InvalidVersion, TypeError): warnings.warn( "The version specified (%r) is an invalid version, this " "may not work as expected with newer versions of " "setuptools, pip, and PyPI. Please see PEP 440 for more " - "details." % self.metadata.version + "details." % version ) - self._finalize_requires() + return setuptools.sic(version) + return version def _finalize_requires(self): """ @@ -697,13 +708,13 @@ class Distribution(_Distribution): 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'): diff --git a/setuptools/tests/test_build_py.py b/setuptools/tests/test_build_py.py index b3a99f5..92b455d 100644 --- a/setuptools/tests/test_build_py.py +++ b/setuptools/tests/test_build_py.py @@ -1,4 +1,6 @@ import os +import stat +import shutil from setuptools.dist import Distribution @@ -20,3 +22,28 @@ def test_directories_in_package_data_glob(tmpdir_cwd): os.makedirs('path/subpath') dist.parse_command_line() dist.run_commands() + + +def test_read_only(tmpdir_cwd): + """ + Ensure mode is not preserved in copy for package modules + and package data, as that causes problems + with deleting read-only files on Windows. + + #1451 + """ + dist = Distribution(dict( + script_name='setup.py', + script_args=['build_py'], + packages=['pkg'], + package_data={'pkg': ['data.dat']}, + name='pkg', + )) + os.makedirs('pkg') + open('pkg/__init__.py', 'w').close() + open('pkg/data.dat', 'w').close() + os.chmod('pkg/__init__.py', stat.S_IREAD) + os.chmod('pkg/data.dat', stat.S_IREAD) + dist.parse_command_line() + dist.run_commands() + shutil.rmtree('build') diff --git a/setuptools/tests/test_dist.py b/setuptools/tests/test_dist.py index 6e8c45f..531ea1b 100644 --- a/setuptools/tests/test_dist.py +++ b/setuptools/tests/test_dist.py @@ -5,12 +5,14 @@ from __future__ import unicode_literals import io import collections import re +import functools from distutils.errors import DistutilsSetupError from setuptools.dist import ( _get_unpatched, check_package_data, DistDeprecationWarning, ) +from setuptools import sic from setuptools import Distribution from setuptools.extern.six.moves.urllib.request import pathname2url from setuptools.extern.six.moves.urllib_parse import urljoin @@ -70,75 +72,72 @@ def test_dist__get_unpatched_deprecated(): def __read_test_cases(): - # Metadata version 1.0 - base_attrs = { - "name": "package", - "version": "0.0.1", - "author": "Foo Bar", - "author_email": "foo@bar.net", - "long_description": "Long\ndescription", - "description": "Short description", - "keywords": ["one", "two"] - } - - def merge_dicts(d1, d2): - d1 = d1.copy() - d1.update(d2) - - return d1 + base = dict( + name="package", + version="0.0.1", + author="Foo Bar", + author_email="foo@bar.net", + long_description="Long\ndescription", + description="Short description", + keywords=["one", "two"], + ) + + params = functools.partial(dict, base) test_cases = [ - ('Metadata version 1.0', base_attrs.copy()), - ('Metadata version 1.1: Provides', merge_dicts(base_attrs, { - 'provides': ['package'] - })), - ('Metadata version 1.1: Obsoletes', merge_dicts(base_attrs, { - 'obsoletes': ['foo'] - })), - ('Metadata version 1.1: Classifiers', merge_dicts(base_attrs, { - 'classifiers': [ + ('Metadata version 1.0', params()), + ('Metadata version 1.1: Provides', params( + provides=['package'], + )), + ('Metadata version 1.1: Obsoletes', params( + obsoletes=['foo'], + )), + ('Metadata version 1.1: Classifiers', params( + classifiers=[ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.7', 'License :: OSI Approved :: MIT License', - ]})), - ('Metadata version 1.1: Download URL', merge_dicts(base_attrs, { - 'download_url': 'https://example.com' - })), - ('Metadata Version 1.2: Requires-Python', merge_dicts(base_attrs, { - 'python_requires': '>=3.7' - })), + ], + )), + ('Metadata version 1.1: Download URL', params( + download_url='https://example.com', + )), + ('Metadata Version 1.2: Requires-Python', params( + python_requires='>=3.7', + )), pytest.param( 'Metadata Version 1.2: Project-Url', - merge_dicts(base_attrs, { - 'project_urls': { - 'Foo': 'https://example.bar' - } - }), marks=pytest.mark.xfail( - reason="Issue #1578: project_urls not read" - )), - ('Metadata Version 2.1: Long Description Content Type', - merge_dicts(base_attrs, { - 'long_description_content_type': 'text/x-rst; charset=UTF-8' - })), + params(project_urls=dict(Foo='https://example.bar')), + marks=pytest.mark.xfail( + reason="Issue #1578: project_urls not read", + ), + ), + ('Metadata Version 2.1: Long Description Content Type', params( + long_description_content_type='text/x-rst; charset=UTF-8', + )), pytest.param( 'Metadata Version 2.1: Provides Extra', - merge_dicts(base_attrs, { - 'provides_extras': ['foo', 'bar'] - }), marks=pytest.mark.xfail(reason="provides_extras not read")), - ('Missing author, missing author e-mail', - {'name': 'foo', 'version': '1.0.0'}), - ('Missing author', - {'name': 'foo', - 'version': '1.0.0', - 'author_email': 'snorri@sturluson.name'}), - ('Missing author e-mail', - {'name': 'foo', - 'version': '1.0.0', - 'author': 'Snorri Sturluson'}), - ('Missing author', - {'name': 'foo', - 'version': '1.0.0', - 'author': 'Snorri Sturluson'}), + params(provides_extras=['foo', 'bar']), + marks=pytest.mark.xfail(reason="provides_extras not read"), + ), + ('Missing author', dict( + name='foo', + version='1.0.0', + author_email='snorri@sturluson.name', + )), + ('Missing author e-mail', dict( + name='foo', + version='1.0.0', + author='Snorri Sturluson', + )), + ('Missing author and e-mail', dict( + name='foo', + version='1.0.0', + )), + ('Bypass normalized version', dict( + name='foo', + version=sic('1.0.0a'), + )), ] return test_cases diff --git a/tools/finalize.py b/tools/finalize.py new file mode 100644 index 0000000..3b66341 --- /dev/null +++ b/tools/finalize.py @@ -0,0 +1,59 @@ +""" +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() diff --git a/tox.ini b/tox.ini index 1ebb922..347106e 100644 --- a/tox.ini +++ b/tox.ini @@ -60,6 +60,14 @@ source= omit= */_vendor/* +[testenv:finalize] +skip_install = True +deps = + towncrier + bump2version +commands = + python tools/finalize.py + [testenv:release] skip_install = True deps =