From 92921a032a371f159b5697783f693b88ed5d7a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sun, 10 Mar 2024 13:57:14 +0200 Subject: [PATCH] Updated vendored `packaging` to 24.0 --- docs/news.rst | 1 + src/wheel/bdist_wheel.py | 6 +- src/wheel/cli/pack.py | 4 +- src/wheel/metadata.py | 1 + src/wheel/vendored/packaging/_manylinux.py | 16 +++-- src/wheel/vendored/packaging/_musllinux.py | 2 +- src/wheel/vendored/packaging/_parser.py | 7 +- src/wheel/vendored/packaging/markers.py | 3 +- src/wheel/vendored/packaging/requirements.py | 2 +- src/wheel/vendored/packaging/specifiers.py | 69 ++++++++++---------- src/wheel/vendored/packaging/tags.py | 44 +++++++++---- src/wheel/vendored/packaging/version.py | 2 - src/wheel/vendored/vendor.txt | 2 +- src/wheel/wheelfile.py | 4 +- 14 files changed, 95 insertions(+), 68 deletions(-) diff --git a/docs/news.rst b/docs/news.rst index 98c0b5b5..1f5f8f38 100644 --- a/docs/news.rst +++ b/docs/news.rst @@ -4,6 +4,7 @@ Release Notes **UNRELEASED** - Dropped support for Python 3.7 +- Updated vendored ``packaging`` to 24.0 **0.42.0 (2023-11-26)** diff --git a/src/wheel/bdist_wheel.py b/src/wheel/bdist_wheel.py index cc3e2597..6b811ee3 100644 --- a/src/wheel/bdist_wheel.py +++ b/src/wheel/bdist_wheel.py @@ -412,9 +412,9 @@ def run(self): ) self.set_undefined_options("install_egg_info", ("target", "egginfo_dir")) - distinfo_dirname = "{}-{}.dist-info".format( - safer_name(self.distribution.get_name()), - safer_version(self.distribution.get_version()), + distinfo_dirname = ( + f"{safer_name(self.distribution.get_name())}-" + f"{safer_version(self.distribution.get_version())}.dist-info" ) distinfo_dir = os.path.join(self.bdist_dir, distinfo_dirname) self.egg2dist(self.egginfo_dir, distinfo_dir) diff --git a/src/wheel/cli/pack.py b/src/wheel/cli/pack.py index e7bb96d9..64469c0c 100644 --- a/src/wheel/cli/pack.py +++ b/src/wheel/cli/pack.py @@ -45,8 +45,8 @@ def pack(directory: str, dest_dir: str, build_number: str | None) -> None: if not tags: raise WheelError( - "No tags present in {}/WHEEL; cannot determine target wheel " - "filename".format(dist_info_dir) + f"No tags present in {dist_info_dir}/WHEEL; cannot determine target " + f"wheel filename" ) # Set the wheel file name and add/replace/remove the Build tag in .dist-info/WHEEL diff --git a/src/wheel/metadata.py b/src/wheel/metadata.py index dbd1577d..6aa43628 100644 --- a/src/wheel/metadata.py +++ b/src/wheel/metadata.py @@ -1,6 +1,7 @@ """ Tools for converting old- to new-style metadata. """ + from __future__ import annotations import functools diff --git a/src/wheel/vendored/packaging/_manylinux.py b/src/wheel/vendored/packaging/_manylinux.py index 3705d50d..1f5f4ab3 100644 --- a/src/wheel/vendored/packaging/_manylinux.py +++ b/src/wheel/vendored/packaging/_manylinux.py @@ -55,7 +55,15 @@ def _have_compatible_abi(executable: str, archs: Sequence[str]) -> bool: return _is_linux_armhf(executable) if "i686" in archs: return _is_linux_i686(executable) - allowed_archs = {"x86_64", "aarch64", "ppc64", "ppc64le", "s390x", "loongarch64"} + allowed_archs = { + "x86_64", + "aarch64", + "ppc64", + "ppc64le", + "s390x", + "loongarch64", + "riscv64", + } return any(arch in allowed_archs for arch in archs) @@ -82,7 +90,7 @@ def _glibc_version_string_confstr() -> Optional[str]: # https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183 try: # Should be a string like "glibc 2.17". - version_string: str = getattr(os, "confstr")("CS_GNU_LIBC_VERSION") + version_string: Optional[str] = os.confstr("CS_GNU_LIBC_VERSION") assert version_string is not None _, version = version_string.rsplit() except (AssertionError, AttributeError, OSError, ValueError): @@ -159,7 +167,7 @@ def _parse_glibc_version(version_str: str) -> Tuple[int, int]: return int(m.group("major")), int(m.group("minor")) -@functools.lru_cache() +@functools.lru_cache def _get_glibc_version() -> Tuple[int, int]: version_str = _glibc_version_string() if version_str is None: @@ -174,7 +182,7 @@ def _is_compatible(arch: str, version: _GLibCVersion) -> bool: return False # Check for presence of _manylinux module. try: - import _manylinux # noqa + import _manylinux except ImportError: return True if hasattr(_manylinux, "manylinux_compatible"): diff --git a/src/wheel/vendored/packaging/_musllinux.py b/src/wheel/vendored/packaging/_musllinux.py index 86419df9..eb4251b5 100644 --- a/src/wheel/vendored/packaging/_musllinux.py +++ b/src/wheel/vendored/packaging/_musllinux.py @@ -28,7 +28,7 @@ def _parse_musl_version(output: str) -> Optional[_MuslVersion]: return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2))) -@functools.lru_cache() +@functools.lru_cache def _get_musl_version(executable: str) -> Optional[_MuslVersion]: """Detect currently-running musl runtime version. diff --git a/src/wheel/vendored/packaging/_parser.py b/src/wheel/vendored/packaging/_parser.py index 4576981c..513686a2 100644 --- a/src/wheel/vendored/packaging/_parser.py +++ b/src/wheel/vendored/packaging/_parser.py @@ -1,6 +1,6 @@ """Handwritten parser of dependency specifiers. -The docstring for each __parse_* function contains ENBF-inspired grammar representing +The docstring for each __parse_* function contains EBNF-inspired grammar representing the implementation. """ @@ -324,10 +324,7 @@ def _parse_marker_var(tokenizer: Tokenizer) -> MarkerVar: def process_env_var(env_var: str) -> Variable: - if ( - env_var == "platform_python_implementation" - or env_var == "python_implementation" - ): + if env_var in ("platform_python_implementation", "python_implementation"): return Variable("platform_python_implementation") else: return Variable(env_var) diff --git a/src/wheel/vendored/packaging/markers.py b/src/wheel/vendored/packaging/markers.py index 8b98fca7..c96d22a5 100644 --- a/src/wheel/vendored/packaging/markers.py +++ b/src/wheel/vendored/packaging/markers.py @@ -14,6 +14,8 @@ Op, Value, Variable, +) +from ._parser import ( parse_marker as _parse_marker, ) from ._tokenizer import ParserSyntaxError @@ -69,7 +71,6 @@ def _normalize_extra_values(results: Any) -> Any: def _format_marker( marker: Union[List[str], MarkerAtom, str], first: Optional[bool] = True ) -> str: - assert isinstance(marker, (list, tuple, str)) # Sometimes we have a structure like [[...]] which is a single item list diff --git a/src/wheel/vendored/packaging/requirements.py b/src/wheel/vendored/packaging/requirements.py index 0c00eba3..bdc43a7e 100644 --- a/src/wheel/vendored/packaging/requirements.py +++ b/src/wheel/vendored/packaging/requirements.py @@ -38,7 +38,7 @@ def __init__(self, requirement_string: str) -> None: self.name: str = parsed.name self.url: Optional[str] = parsed.url or None - self.extras: Set[str] = set(parsed.extras if parsed.extras else []) + self.extras: Set[str] = set(parsed.extras or []) self.specifier: SpecifierSet = SpecifierSet(parsed.specifier) self.marker: Optional[Marker] = None if parsed.marker is not None: diff --git a/src/wheel/vendored/packaging/specifiers.py b/src/wheel/vendored/packaging/specifiers.py index ba8fe37b..6d4066ae 100644 --- a/src/wheel/vendored/packaging/specifiers.py +++ b/src/wheel/vendored/packaging/specifiers.py @@ -11,17 +11,7 @@ import abc import itertools import re -from typing import ( - Callable, - Iterable, - Iterator, - List, - Optional, - Set, - Tuple, - TypeVar, - Union, -) +from typing import Callable, Iterable, Iterator, List, Optional, Tuple, TypeVar, Union from .utils import canonicalize_version from .version import Version @@ -374,7 +364,6 @@ def _get_operator(self, op: str) -> CallableOperator: return operator_callable def _compare_compatible(self, prospective: Version, spec: str) -> bool: - # Compatible releases have an equivalent combination of >= and ==. That # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to # implement this in terms of the other specifiers instead of @@ -383,7 +372,7 @@ def _compare_compatible(self, prospective: Version, spec: str) -> bool: # We want everything but the last item in the version, but we want to # ignore suffix segments. - prefix = ".".join( + prefix = _version_join( list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1] ) @@ -395,7 +384,6 @@ def _compare_compatible(self, prospective: Version, spec: str) -> bool: ) def _compare_equal(self, prospective: Version, spec: str) -> bool: - # We need special logic to handle prefix matching if spec.endswith(".*"): # In the case of prefix matching we want to ignore local segment. @@ -404,13 +392,13 @@ def _compare_equal(self, prospective: Version, spec: str) -> bool: ) # 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 - # dot in between a release segment and a pre-release segment. + # Split the spec out by bangs and dots, and pretend that there is + # an implicit dot in between a release segment and a pre-release segment. split_spec = _version_split(normalized_spec) - # Split the prospective version out by dots, and pretend that there - # is an implicit dot in between a release segment and a pre-release - # segment. + # Split the prospective version out by bangs and dots, and pretend + # that there is an implicit dot in between a release segment and + # a pre-release segment. split_prospective = _version_split(normalized_prospective) # 0-pad the prospective version before shortening it to get the correct @@ -439,21 +427,18 @@ def _compare_not_equal(self, prospective: Version, spec: str) -> bool: return not self._compare_equal(prospective, spec) def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool: - # NB: Local version identifiers are NOT permitted in the version # specifier, so local version labels can be universally removed from # the prospective version. return Version(prospective.public) <= Version(spec) def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool: - # NB: Local version identifiers are NOT permitted in the version # specifier, so local version labels can be universally removed from # the prospective version. return Version(prospective.public) >= Version(spec) def _compare_less_than(self, prospective: Version, spec_str: str) -> bool: - # Convert our spec to a Version instance, since we'll want to work with # it as a version. spec = Version(spec_str) @@ -478,7 +463,6 @@ def _compare_less_than(self, prospective: Version, spec_str: str) -> bool: return True def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool: - # Convert our spec to a Version instance, since we'll want to work with # it as a version. spec = Version(spec_str) @@ -644,8 +628,19 @@ def filter( def _version_split(version: str) -> List[str]: + """Split version into components. + + The split components are intended for version comparison. The logic does + not attempt to retain the original version string, so joining the + components back with :func:`_version_join` may not produce the original + version string. + """ result: List[str] = [] - for item in version.split("."): + + epoch, _, rest = version.rpartition("!") + result.append(epoch or "0") + + for item in rest.split("."): match = _prefix_regex.search(item) if match: result.extend(match.groups()) @@ -654,6 +649,17 @@ def _version_split(version: str) -> List[str]: return result +def _version_join(components: List[str]) -> str: + """Join split version components into a version string. + + This function assumes the input came from :func:`_version_split`, where the + first component must be the epoch (either empty or numeric), and all other + components numeric. + """ + epoch, *rest = components + return f"{epoch}!{'.'.join(rest)}" + + def _is_not_suffix(segment: str) -> bool: return not any( segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post") @@ -675,7 +681,10 @@ def _pad_version(left: List[str], right: List[str]) -> Tuple[List[str], List[str left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0]))) right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0]))) - return (list(itertools.chain(*left_split)), list(itertools.chain(*right_split))) + return ( + list(itertools.chain.from_iterable(left_split)), + list(itertools.chain.from_iterable(right_split)), + ) class SpecifierSet(BaseSpecifier): @@ -707,14 +716,8 @@ def __init__( # strip each item to remove leading/trailing whitespace. split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] - # Parsed each individual specifier, attempting first to make it a - # Specifier. - parsed: Set[Specifier] = set() - for specifier in split_specifiers: - parsed.add(Specifier(specifier)) - - # Turn our parsed specifiers into a frozen set and save them for later. - self._specs = frozenset(parsed) + # Make each individual specifier a Specifier and save in a frozen set for later. + self._specs = frozenset(map(Specifier, split_specifiers)) # Store our prereleases value so we can use it later to determine if # we accept prereleases or not. diff --git a/src/wheel/vendored/packaging/tags.py b/src/wheel/vendored/packaging/tags.py index 37f33b1e..89f19261 100644 --- a/src/wheel/vendored/packaging/tags.py +++ b/src/wheel/vendored/packaging/tags.py @@ -4,6 +4,7 @@ import logging import platform +import re import struct import subprocess import sys @@ -124,20 +125,37 @@ def _normalize_string(string: str) -> str: return string.replace(".", "_").replace("-", "_").replace(" ", "_") -def _abi3_applies(python_version: PythonVersion) -> bool: +def _is_threaded_cpython(abis: List[str]) -> bool: + """ + Determine if the ABI corresponds to a threaded (`--disable-gil`) build. + + The threaded builds are indicated by a "t" in the abiflags. + """ + if len(abis) == 0: + return False + # expect e.g., cp313 + m = re.match(r"cp\d+(.*)", abis[0]) + if not m: + return False + abiflags = m.group(1) + return "t" in abiflags + + +def _abi3_applies(python_version: PythonVersion, threading: bool) -> bool: """ Determine if the Python version supports abi3. - PEP 384 was first implemented in Python 3.2. + PEP 384 was first implemented in Python 3.2. The threaded (`--disable-gil`) + builds do not support abi3. """ - return len(python_version) > 1 and tuple(python_version) >= (3, 2) + return len(python_version) > 1 and tuple(python_version) >= (3, 2) and not threading def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]: py_version = tuple(py_version) # To allow for version comparison. abis = [] version = _version_nodot(py_version[:2]) - debug = pymalloc = ucs4 = "" + threading = debug = pymalloc = ucs4 = "" with_debug = _get_config_var("Py_DEBUG", warn) has_refcount = hasattr(sys, "gettotalrefcount") # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled @@ -146,6 +164,8 @@ def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]: has_ext = "_d.pyd" in EXTENSION_SUFFIXES if with_debug or (with_debug is None and (has_refcount or has_ext)): debug = "d" + if py_version >= (3, 13) and _get_config_var("Py_GIL_DISABLED", warn): + threading = "t" if py_version < (3, 8): with_pymalloc = _get_config_var("WITH_PYMALLOC", warn) if with_pymalloc or with_pymalloc is None: @@ -159,13 +179,8 @@ def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]: elif debug: # Debug builds can also load "normal" extension modules. # We can also assume no UCS-4 or pymalloc requirement. - abis.append(f"cp{version}") - abis.insert( - 0, - "cp{version}{debug}{pymalloc}{ucs4}".format( - version=version, debug=debug, pymalloc=pymalloc, ucs4=ucs4 - ), - ) + abis.append(f"cp{version}{threading}") + abis.insert(0, f"cp{version}{threading}{debug}{pymalloc}{ucs4}") return abis @@ -213,11 +228,14 @@ def cpython_tags( for abi in abis: for platform_ in platforms: yield Tag(interpreter, abi, platform_) - if _abi3_applies(python_version): + + threading = _is_threaded_cpython(abis) + use_abi3 = _abi3_applies(python_version, threading) + if use_abi3: yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms) yield from (Tag(interpreter, "none", platform_) for platform_ in platforms) - if _abi3_applies(python_version): + if use_abi3: for minor_version in range(python_version[1] - 1, 1, -1): for platform_ in platforms: interpreter = "cp{version}".format( diff --git a/src/wheel/vendored/packaging/version.py b/src/wheel/vendored/packaging/version.py index 5faab9bd..cda8e999 100644 --- a/src/wheel/vendored/packaging/version.py +++ b/src/wheel/vendored/packaging/version.py @@ -452,7 +452,6 @@ def micro(self) -> int: def _parse_letter_version( letter: Optional[str], number: Union[str, bytes, SupportsInt, None] ) -> Optional[Tuple[str, int]]: - if letter: # We consider there to be an implicit 0 in a pre-release if there is # not a numeral associated with it. @@ -508,7 +507,6 @@ def _cmpkey( dev: Optional[Tuple[str, int]], local: Optional[LocalType], ) -> CmpKey: - # When we compare a release version, we want to compare it with all of the # trailing zeros removed. So we'll use a reverse the list, drop all the now # leading zeros until we come to something non zero, then take the rest diff --git a/src/wheel/vendored/vendor.txt b/src/wheel/vendored/vendor.txt index 7feeca34..14666103 100644 --- a/src/wheel/vendored/vendor.txt +++ b/src/wheel/vendored/vendor.txt @@ -1 +1 @@ -packaging==23.2 +packaging==24.0 diff --git a/src/wheel/wheelfile.py b/src/wheel/wheelfile.py index 0cefbdbd..6440e90a 100644 --- a/src/wheel/wheelfile.py +++ b/src/wheel/wheelfile.py @@ -81,8 +81,8 @@ def __init__(self, file, mode="r", compression=ZIP_DEFLATED): if algorithm.lower() in {"md5", "sha1"}: raise WheelError( - "Weak hash algorithm ({}) is not permitted by PEP " - "427".format(algorithm) + f"Weak hash algorithm ({algorithm}) is not permitted by " + f"PEP 427" ) self._file_hashes[path] = (