From 0afaeaa89ca4455734fd18cdb754db681fce2cc6 Mon Sep 17 00:00:00 2001 From: JinWang An Date: Mon, 27 Mar 2023 17:02:49 +0900 Subject: [PATCH] Imported Upstream version 62.2.0 --- .bumpversion.cfg | 2 +- .github/workflows/main.yml | 34 ++++++++---- CHANGES.rst | 13 +++++ docs/build_meta.rst | 2 +- docs/deprecated/distutils/examples.rst | 4 +- docs/deprecated/distutils/setupscript.rst | 6 +- docs/userguide/package_discovery.rst | 2 +- docs/userguide/quickstart.rst | 2 +- setup.cfg | 2 +- setuptools/_distutils/_functools.py | 20 +++++++ setuptools/_distutils/command/bdist_msi.py | 6 +- setuptools/_distutils/command/bdist_rpm.py | 8 +-- setuptools/_distutils/command/check.py | 43 ++------------- setuptools/_distutils/dist.py | 55 ++++++++++--------- setuptools/_distutils/sysconfig.py | 22 ++++++-- setuptools/_distutils/tests/test_check.py | 11 ++-- setuptools/_distutils/tests/test_dist.py | 2 +- setuptools/_distutils/tests/test_register.py | 4 +- setuptools/_distutils/tests/test_sdist.py | 2 +- setuptools/_distutils/tests/test_sysconfig.py | 28 ++++++++++ setuptools/config/_apply_pyprojecttoml.py | 16 +----- setuptools/dist.py | 22 ++++++-- setuptools/tests/config/downloads/__init__.py | 10 +++- .../tests/config/test_apply_pyprojecttoml.py | 23 +++++--- tox.ini | 10 +--- 25 files changed, 198 insertions(+), 151 deletions(-) create mode 100644 setuptools/_distutils/_functools.py diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 1125d38..7fb9cd1 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 62.1.0 +current_version = 62.2.0 commit = True tag = True diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c680fb3..092c0dc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,6 +39,17 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python }} + - uses: actions/cache@v3 + id: cache + with: + path: setuptools/tests/config/downloads/*.cfg + key: >- + ${{ hashFiles('setuptools/tests/config/setupcfg_examples.txt') }}- + ${{ hashFiles('setuptools/tests/config/downloads/*.py') }} + - name: Populate download cache + if: steps.cache.outputs.cache-hit != 'true' + working-directory: setuptools/tests/config + run: python -m downloads.preload setupcfg_examples.txt - name: Install tox run: | python -m pip install tox @@ -56,7 +67,13 @@ jobs: ${{ matrix.python }} test_cygwin: - runs-on: windows-latest + strategy: + matrix: + python: + - 39 + platform: + - windows-latest + runs-on: ${{ matrix.platform }} timeout-minutes: 75 steps: - uses: actions/checkout@v2 @@ -65,19 +82,14 @@ jobs: with: platform: x86_64 packages: >- - git, + python${{ matrix.python }}, + python${{ matrix.python }}-devel, + python${{ matrix.python }}-tox, gcc-core, - python38, - python38-devel, - python38-pip - - name: Install tox - shell: C:\cygwin\bin\env.exe CYGWIN_NOWINPATH=1 CHERE_INVOKING=1 C:\cygwin\bin\bash.exe -leo pipefail -o igncr {0} - run: | - python3.8 -m pip install tox + git, - name: Run tests shell: C:\cygwin\bin\env.exe CYGWIN_NOWINPATH=1 CHERE_INVOKING=1 C:\cygwin\bin\bash.exe -leo pipefail -o igncr {0} - run: | - tox -- --cov-report xml + run: tox integration-test: needs: test diff --git a/CHANGES.rst b/CHANGES.rst index 5061ecb..54fc15b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,16 @@ +v62.2.0 +------- + + +Changes +^^^^^^^ +* #3299: Optional metadata fields are now truly optional. Includes merge with pypa/distutils@a7cfb56 per pypa/distutils#138. + +Misc +^^^^ +* #3282: Added CI cache for ``setup.cfg`` examples used when testing ``setuptools.config``. + + v62.1.0 ------- diff --git a/docs/build_meta.rst b/docs/build_meta.rst index cb37272..57aea98 100644 --- a/docs/build_meta.rst +++ b/docs/build_meta.rst @@ -114,7 +114,7 @@ specified by :pep:`517`, is to "tweak" ``setuptools.build_meta`` by using a with **environment markers** are enough to differentiate operating systems and platforms. -If you add the following configuration to your ``pyprojec.toml``: +If you add the following configuration to your ``pyproject.toml``: .. code-block:: toml diff --git a/docs/deprecated/distutils/examples.rst b/docs/deprecated/distutils/examples.rst index d098465..00eef73 100644 --- a/docs/deprecated/distutils/examples.rst +++ b/docs/deprecated/distutils/examples.rst @@ -253,9 +253,7 @@ Running the ``check`` command will display some warnings: $ python setup.py check running check - warning: check: missing required meta-data: version, url - warning: check: missing meta-data: either (author and author_email) or - (maintainer and maintainer_email) should be supplied + warning: check: missing required meta-data: version If you use the reStructuredText syntax in the ``long_description`` field and diff --git a/docs/deprecated/distutils/setupscript.rst b/docs/deprecated/distutils/setupscript.rst index f49c4f8..ec9cf34 100644 --- a/docs/deprecated/distutils/setupscript.rst +++ b/docs/deprecated/distutils/setupscript.rst @@ -582,7 +582,7 @@ This information includes: | ``maintainer_email`` | email address of the | email address | \(3) | | | package maintainer | | | +----------------------+---------------------------+-----------------+--------+ -| ``url`` | home page for the package | URL | \(1) | +| ``url`` | home page for the package | URL | | +----------------------+---------------------------+-----------------+--------+ | ``description`` | short, summary | short string | | | | description of the | | | @@ -612,8 +612,8 @@ Notes: It is recommended that versions take the form *major.minor[.patch[.sub]]*. (3) - Either the author or the maintainer must be identified. If maintainer is - provided, distutils lists it as the author in :file:`PKG-INFO`. + If maintainer is provided and author is not, distutils lists maintainer as + the author in :file:`PKG-INFO`. (4) The ``long_description`` field is used by PyPI when you publish a package, diff --git a/docs/userguide/package_discovery.rst b/docs/userguide/package_discovery.rst index ee8e983..38119bc 100644 --- a/docs/userguide/package_discovery.rst +++ b/docs/userguide/package_discovery.rst @@ -189,7 +189,7 @@ The package folder(s) are placed directly under the project root:: └── mymodule.py This layout is very practical for using the REPL, but in some situations -it can be can be more error-prone (e.g. during tests or if you have a bunch +it can be more error-prone (e.g. during tests or if you have a bunch of folders or Python files hanging around your project root) To avoid confusion, file and folder names that are used by popular tools (or diff --git a/docs/userguide/quickstart.rst b/docs/userguide/quickstart.rst index c72db26..2f77852 100644 --- a/docs/userguide/quickstart.rst +++ b/docs/userguide/quickstart.rst @@ -121,7 +121,7 @@ Automatic package discovery For simple projects, it's usually easy enough to manually add packages to the ``packages`` keyword in ``setup.cfg``. However, for very large projects, it can be a big burden to keep the package list updated. -Therefore, ``setuptoops`` provides a convenient way to automatically list all +Therefore, ``setuptools`` provides a convenient way to automatically list all the packages in your project directory: .. tab:: setup.cfg diff --git a/setup.cfg b/setup.cfg index 4b38624..0dec946 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = setuptools -version = 62.1.0 +version = 62.2.0 author = Python Packaging Authority author_email = distutils-sig@python.org description = Easily download, build, install, upgrade, and uninstall Python packages diff --git a/setuptools/_distutils/_functools.py b/setuptools/_distutils/_functools.py new file mode 100644 index 0000000..e7053ba --- /dev/null +++ b/setuptools/_distutils/_functools.py @@ -0,0 +1,20 @@ +import functools + + +# from jaraco.functools 3.5 +def pass_none(func): + """ + Wrap func so it's not called if its first param is None + + >>> print_text = pass_none(print) + >>> print_text('text') + text + >>> print_text(None) + """ + + @functools.wraps(func) + def wrapper(param, *args, **kwargs): + if param is not None: + return func(param, *args, **kwargs) + + return wrapper diff --git a/setuptools/_distutils/command/bdist_msi.py b/setuptools/_distutils/command/bdist_msi.py index 1525953..56c4b98 100644 --- a/setuptools/_distutils/command/bdist_msi.py +++ b/setuptools/_distutils/command/bdist_msi.py @@ -231,11 +231,7 @@ class bdist_msi(Command): if os.path.exists(installer_name): os.unlink(installer_name) metadata = self.distribution.metadata - author = metadata.author - if not author: - author = metadata.maintainer - if not author: - author = "UNKNOWN" + author = metadata.author or metadata.maintainer version = metadata.get_version() # ProductVersion must be strictly numeric # XXX need to deal with prerelease versions diff --git a/setuptools/_distutils/command/bdist_rpm.py b/setuptools/_distutils/command/bdist_rpm.py index 550cbfa..a2a9e8e 100644 --- a/setuptools/_distutils/command/bdist_rpm.py +++ b/setuptools/_distutils/command/bdist_rpm.py @@ -399,7 +399,7 @@ class bdist_rpm(Command): '%define unmangled_version ' + self.distribution.get_version(), '%define release ' + self.release.replace('-','_'), '', - 'Summary: ' + self.distribution.get_description(), + 'Summary: ' + (self.distribution.get_description() or "UNKNOWN"), ] # Workaround for #14443 which affects some RPM based systems such as @@ -438,7 +438,7 @@ class bdist_rpm(Command): spec_file.append('Source0: %{name}-%{unmangled_version}.tar.gz') spec_file.extend([ - 'License: ' + self.distribution.get_license(), + 'License: ' + (self.distribution.get_license() or "UNKNOWN"), 'Group: ' + self.group, 'BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot', 'Prefix: %{_prefix}', ]) @@ -464,7 +464,7 @@ class bdist_rpm(Command): spec_file.append('%s: %s' % (field, val)) - if self.distribution.get_url() != 'UNKNOWN': + if self.distribution.get_url(): spec_file.append('Url: ' + self.distribution.get_url()) if self.distribution_name: @@ -483,7 +483,7 @@ class bdist_rpm(Command): spec_file.extend([ '', '%description', - self.distribution.get_long_description() + self.distribution.get_long_description() or "", ]) # put locale descriptions into spec file diff --git a/setuptools/_distutils/command/check.py b/setuptools/_distutils/command/check.py index af311ca..8a02dbc 100644 --- a/setuptools/_distutils/command/check.py +++ b/setuptools/_distutils/command/check.py @@ -82,54 +82,19 @@ class check(Command): """Ensures that all required elements of meta-data are supplied. Required fields: - name, version, URL - - Recommended fields: - (author and author_email) or (maintainer and maintainer_email)) + name, version Warns if any are missing. """ metadata = self.distribution.metadata missing = [] - for attr in ('name', 'version', 'url'): - if not (hasattr(metadata, attr) and getattr(metadata, attr)): + for attr in 'name', 'version': + if not getattr(metadata, attr, None): missing.append(attr) if missing: - self.warn("missing required meta-data: %s" % ', '.join(missing)) - if not ( - self._check_contact("author", metadata) or - self._check_contact("maintainer", metadata) - ): - self.warn("missing meta-data: either (author and author_email) " + - "or (maintainer and maintainer_email) " + - "should be supplied") - - def _check_contact(self, kind, metadata): - """ - Returns True if the contact's name is specified and False otherwise. - This function will warn if the contact's email is not specified. - """ - name = getattr(metadata, kind) or '' - email = getattr(metadata, kind + '_email') or '' - - msg = ("missing meta-data: if '{}' supplied, " + - "'{}' should be supplied too") - - if name and email: - return True - - if name: - self.warn(msg.format(kind, kind + '_email')) - return True - - addresses = [(alias, addr) for alias, addr in getaddresses([email])] - if any(alias and addr for alias, addr in addresses): - # The contact's name can be encoded in the email: `Name ` - return True - - return False + self.warn("missing required meta-data: %s" % ', '.join(missing)) def check_restructuredtext(self): """Checks if the long string fields are reST-compliant.""" diff --git a/setuptools/_distutils/dist.py b/setuptools/_distutils/dist.py index 37db4d6..4502497 100644 --- a/setuptools/_distutils/dist.py +++ b/setuptools/_distutils/dist.py @@ -1064,9 +1064,8 @@ class DistributionMetadata: def _read_field(name): value = msg[name] - if value == 'UNKNOWN': - return None - return value + if value and value != "UNKNOWN": + return value def _read_list(name): values = msg.get_all(name, None) @@ -1125,23 +1124,24 @@ class DistributionMetadata: self.classifiers or self.download_url): version = '1.1' + # required fields file.write('Metadata-Version: %s\n' % version) file.write('Name: %s\n' % self.get_name()) file.write('Version: %s\n' % self.get_version()) - file.write('Summary: %s\n' % self.get_description()) - file.write('Home-page: %s\n' % self.get_url()) - file.write('Author: %s\n' % self.get_contact()) - file.write('Author-email: %s\n' % self.get_contact_email()) - file.write('License: %s\n' % self.get_license()) - if self.download_url: - file.write('Download-URL: %s\n' % self.download_url) - long_desc = rfc822_escape(self.get_long_description()) - file.write('Description: %s\n' % long_desc) + def maybe_write(header, val): + if val: + file.write("{}: {}\n".format(header, val)) - keywords = ','.join(self.get_keywords()) - if keywords: - file.write('Keywords: %s\n' % keywords) + # optional fields + maybe_write("Summary", self.get_description()) + maybe_write("Home-page", self.get_url()) + maybe_write("Author", self.get_contact()) + maybe_write("Author-email", self.get_contact_email()) + maybe_write("License", self.get_license()) + maybe_write("Download-URL", self.download_url) + maybe_write("Description", rfc822_escape(self.get_long_description() or "")) + maybe_write("Keywords", ",".join(self.get_keywords())) self._write_list(file, 'Platform', self.get_platforms()) self._write_list(file, 'Classifier', self.get_classifiers()) @@ -1152,6 +1152,7 @@ class DistributionMetadata: self._write_list(file, 'Obsoletes', self.get_obsoletes()) def _write_list(self, file, name, values): + values = values or [] for value in values: file.write('%s: %s\n' % (name, value)) @@ -1167,35 +1168,35 @@ class DistributionMetadata: return "%s-%s" % (self.get_name(), self.get_version()) def get_author(self): - return self.author or "UNKNOWN" + return self.author def get_author_email(self): - return self.author_email or "UNKNOWN" + return self.author_email def get_maintainer(self): - return self.maintainer or "UNKNOWN" + return self.maintainer def get_maintainer_email(self): - return self.maintainer_email or "UNKNOWN" + return self.maintainer_email def get_contact(self): - return self.maintainer or self.author or "UNKNOWN" + return self.maintainer or self.author def get_contact_email(self): - return self.maintainer_email or self.author_email or "UNKNOWN" + return self.maintainer_email or self.author_email def get_url(self): - return self.url or "UNKNOWN" + return self.url def get_license(self): - return self.license or "UNKNOWN" + return self.license get_licence = get_license def get_description(self): - return self.description or "UNKNOWN" + return self.description def get_long_description(self): - return self.long_description or "UNKNOWN" + return self.long_description def get_keywords(self): return self.keywords or [] @@ -1204,7 +1205,7 @@ class DistributionMetadata: self.keywords = _ensure_list(value, 'keywords') def get_platforms(self): - return self.platforms or ["UNKNOWN"] + return self.platforms def set_platforms(self, value): self.platforms = _ensure_list(value, 'platforms') @@ -1216,7 +1217,7 @@ class DistributionMetadata: self.classifiers = _ensure_list(value, 'classifiers') def get_download_url(self): - return self.download_url or "UNKNOWN" + return self.download_url # PEP 314 def get_requires(self): diff --git a/setuptools/_distutils/sysconfig.py b/setuptools/_distutils/sysconfig.py index 55a42e1..7543f79 100644 --- a/setuptools/_distutils/sysconfig.py +++ b/setuptools/_distutils/sysconfig.py @@ -16,6 +16,7 @@ import sysconfig from .errors import DistutilsPlatformError from . import py39compat +from ._functools import pass_none IS_PYPY = '__pypy__' in sys.builtin_module_names @@ -51,12 +52,25 @@ def _is_python_source_dir(d): _sys_home = getattr(sys, '_home', None) + +def _is_parent(dir_a, dir_b): + """ + Return True if a is a parent of b. + """ + return os.path.normcase(dir_a).startswith(os.path.normcase(dir_b)) + + if os.name == 'nt': + @pass_none def _fix_pcbuild(d): - if d and os.path.normcase(d).startswith( - os.path.normcase(os.path.join(PREFIX, "PCbuild"))): - return PREFIX - return d + # In a venv, sys._home will be inside BASE_PREFIX rather than PREFIX. + prefixes = PREFIX, BASE_PREFIX + matched = ( + prefix + for prefix in prefixes + if _is_parent(d, os.path.join(prefix, "PCbuild")) + ) + return next(matched, d) project_base = _fix_pcbuild(project_base) _sys_home = _fix_pcbuild(_sys_home) diff --git a/setuptools/_distutils/tests/test_check.py b/setuptools/_distutils/tests/test_check.py index b41dba3..2414d6e 100644 --- a/setuptools/_distutils/tests/test_check.py +++ b/setuptools/_distutils/tests/test_check.py @@ -43,7 +43,7 @@ class CheckTestCase(support.LoggingSilencer, # by default, check is checking the metadata # should have some warnings cmd = self._run() - self.assertEqual(cmd._warnings, 2) + self.assertEqual(cmd._warnings, 1) # now let's add the required fields # and run it again, to make sure we don't get @@ -81,17 +81,16 @@ class CheckTestCase(support.LoggingSilencer, cmd = self._run(metadata) self.assertEqual(cmd._warnings, 0) - # the check should warn if only email is given and it does not - # contain the name + # the check should not warn if only email is given metadata[kind + '_email'] = 'name@email.com' cmd = self._run(metadata) - self.assertEqual(cmd._warnings, 1) + self.assertEqual(cmd._warnings, 0) - # the check should warn if only the name is given + # the check should not warn if only the name is given metadata[kind] = "Name" del metadata[kind + '_email'] cmd = self._run(metadata) - self.assertEqual(cmd._warnings, 1) + self.assertEqual(cmd._warnings, 0) @unittest.skipUnless(HAS_DOCUTILS, "won't test without docutils") def test_check_document(self): diff --git a/setuptools/_distutils/tests/test_dist.py b/setuptools/_distutils/tests/test_dist.py index 36155be..9132bc0 100644 --- a/setuptools/_distutils/tests/test_dist.py +++ b/setuptools/_distutils/tests/test_dist.py @@ -519,7 +519,7 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, self.assertEqual(metadata.description, "xxx") self.assertEqual(metadata.download_url, 'http://example.com') self.assertEqual(metadata.keywords, ['one', 'two']) - self.assertEqual(metadata.platforms, ['UNKNOWN']) + self.assertEqual(metadata.platforms, None) self.assertEqual(metadata.obsoletes, None) self.assertEqual(metadata.requires, ['foo']) diff --git a/setuptools/_distutils/tests/test_register.py b/setuptools/_distutils/tests/test_register.py index 5770ed5..4556768 100644 --- a/setuptools/_distutils/tests/test_register.py +++ b/setuptools/_distutils/tests/test_register.py @@ -154,8 +154,8 @@ class RegisterTestCase(BasePyPIRCCommandTestCase): req1 = dict(self.conn.reqs[0].headers) req2 = dict(self.conn.reqs[1].headers) - self.assertEqual(req1['Content-length'], '1374') - self.assertEqual(req2['Content-length'], '1374') + self.assertEqual(req1['Content-length'], '1359') + self.assertEqual(req2['Content-length'], '1359') self.assertIn(b'xxx', self.conn.reqs[1].data) def test_password_not_in_file(self): diff --git a/setuptools/_distutils/tests/test_sdist.py b/setuptools/_distutils/tests/test_sdist.py index 4c51717..aa04dd0 100644 --- a/setuptools/_distutils/tests/test_sdist.py +++ b/setuptools/_distutils/tests/test_sdist.py @@ -251,7 +251,7 @@ class SDistTestCase(BasePyPIRCCommandTestCase): cmd.run() warnings = [msg for msg in self.get_logs(WARN) if msg.startswith('warning: check:')] - self.assertEqual(len(warnings), 2) + self.assertEqual(len(warnings), 1) # trying with a complete set of metadata self.clear_logs() diff --git a/setuptools/_distutils/tests/test_sysconfig.py b/setuptools/_distutils/tests/test_sysconfig.py index e671f9e..1c88cc8 100644 --- a/setuptools/_distutils/tests/test_sysconfig.py +++ b/setuptools/_distutils/tests/test_sysconfig.py @@ -7,6 +7,9 @@ import sys import textwrap import unittest +import jaraco.envs + +import distutils from distutils import sysconfig from distutils.ccompiler import get_default_compiler from distutils.unixccompiler import UnixCCompiler @@ -309,6 +312,31 @@ class SysconfigTestCase(support.EnvironGuard, unittest.TestCase): self.assertTrue(sysconfig.get_config_var("EXT_SUFFIX").endswith(".pyd")) self.assertNotEqual(sysconfig.get_config_var("EXT_SUFFIX"), ".pyd") + @unittest.skipUnless( + sys.platform == 'win32', + 'Testing Windows build layout') + @unittest.skipUnless( + sys.implementation.name == 'cpython', + 'Need cpython for this test') + @unittest.skipUnless( + '\\PCbuild\\'.casefold() in sys.executable.casefold(), + 'Need sys.executable to be in a source tree') + def test_win_build_venv_from_source_tree(self): + """Ensure distutils.sysconfig detects venvs from source tree builds.""" + env = jaraco.envs.VEnv() + env.create_opts = env.clean_opts + env.root = TESTFN + env.ensure_env() + cmd = [ + env.exe(), + "-c", + "import distutils.sysconfig; print(distutils.sysconfig.python_build)" + ] + distutils_path = os.path.dirname(os.path.dirname(distutils.__file__)) + out = subprocess.check_output(cmd, env={**os.environ, "PYTHONPATH": distutils_path}) + assert out == "True" + + def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.TestLoader().loadTestsFromTestCase(SysconfigTestCase)) diff --git a/setuptools/config/_apply_pyprojecttoml.py b/setuptools/config/_apply_pyprojecttoml.py index fce5c40..a580b63 100644 --- a/setuptools/config/_apply_pyprojecttoml.py +++ b/setuptools/config/_apply_pyprojecttoml.py @@ -171,21 +171,7 @@ def _people(dist: "Distribution", val: List[dict], _root_dir: _Path, kind: str): def _project_urls(dist: "Distribution", val: dict, _root_dir): - special = {"downloadurl": "download_url", "homepage": "url"} - for key, url in val.items(): - norm_key = json_compatible_key(key).replace("_", "") - _set_config(dist, special.get(norm_key, key), url) - # If `homepage` is missing, distutils will warn the following message: - # "warning: check: missing required meta-data: url" - # In the context of PEP 621, users might ask themselves: "which url?". - # Let's add a warning before distutils check to help users understand the problem: - if not dist.metadata.url: - msg = ( - "Missing `Homepage` url.\nIt is advisable to link some kind of reference " - "for your project (e.g. source code or documentation).\n" - ) - _logger.warning(msg) - _set_config(dist, "project_urls", val.copy()) + _set_config(dist, "project_urls", val) def _python_requires(dist: "Distribution", val: dict, _root_dir): diff --git a/setuptools/dist.py b/setuptools/dist.py index 215c88e..5507167 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -102,7 +102,7 @@ def _read_list_from_msg(msg: "Message", field: str) -> Optional[List[str]]: def _read_payload_from_msg(msg: "Message") -> Optional[str]: value = msg.get_payload().strip() - if value == 'UNKNOWN': + if value == 'UNKNOWN' or not value: return None return value @@ -174,7 +174,10 @@ def write_pkg_file(self, file): # noqa: C901 # is too complex (14) # FIXME write_field('Metadata-Version', str(version)) write_field('Name', self.get_name()) write_field('Version', self.get_version()) - write_field('Summary', single_line(self.get_description())) + + summary = self.get_description() + if summary: + write_field('Summary', single_line(summary)) optional_fields = ( ('Home-page', 'url'), @@ -190,8 +193,10 @@ def write_pkg_file(self, file): # noqa: C901 # is too complex (14) # FIXME if attr_val is not None: write_field(field, attr_val) - license = rfc822_escape(self.get_license()) - write_field('License', license) + license = self.get_license() + if license: + write_field('License', rfc822_escape(license)) + for project_url in self.project_urls.items(): write_field('Project-URL', '%s, %s' % project_url) @@ -199,7 +204,8 @@ def write_pkg_file(self, file): # noqa: C901 # is too complex (14) # FIXME if keywords: write_field('Keywords', keywords) - for platform in self.get_platforms(): + platforms = self.get_platforms() or [] + for platform in platforms: write_field('Platform', platform) self._write_list(file, 'Classifier', self.get_classifiers()) @@ -222,7 +228,11 @@ def write_pkg_file(self, file): # noqa: C901 # is too complex (14) # FIXME self._write_list(file, 'License-File', self.license_files or []) - file.write("\n%s\n\n" % self.get_long_description()) + long_description = self.get_long_description() + if long_description: + file.write("\n%s" % long_description) + if not long_description.endswith("\n"): + file.write("\n") sequence = tuple, list diff --git a/setuptools/tests/config/downloads/__init__.py b/setuptools/tests/config/downloads/__init__.py index de43cff..9fb9b14 100644 --- a/setuptools/tests/config/downloads/__init__.py +++ b/setuptools/tests/config/downloads/__init__.py @@ -1,5 +1,7 @@ import re +import time from pathlib import Path +from urllib.error import HTTPError from urllib.request import urlopen __all__ = ["DOWNLOAD_DIR", "retrieve_file", "output_file", "urls_from_file"] @@ -21,14 +23,18 @@ def output_file(url: str, download_dir: Path = DOWNLOAD_DIR): return Path(download_dir, re.sub(r"[^\-_\.\w\d]+", "_", file_name)) -def retrieve_file(url: str, download_dir: Path = DOWNLOAD_DIR): +def retrieve_file(url: str, download_dir: Path = DOWNLOAD_DIR, wait: float = 5): path = output_file(url, download_dir) if path.exists(): print(f"Skipping {url} (already exists: {path})") else: download_dir.mkdir(exist_ok=True, parents=True) print(f"Downloading {url} to {path}") - download(url, path) + try: + download(url, path) + except HTTPError: + time.sleep(wait) # wait a few seconds and try again. + download(url, path) return path diff --git a/setuptools/tests/config/test_apply_pyprojecttoml.py b/setuptools/tests/config/test_apply_pyprojecttoml.py index 045d7f4..4f54169 100644 --- a/setuptools/tests/config/test_apply_pyprojecttoml.py +++ b/setuptools/tests/config/test_apply_pyprojecttoml.py @@ -298,19 +298,26 @@ class TestMeta: def core_metadata(dist) -> str: with io.StringIO() as buffer: dist.metadata.write_pkg_file(buffer) - value = "\n".join(buffer.getvalue().strip().splitlines()) + pkg_file_txt = buffer.getvalue() + skip_prefixes = () + skip_lines = set() # ---- DIFF NORMALISATION ---- # PEP 621 is very particular about author/maintainer metadata conversion, so skip - value = re.sub(r"^(Author|Maintainer)(-email)?:.*$", "", value, flags=re.M) + skip_prefixes += ("Author:", "Author-email:", "Maintainer:", "Maintainer-email:") # May be redundant with Home-page - value = re.sub(r"^Project-URL: Homepage,.*$", "", value, flags=re.M) + skip_prefixes += ("Project-URL: Homepage,", "Home-page:") # May be missing in original (relying on default) but backfilled in the TOML - value = re.sub(r"^Description-Content-Type:.*$", "", value, flags=re.M) + skip_prefixes += ("Description-Content-Type:",) # ini2toml can automatically convert `tests_require` to `testing` extra - value = value.replace("Provides-Extra: testing\n", "") + skip_lines.add("Provides-Extra: testing") # Remove empty lines - value = re.sub(r"^\s*$", "", value, flags=re.M) - value = re.sub(r"^\n", "", value, flags=re.M) + skip_lines.add("") - return value + result = [] + for line in pkg_file_txt.splitlines(): + if line.startswith(skip_prefixes) or line in skip_lines: + continue + result.append(line + "\n") + + return "".join(result) diff --git a/tox.ini b/tox.ini index 973f376..bb2e7cb 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = python -minversion = 3.2 +minversion = 3.25 # https://github.com/jaraco/skeleton/issues/6 tox_pip_extensions_ext_venv_update = true toxworkdir={env:TOX_WORK_DIR:.tox} @@ -20,10 +20,6 @@ passenv = windir # required for test_pkg_resources # honor git config in pytest-perf HOME - # workaround for tox-dev/tox#2382 - PROGRAMDATA - PROGRAMFILES - PROGRAMFILES(x86) [testenv:integration] deps = {[testenv]deps} @@ -31,10 +27,6 @@ extras = testing-integration passenv = {[testenv]passenv} DOWNLOAD_PATH - # workaround for tox-dev/tox#2382 - PROGRAMDATA - PROGRAMFILES - PROGRAMFILES(x86) setenv = PROJECT_ROOT = {toxinidir} commands = -- 2.34.1