From c8796655cb416c073bf2b6b14b6afb5f2dbd349e Mon Sep 17 00:00:00 2001 From: Joaquin Hui Gomez <132194176+joaquinhuigomez@users.noreply.github.com> Date: Sun, 29 Mar 2026 16:43:35 +0100 Subject: [PATCH] cargo: fix pre-release version constraint handling Meson's version_compare treats pre-release suffixes (e.g. 0.8.0-rc.7) as greater than the stable release, but semver specifies the opposite. Strip pre-release suffixes when converting Cargo version constraints so that stable versions correctly satisfy pre-release constraints. --- mesonbuild/cargo/version.py | 32 ++++++++++++++++++++++++++++---- unittests/cargotests.py | 8 ++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/mesonbuild/cargo/version.py b/mesonbuild/cargo/version.py index f92be582f5b1..5da7687f3007 100644 --- a/mesonbuild/cargo/version.py +++ b/mesonbuild/cargo/version.py @@ -4,9 +4,22 @@ """Convert Cargo versions into Meson compatible ones.""" from __future__ import annotations +import re import typing as T +def _strip_prerelease(version: str) -> str: + """Strip pre-release suffix from a version string. + + Cargo/semver pre-release versions like 0.8.0-rc.7 are not understood + by meson's version comparison, which treats the suffix as making the + version greater than the stable release. Per semver, pre-release + versions have lower precedence than the associated normal version, + so we strip them to avoid incorrect comparisons. + """ + return re.sub(r'-[A-Za-z0-9]+(\.[A-Za-z0-9]+)*$', '', version) + + def api(version: str) -> str: # x.y.z -> x # 0.x.y -> 0.x @@ -38,24 +51,34 @@ def convert(cargo_ver: str) -> T.List[str]: # <= 3 allows 3.0.0 where meson version compare does not # So change <= into < with a bumped version if ver.startswith('<='): - v = ver[2:].strip().split('.') + v = _strip_prerelease(ver[2:].strip()).split('.') if len(v) == 1: out.append(f'< {int(v[0]) + 1}') elif len(v) == 2: out.append(f'< {v[0]}.{int(v[1]) + 1}') else: - out.append(ver) + out.append(f'<= {".".join(v)}') # This covers >= as well elif ver.startswith(('>', '<', '=')): - out.append(ver) + # Strip pre-release suffixes since meson's version comparison + # does not handle them correctly (it treats 0.8.0-rc.7 as + # greater than 0.8.0, but semver says the opposite). + if ver.startswith('>='): + out.append(f'>= {_strip_prerelease(ver[2:].strip())}') + elif ver.startswith('>'): + out.append(f'> {_strip_prerelease(ver[1:].strip())}') + elif ver.startswith('='): + out.append(f'= {_strip_prerelease(ver[1:].strip())}') + else: + out.append(f'< {_strip_prerelease(ver[1:].strip())}') elif ver.startswith('~'): # Rust has these tilde requirements, which means that it is >= to # the version, but less than the next version # https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#tilde-requirements # we convert those into a pair of constraints - v = ver[1:].split('.') + v = _strip_prerelease(ver[1:]).split('.') out.append(f'>= {".".join(v)}') if len(v) == 3: out.append(f'< {v[0]}.{int(v[1]) + 1}.0') @@ -80,6 +103,7 @@ def convert(cargo_ver: str) -> T.List[str]: # https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#caret-requirements if ver.startswith('^'): ver = ver[1:] + ver = _strip_prerelease(ver) # If there is no qualifier, then it means this or the next non-zero version # That means that if this is `1.1.0``, then we need `>= 1.1.0` && `< 2.0.0` diff --git a/unittests/cargotests.py b/unittests/cargotests.py index fc571ee786ee..e23d130bfd8b 100644 --- a/unittests/cargotests.py +++ b/unittests/cargotests.py @@ -64,6 +64,14 @@ def test_cargo_to_meson(self) -> None: # Multiple requirements ('>= 1.2.3, < 1.4.7', ['>= 1.2.3', '< 1.4.7']), + + # Pre-release versions (semver pre-release < stable) + ('>= 0.8.0-rc.7', ['>= 0.8.0']), + ('>= 0.8.0-rc.7, < 0.9', ['>= 0.8.0', '< 0.9']), + ('0.8.0-rc.7', ['>= 0.8.0', '< 0.9']), + ('^0.8.0-rc.7', ['>= 0.8.0', '< 0.9']), + ('~0.8.0-rc.7', ['>= 0.8.0', '< 0.9.0']), + ('0.14.0-alpha.1', ['>= 0.14.0', '< 0.15']), ] for (data, expected) in cases: