From: DongHun Kwak Date: Tue, 29 Dec 2020 22:04:09 +0000 (+0900) Subject: Imported Upstream version 41.1.0 X-Git-Tag: upstream/41.1.0^0 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=bd320784000438a2eb2b3694932edb6674728da6;p=platform%2Fupstream%2Fpython-setuptools.git Imported Upstream version 41.1.0 --- diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 87acb5e..5a45366 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 41.0.1 +current_version = 41.1.0 commit = True tag = True diff --git a/.travis.yml b/.travis.yml index ffcad99..64d0544 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,9 +8,9 @@ jobs: python: 2.7 - <<: *latest_py2 env: LANG=C - - python: pypy2.7-6.0.0 + - python: pypy env: DISABLE_COVERAGE=1 # Don't run coverage on pypy (too slow). - - python: pypy3.5-6.0.0 + - python: pypy3 env: DISABLE_COVERAGE=1 - python: 3.4 - python: 3.5 diff --git a/CHANGES.rst b/CHANGES.rst index 9da2253..9f9603c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,16 @@ +v41.1.0 +------- + +* #1697: Moved most of the constants from setup.py to setup.cfg +* #1749: Fixed issue with the PEP 517 backend where building a source distribution would fail if any tarball existed in the destination directory. +* #1750: Fixed an issue with PEP 517 backend where wheel builds would fail if the destination directory did not already exist. +* #1756: Forse metadata-version >= 1.2. when project urls are present. +* #1769: Improve ``package_data`` check: ensure the dictionary values are lists/tuples of strings. +* #1788: Changed compatibility fallback logic for ``html.unescape`` to avoid accessing ``HTMLParser.unescape`` when not necessary. ``HTMLParser.unescape`` is deprecated and will be removed in Python 3.9. +* #1790: Added the file path to the error message when a ``UnicodeDecodeError`` occurs while reading a metadata file. +* #1776: Use license classifiers rather than the license field. + + v41.0.1 ------- diff --git a/appveyor.yml b/appveyor.yml index 7a3d174..0881806 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,7 +26,7 @@ cache: test_script: - python --version - python -m pip install --disable-pip-version-check --upgrade pip setuptools wheel - - pip install --upgrade tox tox-venv + - pip install --upgrade tox tox-venv virtualenv - pip freeze --all - python bootstrap.py - tox -- --cov diff --git a/docs/developer-guide.txt b/docs/developer-guide.txt index a5942c8..d145fba 100644 --- a/docs/developer-guide.txt +++ b/docs/developer-guide.txt @@ -137,3 +137,17 @@ To build the docs locally, use tox:: .. _Sphinx: http://www.sphinx-doc.org/en/master/ .. _published documentation: https://setuptools.readthedocs.io/en/latest/ + +--------------------- +Vendored Dependencies +--------------------- + +Setuptools has some dependencies, but due to `bootstrapping issues +`, those dependencies +cannot be declared as they won't be resolved soon enough to build +setuptools from source. Eventually, this limitation may be lifted as +PEP 517/518 reach ubiquitous adoption, but for now, Setuptools +cannot declare dependencies other than through +``setuptools/_vendor/vendored.txt`` and +``pkg_reosurces/_vendor/vendored.txt`` and refreshed by way of +``paver update_vendored`` (pavement.py). diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 64b385c..2e7fe3b 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -136,16 +136,18 @@ dependencies, and perhaps some data files and scripts:: author="Me", author_email="me@example.com", description="This is an Example Package", - license="PSF", keywords="hello world example examples", url="http://example.com/HelloWorld/", # project home page, if any project_urls={ "Bug Tracker": "https://bugs.example.com/HelloWorld/", "Documentation": "https://docs.example.com/HelloWorld/", "Source Code": "https://code.example.com/HelloWorld/", - } + }, + classifiers=[ + 'License :: OSI Approved :: Python Software Foundation License' + ] - # could also include long_description, download_url, classifiers, etc. + # could also include long_description, download_url, etc. ) In the sections that follow, we'll explain what most of these ``setup()`` @@ -2234,6 +2236,7 @@ boilerplate code in some cases. license = BSD 3-Clause License classifiers = Framework :: Django + License :: OSI Approved :: BSD License Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index 97e08d6..1f170cf 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -1416,8 +1416,17 @@ class NullProvider: def get_metadata(self, name): if not self.egg_info: return "" - value = self._get(self._fn(self.egg_info, name)) - return value.decode('utf-8') if six.PY3 else value + path = self._get_metadata_path(name) + value = self._get(path) + if six.PY2: + return value + try: + return value.decode('utf-8') + except UnicodeDecodeError as exc: + # Include the path in the error message to simplify + # troubleshooting, and without changing the exception type. + exc.reason += ' in {} file at path: {}'.format(name, path) + raise def get_metadata_lines(self, name): return yield_lines(self.get_metadata(name)) diff --git a/pkg_resources/tests/test_pkg_resources.py b/pkg_resources/tests/test_pkg_resources.py index fb77c68..5960868 100644 --- a/pkg_resources/tests/test_pkg_resources.py +++ b/pkg_resources/tests/test_pkg_resources.py @@ -18,6 +18,7 @@ except ImportError: import mock from pkg_resources import DistInfoDistribution, Distribution, EggInfoDistribution +from setuptools.extern import six from pkg_resources.extern.six.moves import map from pkg_resources.extern.six import text_type, string_types @@ -191,6 +192,59 @@ class TestResourceManager: subprocess.check_call(cmd) +def make_test_distribution(metadata_path, metadata): + """ + Make a test Distribution object, and return it. + + :param metadata_path: the path to the metadata file that should be + created. This should be inside a distribution directory that should + also be created. For example, an argument value might end with + ".dist-info/METADATA". + :param metadata: the desired contents of the metadata file, as bytes. + """ + dist_dir = os.path.dirname(metadata_path) + os.mkdir(dist_dir) + with open(metadata_path, 'wb') as f: + f.write(metadata) + dists = list(pkg_resources.distributions_from_metadata(dist_dir)) + dist, = dists + + return dist + + +def test_get_metadata__bad_utf8(tmpdir): + """ + Test a metadata file with bytes that can't be decoded as utf-8. + """ + filename = 'METADATA' + # Convert the tmpdir LocalPath object to a string before joining. + metadata_path = os.path.join(str(tmpdir), 'foo.dist-info', filename) + # Encode a non-ascii string with the wrong encoding (not utf-8). + metadata = 'née'.encode('iso-8859-1') + dist = make_test_distribution(metadata_path, metadata=metadata) + + if six.PY2: + # In Python 2, get_metadata() doesn't do any decoding. + actual = dist.get_metadata(filename) + assert actual == metadata + return + + # Otherwise, we are in the Python 3 case. + with pytest.raises(UnicodeDecodeError) as excinfo: + dist.get_metadata(filename) + + exc = excinfo.value + actual = str(exc) + expected = ( + # The error message starts with "'utf-8' codec ..." However, the + # spelling of "utf-8" can vary (e.g. "utf8") so we don't include it + "codec can't decode byte 0xe9 in position 1: " + 'invalid continuation byte in METADATA file at path: ' + ) + assert expected in actual, 'actual: {}'.format(actual) + assert actual.endswith(metadata_path), 'actual: {}'.format(actual) + + # TODO: remove this in favor of Path.touch() when Python 2 is dropped. def touch_file(path): """ @@ -242,7 +296,7 @@ def test_distribution_version_missing(tmpdir, suffix, expected_filename, with pytest.raises(ValueError) as excinfo: dist.version - err = str(excinfo) + err = str(excinfo.value) # 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)) diff --git a/pytest.ini b/pytest.ini index 1c5b6b0..612fb91 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,5 +1,5 @@ [pytest] -addopts=--doctest-modules --doctest-glob=pkg_resources/api_tests.txt -rsxX +addopts=--doctest-modules --doctest-glob=pkg_resources/api_tests.txt -r sxX norecursedirs=dist build *.egg setuptools/extern pkg_resources/extern .* flake8-ignore = setuptools/site-patch.py F821 diff --git a/setup.cfg b/setup.cfg index 3945408..3bcf442 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,5 +18,46 @@ formats = zip universal = 1 [metadata] +name = setuptools +version = 41.1.0 +description = Easily download, build, install, upgrade, and uninstall Python packages +author = Python Packaging Authority +author_email = distutils-sig@python.org +long_description = file: README.rst +long_description_content_type = text/x-rst; charset=UTF-8 license_file = LICENSE -version = 41.0.1 +keywords = CPAN PyPI distutils eggs package management +url = https://github.com/pypa/setuptools +project_urls = + Documentation = https://setuptools.readthedocs.io/ +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Developers + License :: OSI Approved :: MIT License + Operating System :: OS Independent + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.4 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Topic :: Software Development :: Libraries :: Python Modules + Topic :: System :: Archiving :: Packaging + Topic :: System :: Systems Administration + Topic :: Utilities + +[options] +zip_safe = True +python_requires = >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.* +py_modules = easy_install +packages = find: + +[options.packages.find] +exclude = *.tests + +[options.extras_require] +ssl = + wincertstore==0.2; sys_platform=='win32' +certs = + certifi==2016.9.26 diff --git a/setup.py b/setup.py index 78d7018..f5030dd 100755 --- a/setup.py +++ b/setup.py @@ -3,10 +3,8 @@ Distutils setup file, used to install or test 'setuptools' """ -import io import os import sys -import textwrap import setuptools @@ -49,10 +47,6 @@ def _gen_console_scripts(): yield tmpl.format(shortver=sys.version[:3]) -readme_path = os.path.join(here, 'README.rst') -with io.open(readme_path, encoding='utf-8') as readme_file: - long_description = readme_file.read() - package_data = dict( setuptools=['script (dev).tmpl', 'script.tmpl', 'site-patch.py'], ) @@ -88,25 +82,8 @@ def pypi_link(pkg_filename): setup_params = dict( - name="setuptools", - description=( - "Easily download, build, install, upgrade, and uninstall " - "Python packages" - ), - author="Python Packaging Authority", - author_email="distutils-sig@python.org", - long_description=long_description, - long_description_content_type='text/x-rst; charset=UTF-8', - keywords="CPAN PyPI distutils eggs package management", - url="https://github.com/pypa/setuptools", - project_urls={ - "Documentation": "https://setuptools.readthedocs.io/", - }, src_root=None, - packages=setuptools.find_packages(exclude=['*.tests']), package_data=package_data, - py_modules=['easy_install'], - zip_safe=True, entry_points={ "distutils.commands": [ "%(cmd)s = setuptools.command.%(cmd)s:%(cmd)s" % locals() @@ -152,28 +129,6 @@ setup_params = dict( "setuptools.installation": ['eggsecutable = setuptools.command.easy_install:bootstrap'], }, - classifiers=textwrap.dedent(""" - Development Status :: 5 - Production/Stable - Intended Audience :: Developers - License :: OSI Approved :: MIT License - Operating System :: OS Independent - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.7 - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.4 - Programming Language :: Python :: 3.5 - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 - Topic :: Software Development :: Libraries :: Python Modules - Topic :: System :: Archiving :: Packaging - Topic :: System :: Systems Administration - Topic :: Utilities - """).strip().splitlines(), - python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*', - extras_require={ - "ssl:sys_platform=='win32'": "wincertstore==0.2", - "certs": "certifi==2016.9.26", - }, dependency_links=[ pypi_link( 'certifi-2016.9.26.tar.gz#md5=baa81e951a29958563689d868ef1064d', @@ -182,7 +137,6 @@ setup_params = dict( 'wincertstore-0.2.zip#md5=ae728f2f007185648d0c7a8679b361e2', ), ], - scripts=[], setup_requires=[ ] + wheel, ) diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py index e40904a..10c4b52 100644 --- a/setuptools/build_meta.py +++ b/setuptools/build_meta.py @@ -38,6 +38,7 @@ import distutils from setuptools.py31compat import TemporaryDirectory from pkg_resources import parse_requirements +from pkg_resources.py31compat import makedirs __all__ = ['get_requires_for_build_sdist', 'get_requires_for_build_wheel', @@ -179,36 +180,38 @@ class _BuildMetaBackend(object): return dist_infos[0] - def build_wheel(self, wheel_directory, config_settings=None, - metadata_directory=None): + def _build_with_temp_dir(self, setup_command, result_extension, + result_directory, config_settings): config_settings = self._fix_config(config_settings) - wheel_directory = os.path.abspath(wheel_directory) + result_directory = os.path.abspath(result_directory) - # Build the wheel in a temporary directory, then copy to the target - with TemporaryDirectory(dir=wheel_directory) as tmp_dist_dir: - sys.argv = (sys.argv[:1] + - ['bdist_wheel', '--dist-dir', tmp_dist_dir] + + # Build in a temporary directory, then copy to the target. + makedirs(result_directory, exist_ok=True) + with TemporaryDirectory(dir=result_directory) as tmp_dist_dir: + sys.argv = (sys.argv[:1] + setup_command + + ['--dist-dir', tmp_dist_dir] + config_settings["--global-option"]) self.run_setup() - wheel_basename = _file_with_extension(tmp_dist_dir, '.whl') - wheel_path = os.path.join(wheel_directory, wheel_basename) - if os.path.exists(wheel_path): - # os.rename will fail overwriting on non-unix env - os.remove(wheel_path) - os.rename(os.path.join(tmp_dist_dir, wheel_basename), wheel_path) + result_basename = _file_with_extension(tmp_dist_dir, result_extension) + result_path = os.path.join(result_directory, result_basename) + if os.path.exists(result_path): + # os.rename will fail overwriting on non-Unix. + os.remove(result_path) + os.rename(os.path.join(tmp_dist_dir, result_basename), result_path) - return wheel_basename + return result_basename - def build_sdist(self, sdist_directory, config_settings=None): - config_settings = self._fix_config(config_settings) - sdist_directory = os.path.abspath(sdist_directory) - sys.argv = sys.argv[:1] + ['sdist', '--formats', 'gztar'] + \ - config_settings["--global-option"] + \ - ["--dist-dir", sdist_directory] - self.run_setup() - return _file_with_extension(sdist_directory, '.tar.gz') + def build_wheel(self, wheel_directory, config_settings=None, + metadata_directory=None): + return self._build_with_temp_dir(['bdist_wheel'], '.whl', + wheel_directory, config_settings) + + def build_sdist(self, sdist_directory, config_settings=None): + return self._build_with_temp_dir(['sdist', '--formats', 'gztar'], + '.tar.gz', sdist_directory, + config_settings) class _BuildMetaLegacyBackend(_BuildMetaBackend): diff --git a/setuptools/dist.py b/setuptools/dist.py index 9a165de..f0f030b 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -54,7 +54,8 @@ def get_metadata_version(self): mv = StrictVersion('2.1') elif (self.maintainer is not None or self.maintainer_email is not None or - getattr(self, 'python_requires', None) is not None): + getattr(self, 'python_requires', None) is not None or + self.project_urls): mv = StrictVersion('1.2') elif (self.provides or self.requires or self.obsoletes or self.classifiers or self.download_url): @@ -212,8 +213,12 @@ def check_importable(dist, attr, value): def assert_string_list(dist, attr, value): - """Verify that value is a string list or None""" + """Verify that value is a string list""" try: + # verify that value is a list or tuple to exclude unordered + # or single-use iterables + assert isinstance(value, (list, tuple)) + # verify that elements of value are strings assert ''.join(value) != value except (TypeError, ValueError, AttributeError, AssertionError): raise DistutilsSetupError( @@ -306,20 +311,17 @@ def check_test_suite(dist, attr, value): def check_package_data(dist, attr, value): """Verify that value is a dictionary of package names to glob lists""" - if isinstance(value, dict): - for k, v in value.items(): - if not isinstance(k, str): - break - try: - iter(v) - except TypeError: - break - else: - return - raise DistutilsSetupError( - attr + " must be a dictionary mapping package names to lists of " - "wildcard patterns" - ) + if not isinstance(value, dict): + raise DistutilsSetupError( + "{!r} must be a dictionary mapping package names to lists of " + "string wildcard patterns".format(attr)) + for k, v in value.items(): + if not isinstance(k, six.string_types): + raise DistutilsSetupError( + "keys of {!r} dict must be strings (got {!r})" + .format(attr, k) + ) + assert_string_list(dist, 'values of {!r} dict'.format(attr), v) def check_packages(dist, attr, value): diff --git a/setuptools/py33compat.py b/setuptools/py33compat.py index 87cf539..cb69443 100644 --- a/setuptools/py33compat.py +++ b/setuptools/py33compat.py @@ -52,4 +52,8 @@ class Bytecode_compat: Bytecode = getattr(dis, 'Bytecode', Bytecode_compat) -unescape = getattr(html, 'unescape', html_parser.HTMLParser().unescape) +unescape = getattr(html, 'unescape', None) +if unescape is None: + # HTMLParser.unescape is deprecated since Python 3.4, and will be removed + # from 3.9. + unescape = html_parser.HTMLParser().unescape diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py index 7612ebd..e1efe56 100644 --- a/setuptools/tests/test_build_meta.py +++ b/setuptools/tests/test_build_meta.py @@ -157,9 +157,10 @@ class TestBuildMetaBackend: assert os.path.isfile(os.path.join(dist_dir, wheel_name)) - def test_build_wheel_with_existing_wheel_file_present(self, tmpdir_cwd): - # Building a wheel should still succeed if there's already a wheel - # in the wheel directory + @pytest.mark.parametrize('build_type', ('wheel', 'sdist')) + def test_build_with_existing_file_present(self, build_type, tmpdir_cwd): + # Building a sdist/wheel should still succeed if there's + # already a sdist/wheel in the destination directory. files = { 'setup.py': "from setuptools import setup\nsetup()", 'VERSION': "0.0.1", @@ -177,28 +178,31 @@ class TestBuildMetaBackend: build_files(files) - dist_dir = os.path.abspath('pip-wheel-preexisting') - os.makedirs(dist_dir) + dist_dir = os.path.abspath('preexisting-' + build_type) - # make first wheel build_backend = self.get_build_backend() - wheel_one = build_backend.build_wheel(dist_dir) + build_method = getattr(build_backend, 'build_' + build_type) + + # Build a first sdist/wheel. + # Note: this also check the destination directory is + # successfully created if it does not exist already. + first_result = build_method(dist_dir) - # change version + # Change version. with open("VERSION", "wt") as version_file: version_file.write("0.0.2") - # make *second* wheel - wheel_two = self.get_build_backend().build_wheel(dist_dir) + # Build a *second* sdist/wheel. + second_result = build_method(dist_dir) - assert os.path.isfile(os.path.join(dist_dir, wheel_one)) - assert wheel_one != wheel_two + assert os.path.isfile(os.path.join(dist_dir, first_result)) + assert first_result != second_result - # and if rebuilding the same wheel? - open(os.path.join(dist_dir, wheel_two), 'w').close() - wheel_three = self.get_build_backend().build_wheel(dist_dir) - assert wheel_three == wheel_two - assert os.path.getsize(os.path.join(dist_dir, wheel_three)) > 0 + # And if rebuilding the exact same sdist/wheel? + open(os.path.join(dist_dir, second_result), 'w').close() + third_result = build_method(dist_dir) + assert third_result == second_result + assert os.path.getsize(os.path.join(dist_dir, third_result)) > 0 def test_build_sdist(self, build_backend): dist_dir = os.path.abspath('pip-sdist') diff --git a/setuptools/tests/test_dist.py b/setuptools/tests/test_dist.py index 390c3df..c771a19 100644 --- a/setuptools/tests/test_dist.py +++ b/setuptools/tests/test_dist.py @@ -3,7 +3,13 @@ from __future__ import unicode_literals import io -from setuptools.dist import DistDeprecationWarning, _get_unpatched +import re +from distutils.errors import DistutilsSetupError +from setuptools.dist import ( + _get_unpatched, + check_package_data, + DistDeprecationWarning, +) from setuptools import Distribution from setuptools.extern.six.moves.urllib.request import pathname2url from setuptools.extern.six.moves.urllib_parse import urljoin @@ -263,3 +269,48 @@ def test_maintainer_author(name, attrs, tmpdir): else: line = '%s: %s' % (fkey, val) assert line in pkg_lines_set + + +CHECK_PACKAGE_DATA_TESTS = ( + # Valid. + ({ + '': ['*.txt', '*.rst'], + 'hello': ['*.msg'], + }, None), + # Not a dictionary. + (( + ('', ['*.txt', '*.rst']), + ('hello', ['*.msg']), + ), ( + "'package_data' must be a dictionary mapping package" + " names to lists of string wildcard patterns" + )), + # Invalid key type. + ({ + 400: ['*.txt', '*.rst'], + }, ( + "keys of 'package_data' dict must be strings (got 400)" + )), + # Invalid value type. + ({ + 'hello': str('*.msg'), + }, ( + "\"values of 'package_data' dict\" must be a list of strings (got '*.msg')" + )), + # Invalid value type (generators are single use) + ({ + 'hello': (x for x in "generator"), + }, ( + "\"values of 'package_data' dict\" must be a list of strings " + "(got =13.0.0 pytest-virtualenv>=1.2.7 -# pytest pinned to <4 due to #1638 -pytest>=3.7,<4 +pytest>=3.7 wheel coverage>=4.5.1 pytest-cov>=2.5.1 diff --git a/tox.ini b/tox.ini index bb940ac..e0eef95 100644 --- a/tox.ini +++ b/tox.ini @@ -14,7 +14,7 @@ deps=-rtests/requirements.txt # from being added to `sys.path`. install_command=python -c 'import sys; sys.path.remove(""); from pkg_resources import load_entry_point; load_entry_point("pip", "console_scripts", "pip")()' install {opts} {packages} # Same as above. -list_dependencies_command={envbindir}/pip freeze +list_dependencies_command={envbindir}/pip freeze --all 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.