diff --git a/setup.py b/setup.py index 81514361..ac8fd1a0 100755 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ # - src/rosdistro/__init__.py # - stdeb.cfg 'version': '1.0.1', - 'install_requires': ['PyYAML', 'setuptools'], + 'install_requires': ['packaging>=16.8.0', 'PyYAML', 'setuptools'], 'python_requires': '>=3.6', 'packages': find_packages('src'), 'package_dir': {'': 'src'}, diff --git a/src/rosdistro/vcs.py b/src/rosdistro/vcs.py index 76448a1e..001ba6b1 100644 --- a/src/rosdistro/vcs.py +++ b/src/rosdistro/vcs.py @@ -36,7 +36,40 @@ import re import subprocess -from distutils.version import LooseVersion +from packaging.version import parse, InvalidVersion +try: + from packaging.version import LegacyVersion + packaging_lte_22 = True +except ImportError: + packaging_lte_22 = False + + +def _version_gte(version: str, required_version: str) -> bool: + """Check if a version string is greater than or equal to a required version. + + Args: + version: The version string to check. + required_version: The required version string. + + Returns: + True if the version is greater than or equal to the required version, False otherwise. + """ + try: + parsed_version = parse(version) + if packaging_lte_22: + # In packaging 22.0 and earlier, parse() returns a LegacyVersion for non-standard version strings, + # which will compare greater than any valid version. We want to raise an error instead. + if isinstance(parsed_version, LegacyVersion): + raise InvalidVersion + except InvalidVersion: + if "windows" in version.lower(): + # Git for Windows uses a non-standard version string + version = version.lower().replace("windows", "post").strip() + parsed_version = parse(version) + else: + raise + + return parsed_version >= parse(required_version) class Git(object): @@ -57,7 +90,8 @@ def version_gte(cls, version): if not cls._client_version: result = cls().command('--version') cls._client_version = result['output'].split()[-1] - return LooseVersion(cls._client_version) >= LooseVersion(version) + + return _version_gte(cls._client_version, version) def ref_is_hash(ref): diff --git a/stdeb.cfg b/stdeb.cfg index 1b430c35..37045d55 100644 --- a/stdeb.cfg +++ b/stdeb.cfg @@ -3,7 +3,7 @@ Debian-Version: 100 ; rosdistro-modules same version as in: ; - setup.py ; - src/rosdistro/__init__.py -Depends3: ca-certificates, python3-rosdistro-modules (>= 1.0.1), python3-setuptools, python3-yaml +Depends3: ca-certificates, python3-packaging (>=16.8.0), python3-rosdistro-modules (>= 1.0.1), python3-setuptools, python3-yaml Conflicts3: python-rosdistro Copyright-File: LICENSE.txt Suite3: focal jammy noble bookworm trixie @@ -12,7 +12,7 @@ X-Python3-Version: >= 3.6 Setup-Env-Vars: SKIP_PYTHON_MODULES=1 [rosdistro_modules] -Depends3: ca-certificates, python3-catkin-pkg-modules, python3-rospkg-modules, python3-setuptools, python3-yaml +Depends3: ca-certificates, python3-catkin-pkg-modules, python3-packaging (>=16.8.0), python3-rospkg-modules, python3-setuptools, python3-yaml Conflicts3: python3-rosdistro (<< 0.6.0) Replaces3: python3-rosdistro (<< 0.6.0) Copyright-File: LICENSE.txt diff --git a/test/test_vcs.py b/test/test_vcs.py new file mode 100644 index 00000000..aac1e721 --- /dev/null +++ b/test/test_vcs.py @@ -0,0 +1,20 @@ +from rosdistro.vcs import _version_gte + +import pytest + +@pytest.mark.parametrize("version, required_version, expected", [ + ("2.50.0", "2.51.0", False), + ("2.51.0", "2.51.0", True), + ("2.52.0", "2.51.0", True), + ("2.51.0-rc1", "2.51.0", False), + ("2.51.0-rc1", "2.51.0-rc1", True), + ("2.51.0-rc1", "2.51.0-rc2", False), + ("2.51.0-rc1", "2.51.0-rc0", True), + ("2.51.0-rc1", "2.51.0-rc0+post1", True), + ("2.51.0-rc1", "2.51.0-rc1+post1", False), + ("2.51.0.windows.1", "2.50.0", True), + ("2.51.0.windows.1", "2.51.0", True), + ("2.51.0.windows.1", "2.52", False), +]) +def test_version_gte(version: str, required_version: str, expected: bool): + assert _version_gte(version, required_version) == expected