Skip to content

Commit d8f1388

Browse files
committed
interpreterbase: warn on redundant Meson version checks
We might encounter a Meson version check that always evaluates to true: project('t', 'c', meson_version: '>=0.60.0') if meson.version().version_compare('>=0.55.0') v = 1 endif Print a warning in this case. Unusual comparisons (like '<0.60') currently do not reach the new test in InterpreterBase. For now, don't attempt to change that. However, version_compare_conditions() is written to handle all possibilities.
1 parent e38b2e8 commit d8f1388

File tree

5 files changed

+141
-0
lines changed

5 files changed

+141
-0
lines changed

mesonbuild/interpreterbase/interpreterbase.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,11 @@ def evaluate_if(self, node: mparser.IfClauseNode) -> T.Optional[Disabler]:
312312
if res:
313313
prev_meson_version = mesonlib.project_meson_versions[self.subproject]
314314
if self.tmp_meson_version:
315+
always = mesonlib.version_compare_conditions(mesonlib.project_meson_versions[self.subproject],
316+
self.tmp_meson_version)
317+
if always is not None:
318+
mlog.warning(f"Version comparison '{self.tmp_meson_version}' always evaluates to {str(always).lower()}",
319+
location=self.current_node)
315320
mesonlib.project_meson_versions[self.subproject] = self.tmp_meson_version
316321
try:
317322
self.evaluate_codeblock(i.block)

mesonbuild/utils/universal.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ class _VerPickleLoadable(Protocol):
160160
'unique_list',
161161
'verbose_git',
162162
'version_compare',
163+
'version_compare_conditions',
163164
'version_compare_condition_with_min',
164165
'version_compare_many',
165166
'search_version',
@@ -991,6 +992,52 @@ def version_compare_condition_with_min(condition: str, minimum: str) -> bool:
991992

992993
return T.cast('bool', cmpop(Version(minimum), Version(condition)))
993994

995+
996+
# given the Meson version condition |outer_cond|, determine if the version
997+
# condition |inner_cond| is always true or always false
998+
def version_compare_conditions(outer_cond: str, inner_cond: str) -> T.Optional[bool]:
999+
class Extracted:
1000+
def __init__(self, v: str):
1001+
self.op, val = _version_extract_cmpop(v)
1002+
val += '.0' * (2 - val.count('.'))
1003+
self.val = Version(val)
1004+
if self.op == operator.ne:
1005+
self.direction: T.Callable[[T.Any, T.Any], bool] = operator.eq
1006+
elif self.op == operator.le:
1007+
self.direction = operator.lt
1008+
elif self.op == operator.ge:
1009+
self.direction = operator.gt
1010+
else:
1011+
self.direction = self.op
1012+
self.inclusive = self.op in (operator.eq, operator.le, operator.ge)
1013+
1014+
inner, outer = Extracted(inner_cond), Extracted(outer_cond)
1015+
if inner.val == outer.val:
1016+
if outer.op == inner.op:
1017+
return True
1018+
if outer.direction == operator.eq:
1019+
if outer.inclusive and inner.inclusive:
1020+
return True
1021+
if outer.inclusive and not inner.inclusive:
1022+
return False
1023+
if not outer.inclusive and inner.op == operator.eq:
1024+
return False
1025+
elif outer.inclusive:
1026+
if not inner.inclusive and inner.direction not in (operator.eq, outer.direction):
1027+
return False
1028+
elif outer.direction != inner.direction:
1029+
return inner.op == operator.ne
1030+
else:
1031+
return True
1032+
if inner.val < outer.val:
1033+
if outer.op == operator.eq or outer.direction == operator.gt:
1034+
return inner.direction == operator.gt or inner.op == operator.ne
1035+
if inner.val > outer.val:
1036+
if outer.op == operator.eq or outer.direction == operator.lt:
1037+
return inner.direction == operator.lt or inner.op == operator.ne
1038+
return None
1039+
1040+
9941041
def search_version(text: str) -> str:
9951042
# Usually of the type 4.1.4 but compiler output may contain
9961043
# stuff like this:
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
project('t', 'c', meson_version: '>=0.60.0')
2+
if meson.version().version_compare('>=0.55.0')
3+
v = 1
4+
endif
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"stdout": [
3+
{
4+
"line": "test cases/warning/10 redundant version check/meson.build:2: WARNING: Version comparison '>=0.55.0' always evaluates to true"
5+
}
6+
]
7+
}

unittests/internaltests.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,84 @@ def test_version_compare(self):
830830
for o, name in [(operator.lt, 'lt'), (operator.le, 'le'), (operator.eq, 'eq')]:
831831
self.assertFalse(o(ver_a, ver_b), f'{ver_a} {name} {ver_b}')
832832

833+
def test_version_compare_conditions(self):
834+
# {outer_op: {inner_op: [inner < outer, inner == outer, inner > outer]}}
835+
tests = {
836+
'>=': {
837+
'>=': [True, True, None],
838+
'>': [True, None, None],
839+
'<': [False, False, None],
840+
'<=': [False, None, None],
841+
'=': [False, None, None],
842+
'==': [False, None, None],
843+
'!=': [True, None, None],
844+
},
845+
'>': {
846+
'>=': [True, True, None],
847+
'>': [True, True, None],
848+
'<': [False, False, None],
849+
'<=': [False, False, None],
850+
'=': [False, False, None],
851+
'==': [False, False, None],
852+
'!=': [True, True, None],
853+
},
854+
'<': {
855+
'>=': [None, False, False],
856+
'>': [None, False, False],
857+
'<': [None, True, True],
858+
'<=': [None, True, True],
859+
'=': [None, False, False],
860+
'==': [None, False, False],
861+
'!=': [None, True, True],
862+
},
863+
'<=': {
864+
'>=': [None, None, False],
865+
'>': [None, False, False],
866+
'<': [None, None, True],
867+
'<=': [None, True, True],
868+
'=': [None, None, False],
869+
'==': [None, None, False],
870+
'!=': [None, None, True],
871+
},
872+
'=': {
873+
'>=': [True, True, False],
874+
'>': [True, False, False],
875+
'<': [False, False, True],
876+
'<=': [False, True, True],
877+
'=': [False, True, False],
878+
'==': [False, True, False],
879+
'!=': [True, False, True],
880+
},
881+
'==': {
882+
'>=': [True, True, False],
883+
'>': [True, False, False],
884+
'<': [False, False, True],
885+
'<=': [False, True, True],
886+
'=': [False, True, False],
887+
'==': [False, True, False],
888+
'!=': [True, False, True],
889+
},
890+
'!=': {
891+
'>=': [None, None, None],
892+
'>': [None, None, None],
893+
'<': [None, None, None],
894+
'<=': [None, None, None],
895+
'=': [None, False, None],
896+
'==': [None, False, None],
897+
'!=': [None, True, None],
898+
},
899+
}
900+
for outer_op, inner in tests.items():
901+
for inner_op, results in inner.items():
902+
for inner_val, result in zip((40, 50, 60), results):
903+
for outer_ext, inner_ext in (('', ''),
904+
('', '.0'),
905+
('.0', ''),
906+
('.0', '.0')):
907+
self.assertEqual(mesonbuild.mesonlib.version_compare_conditions(f'{outer_op}0.50{outer_ext}',
908+
f'{inner_op}0.{inner_val}{inner_ext}'),
909+
result)
910+
833911
def test_msvc_toolset_version(self):
834912
'''
835913
Ensure that the toolset version returns the correct value for this MSVC

0 commit comments

Comments
 (0)