diff options
Diffstat (limited to 'util/cmake/pro2cmake.py')
-rwxr-xr-x | util/cmake/pro2cmake.py | 736 |
1 files changed, 736 insertions, 0 deletions
diff --git a/util/cmake/pro2cmake.py b/util/cmake/pro2cmake.py new file mode 100755 index 0000000000..37364d63ad --- /dev/null +++ b/util/cmake/pro2cmake.py @@ -0,0 +1,736 @@ +#!/usr/bin/env python3 +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the plugins of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:GPL-EXCEPT$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 3 as published by the Free Software +## Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +from argparse import ArgumentParser +import os.path +import re +import sys +import io +from typing import IO, List, Dict, Union +import typing + +import pyparsing as pp + +from helper import map_qt_library, featureName, substitute_platform, substitute_libs + + +def _parse_commandline(): + parser = ArgumentParser(description='Generate CMakeLists.txt files from .pro files.') + parser.add_argument('--debug', dest='debug', action='store_true', + help='Turn on all debug output') + parser.add_argument('--debug-parser', dest='debug_parser', action='store_true', + help='Print debug output from qmake parser.') + parser.add_argument('--debug-parse-result', dest='debug_parse_result', action='store_true', + help='Dump the qmake parser result.') + parser.add_argument('--debug-parse-dictionary', dest='debug_parse_dictionary', action='store_true', + help='Dump the qmake parser result as dictionary.') + parser.add_argument('--debug-pro-structure', dest='debug_pro_structure', action='store_true', + help='Dump the structure of the qmake .pro-file.') + parser.add_argument('--debug-full-pro-structure', dest='debug_full_pro_structure', action='store_true', + help='Dump the full structure of the qmake .pro-file (with includes).') + parser.add_argument('files', metavar='<.pro/.pri file>', type=str, nargs='+', + help='The .pro/.pri file to process') + + return parser.parse_args() + + +def spaces(indent: int) -> str: + return ' ' * indent + + +def map_to_file(f: str, top_dir: str, current_dir: str, + want_absolute_path: bool = False) -> typing.Optional[str]: + if f == '$$NO_PCH_SOURCES': + return None + if f.startswith('$$PWD/') or f == '$$PWD': # INCLUDEPATH += $$PWD + return os.path.join(os.path.relpath(current_dir, top_dir), f[6:]) + if f.startswith('$$OUT_PWD/'): + return "${CMAKE_CURRENT_BUILD_DIR}/" + f[10:] + if f.startswith('$$QT_SOURCE_TREE'): + return "${PROJECT_SOURCE_DIR}/" + f[17:] + if f.startswith("./"): + return os.path.join(current_dir, f) + if want_absolute_path and not os.path.isabs(f): + return os.path.join(current_dir, f) + return f + + +def map_source_to_cmake(source: str) -> typing.Optional[str]: + if not source or source == '$$NO_PCH_SOURCES': + return None + if source.startswith('$$PWD/'): + return source[6:] + if source == '.': + return "${CMAKE_CURRENT_SOURCE_DIR}" + if source.startswith('$$QT_SOURCE_TREE/'): + return "${PROJECT_SOURCE_DIR}/" + source[17:] + return source + + +def map_source_to_fs(base_dir: str, file: str, source: str) -> typing.Optional[str]: + if source is None or source == '$$NO_PCH_SOURCES': + return None + if source.startswith('$$PWD/'): + return os.path.join(os.path.dirname(file), source[6:]) + if source.startswith('$$QT_SOURCE_TREE/'): + return os.path.join('.', source[17:]) + if source.startswith('${PROJECT_SOURCE_DIR}/'): + return os.path.join('.', source[22:]) + if source.startswith('${CMAKE_CURRENT_SOURCE_DIR}/'): + return os.path.join(base_dir, source[28:]) + return os.path.join(base_dir, source) + + +class Scope: + def __init__(self, file: typing.Optional[str]=None, condition: str='', base_dir: str='') -> None: + self._parent = None # type: Scope + self._basedir = base_dir + if file: + self._currentdir = os.path.dirname(file) + if not self._currentdir: + self._currentdir = '.' + if not self._basedir: + self._basedir = self._currentdir + + self._file = file + self._condition = map_condition(condition) + self._children = [] # type: List[Scope] + self._values = {} # type: Dict[str, List[str]] + + def merge(self, other: 'Scope') -> None: + for c in other._children: + self.add_child(c) + other.set_basedir(self._basedir) + + for k in self._values.keys(): + self.append_value(k, other.get(k, [])) + + for k in other._values.keys(): + if k not in self._values: + self.set_value(k, other.get(k)) + + def set_basedir(self, dir: str) -> None: + self._basedir = dir + for c in self._children: + c.set_basedir(dir) + + def basedir(self) -> str: + return self._basedir + + def currentdir(self) -> str: + return self._currentdir + + @staticmethod + def FromDict(file: str, statements, cond: str = '', base_dir: str = ''): + scope = Scope(file, cond, base_dir) + for statement in statements: + if isinstance(statement, list): # Handle skipped parts... + assert not statement + continue + + operation = statement.get('operation', None) + if operation: + key = statement.get('key', '') + value = statement.get('value', []) + assert key != '' + + if key in ('HEADERS', 'SOURCES', 'INCLUDEPATH') or key.endswith('_HEADERS') or key.endswith('_SOURCES'): + value = [map_to_file(v, scope.basedir(), scope.currentdir()) for v in value] + + if operation == '=': + scope.set_value(key, value) + elif operation == '-=': + scope.substract_value(key, value) + elif operation == '+=' or operation == '*=': + scope.append_value(key, value) + else: + print('Unexpected operation "{}" in scope with condition {}.'.format(operation, cond)) + assert(False) + + continue + + condition = statement.get('condition', None) + if condition: + child = Scope.FromDict(file, statement.get('statements'), condition, scope.basedir()) + scope.add_child(child) + + else_statements = statement.get('else_statements') + if else_statements: + child = Scope.FromDict(file, else_statements, 'NOT ' + condition, scope.basedir()) + scope.add_child(child) + continue + + loaded = statement.get('loaded', None) + if loaded: + scope.append_value('_LOADED', loaded) + continue + + option = statement.get('option', None) + if option: + scope.append_value('_OPTION', option) + continue + + included = statement.get('included', None) + if included: + scope.append_value('_INCLUDED', + map_to_file(included, scope.basedir(), scope.currentdir())) + continue + + return scope + + def file(self) -> str: + return self._file or '' + + def cMakeListsFile(self) -> str: + return os.path.join(self.basedir(), 'CMakeLists.txt') + + def condition(self) -> str: + return self._condition + + def _push_down_TEMPLATE(self, template: str) -> None: + if not self._rawTemplate(): + self.set_value('TEMPLATE', [template, ]) + for c in self._children: + c._push_down_TEMPLATE(template) + + def add_child(self, scope: 'Scope') -> None: + scope._parent = self + if not scope._rawTemplate(): + scope._push_down_TEMPLATE(self.getTemplate()) + self._children.append(scope) + + def set_value(self, key: str, value: List[str]) -> None: + self._values[key] = value + + def append_value(self, key: str, value: Union[str, List[str]]) -> None: + array = self._values.get(key, []) + if isinstance(value, str): + array.append(value) + elif isinstance(value, list): + array += value + else: + assert False + self._values[key] = array + + def substract_value(self, key: str, value: Union[str, List[str]]) -> None: + if isinstance(value, str): + to_remove = [value, ] + if isinstance(value, list): + to_remove = value + + self.append_value(key, ['-{}'.format(v) for v in to_remove]) + + def children(self) -> List['Scope']: + return self._children + + def dump(self, *, indent: int = 0) -> None: + ind = ' ' * indent + if self._condition == '': + print('{}Scope {} in {}.'.format(ind, self._file, self._basedir)) + else: + print('{}Scope {} in {} with condition: {}.'.format(ind, self._file, self._basedir, self._condition)) + print('{}Keys:'.format(ind)) + for k in sorted(self._values.keys()): + print('{} {} = "{}"'.format(ind, k, self._values[k])) + print('{}Children:'.format(ind)) + for c in self._children: + c.dump(indent=indent + 1) + + def get(self, key: str, default=None) -> List[str]: + default = default or [] + return self._values.get(key, default) + + def getString(self, key: str, default: str = '') -> str: + v = self.get(key) + if isinstance(v, list): + if len(v) == 0: + return default + assert len(v) == 1 + return v[0] + elif isinstance(v, str): + return v + else: + assert False + return default + + def getTemplate(self) -> str: + return self.getString('TEMPLATE', 'app') + + def _rawTemplate(self) -> str: + return self.getString('TEMPLATE') + + def getTarget(self) -> str: + return self.getString('TARGET') or os.path.splitext(os.path.basename(self.file()))[0] + + +class QmakeParser: + def __init__(self, *, debug: bool = False) -> None: + self._Grammar = self._generate_grammar(debug) + + def _generate_grammar(self, debug: bool): + # Define grammar: + pp.ParserElement.setDefaultWhitespaceChars(' \t') + + LC = pp.Suppress(pp.Literal('\\') + pp.LineEnd()) + EOL = pp.Suppress(pp.Optional(pp.pythonStyleComment()) + pp.LineEnd()) + + Identifier = pp.Word(pp.alphas + '_', bodyChars=pp.alphanums+'_./') + 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(']')) + ))) + # Do not match word ending in '\' since that breaks line continuation:-/ + LiteralValuePart = pp.Word(pp.printables, excludeChars='$#{}()') + SubstitutionValue = pp.Combine(pp.OneOrMore(Substitution | LiteralValuePart | pp.Literal('$'))) + Value = (pp.QuotedString(quoteChar='"', escChar='\\') | SubstitutionValue) + + Values = pp.ZeroOrMore(Value)('value') + + Op = pp.Literal('=') | pp.Literal('-=') | pp.Literal('+=') | pp.Literal('*=') + + Operation = Identifier('key') + Op('operation') + Values('value') + Load = pp.Keyword('load') + pp.Suppress('(') + Identifier('loaded') + pp.Suppress(')') + Include = pp.Keyword('include') + pp.Suppress('(') + pp.CharsNotIn(':{=}#)\n')('included') + pp.Suppress(')') + Option = pp.Keyword('option') + pp.Suppress('(') + Identifier('option') + pp.Suppress(')') + DefineTest = pp.Suppress(pp.Keyword('defineTest') + pp.Suppress('(') + Identifier + pp.Suppress(')') + + pp.nestedExpr(opener='{', closer='}') + pp.LineEnd()) # ignore the whole thing... + FunctionCall = pp.Suppress(Identifier + pp.nestedExpr()) + + Scope = pp.Forward() + + Statement = pp.Group(Load | Include | Option | DefineTest | FunctionCall | Operation) + StatementLine = Statement + EOL + StatementGroup = pp.ZeroOrMore(Scope | EOL | StatementLine) + + Block = pp.Suppress('{') + pp.Optional(EOL) \ + + pp.ZeroOrMore(EOL | Statement + EOL | Scope) \ + + pp.Optional(Statement) + pp.Optional(EOL) \ + + pp.Suppress('}') + pp.Optional(EOL) + + Condition = pp.Optional(pp.White()) + pp.CharsNotIn(':{=}#\\\n') + Condition.setParseAction(lambda x: ' '.join(x).strip()) + + SingleLineScope = pp.Suppress(pp.Literal(':')) + pp.Group(Scope | Block | StatementLine)('statements') + MultiLineScope = Block('statements') + + SingleLineElse = pp.Suppress(pp.Literal(':')) + pp.Group(Scope | StatementLine)('else_statements') + MultiLineElse = pp.Group(Block)('else_statements') + Else = pp.Suppress(pp.Keyword('else')) + (SingleLineElse | MultiLineElse) + Scope <<= pp.Group(Condition('condition') + (SingleLineScope | MultiLineScope) + pp.Optional(Else)) + + if debug: + for ename in "EOL Identifier Substitution SubstitutionValue LiteralValuePart Value Values SingleLineScope MultiLineScope Scope SingleLineElse MultiLineElse Else Condition Block StatementGroup Statement Load Include Option DefineTest FunctionCall Operation".split(): + expr = locals()[ename] + expr.setName(ename) + expr.setDebug() + + Grammar = StatementGroup('statements') + Grammar.ignore(LC) + + return Grammar + + def parseFile(self, file: str): + print('Parsing \"{}\"...'.format(file)) + try: + result = self._Grammar.parseFile(file, parseAll=True) + except pp.ParseException as pe: + print(pe.line) + print(' '*(pe.col-1) + '^') + print(pe) + raise pe + return result + + +def parseProFile(file: str, *, debug=False): + parser = QmakeParser(debug=debug) + return parser.parseFile(file) + + +def map_condition(condition: str) -> str: + condition = condition.replace('!', 'NOT ') + condition = condition.replace('&&', ' AND ') + condition = condition.replace('|', ' OR ') + condition = condition.replace('==', ' STREQUAL ') + + cmake_condition = '' + for part in condition.split(): + # some features contain e.g. linux, that should not be turned upper case + feature = re.match(r"(qtConfig|qtHaveModule)\(([a-zA-Z0-9_-]+)\)", part) + if feature: + part = 'QT_FEATURE_' + featureName(feature.group(2)) + else: + part = substitute_platform(part) + + part = part.replace('true', 'ON') + part = part.replace('false', 'OFF') + cmake_condition += ' ' + part + + return cmake_condition.strip() + + +def handle_subdir(scope: Scope, cm_fh: IO[str], *, indent: int = 0) -> None: + assert scope.getTemplate() == 'subdirs' + ind = ' ' * indent + for sd in scope.get('SUBDIRS', []): + full_sd = os.path.join(scope.basedir(), sd) + if os.path.isdir(full_sd): + cm_fh.write('{}add_subdirectory({})\n'.format(ind, sd)) + elif os.path.isfile(full_sd): + subdir_result = parseProFile(full_sd, debug=False) + subdir_scope = Scope.FromDict(full_sd, subdir_result.asDict().get('statements'), + '', scope.basedir()) + + cmakeify_scope(subdir_scope, cm_fh, indent=indent + 1) + elif sd.startswith('-'): + cm_fh.write('{}### remove_subdirectory("{}")\n'.format(ind, sd[1:])) + else: + print(' XXXX: SUBDIR {} in {}: Not found.'.format(sd, scope.file())) + + for c in scope.children(): + cond = c.condition() + if cond == 'else': + cm_fh.write('\n{}else()\n'.format(ind)) + elif cond: + cm_fh.write('\n{}if({})\n'.format(ind, cond)) + + handle_subdir(c, cm_fh, indent=indent + 1) + + if cond: + cm_fh.write('{}endif()\n'.format(ind)) + + +def sort_sources(sources) -> List[str]: + to_sort = {} # type: Dict[str, List[str]] + for s in sources: + if s is None: + continue + + dir = os.path.dirname(s) + base = os.path.splitext(os.path.basename(s))[0] + if base.endswith('_p'): + base = base[:-2] + sort_name = os.path.join(dir, base) + + array = to_sort.get(sort_name, []) + array.append(s) + + to_sort[sort_name] = array + + lines = [] + for k in sorted(to_sort.keys()): + lines.append(' '.join(sorted(to_sort[k]))) + + return lines + + +def write_header(cm_fh: IO[str], name: str, typename: str, *, indent: int=0): + cm_fh.write('{}#####################################################################\n'.format(spaces(indent))) + cm_fh.write('{}## {} {}:\n'.format(spaces(indent), name, typename)) + cm_fh.write('{}#####################################################################\n\n'.format(spaces(indent))) + + +def write_scope_header(cm_fh: IO[str], *, indent: int=0): + cm_fh.write('\n{}## Scopes:\n'.format(spaces(indent))) + cm_fh.write('{}#####################################################################\n'.format(spaces(indent))) + + +def write_sources_section(cm_fh: IO[str], scope: Scope, *, indent: int=0, + known_libraries=set()) -> None: + ind = spaces(indent) + + plugin_type = scope.get('PLUGIN_TYPE') + if plugin_type: + cm_fh.write('{} TYPE {}\n'.format(ind, plugin_type[0])) + + sources = scope.get('SOURCES') + scope.get('HEADERS') + scope.get('OBJECTIVE_SOURCES') + scope.get('NO_PCH_SOURCES') + scope.get('FORMS') + resources = scope.get('RESOURCES') + if resources: + qrc_only = True + for r in resources: + if not r.endswith('.qrc'): + qrc_only = False + break + + if not qrc_only: + print(' XXXX Ignoring non-QRC file resources.') + else: + sources += resources + + sources = [map_source_to_cmake(s) for s in sources] + if sources: + cm_fh.write('{} SOURCES\n'.format(ind)) + for l in sort_sources(sources): + cm_fh.write('{} {}\n'.format(ind, l)) + + if scope.get('DEFINES'): + cm_fh.write('{} DEFINES\n'.format(ind)) + for d in scope.get('DEFINES'): + d = d.replace('=\\\\\\"$$PWD/\\\\\\"', '="${CMAKE_CURRENT_SOURCE_DIR}/"') + cm_fh.write('{} {}\n'.format(ind, d)) + if scope.get('INCLUDEPATH'): + cm_fh.write('{} INCLUDE_DIRECTORIES\n'.format(ind)) + for i in scope.get('INCLUDEPATH'): + cm_fh.write('{} {}\n'.format(ind, i)) + + dependencies = [map_qt_library(q) for q in scope.get('QT') if map_qt_library(q) not in known_libraries] + dependencies += [map_qt_library(q) for q in scope.get('QT_FOR_PRIVATE') if map_qt_library(q) not in known_libraries] + dependencies += scope.get('QMAKE_USE_PRIVATE') + scope.get('LIBS_PRIVATE') + scope.get('LIBS') + if dependencies: + cm_fh.write('{} LIBRARIES\n'.format(ind)) + is_framework = False + for d in dependencies: + if d == '-framework': + is_framework = True + continue + if is_framework: + d = '${FW%s}' % d + if d.startswith('-l'): + d = d[2:] + d = substitute_libs(d) + cm_fh.write('{} {}\n'.format(ind, d)) + is_framework = False + + +def write_extend_target(cm_fh: IO[str], target: str, scope: Scope, parent_condition: str='', + previous_conditon: str='', *, indent: int=0) -> str: + total_condition = scope.condition() + if total_condition == 'else': + assert previous_conditon, "Else branch without previous condition in: %s" % scope.file() + total_condition = 'NOT ({})'.format(previous_conditon) + if parent_condition: + total_condition = '({}) AND ({})'.format(parent_condition, total_condition) + + extend_qt_io_string = io.StringIO() + write_sources_section(extend_qt_io_string, scope) + extend_qt_string = extend_qt_io_string.getvalue() + + extend_scope = '\n{}extend_target({} CONDITION {}\n{})\n'.format(spaces(indent), target, total_condition, extend_qt_string) + + if not extend_qt_string: + # Comment out the generated extend_target call because there no sources were found, but keep it commented + # for informational purposes. + extend_scope = ''.join(['#' + line for line in extend_scope.splitlines(keepends=True)]) + cm_fh.write(extend_scope) + + children = scope.children() + if children: + prev_condition = '' + for c in children: + prev_condition = write_extend_target(cm_fh, target, c, total_condition, prev_condition) + + return total_condition + + +def write_main_part(cm_fh: IO[str], name: str, typename: str, + cmake_function: str, scope: Scope, *, + extra_lines: typing.List[str] = [], + indent: int=0, + **kwargs: typing.Any): + write_header(cm_fh, name, typename, indent=indent) + + cm_fh.write('{}{}({}\n'.format(spaces(indent), cmake_function, name)) + for extra_line in extra_lines: + cm_fh.write('{} {}\n'.format(spaces(indent), extra_line)) + + write_sources_section(cm_fh, scope, indent=indent, **kwargs) + + # Footer: + cm_fh.write('{})\n'.format(spaces(indent))) + + # Scopes: + if not scope.children(): + return + + write_scope_header(cm_fh, indent=indent) + + for c in scope.children(): + write_extend_target(cm_fh, name, c, '', indent=indent) + + + +def write_module(cm_fh: IO[str], scope: Scope, *, indent: int=0) -> None: + module_name = scope.getTarget() + assert module_name.startswith('Qt') + + extra = [] + if 'static' in scope.get('CONFIG'): + extra.append('STATIC') + if 'no_module_headers' in scope.get('CONFIG'): + extra.append('NO_MODULE_HEADERS') + + write_main_part(cm_fh, module_name[2:], 'Module', 'add_qt_module', scope, + extra_lines=extra, indent=indent, known_libraries={'Qt::Core', }) + + + if 'qt_tracepoints' in scope.get('CONFIG'): + tracepoints = map_to_file(scope.getString('TRACEPOINT_PROVIDER'), scope.basedir(), scope.currentdir()) + cm_fh.write('\n\n{}qt_create_tracepoints({} {})\n'.format(spaces(indent), module_name[2:], tracepoints)) + + +def write_tool(cm_fh: IO[str], scope: Scope, *, indent: int=0) -> None: + tool_name = scope.getTarget() + + write_main_part(cm_fh, tool_name, 'Tool', 'add_qt_tool', scope, + indent=indent, known_libraries={'Qt::Core', }) + + +def write_test(cm_fh: IO[str], scope: Scope, *, indent: int=0) -> None: + test_name = scope.getTarget() + assert test_name + + write_main_part(cm_fh, test_name, 'Test', 'add_qt_test', scope, + indent=indent, known_libraries={'Qt::Core', 'Qt::Test', }) + + +def write_binary(cm_fh: IO[str], scope: Scope, gui: bool=False, *, indent: int=0) -> None: + binary_name = scope.getTarget() + assert binary_name + + extra = ['GUI',] if gui else [] + write_main_part(cm_fh, binary_name, 'Binary', 'add_qt_executable', scope, + extra_lines=extra, indent=indent, known_libraries={'Qt::Core', }) + + +def write_plugin(cm_fh, scope, *, indent: int=0): + plugin_name = scope.getTarget() + assert plugin_name + + write_main_part(cm_fh, plugin_name, 'Plugin', 'add_qt_plugin', scope, + indent=indent, known_libraries={'QtCore', }) + + +def handle_app_or_lib(scope: Scope, cm_fh: IO[str], *, indent=0) -> None: + assert scope.getTemplate() in ('app', 'lib', None) + + is_lib = scope.getTemplate() == 'lib' + is_plugin = any('qt_plugin' == s for s in scope.get('_LOADED', [])) + + if is_lib or 'qt_module' in scope.get('_LOADED', []): + write_module(cm_fh, scope, indent=indent) + elif is_plugin: + write_plugin(cm_fh, scope, indent=indent) + elif 'qt_tool' in scope.get('_LOADED', []): + write_tool(cm_fh, scope, indent=indent) + else: + if 'testcase' in scope.get('CONFIG') or 'testlib' in scope.get('CONFIG'): + write_test(cm_fh, scope, indent=indent) + else: + gui = 'console' not in scope.get('CONFIG') + write_binary(cm_fh, scope, gui, indent=indent) + + docs = scope.getString("QMAKE_DOCS") + if docs: + cm_fh.write("\n{}add_qt_docs({})\n".format(spaces(indent), map_to_file(docs, scope.basedir(), scope.currentdir()))) + + +def handle_qt_for_config(scope: Scope, cm_fh: IO[str], *, indent: int=0) -> None: + for config in scope.get("QT_FOR_CONFIG") or []: + lib = map_qt_library(config) + if lib.endswith("Private"): + cm_fh.write('{}qt_pull_features_into_current_scope(PRIVATE_FEATURES {})\n'.format(spaces(indent), lib[:-len("Private")])) + else: + cm_fh.write('{}qt_pull_features_into_current_scope(PUBLIC_FEATURES {})\n'.format(spaces(indent), lib)) + + +def cmakeify_scope(scope: Scope, cm_fh: IO[str], *, indent: int=0) -> None: + template = scope.getTemplate() + handle_qt_for_config(scope, cm_fh) + if template == 'subdirs': + handle_subdir(scope, cm_fh, indent=indent) + elif template in ('app', 'lib', None): + handle_app_or_lib(scope, cm_fh, indent=indent) + else: + print(' XXXX: {}: Template type {} not yet supported.' + .format(scope.file(), template)) + + +def generate_cmakelists(scope: Scope) -> None: + with open(scope.cMakeListsFile(), 'w') as cm_fh: + assert scope.file() + cm_fh.write('# Generated from {}.\n\n'.format(os.path.basename(scope.file()))) + cmakeify_scope(scope, cm_fh) + + +def do_include(scope: Scope, *, debug: bool=False) -> None: + for i in scope.get('_INCLUDED', []): + dir = scope.basedir() + include_file = map_to_file(i, dir, scope.currentdir(), want_absolute_path=True) + if not os.path.isfile(include_file): + print(' XXXX: Failed to include {}.'.format(include_file)) + continue + + include_result = parseProFile(include_file, debug=debug) + include_scope = Scope.FromDict(include_file, include_result.asDict().get('statements'), + '', dir) + + do_include(include_scope) + + scope.merge(include_scope) + + for c in scope.children(): + do_include(c) + + +def main() -> None: + args = _parse_commandline() + + for file in args.files: + parseresult = parseProFile(file, debug=args.debug_parser or args.debug) + + if args.debug_parse_result or args.debug: + print('\n\n#### Parser result:') + print(parseresult) + print('\n#### End of parser result.\n') + if args.debug_parse_dictionary or args.debug: + print('\n\n####Parser result dictionary:') + print(parseresult.asDict()) + print('\n#### End of parser result dictionary.\n') + + file_scope = Scope.FromDict(file, parseresult.asDict().get('statements')) + + if args.debug_pro_structure or args.debug: + print('\n\n#### .pro/.pri file structure:') + print(file_scope.dump()) + print('\n#### End of .pro/.pri file structure.\n') + + do_include(file_scope) + + if args.debug_full_pro_structure or args.debug: + print('\n\n#### Full .pro/.pri file structure:') + print(file_scope.dump()) + print('\n#### End of full .pro/.pri file structure.\n') + + generate_cmakelists(file_scope) + + +if __name__ == '__main__': + main() |