Imported Upstream version 40.9.0 upstream/40.9.0
authorDongHun Kwak <dh0128.kwak@samsung.com>
Tue, 29 Dec 2020 22:03:58 +0000 (07:03 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Tue, 29 Dec 2020 22:03:58 +0000 (07:03 +0900)
15 files changed:
.bumpversion.cfg [new file with mode: 0644]
.travis.yml
CHANGES.rst
README.rst
appveyor.yml
docs/setuptools.txt
pkg_resources/__init__.py
pkg_resources/tests/test_pkg_resources.py
setup.cfg
setup.py
setuptools/build_meta.py
setuptools/dist.py
setuptools/tests/test_build_meta.py
setuptools/tests/test_virtualenv.py
tox.ini

diff --git a/.bumpversion.cfg b/.bumpversion.cfg
new file mode 100644 (file)
index 0000000..9840c08
--- /dev/null
@@ -0,0 +1,7 @@
+[bumpversion]
+current_version = 40.9.0
+commit = True
+tag = True
+
+[bumpversion:file:setup.cfg]
+
index d1febcc..a5b670e 100644 (file)
@@ -8,7 +8,8 @@ jobs:
     python: 2.7
   - <<: *latest_py2
     env: LANG=C
-  - python: pypy
+  - python: pypy2.7-6.0.0
+    dist: xenial
     env: DISABLE_COVERAGE=1  # Don't run coverage on pypy (too slow).
   - python: pypy3
     env: DISABLE_COVERAGE=1
@@ -63,6 +64,7 @@ install:
 - "! grep pyc setuptools.egg-info/SOURCES.txt"
 
 script:
+  - export NETWORK_REQUIRED=1
   - |
     ( # Run testsuite.
       if [ -z "$DISABLE_COVERAGE" ]
index b043e44..2a8d432 100644 (file)
@@ -1,3 +1,13 @@
+v40.9.0
+-------
+
+* #1675: Added support for ``setup.cfg``-only projects when using the ``setuptools.build_meta`` backend. Projects that have enabled PEP 517 no longer need to have a ``setup.py`` and can use the purely declarative ``setup.cfg`` configuration file instead.
+* #1720: Added support for ``pkg_resources.parse_requirements``-style requirements in ``setup_requires`` when ``setup.py`` is invoked from the ``setuptools.build_meta`` build backend.
+* #1664: Added the path to the ``PKG-INFO`` or ``METADATA`` file in the exception
+  text when the ``Version:`` header can't be found.
+* #1705: Removed some placeholder documentation sections referring to deprecated features.
+
+
 v40.8.0
 -------
 
index 0454f2e..dac8a46 100644 (file)
@@ -1,23 +1,23 @@
 .. image:: https://img.shields.io/pypi/v/setuptools.svg
    :target: https://pypi.org/project/setuptools
 
-.. image:: https://readthedocs.org/projects/setuptools/badge/?version=latest
+.. image:: https://img.shields.io/readthedocs/setuptools/latest.svg
     :target: https://setuptools.readthedocs.io
 
-.. image:: https://img.shields.io/travis/pypa/setuptools/master.svg?label=Linux%20build%20%40%20Travis%20CI
+.. image:: https://img.shields.io/travis/pypa/setuptools/master.svg?label=Linux%20CI&logo=travis&logoColor=white
    :target: https://travis-ci.org/pypa/setuptools
 
-.. image:: https://img.shields.io/appveyor/ci/pypa/setuptools/master.svg?label=Windows%20build%20%40%20Appveyor
+.. image:: https://img.shields.io/appveyor/ci/pypa/setuptools/master.svg?label=Windows%20CI&logo=appveyor&logoColor=white
    :target: https://ci.appveyor.com/project/pypa/setuptools/branch/master
 
-.. image:: https://img.shields.io/codecov/c/github/pypa/setuptools/master.svg
+.. image:: https://img.shields.io/codecov/c/github/pypa/setuptools/master.svg?logo=codecov&logoColor=white
    :target: https://codecov.io/gh/pypa/setuptools
 
-.. image:: https://img.shields.io/pypi/pyversions/setuptools.svg
-
-.. image:: https://tidelift.com/badges/github/pypa/setuptools
+.. image:: https://tidelift.com/badges/github/pypa/setuptools?style=flat
    :target: https://tidelift.com/subscription/pkg/pypi-setuptools?utm_source=pypi-setuptools&utm_medium=readme
 
+.. image:: https://img.shields.io/pypi/pyversions/setuptools.svg
+
 See the `Installation Instructions
 <https://packaging.python.org/installing/>`_ in the Python Packaging
 User's Guide for instructions on installing, upgrading, and uninstalling
@@ -29,6 +29,10 @@ Bug reports and especially tested patches may be
 submitted directly to the `bug tracker
 <https://github.com/pypa/setuptools/issues>`_.
 
+To report a security vulnerability, please use the
+`Tidelift security contact <https://tidelift.com/security>`_.
+Tidelift will coordinate the fix and disclosure.
+
 
 Code of Conduct
 ---------------
index ef4a9f7..7a3d174 100644 (file)
@@ -3,6 +3,7 @@ clone_depth: 50
 environment:
 
   APPVEYOR: True
+  NETWORK_REQUIRED: True
   CODECOV_ENV: APPVEYOR_JOB_NAME
 
   matrix:
index a4e05d7..64b385c 100644 (file)
@@ -2679,42 +2679,6 @@ A few important points for writing revision control file finders:
   inform the user of the missing program(s).
 
 
-Subclassing ``Command``
------------------------
-
-Sorry, this section isn't written yet, and neither is a lot of what's below
-this point.
-
-XXX
-
-
-Reusing ``setuptools`` Code
-===========================
-
-``ez_setup``
-------------
-
-XXX
-
-
-``setuptools.archive_util``
----------------------------
-
-XXX
-
-
-``setuptools.sandbox``
-----------------------
-
-XXX
-
-
-``setuptools.package_index``
-----------------------------
-
-XXX
-
-
 Mailing List and Bug Tracker
 ============================
 
index dcfa1d0..97e08d6 100644 (file)
@@ -1403,8 +1403,15 @@ class NullProvider:
     def has_resource(self, resource_name):
         return self._has(self._fn(self.module_path, resource_name))
 
+    def _get_metadata_path(self, name):
+        return self._fn(self.egg_info, name)
+
     def has_metadata(self, name):
-        return self.egg_info and self._has(self._fn(self.egg_info, name))
+        if not self.egg_info:
+            return self.egg_info
+
+        path = self._get_metadata_path(name)
+        return self._has(path)
 
     def get_metadata(self, name):
         if not self.egg_info:
@@ -1512,6 +1519,19 @@ is not allowed.
         ...
         ValueError: Use of .. or absolute path in a resource path \
 is not allowed.
+
+        Blank values are allowed
+
+        >>> vrp('')
+        >>> bool(warned)
+        False
+
+        Non-string values are not.
+
+        >>> vrp(None)
+        Traceback (most recent call last):
+        ...
+        AttributeError: ...
         """
         invalid = (
             os.path.pardir in path.split(posixpath.sep) or
@@ -1855,6 +1875,9 @@ class FileMetadata(EmptyProvider):
     def __init__(self, path):
         self.path = path
 
+    def _get_metadata_path(self, name):
+        return self.path
+
     def has_metadata(self, name):
         return name == 'PKG-INFO' and os.path.isfile(self.path)
 
@@ -2648,10 +2671,14 @@ class Distribution:
         try:
             return self._version
         except AttributeError:
-            version = _version_from_file(self._get_metadata(self.PKG_INFO))
+            version = self._get_version()
             if version is None:
-                tmpl = "Missing 'Version:' header and/or %s file"
-                raise ValueError(tmpl % self.PKG_INFO, self)
+                path = self._get_metadata_path_for_display(self.PKG_INFO)
+                msg = (
+                    "Missing 'Version:' header and/or {} file at path: {}"
+                ).format(self.PKG_INFO, path)
+                raise ValueError(msg, self)
+
             return version
 
     @property
@@ -2709,11 +2736,34 @@ class Distribution:
                 )
         return deps
 
+    def _get_metadata_path_for_display(self, name):
+        """
+        Return the path to the given metadata file, if available.
+        """
+        try:
+            # We need to access _get_metadata_path() on the provider object
+            # directly rather than through this class's __getattr__()
+            # since _get_metadata_path() is marked private.
+            path = self._provider._get_metadata_path(name)
+
+        # Handle exceptions e.g. in case the distribution's metadata
+        # provider doesn't support _get_metadata_path().
+        except Exception:
+            return '[could not detect]'
+
+        return path
+
     def _get_metadata(self, name):
         if self.has_metadata(name):
             for line in self.get_metadata_lines(name):
                 yield line
 
+    def _get_version(self):
+        lines = self._get_metadata(self.PKG_INFO)
+        version = _version_from_file(lines)
+
+        return version
+
     def activate(self, path=None, replace=False):
         """Ensure distribution is importable on `path` (default=sys.path)"""
         if path is None:
@@ -2932,7 +2982,7 @@ class EggInfoDistribution(Distribution):
         take an extra step and try to get the version number from
         the metadata file itself instead of the filename.
         """
-        md_version = _version_from_file(self._get_metadata(self.PKG_INFO))
+        md_version = self._get_version()
         if md_version:
             self._version = md_version
         return self
index 2c2c9c7..fb77c68 100644 (file)
@@ -17,6 +17,7 @@ try:
 except ImportError:
     import mock
 
+from pkg_resources import DistInfoDistribution, Distribution, EggInfoDistribution
 from pkg_resources.extern.six.moves import map
 from pkg_resources.extern.six import text_type, string_types
 
@@ -190,6 +191,89 @@ class TestResourceManager:
         subprocess.check_call(cmd)
 
 
+# TODO: remove this in favor of Path.touch() when Python 2 is dropped.
+def touch_file(path):
+    """
+    Create an empty file.
+    """
+    with open(path, 'w'):
+        pass
+
+
+def make_distribution_no_version(tmpdir, basename):
+    """
+    Create a distribution directory with no file containing the version.
+    """
+    # Convert the LocalPath object to a string before joining.
+    dist_dir = os.path.join(str(tmpdir), basename)
+    os.mkdir(dist_dir)
+    # Make the directory non-empty so distributions_from_metadata()
+    # will detect it and yield it.
+    touch_file(os.path.join(dist_dir, 'temp.txt'))
+
+    dists = list(pkg_resources.distributions_from_metadata(dist_dir))
+    assert len(dists) == 1
+    dist, = dists
+
+    return dist, dist_dir
+
+
+@pytest.mark.parametrize(
+    'suffix, expected_filename, expected_dist_type',
+    [
+        ('egg-info', 'PKG-INFO', EggInfoDistribution),
+        ('dist-info', 'METADATA', DistInfoDistribution),
+    ],
+)
+def test_distribution_version_missing(tmpdir, suffix, expected_filename,
+    expected_dist_type):
+    """
+    Test Distribution.version when the "Version" header is missing.
+    """
+    basename = 'foo.{}'.format(suffix)
+    dist, dist_dir = make_distribution_no_version(tmpdir, basename)
+
+    expected_text = (
+        "Missing 'Version:' header and/or {} file at path: "
+    ).format(expected_filename)
+    metadata_path = os.path.join(dist_dir, expected_filename)
+
+    # Now check the exception raised when the "version" attribute is accessed.
+    with pytest.raises(ValueError) as excinfo:
+        dist.version
+
+    err = str(excinfo)
+    # Include a string expression after the assert so the full strings
+    # will be visible for inspection on failure.
+    assert expected_text in err, str((expected_text, err))
+
+    # Also check the args passed to the ValueError.
+    msg, dist = excinfo.value.args
+    assert expected_text in msg
+    # Check that the message portion contains the path.
+    assert metadata_path in msg, str((metadata_path, msg))
+    assert type(dist) == expected_dist_type
+
+
+def test_distribution_version_missing_undetected_path():
+    """
+    Test Distribution.version when the "Version" header is missing and
+    the path can't be detected.
+    """
+    # Create a Distribution object with no metadata argument, which results
+    # in an empty metadata provider.
+    dist = Distribution('/foo')
+    with pytest.raises(ValueError) as excinfo:
+        dist.version
+
+    msg, dist = excinfo.value.args
+    expected = (
+        "Missing 'Version:' header and/or PKG-INFO file at path: "
+        '[could not detect]'
+    )
+    assert msg == expected
+
+
 class TestDeepVersionLookupDistutils:
     @pytest.fixture
     def env(self, tmpdir):
index f0932e1..01fe6ae 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,8 +1,3 @@
-[bumpversion]
-current_version = 40.8.0
-commit = True
-tag = True
-
 [egg_info]
 tag_build = .post
 tag_date = 1
@@ -24,6 +19,4 @@ universal = 1
 
 [metadata]
 license_file = LICENSE
-
-[bumpversion:file:setup.py]
-
+version = 40.9.0
index 8ec7ce0..78d7018 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -89,7 +89,6 @@ def pypi_link(pkg_filename):
 
 setup_params = dict(
     name="setuptools",
-    version="40.8.0",
     description=(
         "Easily download, build, install, upgrade, and uninstall "
         "Python packages"
index 70b7ab2..47cbcbf 100644 (file)
@@ -26,6 +26,7 @@ bug reports or API stability):
 Again, this is not a formal definition! Just a "taste" of the module.
 """
 
+import io
 import os
 import sys
 import tokenize
@@ -35,6 +36,8 @@ import contextlib
 import setuptools
 import distutils
 
+from pkg_resources import parse_requirements
+
 __all__ = ['get_requires_for_build_sdist',
            'get_requires_for_build_wheel',
            'prepare_metadata_for_build_wheel',
@@ -50,7 +53,9 @@ class SetupRequirementsError(BaseException):
 
 class Distribution(setuptools.dist.Distribution):
     def fetch_build_eggs(self, specifiers):
-        raise SetupRequirementsError(specifiers)
+        specifier_list = list(map(str, parse_requirements(specifiers)))
+
+        raise SetupRequirementsError(specifier_list)
 
     @classmethod
     @contextlib.contextmanager
@@ -95,6 +100,14 @@ def _file_with_extension(directory, extension):
     return file
 
 
+def _open_setup_script(setup_script):
+    if not os.path.exists(setup_script):
+        # Supply a default setup.py
+        return io.StringIO(u"from setuptools import setup; setup()")
+
+    return getattr(tokenize, 'open', open)(setup_script)
+
+
 class _BuildMetaBackend(object):
 
     def _fix_config(self, config_settings):
@@ -120,9 +133,10 @@ class _BuildMetaBackend(object):
         # Correctness comes first, then optimization later
         __file__ = setup_script
         __name__ = '__main__'
-        f = getattr(tokenize, 'open', open)(__file__)
-        code = f.read().replace('\\r\\n', '\\n')
-        f.close()
+
+        with _open_setup_script(__file__) as f:
+            code = f.read().replace(r'\r\n', r'\n')
+
         exec(compile(code, __file__, 'exec'), locals())
 
     def get_requires_for_build_wheel(self, config_settings=None):
index ddb1787..e6d08b9 100644 (file)
@@ -885,7 +885,7 @@ class Distribution(_Distribution):
     def include(self, **attrs):
         """Add items to distribution that are named in keyword arguments
 
-        For example, 'dist.exclude(py_modules=["x"])' would add 'x' to
+        For example, 'dist.include(py_modules=["x"])' would add 'x' to
         the distribution's 'py_modules' attribute, if it was not already
         there.
 
@@ -1103,7 +1103,6 @@ class Distribution(_Distribution):
             return _Distribution.handle_display_options(self, option_order)
 
         # Stdout may be StringIO (e.g. in tests)
-        import io
         if not isinstance(sys.stdout, io.TextIOWrapper):
             return _Distribution.handle_display_options(self, option_order)
 
index 6236b9f..0bdea2d 100644 (file)
@@ -110,6 +110,21 @@ defns = [
                 print('hello')
             """),
     },
+    {
+        'setup.cfg': DALS("""
+        [metadata]
+        name = foo
+        version='0.0.0'
+
+        [options]
+        py_modules=hello
+        setup_requires=six
+        """),
+        'hello.py': DALS("""
+        def run():
+            print('hello')
+        """)
+    },
 ]
 
 
@@ -183,9 +198,13 @@ class TestBuildMetaBackend:
         # if the setup.py changes subsequent call of the build meta
         # should still succeed, given the
         # sdist_directory the frontend specifies is empty
-        with open(os.path.abspath("setup.py"), 'rt') as file_handler:
+        setup_loc = os.path.abspath("setup.py")
+        if not os.path.exists(setup_loc):
+            setup_loc = os.path.abspath("setup.cfg")
+
+        with open(setup_loc, 'rt') as file_handler:
             content = file_handler.read()
-        with open(os.path.abspath("setup.py"), 'wt') as file_handler:
+        with open(setup_loc, 'wt') as file_handler:
             file_handler.write(
                 content.replace("version='0.0.0'", "version='0.0.1'"))
 
@@ -268,6 +287,57 @@ class TestBuildMetaBackend:
         with pytest.raises(ImportError):
             build_backend.build_sdist("temp")
 
+    @pytest.mark.parametrize('setup_literal, requirements', [
+        ("'foo'", ['foo']),
+        ("['foo']", ['foo']),
+        (r"'foo\n'", ['foo']),
+        (r"'foo\n\n'", ['foo']),
+        ("['foo', 'bar']", ['foo', 'bar']),
+        (r"'# Has a comment line\nfoo'", ['foo']),
+        (r"'foo # Has an inline comment'", ['foo']),
+        (r"'foo \\\n >=3.0'", ['foo>=3.0']),
+        (r"'foo\nbar'", ['foo', 'bar']),
+        (r"'foo\nbar\n'", ['foo', 'bar']),
+        (r"['foo\n', 'bar\n']", ['foo', 'bar']),
+    ])
+    @pytest.mark.parametrize('use_wheel', [True, False])
+    def test_setup_requires(self, setup_literal, requirements, use_wheel,
+                            tmpdir_cwd):
+
+        files = {
+            'setup.py': DALS("""
+                from setuptools import setup
+
+                setup(
+                    name="qux",
+                    version="0.0.0",
+                    py_modules=["hello.py"],
+                    setup_requires={setup_literal},
+                )
+            """).format(setup_literal=setup_literal),
+            'hello.py': DALS("""
+            def run():
+                print('hello')
+            """),
+        }
+
+        build_files(files)
+
+        build_backend = self.get_build_backend()
+
+        if use_wheel:
+            base_requirements = ['wheel']
+            get_requires = build_backend.get_requires_for_build_wheel
+        else:
+            base_requirements = []
+            get_requires = build_backend.get_requires_for_build_sdist
+
+        # Ensure that the build requirements are properly parsed
+        expected = sorted(base_requirements + requirements)
+        actual = get_requires()
+
+        assert expected == sorted(actual)
+
 
 class TestBuildMetaLegacyBackend(TestBuildMetaBackend):
     backend_name = 'setuptools.build_meta:__legacy__'
index 3d5c84b..d7b98c7 100644 (file)
@@ -52,10 +52,55 @@ def test_clean_env_install(bare_virtualenv):
     )).format(source=SOURCE_DIR))
 
 
-def test_pip_upgrade_from_source(virtualenv):
+def _get_pip_versions():
+    # This fixture will attempt to detect if tests are being run without
+    # network connectivity and if so skip some tests
+
+    network = True
+    if not os.environ.get('NETWORK_REQUIRED', False):  # pragma: nocover
+        try:
+            from urllib.request import urlopen
+            from urllib.error import URLError
+        except ImportError:
+            from urllib2 import urlopen, URLError # Python 2.7 compat
+
+        try:
+            urlopen('https://pypi.org', timeout=1)
+        except URLError:
+            # No network, disable most of these tests
+            network = False
+
+    network_versions = [
+        'pip==9.0.3',
+        'pip==10.0.1',
+        'pip==18.1',
+        'pip==19.0.1',
+        'https://github.com/pypa/pip/archive/master.zip',
+    ]
+
+    versions = [None] + [
+        pytest.param(v, **({} if network else {'marks': pytest.mark.skip}))
+        for v in network_versions
+    ]
+
+    return versions
+
+
+@pytest.mark.parametrize('pip_version', _get_pip_versions())
+def test_pip_upgrade_from_source(pip_version, virtualenv):
     """
     Check pip can upgrade setuptools from source.
     """
+    # Install pip/wheel, and remove setuptools (as it
+    # should not be needed for bootstraping from source)
+    if pip_version is None:
+        upgrade_pip = ()
+    else:
+        upgrade_pip = ('python -m pip install -U {pip_version} --retries=1',)
+    virtualenv.run(' && '.join((
+        'pip uninstall -y setuptools',
+        'pip install -U wheel',
+    ) + upgrade_pip).format(pip_version=pip_version))
     dist_dir = virtualenv.workspace
     # Generate source distribution / wheel.
     virtualenv.run(' && '.join((
diff --git a/tox.ini b/tox.ini
index a31cb1c..bb940ac 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -18,7 +18,7 @@ list_dependencies_command={envbindir}/pip freeze
 setenv=COVERAGE_FILE={toxworkdir}/.coverage.{envname}
 # TODO: The passed environment variables came from copying other tox.ini files
 # These should probably be individually annotated to explain what needs them.
-passenv=APPDATA HOMEDRIVE HOMEPATH windir APPVEYOR APPVEYOR_* CI CODECOV_* TRAVIS TRAVIS_*
+passenv=APPDATA HOMEDRIVE HOMEPATH windir APPVEYOR APPVEYOR_* CI CODECOV_* TRAVIS TRAVIS_* NETWORK_REQUIRED
 commands=pytest --cov-config={toxinidir}/tox.ini --cov-report= {posargs}
 usedevelop=True