aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/mesonlib.py
diff options
context:
space:
mode:
Diffstat (limited to 'mesonbuild/mesonlib.py')
-rw-r--r--mesonbuild/mesonlib.py156
1 files changed, 77 insertions, 79 deletions
diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py
index 1b9cb42..8a2dc0c 100644
--- a/mesonbuild/mesonlib.py
+++ b/mesonbuild/mesonlib.py
@@ -14,6 +14,7 @@
"""A library of random helper functionality."""
+import functools
import sys
import stat
import time
@@ -390,33 +391,59 @@ def detect_vcs(source_dir):
return vcs
return None
-def grab_leading_numbers(vstr, strict=False):
- result = []
- for x in vstr.rstrip('.').split('.'):
- try:
- result.append(int(x))
- except ValueError as e:
- if strict:
- msg = 'Invalid version to compare against: {!r}; only ' \
- 'numeric digits separated by "." are allowed: ' + str(e)
- raise MesonException(msg.format(vstr))
- break
- return result
+# a helper class which implements the same version ordering as RPM
+@functools.total_ordering
+class Version:
+ def __init__(self, s):
+ self._s = s
-def make_same_len(listA, listB):
- maxlen = max(len(listA), len(listB))
- for i in listA, listB:
- for n in range(len(i), maxlen):
- i.append(0)
+ # split into numeric, alphabetic and non-alphanumeric sequences
+ sequences = re.finditer(r'(\d+|[a-zA-Z]+|[^a-zA-Z\d]+)', s)
+ # non-alphanumeric separators are discarded
+ sequences = [m for m in sequences if not re.match(r'[^a-zA-Z\d]+', m.group(1))]
+ # numeric sequences have leading zeroes discarded
+ sequences = [re.sub(r'^0+(\d)', r'\1', m.group(1), 1) for m in sequences]
+
+ self._v = sequences
+
+ def __str__(self):
+ return '%s (V=%s)' % (self._s, str(self._v))
-numpart = re.compile('[0-9.]+')
+ def __lt__(self, other):
+ return self.__cmp__(other) == -1
-def version_compare(vstr1, vstr2, strict=False):
- match = numpart.match(vstr1.strip())
- if match is None:
- msg = 'Uncomparable version string {!r}.'
- raise MesonException(msg.format(vstr1))
- vstr1 = match.group(0)
+ def __eq__(self, other):
+ return self.__cmp__(other) == 0
+
+ def __cmp__(self, other):
+ def cmp(a, b):
+ return (a > b) - (a < b)
+
+ # compare each sequence in order
+ for i in range(0, min(len(self._v), len(other._v))):
+ # sort a non-digit sequence before a digit sequence
+ if self._v[i].isdigit() != other._v[i].isdigit():
+ return 1 if self._v[i].isdigit() else -1
+
+ # compare as numbers
+ if self._v[i].isdigit():
+ # because leading zeros have already been removed, if one number
+ # has more digits, it is greater
+ c = cmp(len(self._v[i]), len(other._v[i]))
+ if c != 0:
+ return c
+ # fallthrough
+
+ # compare lexicographically
+ c = cmp(self._v[i], other._v[i])
+ if c != 0:
+ return c
+
+ # if equal length, all components have matched, so equal
+ # otherwise, the version with a suffix remaining is greater
+ return cmp(len(self._v), len(other._v))
+
+def _version_extract_cmpop(vstr2):
if vstr2.startswith('>='):
cmpop = operator.ge
vstr2 = vstr2[2:]
@@ -440,10 +467,12 @@ def version_compare(vstr1, vstr2, strict=False):
vstr2 = vstr2[1:]
else:
cmpop = operator.eq
- varr1 = grab_leading_numbers(vstr1, strict)
- varr2 = grab_leading_numbers(vstr2, strict)
- make_same_len(varr1, varr2)
- return cmpop(varr1, varr2)
+
+ return (cmpop, vstr2)
+
+def version_compare(vstr1, vstr2):
+ (cmpop, vstr2) = _version_extract_cmpop(vstr2)
+ return cmpop(Version(vstr1), Version(vstr2))
def version_compare_many(vstr1, conditions):
if not isinstance(conditions, (list, tuple, frozenset)):
@@ -451,28 +480,22 @@ def version_compare_many(vstr1, conditions):
found = []
not_found = []
for req in conditions:
- if not version_compare(vstr1, req, strict=True):
+ if not version_compare(vstr1, req):
not_found.append(req)
else:
found.append(req)
return not_found == [], not_found, found
-
+# determine if the minimum version satisfying the condition |condition| exceeds
+# the minimum version for a feature |minimum|
def version_compare_condition_with_min(condition, minimum):
- match = numpart.match(minimum.strip())
- if match is None:
- msg = 'Uncomparable version string {!r}.'
- raise MesonException(msg.format(minimum))
- minimum = match.group(0)
if condition.startswith('>='):
cmpop = operator.le
condition = condition[2:]
elif condition.startswith('<='):
- return True
- condition = condition[2:]
+ return False
elif condition.startswith('!='):
- return True
- condition = condition[2:]
+ return False
elif condition.startswith('=='):
cmpop = operator.le
condition = condition[2:]
@@ -483,49 +506,24 @@ def version_compare_condition_with_min(condition, minimum):
cmpop = operator.lt
condition = condition[1:]
elif condition.startswith('<'):
- return True
- condition = condition[2:]
- else:
- cmpop = operator.le
- varr1 = grab_leading_numbers(minimum, True)
- varr2 = grab_leading_numbers(condition, True)
- make_same_len(varr1, varr2)
- return cmpop(varr1, varr2)
-
-def version_compare_condition_with_max(condition, maximum):
- match = numpart.match(maximum.strip())
- if match is None:
- msg = 'Uncomparable version string {!r}.'
- raise MesonException(msg.format(maximum))
- maximum = match.group(0)
- if condition.startswith('>='):
- return False
- condition = condition[2:]
- elif condition.startswith('<='):
- cmpop = operator.ge
- condition = condition[2:]
- elif condition.startswith('!='):
return False
- condition = condition[2:]
- elif condition.startswith('=='):
- cmpop = operator.ge
- condition = condition[2:]
- elif condition.startswith('='):
- cmpop = operator.ge
- condition = condition[1:]
- elif condition.startswith('>'):
- return False
- condition = condition[1:]
- elif condition.startswith('<'):
- cmpop = operator.gt
- condition = condition[2:]
else:
- cmpop = operator.ge
- varr1 = grab_leading_numbers(maximum, True)
- varr2 = grab_leading_numbers(condition, True)
- make_same_len(varr1, varr2)
- return cmpop(varr1, varr2)
+ cmpop = operator.le
+
+ # Declaring a project(meson_version: '>=0.46') and then using features in
+ # 0.46.0 is valid, because (knowing the meson versioning scheme) '0.46.0' is
+ # the lowest version which satisfies the constraint '>=0.46'.
+ #
+ # But this will fail here, because the minimum version required by the
+ # version constraint ('0.46') is strictly less (in our version comparison)
+ # than the minimum version needed for the feature ('0.46.0').
+ #
+ # Map versions in the constraint of the form '0.46' to '0.46.0', to embed
+ # this knowledge of the meson versioning scheme.
+ if re.match('^\d+.\d+$', condition):
+ condition += '.0'
+ return cmpop(Version(minimum), Version(condition))
def default_libdir():
if is_debianlike():