summaryrefslogtreecommitdiffstats
path: root/util/cmake/pro2cmake.py
diff options
context:
space:
mode:
Diffstat (limited to 'util/cmake/pro2cmake.py')
-rwxr-xr-xutil/cmake/pro2cmake.py736
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()