Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-wheel for openSUSE:Factory 
checked in at 2023-12-28 22:54:41
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-wheel (Old)
 and      /work/SRC/openSUSE:Factory/.python-wheel.new.28375 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-wheel"

Thu Dec 28 22:54:41 2023 rev:35 rq:1135267 version:0.42.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-wheel/python-wheel.changes        
2023-09-07 21:14:02.108081024 +0200
+++ /work/SRC/openSUSE:Factory/.python-wheel.new.28375/python-wheel.changes     
2023-12-28 22:54:46.347771721 +0100
@@ -1,0 +2,17 @@
+Fri Dec 22 15:31:14 UTC 2023 - Ben Greiner <c...@bnavigator.de>
+
+- Update to 0.42.0
+  * Allowed removing build tag with wheel tags --build ""
+  * Fixed wheel pack and wheel tags writing updated WHEEL fields
+    after a blank line, causing other tools to ignore them
+  * Fixed wheel pack and wheel tags writing WHEEL with CRLF line
+    endings or a mix of CRLF and LF
+  * Fixed wheel pack --build-number "" not removing build tag from
+    WHEEL (above changes by Benjamin Gilbert)
+- Release 0.41.3
+  * Updated vendored packaging to 23.2
+  * Fixed ABI tag generation for CPython 3.13a1 on Windows (PR by
+    Sam Gross)
+- Remove pip dependency for easier bootstrap
+
+-------------------------------------------------------------------

Old:
----
  wheel-0.41.2.tar.gz

New:
----
  wheel-0.42.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-wheel.spec ++++++
--- /var/tmp/diff_new_pack.M6B3Lu/_old  2023-12-28 22:54:47.147800927 +0100
+++ /var/tmp/diff_new_pack.M6B3Lu/_new  2023-12-28 22:54:47.151801073 +0100
@@ -32,16 +32,16 @@
 %endif
 %{?sle15_python_module_pythons}
 Name:           python-wheel%{psuffix}
-Version:        0.41.2
+Version:        0.42.0
 Release:        0
 Summary:        A built-package format for Python
 License:        MIT
 Group:          Development/Languages/Python
 URL:            https://github.com/pypa/wheel
 Source:         
https://github.com/pypa/wheel/archive/%{version}.tar.gz#/wheel-%{version}.tar.gz
+# Bootstrap: Don't BuildRequire setuptools or pip here!
 BuildRequires:  %{python_module base >= 3.7}
 BuildRequires:  %{python_module flit-core}
-BuildRequires:  %{python_module pip}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros >= 20210929
 %if %{with libalternatives}
@@ -74,11 +74,21 @@
 %autosetup -p1 -n wheel-%{version}
 
 %build
-%pyproject_wheel
+%if !%{with test}
+%{python_expand # bootstrap with built-in pip
+$python -m venv build/env
+build/env/bin/python -m ensurepip
+export PYTHONPATH=build/env/lib/python%{$python_bin_suffix}/site-packages
+%{$python_pyproject_wheel}
+}
+%endif
 
 %install
 %if !%{with test}
-%pyproject_install
+%{python_expand # use pip bootstrapped above
+export PYTHONPATH=build/env/lib/python%{$python_bin_suffix}/site-packages
+%{$python_pyproject_install}
+}
 %python_clone -a %{buildroot}%{_bindir}/wheel
 %python_expand %fdupes %{buildroot}%{$python_sitelib}
 %endif

++++++ wheel-0.41.2.tar.gz -> wheel-0.42.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wheel-0.41.2/.github/workflows/publish.yml 
new/wheel-0.42.0/.github/workflows/publish.yml
--- old/wheel-0.41.2/.github/workflows/publish.yml      2023-08-22 
11:29:26.000000000 +0200
+++ new/wheel-0.42.0/.github/workflows/publish.yml      2023-11-26 
15:35:45.000000000 +0100
@@ -13,7 +13,7 @@
     runs-on: ubuntu-latest
     environment: release
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - name: Set up Python
       uses: actions/setup-python@v4
       with:
@@ -39,3 +39,20 @@
       uses: actions/download-artifact@v3
     - name: Upload packages
       uses: pypa/gh-action-pypi-publish@release/v1
+
+  release:
+    name: Create a GitHub release
+    needs: build
+    runs-on: ubuntu-latest
+    permissions:
+      contents: write
+    steps:
+    - uses: actions/checkout@v4
+    - id: changelog
+      uses: agronholm/release-notes@v1
+      with:
+        path: docs/news.rst
+        version_pattern: "^\\*\\*([0-9a-z.]+) "
+    - uses: ncipollo/release-action@v1
+      with:
+        body: ${{ steps.changelog.outputs.changelog }}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wheel-0.41.2/.github/workflows/test.yml 
new/wheel-0.42.0/.github/workflows/test.yml
--- old/wheel-0.41.2/.github/workflows/test.yml 2023-08-22 11:29:26.000000000 
+0200
+++ new/wheel-0.42.0/.github/workflows/test.yml 2023-11-26 15:35:45.000000000 
+0100
@@ -14,7 +14,7 @@
       fail-fast: false
       matrix:
         os: [ubuntu-latest]
-        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", 
"pypy-3.8", "pypy-3.9"]
+        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", 
"pypy-3.8", "pypy-3.9"]
         include:
         - os: macos-latest
           python-version: "3.7"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wheel-0.41.2/.pre-commit-config.yaml 
new/wheel-0.42.0/.pre-commit-config.yaml
--- old/wheel-0.41.2/.pre-commit-config.yaml    2023-08-22 11:29:26.000000000 
+0200
+++ new/wheel-0.42.0/.pre-commit-config.yaml    2023-11-26 15:35:45.000000000 
+0100
@@ -2,7 +2,7 @@
 
 repos:
 - repo: https://github.com/pre-commit/pre-commit-hooks
-  rev: v4.4.0
+  rev: v4.5.0
   hooks:
   - id: check-added-large-files
   - id: check-case-conflict
@@ -18,18 +18,14 @@
   - id: trailing-whitespace
 
 - repo: https://github.com/astral-sh/ruff-pre-commit
-  rev: v0.0.285
+  rev: v0.1.6
   hooks:
     - id: ruff
       args: [--fix, --show-fixes]
-
-- repo: https://github.com/psf/black-pre-commit-mirror
-  rev: 23.7.0
-  hooks:
-  - id: black
+    - id: ruff-format
 
 - repo: https://github.com/codespell-project/codespell
-  rev: v2.2.5
+  rev: v2.2.6
   hooks:
   - id: codespell
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wheel-0.41.2/docs/news.rst 
new/wheel-0.42.0/docs/news.rst
--- old/wheel-0.41.2/docs/news.rst      2023-08-22 11:29:26.000000000 +0200
+++ new/wheel-0.42.0/docs/news.rst      2023-11-26 15:35:45.000000000 +0100
@@ -1,6 +1,21 @@
 Release Notes
 =============
 
