Improve qmake syntax parser in pro2cmake.py

Some qtdeclarative pro files caused exceptions when trying to parse
them using the script. This included the following:
- handling conditions divided by newlines and backslashes
- handling conditions that have no scope

The parser has been fixed to deal with those cases and relevant
tests were added.

After the change, all qtdeclarative project files are parseable by
the script.

Change-Id: Ib9736423f7fb3bcc1944b26cfb3114306b4db9a7
Reviewed-by: Qt CMake Build Bot
Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
This commit is contained in:
Alexandru Croitor 2019-05-23 11:53:41 +02:00
parent 80271e8280
commit 6dfeb81bcb
5 changed files with 41 additions and 6 deletions

View File

@ -794,13 +794,26 @@ class QmakeParser:
+ pp.Optional(LC) + (pp.Literal(':') \ + pp.Optional(LC) + (pp.Literal(':') \
| pp.Literal('{') \ | pp.Literal('{') \
| pp.Literal('|')))) | pp.Literal('|'))))
ConditionPart = ((pp.Optional('!') + Identifier + pp.Optional(BracedValue)) \
^ pp.CharsNotIn('#{}|:=\\\n')) + pp.Optional(LC) + ConditionEnd ConditionPart1 = (pp.Optional('!') + Identifier + pp.Optional(BracedValue))
Condition = pp.Combine(ConditionPart \ ConditionPart2 = pp.CharsNotIn('#{}|:=\\\n')
+ pp.ZeroOrMore((pp.Literal('|') ^ pp.Literal(':')) \ ConditionPart = (ConditionPart1 ^ ConditionPart2) + pp.Optional(LC) + ConditionEnd
+ ConditionPart))
ConditionOp = pp.Literal('|') ^ pp.Literal(':')
ConditionLC = pp.Suppress(pp.Optional(pp.White(' ') + LC + pp.White(' ')))
ConditionRepeated = pp.ZeroOrMore((ConditionOp) + ConditionLC + ConditionPart)
Condition = pp.Combine(ConditionPart + ConditionRepeated)
Condition.setParseAction(lambda x: ' '.join(x).strip().replace(':', ' && ').strip(' && ')) Condition.setParseAction(lambda x: ' '.join(x).strip().replace(':', ' && ').strip(' && '))
# 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
# a scope with a list of statements, so create a fake empty statement.
ConditionEndingInFunctionCall = pp.Suppress(ConditionOp) + FunctionCall \
+ pp.Empty().setParseAction(lambda x: [[]])\
.setResultsName('statements')
SingleLineScope = pp.Suppress(pp.Literal(':')) + pp.Optional(LC) \ SingleLineScope = pp.Suppress(pp.Literal(':')) + pp.Optional(LC) \
+ pp.Group(Block | (Statement + EOL))('statements') + pp.Group(Block | (Statement + EOL))('statements')
MultiLineScope = pp.Optional(LC) + Block('statements') MultiLineScope = pp.Optional(LC) + Block('statements')
@ -811,7 +824,7 @@ class QmakeParser:
ElseBranch = pp.Suppress(Else) + (SingleLineElse | MultiLineElse) ElseBranch = pp.Suppress(Else) + (SingleLineElse | MultiLineElse)
Scope <<= pp.Optional(LC) \ Scope <<= pp.Optional(LC) \
+ pp.Group(Condition('condition') \ + pp.Group(Condition('condition') \
+ (SingleLineScope | MultiLineScope) \ + (SingleLineScope | MultiLineScope | ConditionEndingInFunctionCall) \
+ pp.Optional(ElseBranch)('else_statements')) + pp.Optional(ElseBranch)('else_statements'))
if debug: if debug:

View File

@ -0,0 +1,2 @@
write_file("a", contents)|error()

View File

@ -0,0 +1,3 @@
equals(a): \
greaterThan(a):flags += 1

View File

@ -0,0 +1,2 @@
requires(qtConfig(dlopen))

View File

@ -309,3 +309,18 @@ def test_realworld_lc():
def test_realworld_lc_with_comment_in_between(): def test_realworld_lc_with_comment_in_between():
result = parse_file(_tests_path + '/data/lc_with_comment.pro') result = parse_file(_tests_path + '/data/lc_with_comment.pro')
assert len(result) == 6 assert len(result) == 6
def test_condition_without_scope():
result = parse_file(_tests_path + '/data/condition_without_scope.pro')
assert len(result) == 1
def test_multi_condition_divided_by_lc():
result = parse_file(_tests_path + '/data/multi_condition_divided_by_lc.pro')
assert len(result) == 1
def test_nested_function_calls():
result = parse_file(_tests_path + '/data/nested_function_calls.pro')
assert len(result) == 1