The distutils module was deprecated in Python 3.10 and removed in
Python 3.12. This commit replaces the deprecated distutils.version
imports with the a homebrew LooseVersion implementation.
Changes:
- implement LooseVersion which is able to parse versions like
'
10.2.2-63-g8542898-1trusty'.
- Replace distutils.version.LooseVersion with
teuthology.util.version.LooseVersion packaging.version.LooseVersion
Fixes:
```
Traceback (most recent call last):
File "/home/jenkins-build/build/workspace/ceph-api/build/../qa/tasks/vstart_runner.py", line 81, in <module>
from teuthology.orchestra.remote import RemoteShell
File "/tmp/tmp.xwxq8FOScf/teuthology/teuthology/orchestra/remote.py", line 6, in <module>
import teuthology.lock.util
File "/tmp/tmp.xwxq8FOScf/teuthology/teuthology/lock/util.py", line 6, in <module>
import teuthology.provision.downburst
File "/tmp/tmp.xwxq8FOScf/teuthology/teuthology/provision/__init__.py", line 4, in <module>
import teuthology.exporter
File "/tmp/tmp.xwxq8FOScf/teuthology/teuthology/exporter.py", line 11, in <module>
import teuthology.dispatcher
File "/tmp/tmp.xwxq8FOScf/teuthology/teuthology/dispatcher/__init__.py", line 22, in <module>
from teuthology.dispatcher import supervisor
File "/tmp/tmp.xwxq8FOScf/teuthology/teuthology/dispatcher/supervisor.py", line 18, in <module>
from teuthology.task import internal
File "/tmp/tmp.xwxq8FOScf/teuthology/teuthology/task/internal/__init__.py", line 27, in <module>
from teuthology.task.internal.redhat import (setup_cdn_repo, setup_base_repo, # noqa
File "/tmp/tmp.xwxq8FOScf/teuthology/teuthology/task/internal/redhat.py", line 13, in <module>
from teuthology.task.install.redhat import set_deb_repo
File "/tmp/tmp.xwxq8FOScf/teuthology/teuthology/task/install/__init__.py", line 14, in <module>
from distutils.version import LooseVersion
ModuleNotFoundError: No module named 'distutils'
```
Related: https://peps.python.org/pep-0632/
Signed-off-by: Kefu Chai <tchaikov@gmail.com>
from teuthology.parallel import parallel
from teuthology.task import ansible
-from distutils.version import LooseVersion
from teuthology.task.install.util import (
_get_builder_project, get_flavor, ship_utilities,
)
from teuthology.task.install import rpm, deb, redhat
+from teuthology.util.version import LooseVersion
log = logging.getLogger(__name__)
import os.path
from io import StringIO
-from distutils.version import LooseVersion
-
from teuthology.config import config as teuth_config
from teuthology.contextutil import safe_while
from teuthology.orchestra import run
from teuthology import packaging
from teuthology.task.install.util import _get_builder_project, _get_local_dir
+from teuthology.util.version import LooseVersion
log = logging.getLogger(__name__)
--- /dev/null
+import re
+from functools import total_ordering
+from typing import Union, List
+
+
+@total_ordering
+class LooseVersion:
+ """
+ A flexible version comparison class that handles arbitrary version strings.
+ Compares numeric components numerically and alphabetic components lexically.
+ """
+
+ _component_re = re.compile(r'(\d+|[a-z]+|\.)', re.IGNORECASE)
+
+ def __init__(self, vstring: str):
+ self.vstring = str(vstring)
+ self.version = self._parse(self.vstring)
+
+ def _parse(self, vstring: str) -> List[Union[int, str]]:
+ """Parse version string into comparable components."""
+ components = []
+ for match in self._component_re.finditer(vstring.lower()):
+ component = match.group()
+ if component != '.':
+ # Try to convert to int, fall back to string
+ try:
+ components.append(int(component))
+ except ValueError:
+ components.append(component)
+ return components
+
+ def __str__(self) -> str:
+ return self.vstring
+
+ def __repr__(self) -> str:
+ return f"{self.__class__.__name__}('{self.vstring}')"
+
+ def __eq__(self, other) -> bool:
+ if not isinstance(other, LooseVersion):
+ other = LooseVersion(str(other))
+ return self.version == other.version
+
+ def __lt__(self, other) -> bool:
+ if not isinstance(other, LooseVersion):
+ other = LooseVersion(str(other))
+ return self.version < other.version
+
+ def __hash__(self) -> int:
+ return hash(tuple(self.version))