+**0.42.0 (2023-11-26)**
+
+- Allowed removing build tag with ``wheel tags --build ""``
+- Fixed ``wheel pack`` and ``wheel tags`` writing updated ``WHEEL`` fields 
after a
+  blank line, causing other tools to ignore them
+- Fixed ``wheel pack`` and ``wheel tags`` writing ``WHEEL`` with CRLF line 
endings or
+  a mix of CRLF and LF
+- Fixed ``wheel pack --build-number ""`` not removing build tag from ``WHEEL``
+  (above changes by Benjamin Gilbert)
+
+**0.41.3 (2023-10-30)**
+
+- Updated vendored ``packaging`` to 23.2
+- Fixed ABI tag generation for CPython 3.13a1 on Windows (PR by Sam Gross)
+
 **0.41.2 (2023-08-22)**
 
 - Fixed platform tag detection for GraalPy and 32-bit python running on an 
aarch64
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wheel-0.41.2/pyproject.toml 
new/wheel-0.42.0/pyproject.toml
--- old/wheel-0.41.2/pyproject.toml     2023-08-22 11:29:26.000000000 +0200
+++ new/wheel-0.42.0/pyproject.toml     2023-11-26 15:35:45.000000000 +0100
@@ -30,6 +30,7 @@
 Documentation = "https://wheel.readthedocs.io/";
 Changelog = "https://wheel.readthedocs.io/en/stable/news.html";
 "Issue Tracker" = "https://github.com/pypa/wheel/issues";
+Source = "https://github.com/pypa/wheel";
 
 [project.scripts]
 wheel = "wheel.cli:main"
