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 > >