summaryrefslogtreecommitdiffstats
path: root/util
diff options
context:
space:
mode:
authorAlexandru Croitor <alexandru.croitor@qt.io>2019-10-07 08:38:08 +0200
committerJoerg Bornemann <joerg.bornemann@qt.io>2022-02-28 15:41:11 +0100
commit2389aaf8c754c78464f27cf480e22a41a89de1b0 (patch)
treefe330ef8f5778ada633bbae76ce1b13ced11fc91 /util
parent6708fad936319eba56577ad76620a8a72f78ccdb (diff)
pro2cmake: Handle qmake condition operator precedence
Unfortunately qmake does not have operator precedence in conditions, and each sub-expression is simply evaluated left to right. So c1|c2:c3 is evaluated as (c1|c2):c3 and not c1|(c2:c3). To handle that in pro2cmake, wrap each condition sub-expression in parentheses. It's ugly, but there doesn't seem to be another way of handling it, because SymPy uses Python operator precedence for condition operators, and it's not possible to change the precendece. Fixes: QTBUG-78929 Change-Id: I6ab767c4243e3f2d0fea1c36cd004409faba3a53 Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'util')
-rwxr-xr-x[-rw-r--r--]util/cmake/qmake_parser.py51
-rw-r--r--util/cmake/tests/data/condition_operator_precedence.pro11
-rwxr-xr-xutil/cmake/tests/test_parsing.py14
3 files changed, 75 insertions, 1 deletions
diff --git a/util/cmake/qmake_parser.py b/util/cmake/qmake_parser.py
index 836e9a4319..a6be81d6da 100644..100755
--- a/util/cmake/qmake_parser.py
+++ b/util/cmake/qmake_parser.py
@@ -333,12 +333,61 @@ class QmakeParser:
"ConditionWhiteSpace", pp.Suppress(pp.Optional(pp.White(" ")))
)
+ # Unfortunately qmake condition operators have no precedence,
+ # and are simply evaluated left to right. To emulate that, wrap
+ # each condition sub-expression in parentheses.
+ # So c1|c2:c3 is evaluated by qmake as (c1|c2):c3.
+ # The following variable keeps count on how many parentheses
+ # should be added to the beginning of the condition. Each
+ # condition sub-expression always gets an ")", and in the
+ # end the whole condition gets many "(". Note that instead
+ # inserting the actual parentheses, we insert special markers
+ # which get replaced in the end.
+ condition_parts_count = 0
+ # Whitespace in the markers is important. Assumes the markers
+ # never appear in .pro files.
+ l_paren_marker = "_(_ "
+ r_paren_marker = " _)_"
+
+ def handle_condition_part(condition_part_parse_result: pp.ParseResults) -> str:
+ condition_part_list = [*condition_part_parse_result]
+ nonlocal condition_parts_count
+ condition_parts_count += 1
+ condition_part_joined = "".join(condition_part_list)
+ # Add ending parenthesis marker. The counterpart is added
+ # in handle_condition.
+ return f"{condition_part_joined}{r_paren_marker}"
+
+ ConditionPart.setParseAction(handle_condition_part)
ConditionRepeated = add_element(
"ConditionRepeated", pp.ZeroOrMore(ConditionOp + ConditionWhiteSpace + ConditionPart)
)
+ def handle_condition(condition_parse_results: pp.ParseResults) -> str:
+ nonlocal condition_parts_count
+ prepended_parentheses = l_paren_marker * condition_parts_count
+ result = prepended_parentheses + " ".join(condition_parse_results).strip().replace(
+ ":", " && "
+ ).strip(" && ")
+ # If there are only 2 condition sub-expressions, there is no
+ # need for parentheses.
+ if condition_parts_count < 3:
+ result = result.replace(l_paren_marker, "")
+ result = result.replace(r_paren_marker, "")
+ result = result.strip(" ")
+ else:
+ result = result.replace(l_paren_marker, "( ")
+ result = result.replace(r_paren_marker, " )")
+ # Strip parentheses and spaces around the final
+ # condition.
+ result = result[1:-1]
+ result = result.strip(" ")
+ # Reset the parenthesis count for the next condition.
+ condition_parts_count = 0
+ return result
+
Condition = add_element("Condition", pp.Combine(ConditionPart + ConditionRepeated))
- Condition.setParseAction(lambda x: " ".join(x).strip().replace(":", " && ").strip(" && "))
+ Condition.setParseAction(handle_condition)
# Weird thing like write_file(a)|error() where error() is the alternative condition
# which happens to be a function call. In this case there is no scope, but our code expects
diff --git a/util/cmake/tests/data/condition_operator_precedence.pro b/util/cmake/tests/data/condition_operator_precedence.pro
new file mode 100644
index 0000000000..8af628404d
--- /dev/null
+++ b/util/cmake/tests/data/condition_operator_precedence.pro
@@ -0,0 +1,11 @@
+a1|a2 {
+ DEFINES += d
+}
+
+b1|b2:b3 {
+ DEFINES += d
+}
+
+c1|c2:c3|c4 {
+ DEFINES += d
+}
diff --git a/util/cmake/tests/test_parsing.py b/util/cmake/tests/test_parsing.py
index 02ca7f8ae4..bd784edd86 100755
--- a/util/cmake/tests/test_parsing.py
+++ b/util/cmake/tests/test_parsing.py
@@ -28,7 +28,9 @@
#############################################################################
import os
+from pro2cmake import map_condition
from qmake_parser import QmakeParser
+from condition_simplifier import simplify_condition
_tests_path = os.path.dirname(os.path.abspath(__file__))
@@ -352,3 +354,15 @@ def test_value_function():
assert target == 'Dummy'
value = result[1]['value']
assert value[0] == '$$TARGET'
+
+
+def test_condition_operator_precedence():
+ result = parse_file(_tests_path + '/data/condition_operator_precedence.pro')
+
+ def validate_simplify(input_str: str, expected: str) -> None:
+ output = simplify_condition(map_condition(input_str))
+ assert output == expected
+
+ validate_simplify(result[0]["condition"], "a1 OR a2")
+ validate_simplify(result[1]["condition"], "b3 AND (b1 OR b2)")
+ validate_simplify(result[2]["condition"], "c4 OR (c1 AND c3) OR (c2 AND c3)")