[PATCH] mkvenv: support pip 25.2; prefer packaging over distlib

John Snow posted 1 patch 3 months ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/qemu tags/patchew/20250811204918.260524-1-jsnow@redhat.com
Maintainers: John Snow <jsnow@redhat.com>, Cleber Rosa <crosa@redhat.com>
python/scripts/mkvenv.py | 39 +++++++++++++++++++++++++++++++++------
1 file changed, 33 insertions(+), 6 deletions(-)
[PATCH] mkvenv: support pip 25.2; prefer packaging over distlib
Posted by John Snow 3 months ago
Stop using distlib LegacyVersion parsing and begin using `packaging`
version parsing instead.

Signed-off-by: John Snow <jsnow@redhat.com>
---
 python/scripts/mkvenv.py | 39 +++++++++++++++++++++++++++++++++------
 1 file changed, 33 insertions(+), 6 deletions(-)

diff --git a/python/scripts/mkvenv.py b/python/scripts/mkvenv.py
index 8ac5b0b2a05..1aa9fc7badb 100644
--- a/python/scripts/mkvenv.py
+++ b/python/scripts/mkvenv.py
@@ -94,17 +94,32 @@
 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
 
+
+HAVE_PACKAGING = True
+try:
+    import packaging.requirements  # type: ignore
+    import packaging.version  # type: ignore
+except ImportError:
+    try:
+        # Reach into pip's cookie jar yet again.
+        # Shush all the tools rightly telling us this is a bad idea.
+        # pylint: disable=ungrouped-imports, unused-import
+        from pip._vendor import packaging  # type: ignore
+        import pip._vendor.packaging.requirements
+        import pip._vendor.packaging.version  # noqa
+    except ImportError:
+        HAVE_PACKAGING = 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().
@@ -669,12 +684,22 @@ def _do_ensure(
     canary = None
     for name, info in group.items():
         constraint = _make_version_constraint(info, False)
-        matcher = distlib.version.LegacyMatcher(name + constraint)
-        print(f"mkvenv: checking for {matcher}", file=sys.stderr)
+        req = packaging.requirements.Requirement(name + constraint)
+
+        def _match(
+            req: 'packaging.requirements.Requirement',
+            version_str: str
+        ) -> bool:
+            return bool(req.specifier.contains(
+                packaging.version.Version(version_str),
+                prereleases=True
+            ))
+
+        print(f"mkvenv: checking for {req}", file=sys.stderr)
 
         dist: Optional[Distribution] = None
         try:
-            dist = distribution(matcher.name)
+            dist = distribution(req.name)
         except PackageNotFoundError:
             pass
 
@@ -683,7 +708,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 _match(req, dist.version)
         ):
             absent.append(name + _make_version_constraint(info, True))
             if len(absent) == 1:
@@ -753,6 +778,8 @@ def ensure_group(
 
     if not HAVE_DISTLIB:
         raise Ouch("found no usable distlib, please install it")
+    if not HAVE_PACKAGING:
+        raise Ouch("found no usable packaging lib, please install it")
 
     parsed_deps = _parse_groups(file)
 
-- 
2.50.1
Re: [PATCH] mkvenv: support pip 25.2; prefer packaging over distlib
Posted by Michael Tokarev 3 months ago
On 11.08.2025 23:49, John Snow wrote:
> Stop using distlib LegacyVersion parsing and begin using `packaging`
> version parsing instead.
> 
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
>   python/scripts/mkvenv.py | 39 +++++++++++++++++++++++++++++++++------
>   1 file changed, 33 insertions(+), 6 deletions(-)

I hoped this version will be much shorter, removing a lot of code
in the process.  but it is of about the same complexity level as
the previous one.  Sigh.

So the benefit isn't as much as I hoped, so maybe the first version
is better for now after all.

Either way, I tested both versions on debian bullseye (old, unsupported,
python 3.9) and debian unstable with pip 25.2 (where distutils does not
exist) - both versions works fine.

Tested-by: Michael Tokarev <mjt@tls.msk.ru>

for both variants.

Thank you!

/mjt

> diff --git a/python/scripts/mkvenv.py b/python/scripts/mkvenv.py
> index 8ac5b0b2a05..1aa9fc7badb 100644
> --- a/python/scripts/mkvenv.py
> +++ b/python/scripts/mkvenv.py
> @@ -94,17 +94,32 @@
>   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
>   
> +
> +HAVE_PACKAGING = True
> +try:
> +    import packaging.requirements  # type: ignore
> +    import packaging.version  # type: ignore
> +except ImportError:
> +    try:
> +        # Reach into pip's cookie jar yet again.
> +        # Shush all the tools rightly telling us this is a bad idea.
> +        # pylint: disable=ungrouped-imports, unused-import
> +        from pip._vendor import packaging  # type: ignore
> +        import pip._vendor.packaging.requirements
> +        import pip._vendor.packaging.version  # noqa
> +    except ImportError:
> +        HAVE_PACKAGING = 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().
> @@ -669,12 +684,22 @@ def _do_ensure(
>       canary = None
>       for name, info in group.items():
>           constraint = _make_version_constraint(info, False)
> -        matcher = distlib.version.LegacyMatcher(name + constraint)
> -        print(f"mkvenv: checking for {matcher}", file=sys.stderr)
> +        req = packaging.requirements.Requirement(name + constraint)
> +
> +        def _match(
> +            req: 'packaging.requirements.Requirement',
> +            version_str: str
> +        ) -> bool:
> +            return bool(req.specifier.contains(
> +                packaging.version.Version(version_str),
> +                prereleases=True
> +            ))
> +
> +        print(f"mkvenv: checking for {req}", file=sys.stderr)
>   
>           dist: Optional[Distribution] = None
>           try:
> -            dist = distribution(matcher.name)
> +            dist = distribution(req.name)
>           except PackageNotFoundError:
>               pass
>   
> @@ -683,7 +708,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 _match(req, dist.version)
>           ):
>               absent.append(name + _make_version_constraint(info, True))
>               if len(absent) == 1:
> @@ -753,6 +778,8 @@ def ensure_group(
>   
>       if not HAVE_DISTLIB:
>           raise Ouch("found no usable distlib, please install it")
> +    if not HAVE_PACKAGING:
> +        raise Ouch("found no usable packaging lib, please install it")
>   
>       parsed_deps = _parse_groups(file)
>