On Mon, Aug 11, 2025 at 3:03 PM John Snow <js...@redhat.com> wrote:
>
> From: "Sv. Lockal" <lockals...@gmail.com>
>
> Fix compilation with pip-25.2 due to missing distlib.version
>
> Bug: https://gitlab.com/qemu-project/qemu/-/issues/3062
>
> Signed-off-by: Sv. Lockal <lockals...@gmail.com>
> [Edits: Type "safety" whackamole --js]
> Signed-off-by: John Snow <js...@redhat.com>
> ---
>  python/scripts/mkvenv.py | 64 +++++++++++++++++++++++++++++++++++++---
>  1 file changed, 60 insertions(+), 4 deletions(-)

Applied for QEMU v10.1.0-rc3. Thanks!

Stefan

> diff --git a/python/scripts/mkvenv.py b/python/scripts/mkvenv.py
> index 8ac5b0b2a05..f102527c4de 100644
> --- a/python/scripts/mkvenv.py
> +++ b/python/scripts/mkvenv.py
> @@ -84,6 +84,7 @@
>      Sequence,
>      Tuple,
>      Union,
> +    cast,
>  )
>  import venv
>
> @@ -94,17 +95,39 @@
>  HAVE_DISTLIB = True
>  try:
>      import distlib.scripts
> -    import distlib.version
>  except ImportError:
>      try:
>          # Reach into pip's cookie jar.  pylint and flake8 don't understand
>          # that these imports will be used via distlib.xxx.
>          from pip._vendor import distlib
>          import pip._vendor.distlib.scripts  # noqa, pylint: 
> disable=unused-import
> -        import pip._vendor.distlib.version  # noqa, pylint: 
> disable=unused-import
>      except ImportError:
>          HAVE_DISTLIB = False
>
> +# pip 25.2 does not vendor distlib.version, but it uses vendored
> +# packaging.version
> +HAVE_DISTLIB_VERSION = True
> +try:
> +    import distlib.version  # pylint: disable=ungrouped-imports
> +except ImportError:
> +    try:
> +        # pylint: disable=unused-import,ungrouped-imports
> +        import pip._vendor.distlib.version  # noqa
> +    except ImportError:
> +        HAVE_DISTLIB_VERSION = False
> +
> +HAVE_PACKAGING_VERSION = True
> +try:
> +    # Do not bother importing non-vendored packaging, because it is not
> +    # in stdlib.
> +    from pip._vendor import packaging
> +    # pylint: disable=unused-import
> +    import pip._vendor.packaging.requirements  # noqa
> +    import pip._vendor.packaging.version  # noqa
> +except ImportError:
> +    HAVE_PACKAGING_VERSION = False
> +
> +
>  # Try to load tomllib, with a fallback to tomli.
>  # HAVE_TOMLLIB is checked below, just-in-time, so that mkvenv does not fail
>  # outside the venv or before a potential call to ensurepip in checkpip().
> @@ -133,6 +156,39 @@ class Ouch(RuntimeError):
>      """An Exception class we can't confuse with a builtin."""
>
>
> +class Matcher:
> +    """Compatibility appliance for version/requirement string parsing."""
> +    def __init__(self, name_and_constraint: str):
> +        """Create a matcher from a requirement-like string."""
> +        if HAVE_DISTLIB_VERSION:
> +            self._m = distlib.version.LegacyMatcher(name_and_constraint)
> +        elif HAVE_PACKAGING_VERSION:
> +            self._m = packaging.requirements.Requirement(name_and_constraint)
> +        else:
> +            raise Ouch("found neither distlib.version nor packaging.version")
> +        self.name = self._m.name
> +
> +    def match(self, version_str: str) -> bool:
> +        """Return True if `version` satisfies the stored constraint."""
> +        if HAVE_DISTLIB_VERSION:
> +            return cast(
> +                bool,
> +                self._m.match(distlib.version.LegacyVersion(version_str))
> +            )
> +
> +        assert HAVE_PACKAGING_VERSION
> +        return cast(
> +            bool,
> +            self._m.specifier.contains(
> +                packaging.version.Version(version_str), prereleases=True
> +            )
> +        )
> +
> +    def __repr__(self) -> str:
> +        """Stable debug representation delegated to the backend."""
> +        return repr(self._m)
> +
> +
>  class QemuEnvBuilder(venv.EnvBuilder):
>      """
>      An extension of venv.EnvBuilder for building QEMU's configure-time venv.
> @@ -669,7 +725,7 @@ def _do_ensure(
>      canary = None
>      for name, info in group.items():
>          constraint = _make_version_constraint(info, False)
> -        matcher = distlib.version.LegacyMatcher(name + constraint)
> +        matcher = Matcher(name + constraint)
>          print(f"mkvenv: checking for {matcher}", file=sys.stderr)
>
>          dist: Optional[Distribution] = None
> @@ -683,7 +739,7 @@ def _do_ensure(
>              # Always pass installed package to pip, so that they can be
>              # updated if the requested version changes
>              or not _is_system_package(dist)
> -            or not matcher.match(distlib.version.LegacyVersion(dist.version))
> +            or not matcher.match(dist.version)
>          ):
>              absent.append(name + _make_version_constraint(info, True))
>              if len(absent) == 1:
> --
> 2.50.1
>
>

Reply via email to