On Mon, Aug 11, 2025 at 3:03 PM John Snow <[email protected]> wrote:
>
> From: "Sv. Lockal" <[email protected]>
>
> 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 <[email protected]>
> [Edits: Type "safety" whackamole --js]
> Signed-off-by: John Snow <[email protected]>
> ---
> 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
>
>