Imported Upstream version 46.1.0 upstream/46.1.0
authorDongHun Kwak <dh0128.kwak@samsung.com>
Tue, 29 Dec 2020 22:05:11 +0000 (07:05 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Tue, 29 Dec 2020 22:05:11 +0000 (07:05 +0900)
14 files changed:
.bumpversion.cfg
CHANGES.rst
docs/releases.txt
pkg_resources/__init__.py
pytest.ini
setup.cfg
setuptools/__init__.py
setuptools/command/build_py.py
setuptools/command/easy_install.py
setuptools/dist.py
setuptools/tests/test_build_py.py
setuptools/tests/test_dist.py
tools/finalize.py [new file with mode: 0644]
tox.ini

index 5fabdff0ebcf1f49f1a36d4b19283f7839706990..28a327b2f8aecc29e73dde8fdbf77c126ae8f24d 100644 (file)
@@ -1,7 +1,6 @@
 [bumpversion]
-current_version = 46.0.0
+current_version = 46.1.0
 commit = True
 tag = True
 
 [bumpversion:file:setup.cfg]
-
index 93c1f890ef82dc7bbf130acc69269a60987b9f97..12680a4d20f67e3561615e3ff4f8ccd28f754a41 100644 (file)
@@ -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
 -------
 
index 98ba39e88d1089b12ddf830d13722e4b2be5f06e..35b415c2653d138eed04a7cf8d7916da5edf283d 100644 (file)
@@ -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 <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
 -----------------
index 88d4bdcaedf65d99bdd546b22b6ae599bb401e50..15a4401afedb89cff6bdc739dd4d5bb38c3291ca 100644 (file)
@@ -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
index af61043fb2288a70686cf25d55d49f1c78e51b2f..b13b7f3363c58412b1eea4380a0f4c725ba8cfa6 100644 (file)
@@ -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
index d231a3e11ebcdb2299623ccd5e613c7cb40ae812..0e9a05ed53bf6ddbed9703353a26b8b1a501e928 100644 (file)
--- 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
index 4485852f050eb29ac85e0013589d0724515acfcd..811f3fd2e8dc97031b91c546cd3ddfdbdc9d3b52 100644 (file)
@@ -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()
index b0314fd413ae7f8c1027ccde0b092fd493fb104b..6fc0a4e46d7e3a866da8ce00d2d5fb68fe9e1e4c 100644 (file)
@@ -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):
index abca1ae1996f82b600a3a118900f23d2ac45aac5..64ff045718eef5a04bc7982954672818048e7054 100644 (file)
@@ -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
 
index 7ffe0ba1fd8ce68439139b5d7ad85e595a634a9e..fe64afa919ecee1f6c7f7541073ed7fafc6b73c7 100644 (file)
@@ -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'):
index b3a99f56606fdf13c31becc3003beaac04136e89..92b455ddca65bbd0cfc9ffd0ca86e2c2e27f927c 100644 (file)
@@ -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')
index 6e8c45fd302f699b79955f2a0379c37aa0673058..531ea1b4178eecb9cda8a38af74351d0177d4489 100644 (file)
@@ -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 (file)
index 0000000..3b66341
--- /dev/null
@@ -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 1ebb922b5e25925548e9a6fe733790f651aa2d41..347106ec37e3d35ae53cd710424f079d0a7d33a7 100644 (file)
--- 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 =