mkvenv: Support pip 25.2

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 <lockalsash@gmail.com>
[Edits: Type "safety" whackamole --js]
Signed-off-by: John Snow <jsnow@redhat.com>
Message-ID: <20250811190159.237321-1-jsnow@redhat.com>
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
Sv. Lockal 2025-08-11 15:01:59 -04:00 committed by Stefan Hajnoczi
parent 624d746304
commit 6ad034e712

View file

@ -84,6 +84,7 @@ from typing import (
Sequence,
Tuple,
Union,
cast,
)
import venv
@ -94,17 +95,39 @@ import venv
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: