Submitted By: Douglas R. Reno Date: 2025-08-10 Initial Package Version: 10.0.3 Origin: Upstream Ticket #3062 Upstream Status: Pending Description: Fixes compiling qemu-10.0.3 with Python-3.13.6. The problem occurs because pip-25.2 removes distlib from it's module, and Python-3.13.6 ships with pip-25.2. Thanks goes to Joe Locash for the report. Fix compilation with pip-25.2 due to missing distlib.version Bug: https://gitlab.com/qemu-project/qemu/-/issues/3062 --- a/python/scripts/mkvenv.py +++ b/python/scripts/mkvenv.py @@ -94,17 +94,35 @@ 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 +except ImportError: + try: + import pip._vendor.distlib.version # noqa, pylint: disable=unused-import + except ImportError: + HAVE_DISTLIB_VERSION = False + +HAVE_PACKAGING_VERSION = True +try: + # Do not bother importing non-vendored packaging, because it is not in stdlib. + import pip._vendor.packaging as packaging + import pip._vendor.packaging.version # noqa, pylint: disable=unused-import + import pip._vendor.packaging.requirements # noqa, pylint: disable=unused-import +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 +151,29 @@ class Ouch(RuntimeError): """An Exception class we can't confuse with a builtin.""" +class Matcher: + 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) -> bool: + """Return True if `version` satisfies the stored constraint.""" + if HAVE_DISTLIB_VERSION: + return self._m.match(distlib.version.LegacyVersion(version)) + elif HAVE_PACKAGING_VERSION: + return self._m.specifier.contains(packaging.version.Version(version), prereleases=True) + + def __repr__(self): + """Stable debug representation delegated to the backend.""" + return self._m.__repr__() + + class QemuEnvBuilder(venv.EnvBuilder): """ An extension of venv.EnvBuilder for building QEMU's configure-time venv. @@ -669,7 +710,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 +724,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: