diff options
Diffstat (limited to 'util/cmake/pro2cmake.py')
| -rwxr-xr-x | util/cmake/pro2cmake.py | 345 |
1 files changed, 2 insertions, 343 deletions
diff --git a/util/cmake/pro2cmake.py b/util/cmake/pro2cmake.py index 211678a8863..5b7c02a7393 100755 --- a/util/cmake/pro2cmake.py +++ b/util/cmake/pro2cmake.py @@ -48,7 +48,6 @@ import xml.etree.ElementTree as ET from argparse import ArgumentParser from textwrap import dedent from textwrap import indent as textwrap_indent -from itertools import chain from functools import lru_cache from shutil import copyfile from typing import ( @@ -65,6 +64,8 @@ from typing import ( Match, Type, ) + +from qmake_parser import parseProFile from special_case_helper import SpecialCaseHandler from helper import ( map_qt_library, @@ -76,9 +77,6 @@ from helper import ( generate_find_package_info, LibraryMapping, ) -from helper import _set_up_py_parsing_nicer_debug_output - -_set_up_py_parsing_nicer_debug_output(pp) cmake_version_string = "3.15.0" @@ -530,37 +528,6 @@ class QmlDir: raise RuntimeError(f"Uhandled qmldir entry {line}") -def fixup_linecontinuation(contents: str) -> str: - # Remove all line continuations, aka a backslash followed by - # a newline character with an arbitrary amount of whitespace - # between the backslash and the newline. - # This greatly simplifies the qmake parsing grammar. - contents = re.sub(r"([^\t ])\\[ \t]*\n", "\\1 ", contents) - contents = re.sub(r"\\[ \t]*\n", "", contents) - return contents - - -def fixup_comments(contents: str) -> str: - # Get rid of completely commented out lines. - # So any line which starts with a '#' char and ends with a new line - # will be replaced by a single new line. - # - # This is needed because qmake syntax is weird. In a multi line - # assignment (separated by backslashes and newlines aka - # # \\\n ), if any of the lines are completely commented out, in - # principle the assignment should fail. - # - # It should fail because you would have a new line separating - # the previous value from the next value, and the next value would - # not be interpreted as a value, but as a new token / operation. - # qmake is lenient though, and accepts that, so we need to take - # care of it as well, as if the commented line didn't exist in the - # first place. - - contents = re.sub(r"\n#[^\n]*?\n", "\n", contents, re.DOTALL) - return contents - - def spaces(indent: int) -> str: return " " * indent @@ -611,48 +578,6 @@ def handle_vpath(source: str, base_dir: str, vpath: List[str]) -> str: return f"{source}-NOTFOUND" -def flatten_list(l): - """ Flattens an irregular nested list into a simple list.""" - for el in l: - if isinstance(el, collections.abc.Iterable) and not isinstance(el, (str, bytes)): - yield from flatten_list(el) - else: - yield el - - -def handle_function_value(group: pp.ParseResults): - function_name = group[0] - function_args = group[1] - if function_name == "qtLibraryTarget": - if len(function_args) > 1: - raise RuntimeError( - "Don't know what to with more than one function argument " - "for $$qtLibraryTarget()." - ) - return str(function_args[0]) - - if function_name == "quote": - # Do nothing, just return a string result - return str(group) - - if function_name == "files": - return str(function_args[0]) - - if function_name == "basename": - if len(function_args) != 1: - print(f"XXXX basename with more than one argument") - if function_args[0] == '_PRO_FILE_PWD_': - return os.path.basename(os.getcwd()) - print(f"XXXX basename with value other than _PRO_FILE_PWD_") - return os.path.basename(str(function_args[0])) - - if isinstance(function_args, pp.ParseResults): - function_args = list(flatten_list(function_args.asList())) - - # For other functions, return the whole expression as a string. - return f"$${function_name}({' '.join(function_args)})" - - class Operation: def __init__(self, value: Union[List[str], str]) -> None: if isinstance(value, list): @@ -1223,272 +1148,6 @@ class Scope(object): return self.get("_INCLUDED") -class QmakeParser: - def __init__(self, *, debug: bool = False) -> None: - self.debug = debug - self._Grammar = self._generate_grammar() - - def _generate_grammar(self): - # Define grammar: - pp.ParserElement.setDefaultWhitespaceChars(" \t") - - def add_element(name: str, value: pp.ParserElement): - nonlocal self - if self.debug: - value.setName(name) - value.setDebug() - return value - - EOL = add_element("EOL", pp.Suppress(pp.LineEnd())) - Else = add_element("Else", pp.Keyword("else")) - Identifier = add_element( - "Identifier", pp.Word(f"{pp.alphas}_", bodyChars=pp.alphanums + "_-./") - ) - BracedValue = add_element( - "BracedValue", - pp.nestedExpr( - ignoreExpr=pp.quotedString - | pp.QuotedString( - quoteChar="$(", endQuoteChar=")", escQuote="\\", unquoteResults=False - ) - ).setParseAction(lambda s, l, t: ["(", *t[0], ")"]), - ) - - Substitution = add_element( - "Substitution", - pp.Combine( - pp.Literal("$") - + ( - ( - (pp.Literal("$") + Identifier + pp.Optional(pp.nestedExpr())) - | (pp.Literal("(") + Identifier + pp.Literal(")")) - | (pp.Literal("{") + Identifier + pp.Literal("}")) - | ( - pp.Literal("$") - + pp.Literal("{") - + Identifier - + pp.Optional(pp.nestedExpr()) - + pp.Literal("}") - ) - | (pp.Literal("$") + pp.Literal("[") + Identifier + pp.Literal("]")) - ) - ) - ), - ) - LiteralValuePart = add_element( - "LiteralValuePart", pp.Word(pp.printables, excludeChars="$#{}()") - ) - SubstitutionValue = add_element( - "SubstitutionValue", - pp.Combine(pp.OneOrMore(Substitution | LiteralValuePart | pp.Literal("$"))), - ) - FunctionValue = add_element( - "FunctionValue", - pp.Group( - pp.Suppress(pp.Literal("$") + pp.Literal("$")) - + Identifier - + pp.nestedExpr() # .setParseAction(lambda s, l, t: ['(', *t[0], ')']) - ).setParseAction(lambda s, l, t: handle_function_value(*t)), - ) - Value = add_element( - "Value", - pp.NotAny(Else | pp.Literal("}") | EOL) - + ( - pp.QuotedString(quoteChar='"', escChar="\\") - | FunctionValue - | SubstitutionValue - | BracedValue - ), - ) - - Values = add_element("Values", pp.ZeroOrMore(Value)("value")) - - Op = add_element( - "OP", pp.Literal("=") | pp.Literal("-=") | pp.Literal("+=") | pp.Literal("*=") | pp.Literal("~=") - ) - - Key = add_element("Key", Identifier) - - Operation = add_element("Operation", Key("key") + Op("operation") + Values("value")) - CallArgs = add_element("CallArgs", pp.nestedExpr()) - - def parse_call_args(results): - out = "" - for item in chain(*results): - if isinstance(item, str): - out += item - else: - out += "(" + parse_call_args(item) + ")" - return out - - CallArgs.setParseAction(parse_call_args) - - Load = add_element("Load", pp.Keyword("load") + CallArgs("loaded")) - Include = add_element("Include", pp.Keyword("include") + CallArgs("included")) - Option = add_element("Option", pp.Keyword("option") + CallArgs("option")) - RequiresCondition = add_element("RequiresCondition", pp.originalTextFor(pp.nestedExpr())) - - def parse_requires_condition(s, l, t): - # The following expression unwraps the condition via the additional info - # set by originalTextFor. - condition_without_parentheses = s[t._original_start + 1 : t._original_end - 1] - - # And this replaces the colons with '&&' similar how it's done for 'Condition'. - condition_without_parentheses = ( - condition_without_parentheses.strip().replace(":", " && ").strip(" && ") - ) - return condition_without_parentheses - - RequiresCondition.setParseAction(parse_requires_condition) - Requires = add_element( - "Requires", pp.Keyword("requires") + RequiresCondition("project_required_condition") - ) - - # ignore the whole thing... - DefineTestDefinition = add_element( - "DefineTestDefinition", - pp.Suppress( - pp.Keyword("defineTest") - + CallArgs - + pp.nestedExpr(opener="{", closer="}", ignoreExpr=pp.LineEnd()) - ), - ) - - # ignore the whole thing... - ForLoop = add_element( - "ForLoop", - pp.Suppress( - pp.Keyword("for") - + CallArgs - + pp.nestedExpr(opener="{", closer="}", ignoreExpr=pp.LineEnd()) - ), - ) - - # ignore the whole thing... - ForLoopSingleLine = add_element( - "ForLoopSingleLine", - pp.Suppress(pp.Keyword("for") + CallArgs + pp.Literal(":") + pp.SkipTo(EOL)), - ) - - # ignore the whole thing... - FunctionCall = add_element("FunctionCall", pp.Suppress(Identifier + pp.nestedExpr())) - - Scope = add_element("Scope", pp.Forward()) - - Statement = add_element( - "Statement", - pp.Group( - Load - | Include - | Option - | Requires - | ForLoop - | ForLoopSingleLine - | DefineTestDefinition - | FunctionCall - | Operation - ), - ) - StatementLine = add_element("StatementLine", Statement + (EOL | pp.FollowedBy("}"))) - StatementGroup = add_element( - "StatementGroup", pp.ZeroOrMore(StatementLine | Scope | pp.Suppress(EOL)) - ) - - Block = add_element( - "Block", - pp.Suppress("{") - + pp.Optional(EOL) - + StatementGroup - + pp.Optional(EOL) - + pp.Suppress("}") - + pp.Optional(EOL), - ) - - ConditionEnd = add_element( - "ConditionEnd", - pp.FollowedBy( - (pp.Optional(pp.White()) + (pp.Literal(":") | pp.Literal("{") | pp.Literal("|"))) - ), - ) - - ConditionPart1 = add_element( - "ConditionPart1", (pp.Optional("!") + Identifier + pp.Optional(BracedValue)) - ) - ConditionPart2 = add_element("ConditionPart2", pp.CharsNotIn("#{}|:=\\\n")) - ConditionPart = add_element( - "ConditionPart", (ConditionPart1 ^ ConditionPart2) + ConditionEnd - ) - - ConditionOp = add_element("ConditionOp", pp.Literal("|") ^ pp.Literal(":")) - ConditionWhiteSpace = add_element( - "ConditionWhiteSpace", pp.Suppress(pp.Optional(pp.White(" "))) - ) - - ConditionRepeated = add_element( - "ConditionRepeated", pp.ZeroOrMore(ConditionOp + ConditionWhiteSpace + ConditionPart) - ) - - Condition = add_element("Condition", pp.Combine(ConditionPart + ConditionRepeated)) - 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 = add_element( - "ConditionEndingInFunctionCall", - pp.Suppress(ConditionOp) - + FunctionCall - + pp.Empty().setParseAction(lambda x: [[]]).setResultsName("statements"), - ) - - SingleLineScope = add_element( - "SingleLineScope", - pp.Suppress(pp.Literal(":")) + pp.Group(Block | (Statement + EOL))("statements"), - ) - MultiLineScope = add_element("MultiLineScope", Block("statements")) - - SingleLineElse = add_element( - "SingleLineElse", - pp.Suppress(pp.Literal(":")) + (Scope | Block | (Statement + pp.Optional(EOL))), - ) - MultiLineElse = add_element("MultiLineElse", Block) - ElseBranch = add_element("ElseBranch", pp.Suppress(Else) + (SingleLineElse | MultiLineElse)) - - # Scope is already add_element'ed in the forward declaration above. - Scope <<= pp.Group( - Condition("condition") - + (SingleLineScope | MultiLineScope | ConditionEndingInFunctionCall) - + pp.Optional(ElseBranch)("else_statements") - ) - - Grammar = StatementGroup("statements") - Grammar.ignore(pp.pythonStyleComment()) - - return Grammar - - def parseFile(self, file: str): - print(f'Parsing "{file}"...') - try: - with open(file, "r") as file_fd: - contents = file_fd.read() - - # old_contents = contents - contents = fixup_comments(contents) - contents = fixup_linecontinuation(contents) - result = self._Grammar.parseString(contents, parseAll=True) - except pp.ParseException as pe: - print(pe.line) - print(f"{' ' * (pe.col-1)}^") - print(pe) - raise pe - return result - - -def parseProFile(file: str, *, debug=False): - parser = QmakeParser(debug=debug) - return parser.parseFile(file) - - # Given "if(a|b):c" returns "(a|b):c". Uses pyparsing to keep the parentheses # balanced. def unwrap_if(input_string): |
