Skip to content

Commit 695ccbd

Browse files
committed
interpreterbase: warn on redundant Meson version checks
We might encounter a Meson version check that always evaluates to true or to false: project('t', 'c', meson_version: '>=0.60.0') if meson.version().version_compare('>=0.55.0') v = 1 endif if meson.version().version_compare('<0.60.0') v = 2 endif Print warnings in such cases. Only the innermost version dependency is available to us, so we don't catch cases like: project('t', 'c', meson_version: '>=0.50') if meson.version().version_compare('<0.60') v = 1 if meson.version().version_compare('>=0.40') v = 2 endif endif but such constructs aren't typical.
1 parent e38b2e8 commit 695ccbd

File tree

5 files changed

+154
-8
lines changed

5 files changed

+154
-8
lines changed

mesonbuild/interpreterbase/interpreterbase.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -309,15 +309,20 @@ def evaluate_if(self, node: mparser.IfClauseNode) -> T.Optional[Disabler]:
309309
res = result.operator_call(MesonOperator.BOOL, None)
310310
if not isinstance(res, bool):
311311
raise InvalidCode(f'If clause {result!r} does not evaluate to true or false.')
312-
if res:
313-
prev_meson_version = mesonlib.project_meson_versions[self.subproject]
314-
if self.tmp_meson_version:
315-
mesonlib.project_meson_versions[self.subproject] = self.tmp_meson_version
316-
try:
312+
prev_meson_version = mesonlib.project_meson_versions[self.subproject]
313+
if self.tmp_meson_version:
314+
always = mesonlib.version_compare_conditions(prev_meson_version,
315+
self.tmp_meson_version)
316+
if always is not None:
317+
mlog.warning(f"Version comparison '{self.tmp_meson_version}' always evaluates to {str(always).lower()}",
318+
location=self.current_node)
319+
mesonlib.project_meson_versions[self.subproject] = self.tmp_meson_version
320+
try:
321+
if res:
317322
self.evaluate_codeblock(i.block)
318-
finally:
319-
mesonlib.project_meson_versions[self.subproject] = prev_meson_version
320-
return None
323+
return None
324+
finally:
325+
mesonlib.project_meson_versions[self.subproject] = prev_meson_version
321326
if not isinstance(node.elseblock, mparser.EmptyNode):
322327
self.evaluate_codeblock(node.elseblock.block)
323328
return None

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' * max(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: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
project('t', 'c', meson_version: '>=0.60.0')
2+
if meson.version().version_compare('>=0.55.0')
3+
v = 1
4+
endif
5+
if meson.version().version_compare('<0.60.0')
6+
v = 2
7+
endif
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"stdout": [
3+
{
4+
"line": "test cases/common/286 redundant version check/meson.build:2: WARNING: Version comparison '>=0.55.0' always evaluates to true"
5+
},
6+
{
7+
"line": "test cases/common/286 redundant version check/meson.build:5: WARNING: Version comparison '<0.60.0' always evaluates to false"
8+
}
9+
]
10+
}

unittests/internaltests.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import argparse
88
import contextlib
99
import io
10+
import itertools
1011
import json
1112
import operator
1213
import os
@@ -830,6 +831,82 @@ def test_version_compare(self):
830831
for o, name in [(operator.lt, 'lt'), (operator.le, 'le'), (operator.eq, 'eq')]:
831832
self.assertFalse(o(ver_a, ver_b), f'{ver_a} {name} {ver_b}')
832833

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

0 commit comments

Comments
 (0)