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>
This commit is contained in:
parent
6708fad936
commit
2389aaf8c7
51
util/cmake/qmake_parser.py
Normal file → Executable file
51
util/cmake/qmake_parser.py
Normal file → Executable file
@ -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
|
||||
|
11
util/cmake/tests/data/condition_operator_precedence.pro
Normal file
11
util/cmake/tests/data/condition_operator_precedence.pro
Normal file
@ -0,0 +1,11 @@
|
||||
a1|a2 {
|
||||
DEFINES += d
|
||||
}
|
||||
|
||||
b1|b2:b3 {
|
||||
DEFINES += d
|
||||
}
|
||||
|
||||
c1|c2:c3|c4 {
|
||||
DEFINES += d
|
||||
}
|
@ -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)")
|
||||
|
Loading…
x
Reference in New Issue
Block a user