@@ -110,7 +111,7 @@
 [tool.tox]
 legacy_tox_ini = '''
 [tox]
-envlist = py37, py38, py39, py310, py311, py312, pypy3, lint, pkg
+envlist = py37, py38, py39, py310, py311, py312, py313, pypy3, lint, pkg
 minversion = 4.0.0
 skip_missing_interpreters = true
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wheel-0.41.2/src/wheel/__init__.py 
new/wheel-0.42.0/src/wheel/__init__.py
--- old/wheel-0.41.2/src/wheel/__init__.py      2023-08-22 11:29:26.000000000 
+0200
+++ new/wheel-0.42.0/src/wheel/__init__.py      2023-11-26 15:35:45.000000000 
+0100
@@ -1,3 +1,3 @@
 from __future__ import annotations
 
-__version__ = "0.41.2"
+__version__ = "0.42.0"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wheel-0.41.2/src/wheel/bdist_wheel.py 
new/wheel-0.42.0/src/wheel/bdist_wheel.py
--- old/wheel-0.41.2/src/wheel/bdist_wheel.py   2023-08-22 11:29:26.000000000 
+0200
+++ new/wheel-0.42.0/src/wheel/bdist_wheel.py   2023-11-26 15:35:45.000000000 
+0100
@@ -17,7 +17,6 @@
 from email.generator import BytesGenerator, Generator
 from email.policy import EmailPolicy
 from glob import iglob
-from io import BytesIO
 from shutil import rmtree
 from zipfile import ZIP_DEFLATED, ZIP_STORED
 
@@ -117,8 +116,12 @@
             m = "m"
 
         abi = f"{impl}{tags.interpreter_version()}{d}{m}{u}"
-    elif soabi and impl == "cp":
+    elif soabi and impl == "cp" and soabi.startswith("cpython"):
+        # non-Windows
         abi = "cp" + soabi.split("-")[1]
+    elif soabi and impl == "cp" and soabi.startswith("cp"):
+        # Windows
+        abi = soabi.split("-")[0]
     elif soabi and impl == "pp":
         # we want something like pypy36-pp73
         abi = "-".join(soabi.split("-")[:2])
@@ -194,8 +197,9 @@
         (
             "compression=",
             None,
-            "zipfile compression (one of: {})"
-            " (default: 'deflated')".format(", ".join(supported_compressions)),
+            "zipfile compression (one of: {})" " (default: 'deflated')".format(
+                ", ".join(supported_compressions)
+            ),
         ),
         (
             "python-tag=",
@@ -463,10 +467,8 @@
 
         wheelfile_path = os.path.join(wheelfile_base, "WHEEL")
         log.info(f"creating {wheelfile_path}")
-        buffer = BytesIO()
-        BytesGenerator(buffer, maxheaderlen=0).flatten(msg)
         with open(wheelfile_path, "wb") as f:
-            f.write(buffer.getvalue().replace(b"\r\n", b"\r"))
+            BytesGenerator(f, maxheaderlen=0).flatten(msg)
 
     def _ensure_relative(self, path):
         # copied from dir_util, deleted
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wheel-0.41.2/src/wheel/cli/__init__.py 
new/wheel-0.42.0/src/wheel/cli/__init__.py
--- old/wheel-0.41.2/src/wheel/cli/__init__.py  2023-08-22 11:29:26.000000000 
+0200
+++ new/wheel-0.42.0/src/wheel/cli/__init__.py  2023-11-26 15:35:45.000000000 
+0100
@@ -58,7 +58,7 @@
 
 
 def parse_build_tag(build_tag: str) -> str:
-    if not build_tag[0].isdigit():
+    if build_tag and not build_tag[0].isdigit():
         raise ArgumentTypeError("build tag must begin with a digit")
     elif "-" in build_tag:
         raise ArgumentTypeError("invalid character ('-') in build tag")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wheel-0.41.2/src/wheel/cli/convert.py 
new/wheel-0.42.0/src/wheel/cli/convert.py
--- old/wheel-0.41.2/src/wheel/cli/convert.py   2023-08-22 11:29:26.000000000 
+0200
+++ new/wheel-0.42.0/src/wheel/cli/convert.py   2023-11-26 15:35:45.000000000 
+0100
@@ -42,7 +42,7 @@
             return bdist_wheel.get_tag(self)
 
 
-def egg2wheel(egg_path: str, dest_dir: str):
+def egg2wheel(egg_path: str, dest_dir: str) -> None:
     filename = os.path.basename(egg_path)
     match = egg_info_re.match(filename)
     if not match:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wheel-0.41.2/src/wheel/cli/pack.py 
new/wheel-0.42.0/src/wheel/cli/pack.py
--- old/wheel-0.41.2/src/wheel/cli/pack.py      2023-08-22 11:29:26.000000000 
+0200
+++ new/wheel-0.42.0/src/wheel/cli/pack.py      2023-11-26 15:35:45.000000000 
+0100
@@ -1,16 +1,18 @@
 from __future__ import annotations
 
+import email.policy
 import os.path
 import re
+from email.generator import BytesGenerator
+from email.parser import BytesParser
 
 from wheel.cli import WheelError
 from wheel.wheelfile import WheelFile
 
 DIST_INFO_RE = 
re.compile(r"^(?P<namever>(?P<name>.+?)-(?P<ver>\d.*?))\.dist-info$")
-BUILD_NUM_RE = re.compile(rb"Build: (\d\w*)$")
 
 
-def pack(directory: str, dest_dir: str, build_number: str | None):
+def pack(directory: str, dest_dir: str, build_number: str | None) -> None:
     """Repack a previously unpacked wheel directory into a new wheel file.
 
     The .dist-info/WHEEL file must contain one or more tags so that the target
@@ -35,10 +37,11 @@
     name_version = DIST_INFO_RE.match(dist_info_dir).group("namever")
 
     # Read the tags and the existing build number from .dist-info/WHEEL
-    existing_build_number = None
     wheel_file_path = os.path.join(directory, dist_info_dir, "WHEEL")
     with open(wheel_file_path, "rb") as f:
-        tags, existing_build_number = read_tags(f.read())
+        info = BytesParser(policy=email.policy.compat32).parse(f)
+        tags: list[str] = info.get_all("Tag", [])
+        existing_build_number = info.get("Build")
 
         if not tags:
             raise WheelError(
@@ -49,17 +52,14 @@
     # Set the wheel file name and add/replace/remove the Build tag in 
.dist-info/WHEEL
     build_number = build_number if build_number is not None else 
existing_build_number
     if build_number is not None:
+        del info["Build"]
         if build_number:
+            info["Build"] = build_number
             name_version += "-" + build_number
 
         if build_number != existing_build_number:
-            with open(wheel_file_path, "rb+") as f:
-                wheel_file_content = f.read()
-                wheel_file_content = set_build_number(wheel_file_content, 
build_number)
-
-                f.seek(0)
-                f.truncate()
-                f.write(wheel_file_content)
+            with open(wheel_file_path, "wb") as f:
+                BytesGenerator(f, maxheaderlen=0).flatten(info)
 
     # Reassemble the tags for the wheel file
     tagline = compute_tagline(tags)
@@ -73,45 +73,6 @@
     print("OK")
 
 
-def read_tags(input_str: bytes) -> tuple[list[str], str | None]:
-    """Read tags from a string.
-
-    :param input_str: A string containing one or more tags, separated by spaces
-    :return: A list of tags and a list of build tags
-    """
-
-    tags = []
-    existing_build_number = None
-    for line in input_str.splitlines():
-        if line.startswith(b"Tag: "):
-            tags.append(line.split(b" ")[1].rstrip().decode("ascii"))
-        elif line.startswith(b"Build: "):
-            existing_build_number = line.split(b" 
")[1].rstrip().decode("ascii")
-
-    return tags, existing_build_number
-
-
-def set_build_number(wheel_file_content: bytes, build_number: str | None) -> 
bytes:
-    """Compute a build tag and add/replace/remove as necessary.
-
-    :param wheel_file_content: The contents of .dist-info/WHEEL
-    :param build_number: The build tags present in .dist-info/WHEEL
-    :return: The (modified) contents of .dist-info/WHEEL
-    """
-    replacement = (
-        ("Build: %s\r\n" % build_number).encode("ascii") if build_number else 
b""
-    )
-
-    wheel_file_content, num_replaced = BUILD_NUM_RE.subn(
-        replacement, wheel_file_content
-    )
-
-    if not num_replaced:
-        wheel_file_content += replacement
-
-    return wheel_file_content
-
-
 def compute_tagline(tags: list[str]) -> str:
     """Compute a tagline from a list of tags.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wheel-0.41.2/src/wheel/cli/tags.py 
new/wheel-0.42.0/src/wheel/cli/tags.py
--- old/wheel-0.41.2/src/wheel/cli/tags.py      2023-08-22 11:29:26.000000000 
+0200
+++ new/wheel-0.42.0/src/wheel/cli/tags.py      2023-11-26 15:35:45.000000000 
+0100
@@ -1,11 +1,12 @@
 from __future__ import annotations
 
+import email.policy
 import itertools
 import os
 from collections.abc import Iterable
+from email.parser import BytesParser
 
 from ..wheelfile import WheelFile
-from .pack import read_tags, set_build_number
 
 
 def _compute_tags(original_tags: Iterable[str], new_tags: str | None) -> 
set[str]:
@@ -48,6 +49,7 @@
         assert f.filename, f"{f.filename} must be available"
 
         wheel_info = f.read(f.dist_info_path + "/WHEEL")
+        info = BytesParser(policy=email.policy.compat32).parsebytes(wheel_info)
 
         original_wheel_name = os.path.basename(f.filename)
         namever = f.parsed_filename.group("namever")
@@ -56,7 +58,8 @@
         original_abi_tags = f.parsed_filename.group("abi").split(".")
         original_plat_tags = f.parsed_filename.group("plat").split(".")
 
-    tags, existing_build_tag = read_tags(wheel_info)
+    tags: list[str] = info.get_all("Tag", [])
+    existing_build_tag = info.get("Build")
 
     impls = {tag.split("-")[0] for tag in tags}
     abivers = {tag.split("-")[1] for tag in tags}
@@ -103,12 +106,13 @@
     final_wheel_name = "-".join(final_tags) + ".whl"
 
     if original_wheel_name != final_wheel_name:
-        tags = [
-            f"{a}-{b}-{c}"
-            for a, b, c in itertools.product(
-                final_python_tags, final_abi_tags, final_plat_tags
-            )
-        ]
+        del info["Tag"], info["Build"]
+        for a, b, c in itertools.product(
+            final_python_tags, final_abi_tags, final_plat_tags
+        ):
+            info["Tag"] = f"{a}-{b}-{c}"
+        if build:
+            info["Build"] = build
 
         original_wheel_path = os.path.join(
             os.path.dirname(f.filename), original_wheel_name
@@ -125,10 +129,7 @@
                 if item.filename == f.dist_info_path + "/RECORD":
                     continue
                 if item.filename == f.dist_info_path + "/WHEEL":
-                    content = fin.read(item)
-                    content = set_tags(content, tags)
-                    content = set_build_number(content, build)
-                    fout.writestr(item, content)
+                    fout.writestr(item, info.as_bytes())
                 else:
                     fout.writestr(item, fin.read(item))
 
@@ -136,18 +137,3 @@
             os.remove(original_wheel_path)
 
     return final_wheel_name
-
-
-def set_tags(in_string: bytes, tags: Iterable[str]) -> bytes:
-    """Set the tags in the .dist-info/WHEEL file contents.
-
-    :param in_string: The string to modify.
-    :param tags: The tags to set.
-    """
-
-    lines = [line for line in in_string.splitlines() if not 
line.startswith(b"Tag:")]
-    for tag in tags:
-        lines.append(b"Tag: " + tag.encode("ascii"))
-    in_string = b"\r\n".join(lines) + b"\r\n"
-
-    return in_string
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/wheel-0.41.2/src/wheel/vendored/packaging/_manylinux.py 
new/wheel-0.42.0/src/wheel/vendored/packaging/_manylinux.py
--- old/wheel-0.41.2/src/wheel/vendored/packaging/_manylinux.py 2023-08-22 
11:29:26.000000000 +0200
+++ new/wheel-0.42.0/src/wheel/vendored/packaging/_manylinux.py 2023-11-26 
15:35:45.000000000 +0100
@@ -5,7 +5,7 @@
 import re
 import sys
 import warnings
-from typing import Dict, Generator, Iterator, NamedTuple, Optional, Tuple
+from typing import Dict, Generator, Iterator, NamedTuple, Optional, Sequence, 
Tuple
 
 from ._elffile import EIClass, EIData, ELFFile, EMachine
 
@@ -14,6 +14,8 @@
 EF_ARM_ABI_FLOAT_HARD = 0x00000400
 
 
+# `os.PathLike` not a generic type until Python 3.9, so sticking with `str`
+# as the type for `path` until then.
 @contextlib.contextmanager
 def _parse_elf(path: str) -> Generator[Optional[ELFFile], None, None]:
     try:
@@ -48,12 +50,13 @@
         )
 
 
-def _have_compatible_abi(executable: str, arch: str) -> bool:
-    if arch == "armv7l":
+def _have_compatible_abi(executable: str, archs: Sequence[str]) -> bool:
+    if "armv7l" in archs:
         return _is_linux_armhf(executable)
-    if arch == "i686":
+    if "i686" in archs:
         return _is_linux_i686(executable)
-    return arch in {"x86_64", "aarch64", "ppc64", "ppc64le", "s390x"}
+    allowed_archs = {"x86_64", "aarch64", "ppc64", "ppc64le", "s390x", 
"loongarch64"}
+    return any(arch in allowed_archs for arch in archs)
 
 
 # If glibc ever changes its major version, we need to know what the last
@@ -165,7 +168,7 @@
 
 
 # From PEP 513, PEP 600
-def _is_compatible(name: str, arch: str, version: _GLibCVersion) -> bool:
+def _is_compatible(arch: str, version: _GLibCVersion) -> bool:
     sys_glibc = _get_glibc_version()
     if sys_glibc < version:
         return False
@@ -201,12 +204,22 @@
 }
 
 
-def platform_tags(linux: str, arch: str) -> Iterator[str]:
-    if not _have_compatible_abi(sys.executable, arch):
+def platform_tags(archs: Sequence[str]) -> Iterator[str]:
+    """Generate manylinux tags compatible to the current platform.
+
+    :param archs: Sequence of compatible architectures.
+        The first one shall be the closest to the actual architecture and be 
the part of
+        platform tag after the ``linux_`` prefix, e.g. ``x86_64``.
+        The ``linux_`` prefix is assumed as a prerequisite for the current 
platform to
+        be manylinux-compatible.
+
+    :returns: An iterator of compatible manylinux tags.
+    """
+    if not _have_compatible_abi(sys.executable, archs):
         return
     # Oldest glibc to be supported regardless of architecture is (2, 17).
     too_old_glibc2 = _GLibCVersion(2, 16)
-    if arch in {"x86_64", "i686"}:
+    if set(archs) & {"x86_64", "i686"}:
         # On x86/i686 also oldest glibc to be supported is (2, 5).
         too_old_glibc2 = _GLibCVersion(2, 4)
     current_glibc = _GLibCVersion(*_get_glibc_version())
@@ -220,19 +233,20 @@
     for glibc_major in range(current_glibc.major - 1, 1, -1):
         glibc_minor = _LAST_GLIBC_MINOR[glibc_major]
         glibc_max_list.append(_GLibCVersion(glibc_major, glibc_minor))
-    for glibc_max in glibc_max_list:
-        if glibc_max.major == too_old_glibc2.major:
-            min_minor = too_old_glibc2.minor
-        else:
-            # For other glibc major versions oldest supported is (x, 0).
-            min_minor = -1
-        for glibc_minor in range(glibc_max.minor, min_minor, -1):
-            glibc_version = _GLibCVersion(glibc_max.major, glibc_minor)
-            tag = "manylinux_{}_{}".format(*glibc_version)
-            if _is_compatible(tag, arch, glibc_version):
-                yield linux.replace("linux", tag)
-            # Handle the legacy manylinux1, manylinux2010, manylinux2014 tags.
-            if glibc_version in _LEGACY_MANYLINUX_MAP:
-                legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version]
-                if _is_compatible(legacy_tag, arch, glibc_version):
-                    yield linux.replace("linux", legacy_tag)
+    for arch in archs:
+        for glibc_max in glibc_max_list:
+            if glibc_max.major == too_old_glibc2.major:
+                min_minor = too_old_glibc2.minor
+            else:
+                # For other glibc major versions oldest supported is (x, 0).
+                min_minor = -1
+            for glibc_minor in range(glibc_max.minor, min_minor, -1):
+                glibc_version = _GLibCVersion(glibc_max.major, glibc_minor)
+                tag = "manylinux_{}_{}".format(*glibc_version)
+                if _is_compatible(arch, glibc_version):
+                    yield f"{tag}_{arch}"
+                # Handle the legacy manylinux1, manylinux2010, manylinux2014 
tags.
+                if glibc_version in _LEGACY_MANYLINUX_MAP:
+                    legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version]
+                    if _is_compatible(arch, glibc_version):
+                        yield f"{legacy_tag}_{arch}"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/wheel-0.41.2/src/wheel/vendored/packaging/_musllinux.py 
new/wheel-0.42.0/src/wheel/vendored/packaging/_musllinux.py
--- old/wheel-0.41.2/src/wheel/vendored/packaging/_musllinux.py 2023-08-22 
11:29:26.000000000 +0200
+++ new/wheel-0.42.0/src/wheel/vendored/packaging/_musllinux.py 2023-11-26 
15:35:45.000000000 +0100
@@ -8,7 +8,7 @@
 import re
 import subprocess
 import sys
-from typing import Iterator, NamedTuple, Optional
+from typing import Iterator, NamedTuple, Optional, Sequence
 
 from ._elffile import ELFFile
 
@@ -47,24 +47,27 @@
         return None
     if ld is None or "musl" not in ld:
         return None
-    proc = subprocess.run([ld], stderr=subprocess.PIPE, 
universal_newlines=True)
+    proc = subprocess.run([ld], stderr=subprocess.PIPE, text=True)
     return _parse_musl_version(proc.stderr)
 
 
-def platform_tags(arch: str) -> Iterator[str]:
+def platform_tags(archs: Sequence[str]) -> Iterator[str]:
     """Generate musllinux tags compatible to the current platform.
 
-    :param arch: Should be the part of platform tag after the ``linux_``
-        prefix, e.g. ``x86_64``. The ``linux_`` prefix is assumed as a
-        prerequisite for the current platform to be musllinux-compatible.
+    :param archs: Sequence of compatible architectures.
+        The first one shall be the closest to the actual architecture and be 
the part of
+        platform tag after the ``linux_`` prefix, e.g. ``x86_64``.
+        The ``linux_`` prefix is assumed as a prerequisite for the current 
platform to
+        be musllinux-compatible.
 
     :returns: An iterator of compatible musllinux tags.
     """
     sys_musl = _get_musl_version(sys.executable)
     if sys_musl is None:  # Python not dynamically linked against musl.
         return
-    for minor in range(sys_musl.minor, -1, -1):
-        yield f"musllinux_{sys_musl.major}_{minor}_{arch}"
+    for arch in archs:
+        for minor in range(sys_musl.minor, -1, -1):
+            yield f"musllinux_{sys_musl.major}_{minor}_{arch}"
 
 
 if __name__ == "__main__":  # pragma: no cover
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wheel-0.41.2/src/wheel/vendored/packaging/_parser.py 
new/wheel-0.42.0/src/wheel/vendored/packaging/_parser.py
--- old/wheel-0.41.2/src/wheel/vendored/packaging/_parser.py    2023-08-22 
11:29:26.000000000 +0200
+++ new/wheel-0.42.0/src/wheel/vendored/packaging/_parser.py    2023-11-26 
15:35:45.000000000 +0100
@@ -163,7 +163,11 @@
     if not tokenizer.check("LEFT_BRACKET", peek=True):
         return []
 
-    with tokenizer.enclosing_tokens("LEFT_BRACKET", "RIGHT_BRACKET"):
+    with tokenizer.enclosing_tokens(
+        "LEFT_BRACKET",
+        "RIGHT_BRACKET",
+        around="extras",
+    ):
         tokenizer.consume("WS")
         extras = _parse_extras_list(tokenizer)
         tokenizer.consume("WS")
@@ -203,7 +207,11 @@
     specifier = LEFT_PARENTHESIS WS? version_many WS? RIGHT_PARENTHESIS
               | WS? version_many WS?
     """
-    with tokenizer.enclosing_tokens("LEFT_PARENTHESIS", "RIGHT_PARENTHESIS"):
+    with tokenizer.enclosing_tokens(
+        "LEFT_PARENTHESIS",
+        "RIGHT_PARENTHESIS",
+        around="version specifier",
+    ):
         tokenizer.consume("WS")
         parsed_specifiers = _parse_version_many(tokenizer)
         tokenizer.consume("WS")
@@ -217,7 +225,20 @@
     """
     parsed_specifiers = ""
     while tokenizer.check("SPECIFIER"):
+        span_start = tokenizer.position
         parsed_specifiers += tokenizer.read().text
+        if tokenizer.check("VERSION_PREFIX_TRAIL", peek=True):
+            tokenizer.raise_syntax_error(
+                ".* suffix can only be used with `==` or `!=` operators",
+                span_start=span_start,
+                span_end=tokenizer.position + 1,
+            )
+        if tokenizer.check("VERSION_LOCAL_LABEL_TRAIL", peek=True):
+            tokenizer.raise_syntax_error(
+                "Local version label can only be used with `==` or `!=` 
operators",
+                span_start=span_start,
+                span_end=tokenizer.position,
+            )
         tokenizer.consume("WS")
         if not tokenizer.check("COMMA"):
             break
@@ -231,7 +252,13 @@
 # Recursive descent parser for marker expression
 # 
--------------------------------------------------------------------------------------
 def parse_marker(source: str) -> MarkerList:
-    return _parse_marker(Tokenizer(source, rules=DEFAULT_RULES))
+    return _parse_full_marker(Tokenizer(source, rules=DEFAULT_RULES))
+
+
+def _parse_full_marker(tokenizer: Tokenizer) -> MarkerList:
+    retval = _parse_marker(tokenizer)
+    tokenizer.expect("END", expected="end of marker expression")
+    return retval
 
 
 def _parse_marker(tokenizer: Tokenizer) -> MarkerList:
@@ -254,7 +281,11 @@
 
     tokenizer.consume("WS")
     if tokenizer.check("LEFT_PARENTHESIS", peek=True):
-        with tokenizer.enclosing_tokens("LEFT_PARENTHESIS", 
"RIGHT_PARENTHESIS"):
+        with tokenizer.enclosing_tokens(
+            "LEFT_PARENTHESIS",
+            "RIGHT_PARENTHESIS",
+            around="marker expression",
+        ):
             tokenizer.consume("WS")
             marker: MarkerAtom = _parse_marker(tokenizer)
             tokenizer.consume("WS")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/wheel-0.41.2/src/wheel/vendored/packaging/_tokenizer.py 
new/wheel-0.42.0/src/wheel/vendored/packaging/_tokenizer.py
--- old/wheel-0.41.2/src/wheel/vendored/packaging/_tokenizer.py 2023-08-22 
11:29:26.000000000 +0200
+++ new/wheel-0.42.0/src/wheel/vendored/packaging/_tokenizer.py 2023-11-26 
15:35:45.000000000 +0100
@@ -78,6 +78,8 @@
     "AT": r"\@",
     "URL": r"[^ \t]+",
     "IDENTIFIER": r"\b[a-zA-Z0-9][a-zA-Z0-9._-]*\b",
+    "VERSION_PREFIX_TRAIL": r"\.\*",
+    "VERSION_LOCAL_LABEL_TRAIL": r"\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*",
     "WS": r"[ \t]+",
     "END": r"$",
 }
@@ -167,21 +169,23 @@
         )
 
     @contextlib.contextmanager
-    def enclosing_tokens(self, open_token: str, close_token: str) -> 
Iterator[bool]:
+    def enclosing_tokens(
+        self, open_token: str, close_token: str, *, around: str
+    ) -> Iterator[None]:
         if self.check(open_token):
             open_position = self.position
             self.read()
         else:
             open_position = None
 
-        yield open_position is not None
+        yield
 
         if open_position is None:
             return
 
         if not self.check(close_token):
             self.raise_syntax_error(
-                f"Expected closing {close_token}",
+                f"Expected matching {close_token} for {open_token}, after 
{around}",
                 span_start=open_position,
             )
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wheel-0.41.2/src/wheel/vendored/packaging/markers.py 
new/wheel-0.42.0/src/wheel/vendored/packaging/markers.py
--- old/wheel-0.41.2/src/wheel/vendored/packaging/markers.py    2023-08-22 
11:29:26.000000000 +0200
+++ new/wheel-0.42.0/src/wheel/vendored/packaging/markers.py    2023-11-26 
15:35:45.000000000 +0100
@@ -8,7 +8,14 @@
 import sys
 from typing import Any, Callable, Dict, List, Optional, Tuple, Union
 
-from ._parser import MarkerAtom, MarkerList, Op, Value, Variable, parse_marker
+from ._parser import (
+    MarkerAtom,
+    MarkerList,
+    Op,
+    Value,
+    Variable,
+    parse_marker as _parse_marker,
+)
 from ._tokenizer import ParserSyntaxError
 from .specifiers import InvalidSpecifier, Specifier
 from .utils import canonicalize_name
@@ -189,7 +196,7 @@
         #       packaging.requirements.Requirement. If any additional logic is
         #       added here, make sure to mirror/adapt Requirement.
         try:
-            self._markers = _normalize_extra_values(parse_marker(marker))
+            self._markers = _normalize_extra_values(_parse_marker(marker))
             # The attribute `_markers` can be described in terms of a 
recursive type:
             # MarkerList = List[Union[Tuple[Node, ...], str, MarkerList]]
             #
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/wheel-0.41.2/src/wheel/vendored/packaging/requirements.py 
new/wheel-0.42.0/src/wheel/vendored/packaging/requirements.py
--- old/wheel-0.41.2/src/wheel/vendored/packaging/requirements.py       
2023-08-22 11:29:26.000000000 +0200
+++ new/wheel-0.42.0/src/wheel/vendored/packaging/requirements.py       
2023-11-26 15:35:45.000000000 +0100
@@ -2,13 +2,13 @@
 # 2.0, and the BSD License. See the LICENSE file in the root of this repository
 # for complete details.
 
-import urllib.parse
-from typing import Any, List, Optional, Set
+from typing import Any, Iterator, Optional, Set
 
-from ._parser import parse_requirement
+from ._parser import parse_requirement as _parse_requirement
 from ._tokenizer import ParserSyntaxError
 from .markers import Marker, _normalize_extra_values
 from .specifiers import SpecifierSet
+from .utils import canonicalize_name
 
 
 class InvalidRequirement(ValueError):
@@ -32,23 +32,12 @@
 
     def __init__(self, requirement_string: str) -> None:
         try:
-            parsed = parse_requirement(requirement_string)
+            parsed = _parse_requirement(requirement_string)
         except ParserSyntaxError as e:
             raise InvalidRequirement(str(e)) from e
 
         self.name: str = parsed.name
-        if parsed.url:
-            parsed_url = urllib.parse.urlparse(parsed.url)
-            if parsed_url.scheme == "file":
-                if urllib.parse.urlunparse(parsed_url) != parsed.url:
-                    raise InvalidRequirement("Invalid URL given")
-            elif not (parsed_url.scheme and parsed_url.netloc) or (
-                not parsed_url.scheme and not parsed_url.netloc
-            ):
-                raise InvalidRequirement(f"Invalid URL: {parsed.url}")
-            self.url: Optional[str] = parsed.url
-        else:
-            self.url = None
+        self.url: Optional[str] = parsed.url or None
         self.extras: Set[str] = set(parsed.extras if parsed.extras else [])
         self.specifier: SpecifierSet = SpecifierSet(parsed.specifier)
         self.marker: Optional[Marker] = None
@@ -56,38 +45,44 @@
             self.marker = Marker.__new__(Marker)
             self.marker._markers = _normalize_extra_values(parsed.marker)
 
-    def __str__(self) -> str:
-        parts: List[str] = [self.name]
+    def _iter_parts(self, name: str) -> Iterator[str]:
+        yield name
 
         if self.extras:
             formatted_extras = ",".join(sorted(self.extras))
-            parts.append(f"[{formatted_extras}]")
+            yield f"[{formatted_extras}]"
 
         if self.specifier:
-            parts.append(str(self.specifier))
+            yield str(self.specifier)
 
         if self.url:
-            parts.append(f"@ {self.url}")
+            yield f"@ {self.url}"
             if self.marker:
-                parts.append(" ")
+                yield " "
 
         if self.marker:
-            parts.append(f"; {self.marker}")
+            yield f"; {self.marker}"
 
-        return "".join(parts)
+    def __str__(self) -> str:
+        return "".join(self._iter_parts(self.name))
 
     def __repr__(self) -> str:
         return f"<Requirement('{self}')>"
 
     def __hash__(self) -> int:
-        return hash((self.__class__.__name__, str(self)))
+        return hash(
+            (
+                self.__class__.__name__,
+                *self._iter_parts(canonicalize_name(self.name)),
+            )
+        )
 
     def __eq__(self, other: Any) -> bool:
         if not isinstance(other, Requirement):
             return NotImplemented
 
         return (
-            self.name == other.name
+            canonicalize_name(self.name) == canonicalize_name(other.name)
             and self.extras == other.extras
             and self.specifier == other.specifier
             and self.url == other.url
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/wheel-0.41.2/src/wheel/vendored/packaging/specifiers.py 
new/wheel-0.42.0/src/wheel/vendored/packaging/specifiers.py
--- old/wheel-0.41.2/src/wheel/vendored/packaging/specifiers.py 2023-08-22 
11:29:26.000000000 +0200
+++ new/wheel-0.42.0/src/wheel/vendored/packaging/specifiers.py 2023-11-26 
15:35:45.000000000 +0100
@@ -1,5 +1,4 @@
 # This file is dual licensed under the terms of the Apache License, Version
-
 # 2.0, and the BSD License. See the LICENSE file in the root of this repository
 # for complete details.
 """
@@ -253,7 +252,8 @@
         # Store whether or not this Specifier should accept prereleases
         self._prereleases = prereleases
 
-    @property
+    # https://github.com/python/mypy/pull/13475#pullrequestreview-1079784515
+    @property  # type: ignore[override]
     def prereleases(self) -> bool:
         # If there is an explicit prereleases set for this, then we'll just
         # blindly use that.
@@ -399,7 +399,9 @@
         # We need special logic to handle prefix matching
         if spec.endswith(".*"):
             # In the case of prefix matching we want to ignore local segment.
-            normalized_prospective = canonicalize_version(prospective.public)
+            normalized_prospective = canonicalize_version(
+                prospective.public, strip_trailing_zero=False
+            )
             # Get the normalized version string ignoring the trailing .*
             normalized_spec = canonicalize_version(spec[:-2], 
strip_trailing_zero=False)
             # Split the spec out by dots, and pretend that there is an implicit
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wheel-0.41.2/src/wheel/vendored/packaging/tags.py 
new/wheel-0.42.0/src/wheel/vendored/packaging/tags.py
--- old/wheel-0.41.2/src/wheel/vendored/packaging/tags.py       2023-08-22 
11:29:26.000000000 +0200
+++ new/wheel-0.42.0/src/wheel/vendored/packaging/tags.py       2023-11-26 
15:35:45.000000000 +0100
@@ -4,6 +4,7 @@
 
 import logging
 import platform
+import struct
 import subprocess
 import sys
 import sysconfig
@@ -37,7 +38,7 @@
 }
 
 
-_32_BIT_INTERPRETER = sys.maxsize <= 2**32
+_32_BIT_INTERPRETER = struct.calcsize("P") == 4
 
 
 class Tag:
@@ -111,7 +112,7 @@
 
 
 def _get_config_var(name: str, warn: bool = False) -> Union[int, str, None]:
-    value = sysconfig.get_config_var(name)
+    value: Union[int, str, None] = sysconfig.get_config_var(name)
     if value is None and warn:
         logger.debug(
             "Config variable '%s' is unset, Python ABI tag may be incorrect", 
name
@@ -120,7 +121,7 @@
 
 
 def _normalize_string(string: str) -> str:
-    return string.replace(".", "_").replace("-", "_")
+    return string.replace(".", "_").replace("-", "_").replace(" ", "_")
 
 
 def _abi3_applies(python_version: PythonVersion) -> bool:
@@ -406,7 +407,7 @@
                 check=True,
                 env={"SYSTEM_VERSION_COMPAT": "0"},
                 stdout=subprocess.PIPE,
-                universal_newlines=True,
+                text=True,
             ).stdout
             version = cast("MacVersion", tuple(map(int, 
version_str.split(".")[:2])))
     else:
@@ -469,15 +470,21 @@
 
 def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]:
     linux = _normalize_string(sysconfig.get_platform())
+    if not linux.startswith("linux_"):
+        # we should never be here, just yield the sysconfig one and return
+        yield linux
+        return
     if is_32bit:
         if linux == "linux_x86_64":
             linux = "linux_i686"
         elif linux == "linux_aarch64":
-            linux = "linux_armv7l"
+            linux = "linux_armv8l"
     _, arch = linux.split("_", 1)
-    yield from _manylinux.platform_tags(linux, arch)
-    yield from _musllinux.platform_tags(arch)
-    yield linux
+    archs = {"armv8l": ["armv8l", "armv7l"]}.get(arch, [arch])
+    yield from _manylinux.platform_tags(archs)
+    yield from _musllinux.platform_tags(archs)
+    for arch in archs:
+        yield f"linux_{arch}"
 
 
 def _generic_platforms() -> Iterator[str]:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wheel-0.41.2/src/wheel/vendored/packaging/utils.py 
new/wheel-0.42.0/src/wheel/vendored/packaging/utils.py
--- old/wheel-0.41.2/src/wheel/vendored/packaging/utils.py      2023-08-22 
11:29:26.000000000 +0200
+++ new/wheel-0.42.0/src/wheel/vendored/packaging/utils.py      2023-11-26 
15:35:45.000000000 +0100
@@ -12,6 +12,12 @@
 NormalizedName = NewType("NormalizedName", str)
 
 
+class InvalidName(ValueError):
+    """
+    An invalid distribution name; users should refer to the packaging user 
guide.
+    """
+
+
 class InvalidWheelFilename(ValueError):
     """
     An invalid wheel filename was found, users should refer to PEP 427.
@@ -24,17 +30,28 @@
     """
 
 
+# Core metadata spec for `Name`
+_validate_regex = re.compile(
+    r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.IGNORECASE
+)
 _canonicalize_regex = re.compile(r"[-_.]+")
+_normalized_regex = 
re.compile(r"^([a-z0-9]|[a-z0-9]([a-z0-9-](?!--))*[a-z0-9])$")
 # PEP 427: The build number must start with a digit.
 _build_tag_regex = re.compile(r"(\d+)(.*)")
 
 
-def canonicalize_name(name: str) -> NormalizedName:
+def canonicalize_name(name: str, *, validate: bool = False) -> NormalizedName:
+    if validate and not _validate_regex.match(name):
+        raise InvalidName(f"name is invalid: {name!r}")
     # This is taken from PEP 503.
     value = _canonicalize_regex.sub("-", name).lower()
     return cast(NormalizedName, value)
 
 
+def is_normalized_name(name: str) -> bool:
+    return _normalized_regex.match(name) is not None
+
+
 def canonicalize_version(
     version: Union[Version, str], *, strip_trailing_zero: bool = True
 ) -> str:
@@ -100,11 +117,18 @@
 
     parts = filename.split("-", dashes - 2)
     name_part = parts[0]
-    # See PEP 427 for the rules on escaping the project name
+    # See PEP 427 for the rules on escaping the project name.
     if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is 
None:
         raise InvalidWheelFilename(f"Invalid project name: {filename}")
     name = canonicalize_name(name_part)
-    version = Version(parts[1])
+
+    try:
+        version = Version(parts[1])
+    except InvalidVersion as e:
+        raise InvalidWheelFilename(
+            f"Invalid wheel filename (invalid version): {filename}"
+        ) from e
+
     if dashes == 5:
         build_part = parts[2]
         build_match = _build_tag_regex.match(build_part)
@@ -137,5 +161,12 @@
         raise InvalidSdistFilename(f"Invalid sdist filename: {filename}")
 
     name = canonicalize_name(name_part)
-    version = Version(version_part)
+
+    try:
+        version = Version(version_part)
+    except InvalidVersion as e:
+        raise InvalidSdistFilename(
+            f"Invalid sdist filename (invalid version): {filename}"
+        ) from e
+
     return (name, version)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wheel-0.41.2/src/wheel/vendored/packaging/version.py 
new/wheel-0.42.0/src/wheel/vendored/packaging/version.py
--- old/wheel-0.41.2/src/wheel/vendored/packaging/version.py    2023-08-22 
11:29:26.000000000 +0200
+++ new/wheel-0.42.0/src/wheel/vendored/packaging/version.py    2023-11-26 
15:35:45.000000000 +0100
@@ -7,37 +7,39 @@
     from packaging.version import parse, Version
 """
 
-import collections
 import itertools
 import re
-from typing import Callable, Optional, SupportsInt, Tuple, Union
+from typing import Any, Callable, NamedTuple, Optional, SupportsInt, Tuple, 
Union
 
 from ._structures import Infinity, InfinityType, NegativeInfinity, 
NegativeInfinityType
 
 __all__ = ["VERSION_PATTERN", "parse", "Version", "InvalidVersion"]
 
-InfiniteTypes = Union[InfinityType, NegativeInfinityType]
-PrePostDevType = Union[InfiniteTypes, Tuple[str, int]]
-SubLocalType = Union[InfiniteTypes, int, str]
-LocalType = Union[
+LocalType = Tuple[Union[int, str], ...]
+
+CmpPrePostDevType = Union[InfinityType, NegativeInfinityType, Tuple[str, int]]
+CmpLocalType = Union[
     NegativeInfinityType,
-    Tuple[
-        Union[
-            SubLocalType,
-            Tuple[SubLocalType, str],
-            Tuple[NegativeInfinityType, SubLocalType],
-        ],
-        ...,
-    ],
+    Tuple[Union[Tuple[int, str], Tuple[NegativeInfinityType, Union[int, 
str]]], ...],
 ]
 CmpKey = Tuple[
-    int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, 
LocalType
+    int,
+    Tuple[int, ...],
+    CmpPrePostDevType,
+    CmpPrePostDevType,
+    CmpPrePostDevType,
+    CmpLocalType,
 ]
 VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]
 
-_Version = collections.namedtuple(
-    "_Version", ["epoch", "release", "dev", "pre", "post", "local"]
-)
+
+class _Version(NamedTuple):
+    epoch: int
+    release: Tuple[int, ...]
+    dev: Optional[Tuple[str, int]]
+    pre: Optional[Tuple[str, int]]
+    post: Optional[Tuple[str, int]]
+    local: Optional[LocalType]
 
 
 def parse(version: str) -> "Version":
@@ -63,7 +65,7 @@
 
 
 class _BaseVersion:
-    _key: CmpKey
+    _key: Tuple[Any, ...]
 
     def __hash__(self) -> int:
         return hash(self._key)
@@ -117,7 +119,7 @@
         (?P<release>[0-9]+(?:\.[0-9]+)*)                  # release segment
         (?P<pre>                                          # pre-release
             [-_\.]?
-            (?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
+            (?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)
             [-_\.]?
             (?P<pre_n>[0-9]+)?
         )?
@@ -179,6 +181,7 @@
     """
 
     _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | 
re.IGNORECASE)
+    _key: CmpKey
 
     def __init__(self, version: str) -> None:
         """Initialize a Version object.
@@ -268,8 +271,7 @@
         >>> Version("1!2.0.0").epoch
         1
         """
-        _epoch: int = self._version.epoch
-        return _epoch
+        return self._version.epoch
 
     @property
     def release(self) -> Tuple[int, ...]:
@@ -285,8 +287,7 @@
         Includes trailing zeroes but not the epoch or any pre-release / 
development /
         post-release suffixes.
         """
-        _release: Tuple[int, ...] = self._version.release
-        return _release
+        return self._version.release
 
     @property
     def pre(self) -> Optional[Tuple[str, int]]:
@@ -301,8 +302,7 @@
         >>> Version("1.2.3rc1").pre
         ('rc', 1)
         """
-        _pre: Optional[Tuple[str, int]] = self._version.pre
-        return _pre
+        return self._version.pre
 
     @property
     def post(self) -> Optional[int]:
@@ -450,7 +450,7 @@
 
 
 def _parse_letter_version(
-    letter: str, number: Union[str, bytes, SupportsInt]
+    letter: Optional[str], number: Union[str, bytes, SupportsInt, None]
 ) -> Optional[Tuple[str, int]]:
 
     if letter:
@@ -488,7 +488,7 @@
 _local_version_separators = re.compile(r"[\._-]")
 
 
-def _parse_local_version(local: str) -> Optional[LocalType]:
+def _parse_local_version(local: Optional[str]) -> Optional[LocalType]:
     """
     Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
     """
@@ -506,7 +506,7 @@
     pre: Optional[Tuple[str, int]],
     post: Optional[Tuple[str, int]],
     dev: Optional[Tuple[str, int]],
-    local: Optional[Tuple[SubLocalType]],
+    local: Optional[LocalType],
 ) -> CmpKey:
 
     # When we compare a release version, we want to compare it with all of the
@@ -523,7 +523,7 @@
     # if there is not a pre or a post segment. If we have one of those then
     # the normal sorting rules will handle this case correctly.
     if pre is None and post is None and dev is not None:
-        _pre: PrePostDevType = NegativeInfinity
+        _pre: CmpPrePostDevType = NegativeInfinity
     # Versions without a pre-release (except as noted above) should sort after
     # those with one.
     elif pre is None:
@@ -533,21 +533,21 @@
 
     # Versions without a post segment should sort before those with one.
     if post is None:
-        _post: PrePostDevType = NegativeInfinity
+        _post: CmpPrePostDevType = NegativeInfinity
 
     else:
         _post = post
 
     # Versions without a development segment should sort after those with one.
     if dev is None:
-        _dev: PrePostDevType = Infinity
+        _dev: CmpPrePostDevType = Infinity
 
     else:
         _dev = dev
 
     if local is None:
         # Versions without a local segment should sort before those with one.
-        _local: LocalType = NegativeInfinity
+        _local: CmpLocalType = NegativeInfinity
     else:
         # Versions with a local segment need that segment parsed to implement
         # the sorting rules in PEP440.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wheel-0.41.2/src/wheel/vendored/vendor.txt 
new/wheel-0.42.0/src/wheel/vendored/vendor.txt
--- old/wheel-0.41.2/src/wheel/vendored/vendor.txt      2023-08-22 
11:29:26.000000000 +0200
+++ new/wheel-0.42.0/src/wheel/vendored/vendor.txt      2023-11-26 
15:35:45.000000000 +0100
@@ -1 +1 @@
-packaging==23.0
+packaging==23.2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wheel-0.41.2/tests/cli/test_pack.py 
new/wheel-0.42.0/tests/cli/test_pack.py
--- old/wheel-0.41.2/tests/cli/test_pack.py     2023-08-22 11:29:26.000000000 
+0200
+++ new/wheel-0.42.0/tests/cli/test_pack.py     2023-11-26 15:35:45.000000000 
+0100
@@ -1,7 +1,9 @@
 from __future__ import annotations
 
+import email.policy
 import os
-from textwrap import dedent
+from email.message import Message
+from email.parser import BytesParser
 from zipfile import ZipFile
 
 import pytest
@@ -55,24 +57,25 @@
             if line and not line.startswith(b"test-1.0.dist-info/WHEEL,")
         )
 
-        new_wheel_file_content = zf.read("test-1.0.dist-info/WHEEL")
+        parser = BytesParser(policy=email.policy.compat32)
+        new_wheel_file_content = 
parser.parsebytes(zf.read("test-1.0.dist-info/WHEEL"))
 
     assert new_record_lines == old_record_lines
 
-    expected_build_num = build_tag_arg or existing_build_tag
-    expected_wheel_content = dedent(
-        """\
-        Wheel-Version: 1.0
-        Generator: bdist_wheel (0.30.0)
-        Root-Is-Purelib: false
-        Tag: py2-none-any
-        Tag: py3-none-any
-    """.replace(
-            "\n", "\r\n"
-        )
+    # Line endings and trailing blank line will depend on whether WHEEL
+    # was modified.  Circumvent this by comparing parsed key/value pairs.
+    expected_wheel_content = Message()
+    expected_wheel_content["Wheel-Version"] = "1.0"
+    expected_wheel_content["Generator"] = "bdist_wheel (0.30.0)"
+    expected_wheel_content["Root-Is-Purelib"] = "false"
+    expected_wheel_content["Tag"] = "py2-none-any"
+    expected_wheel_content["Tag"] = "py3-none-any"
+    expected_build_num = (
+        build_tag_arg if build_tag_arg is not None else existing_build_tag
     )
     if expected_build_num:
-        expected_wheel_content += "Build: %s\r\n" % expected_build_num
+        expected_wheel_content["Build"] = expected_build_num
 
-    expected_wheel_content = expected_wheel_content.encode("ascii")
-    assert new_wheel_file_content == expected_wheel_content
+    assert sorted(new_wheel_file_content.items()) == sorted(
+        expected_wheel_content.items()
+    )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wheel-0.41.2/tests/cli/test_tags.py 
new/wheel-0.42.0/tests/cli/test_tags.py
--- old/wheel-0.41.2/tests/cli/test_tags.py     2023-08-22 11:29:26.000000000 
+0200
+++ new/wheel-0.42.0/tests/cli/test_tags.py     2023-11-26 15:35:45.000000000 
+0100
@@ -39,8 +39,8 @@
     with WheelFile(str(output_file)) as f:
         output = f.read(f.dist_info_path + "/WHEEL")
     assert (
-        output == b"Wheel-Version: 1.0\r\nGenerator: bdist_wheel (0.30.0)"
-        b"\r\nRoot-Is-Purelib: false\r\nTag: py3-none-any\r\n"
+        output == b"Wheel-Version: 1.0\nGenerator: bdist_wheel (0.30.0)"
+        b"\nRoot-Is-Purelib: false\nTag: py3-none-any\n\n"
     )
     output_file.unlink()
 
@@ -116,6 +116,8 @@
     assert TESTWHEEL_NAME.replace("-py2", "-1bah-py2") == newname
     output_file = wheelpath.parent / newname
     assert output_file.exists()
+    newname = tags(str(wheelpath), build_tag="")
+    assert TESTWHEEL_NAME == newname
     output_file.unlink()
 
 
@@ -151,9 +153,9 @@
         output = f.read(f.dist_info_path + "/WHEEL")
     assert (
         output
-        == b"Wheel-Version: 1.0\r\nGenerator: bdist_wheel 
(0.30.0)\r\nRoot-Is-Purelib:"
-        b" false\r\nTag: py2-none-linux_x86_64\r\nTag: 
py3-none-linux_x86_64\r\nTag:"
-        b" py4-none-linux_x86_64\r\nBuild: 1\r\n"
+        == b"Wheel-Version: 1.0\nGenerator: bdist_wheel 
(0.30.0)\nRoot-Is-Purelib:"
+        b" false\nTag: py2-none-linux_x86_64\nTag: py3-none-linux_x86_64\nTag:"
+        b" py4-none-linux_x86_64\nBuild: 1\n\n"
     )
     output_file.unlink()
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wheel-0.41.2/tests/test_bdist_wheel.py 
new/wheel-0.42.0/tests/test_bdist_wheel.py
--- old/wheel-0.41.2/tests/test_bdist_wheel.py  2023-08-22 11:29:26.000000000 
+0200
+++ new/wheel-0.42.0/tests/test_bdist_wheel.py  2023-11-26 15:35:45.000000000 
+0100
@@ -287,6 +287,12 @@
     )
 
 
+def test_get_abi_tag_windows(monkeypatch):
+    monkeypatch.setattr(tags, "interpreter_name", lambda: "cp")
+    monkeypatch.setattr(sysconfig, "get_config_var", lambda x: 
"cp313-win_amd64")
+    assert get_abi_tag() == "cp313"
+
+
 def test_get_abi_tag_pypy_old(monkeypatch):
     monkeypatch.setattr(tags, "interpreter_name", lambda: "pp")
     monkeypatch.setattr(sysconfig, "get_config_var", lambda x: "pypy36-pp73")

Reply via email to