summaryrefslogtreecommitdiffstats
path: root/util/cmake
diff options
context:
space:
mode:
Diffstat (limited to 'util/cmake')
-rw-r--r--util/cmake/Pipfile14
-rwxr-xr-xutil/cmake/cmakeconversionrate.py127
-rwxr-xr-xutil/cmake/configurejson2cmake.py860
-rwxr-xr-xutil/cmake/generate_module_map.sh38
-rw-r--r--util/cmake/helper.py206
-rwxr-xr-xutil/cmake/pro2cmake.py736
-rwxr-xr-xutil/cmake/run_pro2cmake.py44
-rw-r--r--util/cmake/tests/__init__.py0
-rw-r--r--util/cmake/tests/data/complex_values.pro22
-rw-r--r--util/cmake/tests/data/definetest.pro6
-rw-r--r--util/cmake/tests/data/else.pro6
-rw-r--r--util/cmake/tests/data/else2.pro4
-rw-r--r--util/cmake/tests/data/else3.pro7
-rw-r--r--util/cmake/tests/data/else4.pro6
-rw-r--r--util/cmake/tests/data/else5.pro10
-rw-r--r--util/cmake/tests/data/else6.pro11
-rw-r--r--util/cmake/tests/data/else7.pro2
-rw-r--r--util/cmake/tests/data/else8.pro5
-rw-r--r--util/cmake/tests/data/function_if.pro4
-rw-r--r--util/cmake/tests/data/include.pro3
-rw-r--r--util/cmake/tests/data/load.pro3
-rw-r--r--util/cmake/tests/data/quoted.pro5
-rw-r--r--util/cmake/tests/data/unset.pro2
-rwxr-xr-xutil/cmake/tests/test_parsing.py162
24 files changed, 2283 insertions, 0 deletions
diff --git a/util/cmake/Pipfile b/util/cmake/Pipfile
new file mode 100644
index 0000000000..d7e1905378
--- /dev/null
+++ b/util/cmake/Pipfile
@@ -0,0 +1,14 @@
+[[source]]
+url = "https://pypi.org/simple"
+verify_ssl = true
+name = "pypi"
+
+[packages]
+pytest = "*"
+mypy = "*"
+pyparsing = "*"
+
+[dev-packages]
+
+[requires]
+python_version = "3.7"
diff --git a/util/cmake/cmakeconversionrate.py b/util/cmake/cmakeconversionrate.py
new file mode 100755
index 0000000000..3496ed1b91
--- /dev/null
+++ b/util/cmake/cmakeconversionrate.py
@@ -0,0 +1,127 @@
+#!/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
+import re
+import subprocess
+import sys
+import typing
+
+
+def _parse_commandline():
+ parser = ArgumentParser(description='Calculate the conversion rate to cmake.')
+ parser.add_argument('--debug', dest='debug', action='store_true',
+ help='Turn on debug output')
+ parser.add_argument('source_directory', metavar='<Source Directory>', type=str,
+ help='The Qt module source directory')
+ parser.add_argument('binary_directory', metavar='<CMake build direcotry>', type=str,
+ help='The CMake build directory (might be empty)')
+
+ return parser.parse_args()
+
+
+def calculate_baseline(source_directory: str, *, debug: bool=False) -> int:
+ if debug:
+ print('Scanning "{}" for qmake-based tests.'.format(source_directory))
+ result = subprocess.run('/usr/bin/git grep -E "^\\s*CONFIG\\s*\\+?=.*\\btestcase\\b" | sort -u | wc -l',
+ shell=True, capture_output=True, cwd=source_directory)
+ return int(result.stdout)
+
+
+def build(source_directory: str, binary_directory: str, *, debug=False) -> None:
+ abs_source = os.path.abspath(source_directory)
+ if not os.path.isdir(binary_directory):
+ os.makedirs(binary_directory)
+ if not os.path.exists(os.path.join(binary_directory, 'CMakeCache.txt')):
+
+ if debug:
+ print('Running cmake in "{}".'.format(binary_directory))
+ result = subprocess.run(['/usr/bin/cmake', '-GNinja', abs_source], cwd=binary_directory)
+ if debug:
+ print('CMake return code: {}.'.format(result.returncode))
+
+ assert result.returncode == 0
+
+ if debug:
+ print('Running ninja in "{}".'.format(binary_directory))
+ result = subprocess.run('/usr/bin/ninja', cwd=binary_directory)
+ if debug:
+ print('Ninja return code: {}.'.format(result.returncode))
+
+ assert result.returncode == 0
+
+
+def test(binary_directory: str, *, debug=False) -> typing.Tuple[int, int]:
+ if debug:
+ print('Running ctest in "{}".'.format(binary_directory))
+ result = subprocess.run('/usr/bin/ctest -j 250 | grep "tests passed, "',
+ shell=True, capture_output=True, cwd=binary_directory)
+ summary = result.stdout.decode('utf-8').replace('\n', '')
+ if debug:
+ print('Test summary: {} ({}).'.format(summary, result.returncode))
+
+ matches = re.fullmatch(r'\d+% tests passed, (\d+) tests failed out of (\d+)', summary)
+ if matches:
+ if debug:
+ print('Matches: failed {}, total {}.'.format(matches.group(1), matches.group(2)))
+ return (int(matches.group(2)), int(matches.group(2)) - int(matches.group(1)), )
+
+ return (0, 0,)
+
+
+def main() -> int:
+ args = _parse_commandline()
+
+ base_line = calculate_baseline(args.source_directory, debug=args.debug)
+ if base_line <= 0:
+ print('Could not find the qmake baseline in {}.'.format(args.source_directory))
+ return 1
+
+ if args.debug:
+ print('qmake baseline: {} test binaries.'.format(base_line))
+
+ cmake_total = 0
+ cmake_success = 0
+ try:
+ build(args.source_directory, args.binary_directory, debug=args.debug)
+ (cmake_total, cmake_success, ) = test(args.binary_directory, debug=args.debug)
+ finally:
+ if cmake_total == 0:
+ print('\n\n\nCould not calculate the cmake state.')
+ return 2
+ else:
+ print('\n\n\nCMake test conversion rate: {:.2%}.'.format(cmake_total / base_line))
+ print('CMake test success rate : {:.2%}.'.format(cmake_success / base_line))
+ return 0
+
+
+if __name__ == '__main__':
+ main()
diff --git a/util/cmake/configurejson2cmake.py b/util/cmake/configurejson2cmake.py
new file mode 100755
index 0000000000..4623e8d874
--- /dev/null
+++ b/util/cmake/configurejson2cmake.py
@@ -0,0 +1,860 @@
+#!/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$
+##
+#############################################################################
+
+import json
+import os.path
+import re
+import sys
+from typing import Set, Union, List, Dict
+
+from helper import map_qt_library, featureName, substitute_platform
+
+knownTests = set() # type: Set[str]
+
+
+class LibraryMapping:
+ def __init__(self, package: str, resultVariable: str, appendFoundSuffix: bool = True) -> None:
+ self.package = package
+ self.resultVariable = resultVariable
+ self.appendFoundSuffix = appendFoundSuffix
+
+
+def map_library(lib: str) -> Union[str, LibraryMapping, List[str]]:
+ libmap = {
+ 'zlib': 'ZLIB',
+ 'gbm': 'gbm',
+ 'host_dbus': None,
+ 'libdl': None, # handled by CMAKE_DL_LIBS
+ 'libatomic': 'Atomic',
+ 'double-conversion': 'WrapDoubleConversion',
+ 'gnu_iconv': None,
+ 'sun_iconv': None,
+ 'posix_iconv': None,
+ 'icu': ['ICU', 'COMPONENTS', 'i18n', 'uc', 'data'],
+ 'pcre2': ['PCRE2', 'REQUIRED'],
+ 'libpng': 'PNG',
+ 'libudev': 'Libudev',
+ 'udev': 'Libudev',
+ 'journald': 'Libsystemd',
+ 'vulkan': 'Vulkan',
+ 'glib': 'GLib',
+ 'harfbuzz': 'harfbuzz',
+ 'opengl': LibraryMapping(package="OpenGL", resultVariable="OpenGL_OpenGL"),
+ 'egl': LibraryMapping(package="OpenGL", resultVariable="OpenGL_EGL"),
+ 'openssl_headers': LibraryMapping(package="OpenSSL", resultVariable="OPENSSL_INCLUDE_DIR", appendFoundSuffix=False),
+ 'libpng': 'PNG',
+ 'libjpeg': 'JPEG',
+ 'freetype': 'Freetype',
+ 'fontconfig': LibraryMapping(package='Fontconfig', resultVariable="FONTCONFIG"),
+ 'libinput': 'Libinput',
+ 'xcb': ['XCB', '1.9'],
+ 'libproxy': 'libproxy',
+ 'drm': 'Libdrm',
+ 'xkbcommon': ['XKB', '0.4.1'],
+ 'xlib': 'X11',
+ 'xcb_xlib': 'X11_XCB',
+ 'xrender': LibraryMapping(package="XCB", resultVariable="XCB_RENDER"),
+ 'xcb_render': LibraryMapping(package="XCB", resultVariable="XCB_RENDER"),
+ 'xcb_glx': LibraryMapping(package="XCB", resultVariable="XCB_GLX"),
+ 'xcb_xkb': LibraryMapping(package="XCB", resultVariable="XCB_XKB"),
+ 'xcb_xinput': LibraryMapping(package="XCB", resultVariable="XCB_XINPUT"),
+ 'x11sm': LibraryMapping(package="X11", resultVariable="X11_SM"),
+ 'wayland_server': 'Wayland',
+ } # type: Dict[str, Union[str, List[str], LibraryMapping]]
+ if lib not in libmap:
+ raise Exception(' XXXX Unknown library "{}".'.format(lib))
+
+ return libmap[lib]
+
+
+def map_tests(test: str) -> str:
+ testmap = {
+ 'c++11': '$<COMPILE_FEATURES:cxx_std_11>',
+ 'c++14': '$<COMPILE_FEATURES:cxx_std_14>',
+ 'c++1z': '$<COMPILE_FEATURES:cxx_std_17>',
+ 'c99': '$<COMPILE_FEATURES:c_std_99>',
+ 'c11': '$<COMPILE_FEATURES:c_std_11>',
+
+ 'x86SimdAlways': 'ON', # FIXME: Is this the right thing?
+
+ 'aesni': 'TEST_subarch_aes',
+ 'avx': 'TEST_subarch_avx',
+ 'avx2': 'TEST_subarch_avx2',
+ 'avx512f': 'TEST_subarch_avx512f',
+ 'avx512cd': 'TEST_subarch_avx512cd',
+ 'avx512dq': 'TEST_subarch_avx512dq',
+ 'avx512bw': 'TEST_subarch_avx512bw',
+ 'avx512er': 'TEST_subarch_avx512er',
+ 'avx512pf': 'TEST_subarch_avx512pf',
+ 'avx512vl': 'TEST_subarch_avx512vl',
+ 'avx512ifma': 'TEST_subarch_avx512ifma',
+ 'avx512vbmi': 'TEST_subarch_avx512vbmi',
+ 'avx512vbmi2': 'TEST_subarch_avx512vbmi2',
+ 'avx512vpopcntdq': 'TEST_subarch_avx512vpopcntdq',
+ 'avx5124fmaps': 'TEST_subarch_avx5124fmaps',
+ 'avx5124vnniw': 'TEST_subarch_avx5124vnniw',
+ 'bmi': 'TEST_subarch_bmi',
+ 'bmi2': 'TEST_subarch_bmi2',
+ 'cx16': 'TEST_subarch_cx16',
+ 'f16c': 'TEST_subarch_c16c',
+ 'fma': 'TEST_subarch_fma',
+ 'fma4': 'TEST_subarch_fma4',
+ 'fsgsbase': 'TEST_subarch_fsgsbase',
+ 'gfni': 'TEST_subarch_gfni',
+ 'ibt': 'TEST_subarch_ibt',
+ 'lwp': 'TEST_subarch_lwp',
+ 'lzcnt': 'TEST_subarch_lzcnt',
+ 'mmx': 'TEST_subarch_mmx',
+ 'movbe': 'TEST_subarch_movbe',
+ 'mpx': 'TEST_subarch_mpx',
+ 'no-sahf': 'TEST_subarch_no_shaf',
+ 'pclmul': 'TEST_subarch_pclmul',
+ 'popcnt': 'TEST_subarch_popcnt',
+ 'prefetchwt1': 'TEST_subarch_prefetchwt1',
+ 'prfchw': 'TEST_subarch_prfchw',
+ 'pdpid': 'TEST_subarch_rdpid',
+ 'rdpid': 'TEST_subarch_rdpid',
+ 'rdseed': 'TEST_subarch_rdseed',
+ 'rdrnd': 'TEST_subarch_rdseed', # FIXME: Is this the right thing?
+ 'rtm': 'TEST_subarch_rtm',
+ 'shani': 'TEST_subarch_sha',
+ 'shstk': 'TEST_subarch_shstk',
+ 'sse2': 'TEST_subarch_sse2',
+ 'sse3': 'TEST_subarch_sse3',
+ 'ssse3': 'TEST_subarch_ssse3',
+ 'sse4a': 'TEST_subarch_sse4a',
+ 'sse4_1': 'TEST_subarch_sse4_1',
+ 'sse4_2': 'TEST_subarch_sse4_2',
+ 'tbm': 'TEST_subarch_tbm',
+ 'xop': 'TEST_subarch_xop',
+
+ 'neon': 'TEST_subarch_neon',
+ 'iwmmxt': 'TEST_subarch_iwmmxt',
+ 'crc32': 'TEST_subarch_crc32',
+
+ 'vis': 'TEST_subarch_vis',
+ 'vis2': 'TEST_subarch_vis2',
+ 'vis3': 'TEST_subarch_vis3',
+
+ 'dsp': 'TEST_subarch_dsp',
+ 'dspr2': 'TEST_subarch_dspr2',
+
+ 'altivec': 'TEST_subarch_altivec',
+ 'spe': 'TEST_subarch_spe',
+ 'vsx': 'TEST_subarch_vsx',
+
+ 'posix-iconv': 'TEST_posix_iconv',
+ 'sun-iconv': 'TEST_sun_iconv',
+
+ 'openssl11': '(OPENSSL_VERSION VERSION_GREATER_EQUAL "1.1.0")',
+
+ 'reduce_exports': 'CMAKE_CXX_COMPILE_OPTIONS_VISIBILITY',
+ }
+ if test in testmap:
+ return testmap.get(test, None)
+ if test in knownTests:
+ return 'TEST_{}'.format(featureName(test))
+ return None
+
+
+def cm(ctx, *output):
+ txt = ctx['output']
+ if txt != '' and not txt.endswith('\n'):
+ txt += '\n'
+ txt += '\n'.join(output)
+
+ ctx['output'] = txt
+ return ctx
+
+
+def readJsonFromDir(dir):
+ path = os.path.join(dir, 'configure.json')
+
+ print('Reading {}...'.format(path))
+ assert os.path.exists(path)
+
+ with open(path, 'r') as fh:
+ return json.load(fh)
+
+
+def processFiles(ctx, data):
+ print(' files:')
+ if 'files' in data:
+ for (k, v) in data['files'].items():
+ ctx[k] = v
+ return ctx
+
+def parseLib(ctx, lib, data, cm_fh, cmake_find_packages_set):
+ extra = []
+ try:
+ newlib = map_library(lib)
+ if isinstance(newlib, list):
+ extra = newlib[1:]
+ newlib = newlib[0]
+ elif isinstance(newlib, LibraryMapping):
+ newlib = newlib.package
+ except Exception:
+ return ctx
+
+ if newlib is None:
+ print(' **** Skipping library "{}" -- was masked.'.format(lib))
+ return
+
+ print(' mapped library {} to {}.'.format(lib, newlib))
+
+ # Avoid duplicate find_package calls.
+ if newlib in cmake_find_packages_set:
+ return
+
+ cmake_find_packages_set.add(newlib)
+
+ isRequired = False
+
+ if extra:
+ if "REQUIRED" in extra:
+ isRequired = True
+ extra.remove("REQUIRED")
+
+ if extra:
+ cm_fh.write('find_package({} {})\n'.format(newlib, ' '.join(extra)))
+ else:
+ cm_fh.write('find_package({})\n'.format(newlib))
+
+ cm_fh.write('set_package_properties({} PROPERTIES TYPE {})\n'
+ .format(newlib, 'REQUIRED' if isRequired else 'OPTIONAL')
+ )
+
+def lineify(label, value, quote=True):
+ if value:
+ if quote:
+ return ' {} "{}"\n'.format(label, value.replace('"', '\\"'))
+ return ' {} {}\n'.format(label, value)
+ return ''
+
+def map_condition(condition):
+ # Handle NOT:
+ if isinstance(condition, list):
+ condition = '(' + ') AND ('.join(condition) + ')'
+ if isinstance(condition, bool):
+ if condition:
+ return 'ON'
+ else:
+ return 'OFF'
+ assert isinstance(condition, str)
+
+ mapped_features = {
+ "dlopen": "UNIX",
+ 'gbm': 'gbm_FOUND',
+ "sun-libiconv": "TEST_sun_iconv",
+ "system-xcb": "ON",
+ "system-freetype": "ON",
+ }
+
+ # Turn foo != "bar" into (NOT foo STREQUAL 'bar')
+ condition = re.sub(r"(.+)\s*!=\s*('.+')", '(! \\1 == \\2)', condition)
+
+ condition = condition.replace('!', 'NOT ')
+ condition = condition.replace('&&', ' AND ')
+ condition = condition.replace('||', ' OR ')
+ condition = condition.replace('==', ' STREQUAL ')
+
+ # explicitly handle input.sdk == '':
+ condition = re.sub(r"input\.sdk\s*==\s*''", 'NOT INPUT_SDK', condition)
+
+ last_pos = 0
+ mapped_condition = ''
+ has_failed = False
+ for match in re.finditer(r'([a-zA-Z0-9_]+)\.([a-zA-Z0-9_+-]+)', condition):
+ substitution = None
+ appendFoundSuffix = True
+ if match.group(1) == 'libs':
+ try:
+ substitution = map_library(match.group(2))
+ if isinstance(substitution, list):
+ substitution = substitution[0]
+ elif isinstance(substitution, LibraryMapping):
+ appendFoundSuffix = substitution.appendFoundSuffix
+ substitution = substitution.resultVariable
+ except Exception:
+ substitution = None
+
+ if substitution is not None and appendFoundSuffix:
+ substitution += '_FOUND'
+
+ elif match.group(1) == 'features':
+ feature = match.group(2)
+ if feature in mapped_features:
+ substitution = mapped_features.get(feature)
+ else:
+ substitution = 'QT_FEATURE_{}'.format(featureName(match.group(2)))
+
+ elif match.group(1) == 'subarch':
+ substitution = 'TEST_subarch_{}'.format(match.group(2))
+
+ elif match.group(1) == 'call':
+ if match.group(2) == 'crossCompile':
+ substitution = 'CMAKE_CROSSCOMPILING'
+
+ elif match.group(1) == 'tests':
+ substitution = map_tests(match.group(2))
+
+ elif match.group(1) == 'input':
+ substitution = 'INPUT_{}'.format(featureName(match.group(2)))
+
+ elif match.group(1) == 'config':
+ substitution = substitute_platform(match.group(2))
+
+ elif match.group(1) == 'arch':
+ if match.group(2) == 'i386':
+ # FIXME: Does this make sense?
+ substitution = '(TEST_architecture_arch STREQUAL i386)'
+ elif match.group(2) == 'x86_64':
+ substitution = '(TEST_architecture_arch STREQUAL x86_64)'
+ elif match.group(2) == 'arm':
+ # FIXME: Does this make sense?
+ substitution = '(TEST_architecture_arch STREQUAL arm)'
+ elif match.group(2) == 'arm64':
+ # FIXME: Does this make sense?
+ substitution = '(TEST_architecture_arch STREQUAL arm64)'
+ elif match.group(2) == 'mips':
+ # FIXME: Does this make sense?
+ substitution = '(TEST_architecture_arch STREQUAL mips)'
+
+ if substitution is None:
+ print(' XXXX Unknown condition "{}".'.format(match.group(0)))
+ has_failed = True
+ else:
+ mapped_condition += condition[last_pos:match.start(1)] + substitution
+ last_pos = match.end(2)
+
+ mapped_condition += condition[last_pos:]
+
+ # Space out '(' and ')':
+ mapped_condition = mapped_condition.replace('(', ' ( ')
+ mapped_condition = mapped_condition.replace(')', ' ) ')
+
+ # Prettify:
+ condition = re.sub('\\s+', ' ', mapped_condition)
+ condition = condition.strip()
+
+ if has_failed:
+ condition += ' OR FIXME'
+
+ return condition
+
+
+def parseInput(ctx, input, data, cm_fh):
+ skip_inputs = {
+ "prefix", "hostprefix", "extprefix",
+
+ "archdatadir", "bindir", "datadir", "docdir",
+ "examplesdir", "external-hostbindir", "headerdir",
+ "hostbindir", "hostdatadir", "hostlibdir",
+ "importdir", "libdir", "libexecdir",
+ "plugindir", "qmldir", "settingsdir",
+ "sysconfdir", "testsdir", "translationdir",
+
+ "android-arch", "android-ndk", "android-ndk-host",
+ "android-ndk-platform", "android-sdk",
+ "android-toolchain-version", "android-style-assets",
+
+ "appstore-compliant",
+
+ "avx", "avx2", "avx512", "c++std", "ccache", "commercial",
+ "compile-examples", "confirm-license",
+ "dbus",
+ "dbus-runtime",
+
+ "debug", "debug-and-release",
+
+ "developer-build",
+
+ "device", "device-option",
+
+ "f16c",
+
+ "force-asserts", "force-debug-info", "force-pkg-config",
+ "framework",
+
+ "gc-binaries",
+
+ "gdb-index",
+
+ "gcc-sysroot",
+
+ "gcov",
+
+ "gnumake",
+
+ "gui",
+
+ "headersclean",
+
+ "incredibuild-xge",
+
+ "libudev",
+ "ltcg",
+ "make",
+ "make-tool",
+
+ "mips_dsp",
+ "mips_dspr2",
+ "mp",
+
+ "nomake",
+
+ "opensource",
+
+ "optimize-debug", "optimize-size", "optimized-qmake", "optimized-tools",
+
+ "pch",
+
+ "pkg-config",
+
+ "platform",
+
+ "plugin-manifests",
+ "profile",
+ "qreal",
+
+ "reduce-exports", "reduce-relocations",
+
+ "release",
+
+ "rpath",
+
+ "sanitize",
+
+ "sdk",
+
+ "separate-debug-info",
+
+ "shared",
+
+ "silent",
+
+ "qdbus",
+
+ "sse2",
+ "sse3",
+ "sse4.1",
+ "sse4.2",
+ "ssse3",
+ "static",
+ "static-runtime",
+ "strip",
+ "syncqt",
+ "sysroot",
+ "testcocoon",
+ "use-gold-linker",
+ "warnings-are-errors",
+ "Werror",
+ "widgets",
+ "xplatform",
+ "zlib",
+
+ "doubleconversion",
+
+ "eventfd",
+ "glib",
+ "icu",
+ "inotify",
+ "journald",
+ "pcre",
+ "posix-ipc",
+ "pps",
+ "slog2",
+ "syslog",
+
+ "sqlite",
+ }
+
+ if input in skip_inputs:
+ print(' **** Skipping input {}: masked.'.format(input))
+ return
+
+ type = data
+ if isinstance(data, dict):
+ type = data["type"]
+
+ if type == "boolean":
+ print(' **** Skipping boolean input {}: masked.'.format(input))
+ return
+
+ if type == "enum":
+ cm_fh.write("# input {}\n".format(input))
+ cm_fh.write('set(INPUT_{} "undefined" CACHE STRING "")\n'.format(featureName(input)))
+ cm_fh.write('set_property(CACHE INPUT_{} PROPERTY STRINGS undefined {})\n\n'.format(featureName(input), " ".join(data["values"])))
+ return
+
+ print(' XXXX UNHANDLED INPUT TYPE {} in input description'.format(type))
+ return
+
+
+# "tests": {
+# "cxx11_future": {
+# "label": "C++11 <future>",
+# "type": "compile",
+# "test": {
+# "include": "future",
+# "main": [
+# "std::future<int> f = std::async([]() { return 42; });",
+# "(void)f.get();"
+# ],
+# "qmake": "unix:LIBS += -lpthread"
+# }
+# },
+def parseTest(ctx, test, data, cm_fh):
+ skip_tests = {
+ 'c11', 'c99',
+ 'c++11', 'c++14', 'c++1y', 'c++1z',
+ 'reduce_exports',
+ 'posix-iconv', "sun-iconv",
+ 'separate_debug_info', # FIXME: see if cmake can do this
+ 'gc_binaries',
+ }
+
+ if test in skip_tests:
+ print(' **** Skipping features {}: masked.'.format(test))
+ return
+
+ if data["type"] == "compile":
+ knownTests.add(test)
+
+ details = data["test"]
+
+ if isinstance(details, str):
+ print(' XXXX UNHANDLED TEST SUB-TYPE {} in test description'.format(details))
+ return
+
+ head = details.get("head", "")
+ if isinstance(head, list):
+ head = "\n".join(head)
+
+ sourceCode = head + '\n'
+
+ include = details.get("include", "")
+ if isinstance(include, list):
+ include = '#include <' + '>\n#include <'.join(include) + '>'
+ elif include:
+ include = '#include <{}>'.format(include)
+
+ sourceCode += include + '\n'
+
+ tail = details.get("tail", "")
+ if isinstance(tail, list):
+ tail = "\n".join(tail)
+
+ sourceCode += tail + '\n'
+
+ sourceCode += "int main(int argc, char **argv)\n"
+ sourceCode += "{\n"
+ sourceCode += " (void)argc; (void)argv;\n"
+ sourceCode += " /* BEGIN TEST: */\n"
+
+ main = details.get("main", "")
+ if isinstance(main, list):
+ main = "\n".join(main)
+
+ sourceCode += main + '\n'
+
+ sourceCode += " /* END TEST: */\n"
+ sourceCode += " return 0;\n"
+ sourceCode += "}\n"
+
+ sourceCode = sourceCode.replace('"', '\\"')
+
+ cm_fh.write("# {}\n".format(test))
+ cm_fh.write("qt_config_compile_test({}\n".format(featureName(test)))
+ cm_fh.write(lineify("LABEL", data.get("label", "")))
+ cm_fh.write('"' + sourceCode + '"')
+ if "qmake" in details:
+ cm_fh.write("# FIXME: qmake: {}\n".format(details["qmake"]))
+ cm_fh.write(")\n\n")
+
+ elif data["type"] == "x86Simd":
+ knownTests.add(test)
+
+ label = data["label"]
+
+ cm_fh.write("# {}\n".format(test))
+ cm_fh.write("qt_config_compile_test_x86simd({} \"{}\")\n".format(test, label))
+ cm_fh.write("\n")
+
+# "features": {
+# "android-style-assets": {
+# "label": "Android Style Assets",
+# "condition": "config.android",
+# "output": [ "privateFeature" ],
+# "comment": "This belongs into gui, but the license check needs it here already."
+# },
+ else:
+ print(' XXXX UNHANDLED TEST TYPE {} in test description'.format(data["type"]))
+
+
+def parseFeature(ctx, feature, data, cm_fh):
+ skip_features = {
+ 'c++11', 'c++14', 'c++1y', 'c++1z', # C++ versions
+ 'c89', 'c99', 'c11', # C versions
+ 'stl', # Do we really need to test for this in 2018?!
+ 'rpath', 'rpath_dir', # rpath related
+ 'static', 'shared', # static/shared libs
+ 'debug', 'release', 'debug_and_release', 'build_all', 'optimize_debug', 'optimize_size', # build types
+ 'release_tools', 'gcov', 'silent', 'profile',
+ 'msvc_mp', 'static_runtime', 'incredibuild_xge', 'ccache', # compiler specific stuff
+ 'sanitize_address', 'sanitize_thread', 'sanitize_memory', # sanitizer
+ 'sanitize_undefined', 'sanitizer',
+ 'force_debug_info', 'separate_debug_info', 'warnings_are_errors', # FIXME: Do we need these?
+ 'strip', 'precompile_header', 'ltcg', 'enable_new_dtags',
+ 'enable_gdb_index', 'reduce_relocations',
+ 'stack-protector-strong',
+ 'host-dbus', # dbus related
+ 'cross_compile', 'gcc-sysroot', # cross compile related
+ 'gc_binaries', 'qmakeargs', 'use_gold_linker', 'pkg-config', 'verifyspec', # qmake stuff...
+ 'GNUmake', 'compiler-flags',
+ 'system-doubleconversion', 'system-pcre2', 'system-zlib', 'system-png', 'system-jpeg', 'system-freetype', 'system-xcb', 'xkbcommon-system', # system libraries
+ 'doubleconversion',
+ 'dlopen', # handled by CMAKE_DL_LIBS
+ 'alloc_stdlib_h', 'alloc_h', 'alloc_malloc_h', # handled by alloc target
+ 'posix_fallocate', # Only needed for sqlite, which we do not want to build
+ 'qpa_default_platform', # Not a bool!
+ 'sun-libiconv', # internal feature but not referenced in our system
+ }
+ if feature in skip_features:
+ print(' **** Skipping features {}: masked.'.format(feature))
+ return
+
+ disabled_features = set()
+
+ override_condition = {}
+
+ handled = { 'autoDetect', 'comment', 'condition', 'description', 'disable', 'emitIf', 'enable', 'label', 'output', 'purpose', 'section' }
+ label = data.get('label', '')
+ purpose = data.get('purpose', data.get('description', label))
+ autoDetect = map_condition(data.get('autoDetect', ''))
+ condition = override_condition.get(feature, map_condition(data.get('condition', '')))
+ output = data.get('output', [])
+ comment = data.get('comment', '')
+ section = data.get('section', '')
+ enable = map_condition(data.get('enable', ''))
+ disable = map_condition(data.get('disable', ''))
+ emitIf = map_condition(data.get('emitIf', ''))
+
+ if feature in disabled_features:
+ condition = "FALSE"
+
+ for k in [k for k in data.keys() if k not in handled]:
+ print(' XXXX UNHANDLED KEY {} in feature description'.format(k))
+
+ if not output:
+ # feature that is only used in the conditions of other features
+ output = ["internalFeature"]
+
+ publicInfo = False
+ privateInfo = False
+ internalFeature = False
+
+ for o in output:
+ outputType = o
+ outputArgs = {}
+ if isinstance(o, dict):
+ outputType = o['type']
+ outputArgs = o
+
+ if outputType in ['varAssign', 'varAppend', 'varRemove', 'publicQtConfig', 'privateConfig', 'publicConfig']:
+ continue
+
+ elif outputType in ['feature', 'publicFeature', 'define']:
+ publicInfo = True
+ elif outputType == 'privateFeature':
+ privateInfo = True
+ elif outputType == 'internalFeature':
+ internalFeature = True
+ else:
+ print(' XXXX UNHANDLED OUTPUT TYPE {} in feature {}.'.format(outputType, feature))
+ continue
+
+ if not publicInfo and not privateInfo and not internalFeature:
+ print(' **** Skipping feature {}: Not relevant for C++.'.format(feature))
+ return
+
+ # write feature:
+ cxxFeature = featureName(feature)
+ if comment:
+ cm_fh.write('# {}\n'.format(comment))
+
+ cm_fh.write('qt_feature("{}"'.format(cxxFeature))
+ if publicInfo:
+ cm_fh.write(' PUBLIC')
+ if privateInfo:
+ cm_fh.write(' PRIVATE')
+ cm_fh.write('\n')
+
+ cm_fh.write(lineify('SECTION', section))
+ cm_fh.write(lineify('LABEL', label))
+ if purpose != label:
+ cm_fh.write(lineify('PURPOSE', purpose))
+ cm_fh.write(lineify('AUTODETECT', autoDetect, quote=False))
+ cm_fh.write(lineify('CONDITION', condition, quote=False))
+ cm_fh.write(lineify('ENABLE', enable, quote=False))
+ cm_fh.write(lineify('DISABLE', disable, quote=False))
+ cm_fh.write(lineify('EMIT_IF', emitIf, quote=False))
+ cm_fh.write(')\n')
+
+ for o in output:
+ outputType = o
+ outputArgs = {}
+ if isinstance(o, dict):
+ outputType = o['type']
+ outputArgs = o
+
+ # Map feature to define:
+ if outputType == 'feature':
+ outputType = 'define'
+ outputArgs = {'name': 'QT_NO_{}'.format(cxxFeature.upper()),
+ 'negative': True,
+ 'value': 1,
+ 'type': 'define'}
+
+ if outputType != 'define':
+ continue
+
+ if outputArgs.get('name') is None:
+ print(' XXXX DEFINE output without name in feature {}.'.format(feature))
+ continue
+
+ cm_fh.write('qt_feature_definition("{}" "{}"'.format(cxxFeature, outputArgs.get('name')))
+ if outputArgs.get('negative', False):
+ cm_fh.write(' NEGATE')
+ if outputArgs.get('value') is not None:
+ cm_fh.write(' VALUE "{}"'.format(outputArgs.get('value')))
+ cm_fh.write(')\n')
+
+
+def processInputs(ctx, data, cm_fh):
+ print(' inputs:')
+ if 'commandline' not in data:
+ return
+
+ commandLine = data['commandline']
+ if "options" not in commandLine:
+ return
+
+ for input in commandLine['options']:
+ parseInput(ctx, input, commandLine['options'][input], cm_fh)
+
+
+def processTests(ctx, data, cm_fh):
+ print(' tests:')
+ if 'tests' not in data:
+ return
+
+ for test in data['tests']:
+ parseTest(ctx, test, data['tests'][test], cm_fh)
+
+
+def processFeatures(ctx, data, cm_fh):
+ print(' features:')
+ if 'features' not in data:
+ return
+
+ for feature in data['features']:
+ parseFeature(ctx, feature, data['features'][feature], cm_fh)
+
+
+def processLibraries(ctx, data, cm_fh):
+ cmake_find_packages_set = set()
+ print(' libraries:')
+ if 'libraries' not in data:
+ return
+
+ for lib in data['libraries']:
+ parseLib(ctx, lib, data['libraries'][lib], cm_fh, cmake_find_packages_set)
+
+
+def processSubconfigs(dir, ctx, data):
+ assert ctx is not None
+ if 'subconfigs' in data:
+ for subconf in data['subconfigs']:
+ subconfDir = os.path.join(dir, subconf)
+ subconfData = readJsonFromDir(subconfDir)
+ subconfCtx = ctx
+ processJson(subconfDir, subconfCtx, subconfData)
+
+
+def processJson(dir, ctx, data):
+ ctx['module'] = data.get('module', 'global')
+
+ ctx = processFiles(ctx, data)
+
+ with open(os.path.join(dir, "configure.cmake"), 'w') as cm_fh:
+ cm_fh.write("\n\n#### Inputs\n\n")
+
+ processInputs(ctx, data, cm_fh)
+
+ cm_fh.write("\n\n#### Libraries\n\n")
+
+ processLibraries(ctx, data, cm_fh)
+
+ cm_fh.write("\n\n#### Tests\n\n")
+
+ processTests(ctx, data, cm_fh)
+
+ cm_fh.write("\n\n#### Features\n\n")
+
+ processFeatures(ctx, data, cm_fh)
+
+ if ctx.get('module') == 'global':
+ cm_fh.write('\nqt_extra_definition("QT_VERSION_STR" "\\\"${PROJECT_VERSION}\\\"" PUBLIC)\n')
+ cm_fh.write('qt_extra_definition("QT_VERSION_MAJOR" ${PROJECT_VERSION_MAJOR} PUBLIC)\n')
+ cm_fh.write('qt_extra_definition("QT_VERSION_MINOR" ${PROJECT_VERSION_MINOR} PUBLIC)\n')
+ cm_fh.write('qt_extra_definition("QT_VERSION_PATCH" ${PROJECT_VERSION_PATCH} PUBLIC)\n')
+
+ if ctx.get('module') == 'gui':
+ cm_fh.write('\nqt_extra_definition("QT_QPA_DEFAULT_PLATFORM" "${QT_QPA_DEFAULT_PLATFORM}" PUBLIC)\n')
+
+ # do this late:
+ processSubconfigs(dir, ctx, data)
+
+
+def main():
+ if len(sys.argv) != 2:
+ print("This scripts needs one directory to process!")
+ quit(1)
+
+ dir = sys.argv[1]
+
+ print("Processing: {}.".format(dir))
+
+ data = readJsonFromDir(dir)
+ processJson(dir, {}, data)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/util/cmake/generate_module_map.sh b/util/cmake/generate_module_map.sh
new file mode 100755
index 0000000000..1ca0bfc43c
--- /dev/null
+++ b/util/cmake/generate_module_map.sh
@@ -0,0 +1,38 @@
+#!/usr/bin/bash
+#############################################################################
+##
+## 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$
+##
+#############################################################################
+
+pro_files=$(find . -name \*.pro)
+
+for f in ${pro_files}; do
+ if grep "^load(qt_module)" "${f}" > /dev/null ; then
+ target=$(grep "TARGET" "${f}" | cut -d'=' -f2 | sed -e "s/\s*//g")
+ module=$(basename ${f})
+ echo "'${module%.pro}': '${target}',"
+ fi
+done
diff --git a/util/cmake/helper.py b/util/cmake/helper.py
new file mode 100644
index 0000000000..2ddeee98c1
--- /dev/null
+++ b/util/cmake/helper.py
@@ -0,0 +1,206 @@
+#############################################################################
+##
+## 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$
+##
+#############################################################################
+
+import re
+
+
+def featureName(input: str) -> str:
+ return re.sub(r'[^a-zA-Z0-9_]', '_', input)
+
+
+def map_qt_base_library(lib: str) -> str:
+ library_map = {
+ 'global': 'Qt::Core', # manually added special case
+ 'accessibility_support': 'Qt::AccessibilitySupport',
+ 'androidextras': 'Qt::AndroidExtras',
+ 'animation': 'Qt::3DAnimation',
+ 'application-lib': 'Qt::AppManApplication',
+ 'bluetooth': 'Qt::Bluetooth',
+ 'bootstrap-dbus': 'Qt::BootstrapDBus',
+ 'bootstrap': 'Qt::Bootstrap',
+ 'client': 'Qt::WaylandClient',
+ 'clipboard_support': 'Qt::ClipboardSupport',
+ 'common-lib': 'Qt::AppManCommon',
+ 'compositor': 'Qt::WaylandCompositor',
+ 'concurrent': 'Qt::Concurrent',
+ 'container': 'Qt::AxContainer',
+ 'control': 'Qt::AxServer',
+ 'core_headers': 'Qt::WebEngineCore',
+ 'core': 'Qt::Core',
+ 'coretest': 'Qt::3DCoreTest',
+ 'crypto-lib': 'Qt::AppManCrypto',
+ 'dbus': 'Qt::DBus',
+ 'devicediscovery': 'Qt::DeviceDiscoverySupport',
+ 'edid': 'Qt::EdidSupport',
+ 'eglconvenience': 'Qt::EglSupport',
+ 'eglfsdeviceintegration': 'Qt::EglFSDeviceIntegration',
+ 'eglfs_kms_support': 'Qt::EglFsKmsSupport',
+ 'enginio_client': 'Enginio',
+ 'eventdispatchers': 'Qt::EventDispatcherSupport',
+ 'extras': 'Qt::3DExtras',
+ 'fbconvenience': 'Qt::FbSupport',
+ 'fontdatabase_support': 'Qt::FontDatabaseSupport',
+ 'gamepad': 'Qt::Gamepad',
+ 'glxconvenience': 'Qt::GlxSupport',
+ 'graphics_support': 'Qt::GraphicsSupport',
+ 'gsttools': 'Qt::MultimediaGstTools',
+ 'gui': 'Qt::Gui',
+ 'help': 'Qt::Help',
+ 'hunspellinputmethod': 'Qt::HunspellInputMethod',
+ 'input': 'Qt::InputSupport',
+ 'installer-lib': 'Qt::AppManInstaller',
+ 'kmsconvenience': 'Qt::KmsSupport',
+ 'launcher-lib': 'Qt::AppManLauncher',
+ 'lib': 'Qt::Designer',
+ 'linuxaccessibility': 'Qt::LinuxAccessibilitySupport',
+ 'location': 'Qt::Location',
+ 'logic': 'Qt::3DLogic',
+ 'macextras': 'Qt::MacExtras',
+ 'main-lib': 'Qt::AppManMain',
+ 'manager-lib': 'Qt::AppManManager',
+ 'monitor-lib': 'Qt::AppManMonitor',
+ 'multimedia': 'Qt::Multimedia',
+ 'multimediawidgets': 'Qt::MultimediaWidgets',
+ 'network': 'Qt::Network',
+ 'nfc': 'Qt::Nfc',
+ 'oauth': 'Qt::NetworkAuth',
+ 'openglextensions': 'Qt::OpenGLExtensions',
+ 'opengl': 'Qt::OpenGL',
+ 'package-lib': 'Qt::AppManPackage',
+ 'packetprotocol': 'Qt::PacketProtocol',
+ 'particles': 'Qt::QuickParticles',
+ 'platformcompositor': 'Qt::PlatformCompositorSupport',
+ 'plugin-interfaces': 'Qt::AppManPluginInterfaces',
+ 'positioning': 'Qt::Positioning',
+ 'positioningquick': 'Qt::PositioningQuick',
+ 'printsupport': 'Qt::PrintSupport',
+ 'purchasing': 'Qt::Purchasing',
+ 'qmldebug': 'Qt::QmlDebug',
+ 'qmldevtools': 'Qt::QmlDevTools',
+ 'qml': 'Qt::Qml',
+ 'qmltest': 'Qt::QuickTest',
+ 'qtmultimediaquicktools': 'Qt::MultimediaQuick',
+ 'qtzlib': 'Qt::Zlib',
+ 'quick3danimation': 'Qt::3DQuickAnimation',
+ 'quick3dextras': 'Qt::3DQuickExtras',
+ 'quick3dinput': 'Qt::3DQuickInput',
+ 'quick3d': 'Qt::3DQuick',
+ 'quick3drender': 'Qt::3DQuickRender',
+ 'quick3dscene2d': 'Qt::3DQuickScene2D',
+ 'quickcontrols2': 'Qt::QuickControls2',
+ 'quick': 'Qt::Quick',
+ 'quickshapes': 'Qt::QuickShapes',
+ 'quicktemplates2': 'Qt::QuickTemplates2',
+ 'quickwidgets': 'Qt::QuickWidgets',
+ 'render': 'Qt::3DRender',
+ 'script': 'Qt::Script',
+ 'scripttools': 'Qt::ScriptTools',
+ 'sensors': 'Qt::Sensors',
+ 'serialport': 'Qt::SerialPort',
+ 'services': 'Qt::ServiceSupport',
+ 'sql': 'Qt::Sql',
+ 'svg': 'Qt::Svg',
+ 'testlib': 'Qt::Test',
+ 'theme_support': 'Qt::ThemeSupport',
+ 'service_support': 'Qt::ServiceSupport',
+ 'eventdispatcher_support': 'Qt::EventDispatcherSupport',
+ 'edid_support': 'Qt::EdidSupport',
+ 'tts': 'Qt::TextToSpeech',
+ 'uiplugin': 'Qt::UiPlugin',
+ 'uitools': 'Qt::UiTools',
+ 'virtualkeyboard': 'Qt::VirtualKeyboard',
+ 'vkconvenience': 'Qt::VulkanSupport',
+ 'webchannel': 'Qt::WebChannel',
+ 'webengine': 'Qt::WebEngine',
+ 'webenginewidgets': 'Qt::WebEngineWidgets',
+ 'websockets': 'Qt::WebSockets',
+ 'webview': 'Qt::WebView',
+ 'widgets': 'Qt::Widgets',
+ 'window-lib': 'Qt::AppManWindow',
+ 'windowsuiautomation': 'Qt::WindowsUIAutomationSupport',
+ 'winextras': 'Qt::WinExtras',
+ 'x11extras': 'Qt::X11Extras',
+ 'xcb_qpa_lib': 'Qt::XcbQpa',
+ 'xmlpatterns': 'Qt::XmlPatterns',
+ 'xml': 'Qt::Xml',
+ }
+ return library_map.get(lib, lib)
+
+
+def map_qt_library(lib: str) -> str:
+ private = False
+ if lib.endswith('-private'):
+ private = True
+ lib = lib[:-8]
+ mapped = map_qt_base_library(lib)
+ if private:
+ mapped += 'Private'
+ return mapped
+
+
+platform_mapping = {
+ 'win32': 'WIN32',
+ 'unix': 'UNIX',
+ 'darwin': 'APPLE',
+ 'linux': 'LINUX',
+ 'integrity': 'INTEGRITY',
+ 'qnx': 'QNX',
+ 'vxworks': 'VXWORKS',
+ 'hpux': 'HPUX',
+ 'nacl': 'NACL',
+ 'android': 'ANDROID',
+ 'android-embedded': 'ANDROID_EMBEDDED',
+ 'uikit': 'APPLE_UIKIT',
+ 'tvos': 'APPLE_TVOS',
+ 'watchos': 'APPLE_WATCHOS',
+ 'winrt': 'WINRT',
+ 'wasm': 'WASM',
+ 'msvc': 'MSVC',
+ 'clang': 'CLANG',
+ 'gcc': 'GCC',
+ 'osx': 'APPLE_OSX',
+ 'freebsd': 'FREEBSD',
+ 'haiku': 'HAIKU',
+ 'netbsd': 'NETBSD',
+ 'mac': 'APPLE_OSX',
+}
+
+
+def substitute_platform(platform: str) -> str:
+ """ Return the qmake platform as cmake platform or the unchanged string. """
+ return platform_mapping.get(platform, platform)
+
+
+libray_mapping = {
+ 'zlib': 'ZLIB::ZLIB',
+ 'glib': 'PkgConfig::GLib',
+}
+
+
+def substitute_libs(lib: str) -> str:
+ return libray_mapping.get(lib, lib)
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()
diff --git a/util/cmake/run_pro2cmake.py b/util/cmake/run_pro2cmake.py
new file mode 100755
index 0000000000..4340eab094
--- /dev/null
+++ b/util/cmake/run_pro2cmake.py
@@ -0,0 +1,44 @@
+#!/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$
+##
+#############################################################################
+
+import glob
+import os
+import subprocess
+import sys
+
+script_path = os.path.dirname(os.path.abspath(__file__))
+base_path = os.path.dirname(script_path)
+pro2cmake = script_path + '/pro2cmake.py'
+
+if len(sys.argv) > 1:
+ base_path = os.path.abspath(sys.argv[1])
+
+for filename in glob.iglob(base_path + '/**/*.pro', recursive=True):
+ print('Converting:', filename)
+ subprocess.run([pro2cmake, filename])
diff --git a/util/cmake/tests/__init__.py b/util/cmake/tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/util/cmake/tests/__init__.py
diff --git a/util/cmake/tests/data/complex_values.pro b/util/cmake/tests/data/complex_values.pro
new file mode 100644
index 0000000000..4d747c1dd7
--- /dev/null
+++ b/util/cmake/tests/data/complex_values.pro
@@ -0,0 +1,22 @@
+linux:!static {
+ precompile_header {
+ # we'll get an error if we just use SOURCES +=
+ no_pch_assembler.commands = $$QMAKE_CC -c $(CFLAGS) $(INCPATH) ${QMAKE_FILE_IN} -o ${QMAKE_FILE_OUT}
+ no_pch_assembler.dependency_type = TYPE_C
+ no_pch_assembler.output = ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_OBJ)}
+ no_pch_assembler.input = NO_PCH_ASM
+ no_pch_assembler.name = compiling[no_pch] ${QMAKE_FILE_IN}
+ silent: no_pch_assembler.commands = @echo compiling[no_pch] ${QMAKE_FILE_IN} && $$no_pch_assembler.commands
+ CMAKE_ANGLE_GLES2_IMPLIB_RELEASE = libGLESv2.$${QMAKE_EXTENSION_STATICLIB}
+ HOST_BINS = $$[QT_HOST_BINS]
+ CMAKE_HOST_DATA_DIR = $$[QT_HOST_DATA/src]/
+ TR_EXCLUDE += ../3rdparty/*
+
+ QMAKE_EXTRA_COMPILERS += no_pch_assembler
+ NO_PCH_ASM += global/minimum-linux.S
+ } else {
+ SOURCES += global/minimum-linux.S
+ }
+ HEADERS += global/minimum-linux_p.h
+}
+
diff --git a/util/cmake/tests/data/definetest.pro b/util/cmake/tests/data/definetest.pro
new file mode 100644
index 0000000000..76b63d239f
--- /dev/null
+++ b/util/cmake/tests/data/definetest.pro
@@ -0,0 +1,6 @@
+defineTest(pathIsAbsolute) {
+ p = $$clean_path($$1)
+ !isEmpty(p):isEqual(p, $$absolute_path($$p)): return(true)
+ return(false)
+}
+
diff --git a/util/cmake/tests/data/else.pro b/util/cmake/tests/data/else.pro
new file mode 100644
index 0000000000..bbf9c5ac9f
--- /dev/null
+++ b/util/cmake/tests/data/else.pro
@@ -0,0 +1,6 @@
+
+linux {
+ SOURCES += a.cpp
+} else {
+ SOURCES += b.cpp
+}
diff --git a/util/cmake/tests/data/else2.pro b/util/cmake/tests/data/else2.pro
new file mode 100644
index 0000000000..f2ef36ec28
--- /dev/null
+++ b/util/cmake/tests/data/else2.pro
@@ -0,0 +1,4 @@
+
+osx: A = 1
+else: win32: B = 2
+else: C = 3
diff --git a/util/cmake/tests/data/else3.pro b/util/cmake/tests/data/else3.pro
new file mode 100644
index 0000000000..0de9c2c1d9
--- /dev/null
+++ b/util/cmake/tests/data/else3.pro
@@ -0,0 +1,7 @@
+qtConfig(timezone) {
+ A = 1
+} else:win32 {
+ B = 2
+} else {
+ C = 3
+}
diff --git a/util/cmake/tests/data/else4.pro b/util/cmake/tests/data/else4.pro
new file mode 100644
index 0000000000..9ed676ccfa
--- /dev/null
+++ b/util/cmake/tests/data/else4.pro
@@ -0,0 +1,6 @@
+qtConfig(timezone) {
+ A = 1
+} else:win32: B = 2
+else {
+ C = 3
+}
diff --git a/util/cmake/tests/data/else5.pro b/util/cmake/tests/data/else5.pro
new file mode 100644
index 0000000000..3de76af50a
--- /dev/null
+++ b/util/cmake/tests/data/else5.pro
@@ -0,0 +1,10 @@
+# comments
+qtConfig(timezone) { # bar
+ A = 1
+} else:win32 {
+ B = 2 # foo
+} else { C = 3
+# baz
+ # foobar
+}
+# endcomment
diff --git a/util/cmake/tests/data/else6.pro b/util/cmake/tests/data/else6.pro
new file mode 100644
index 0000000000..9eaa834a19
--- /dev/null
+++ b/util/cmake/tests/data/else6.pro
@@ -0,0 +1,11 @@
+qtConfig(timezone) \
+{
+ A = \
+1
+} \
+else:win32: \
+B = 2
+else: \
+ C \
+= 3
+
diff --git a/util/cmake/tests/data/else7.pro b/util/cmake/tests/data/else7.pro
new file mode 100644
index 0000000000..e663b1c05e
--- /dev/null
+++ b/util/cmake/tests/data/else7.pro
@@ -0,0 +1,2 @@
+msvc:equals(QT_ARCH, i386): QMAKE_LFLAGS += /BASE:0x65000000
+
diff --git a/util/cmake/tests/data/else8.pro b/util/cmake/tests/data/else8.pro
new file mode 100644
index 0000000000..6d4d5f01ed
--- /dev/null
+++ b/util/cmake/tests/data/else8.pro
@@ -0,0 +1,5 @@
+qtConfig(timezone) { A = 1 } else:win32: {\
+B = 2 \
+} else: \
+ C \
+= 3 \
diff --git a/util/cmake/tests/data/function_if.pro b/util/cmake/tests/data/function_if.pro
new file mode 100644
index 0000000000..9af018f864
--- /dev/null
+++ b/util/cmake/tests/data/function_if.pro
@@ -0,0 +1,4 @@
+pathIsAbsolute($$CMAKE_HOST_DATA_DIR) {
+ CMAKE_HOST_DATA_DIR = $$[QT_HOST_DATA/src]/
+}
+
diff --git a/util/cmake/tests/data/include.pro b/util/cmake/tests/data/include.pro
new file mode 100644
index 0000000000..22d8a40919
--- /dev/null
+++ b/util/cmake/tests/data/include.pro
@@ -0,0 +1,3 @@
+A = 42
+include(foo) # load foo
+B=23
diff --git a/util/cmake/tests/data/load.pro b/util/cmake/tests/data/load.pro
new file mode 100644
index 0000000000..c9717e9832
--- /dev/null
+++ b/util/cmake/tests/data/load.pro
@@ -0,0 +1,3 @@
+A = 42
+load(foo)# load foo
+B=23
diff --git a/util/cmake/tests/data/quoted.pro b/util/cmake/tests/data/quoted.pro
new file mode 100644
index 0000000000..61682aa0d0
--- /dev/null
+++ b/util/cmake/tests/data/quoted.pro
@@ -0,0 +1,5 @@
+if(linux*|hurd*):!cross_compile:!static:!*-armcc* {
+ prog=$$quote(if (/program interpreter: (.*)]/) { print $1; })
+ DEFINES += ELF_INTERPRETER=\\\"$$system(LC_ALL=C readelf -l /bin/ls | perl -n -e \'$$prog\')\\\"
+}
+
diff --git a/util/cmake/tests/data/unset.pro b/util/cmake/tests/data/unset.pro
new file mode 100644
index 0000000000..7ffb0582f1
--- /dev/null
+++ b/util/cmake/tests/data/unset.pro
@@ -0,0 +1,2 @@
+unset(f16c_cxx)
+
diff --git a/util/cmake/tests/test_parsing.py b/util/cmake/tests/test_parsing.py
new file mode 100755
index 0000000000..78ac7fed26
--- /dev/null
+++ b/util/cmake/tests/test_parsing.py
@@ -0,0 +1,162 @@
+#!/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$
+##
+#############################################################################
+
+import os
+from pro2cmake import QmakeParser
+
+
+_tests_path = os.path.dirname(os.path.abspath(__file__))
+
+
+def validate_op(key, op, value, to_validate):
+ assert key == to_validate['key']
+ assert op == to_validate['operation']
+ assert value == to_validate['value']
+
+
+def validate_single_op(key, op, value, to_validate):
+ assert len(to_validate) == 1
+ validate_op(key, op, value, to_validate[0])
+
+
+def evaluate_condition(to_validate):
+ assert 'condition' in to_validate
+ assert 'statements' in to_validate
+
+ return (to_validate['condition'], to_validate['statements'], to_validate.get('else_statements', {}))
+
+
+def validate_default_else_test(file_name):
+ result = parse_file(file_name)
+ assert len(result) == 1
+
+ (cond, if_branch, else_branch) = evaluate_condition(result[0])
+ assert cond == 'qtConfig(timezone)'
+ validate_single_op('A', '=', ['1'], if_branch)
+
+ assert len(else_branch) == 1
+ (cond2, if2_branch, else2_branch) = evaluate_condition(else_branch[0])
+ assert cond2 == 'win32'
+ validate_single_op('B', '=', ['2'], if2_branch)
+ validate_single_op('C', '=', ['3'], else2_branch)
+
+
+def parse_file(file):
+ p = QmakeParser(debug=True)
+ result = p.parseFile(file).asDict()
+ assert len(result) == 1
+
+ return result['statements']
+
+
+def test_else():
+ result = parse_file(_tests_path + '/data/else.pro')
+ assert len(result) == 1
+
+ (cond, if_branch, else_branch) = evaluate_condition(result[0])
+
+ assert cond == 'linux'
+ validate_single_op('SOURCES', '+=', ['a.cpp'], if_branch)
+ validate_single_op('SOURCES', '+=', ['b.cpp'], else_branch)
+
+
+def test_else2():
+ result = parse_file(_tests_path + '/data/else2.pro')
+ assert len(result) == 1
+
+ (cond, if_branch, else_branch) = evaluate_condition(result[0])
+ assert cond == 'osx'
+ validate_single_op('A', '=', ['1'], if_branch)
+
+ assert len(else_branch) == 1
+ (cond2, if2_branch, else2_branch) = evaluate_condition(else_branch[0])
+ assert cond2 == 'win32'
+ validate_single_op('B', '=', ['2'], if2_branch)
+
+ validate_single_op('C', '=', ['3'], else2_branch)
+
+
+def test_else3():
+ validate_default_else_test(_tests_path + '/data/else3.pro')
+
+def test_else4():
+ validate_default_else_test(_tests_path + '/data/else4.pro')
+
+def test_else5():
+ validate_default_else_test(_tests_path + '/data/else5.pro')
+
+def test_else6():
+ validate_default_else_test(_tests_path + '/data/else6.pro')
+
+def test_else7():
+ result = parse_file(_tests_path + '/data/else7.pro')
+ assert len(result) == 1
+
+def test_else8():
+ validate_default_else_test(_tests_path + '/data/else8.pro')
+
+def test_include():
+ result = parse_file(_tests_path + '/data/include.pro')
+ assert len(result) == 3
+ validate_op('A', '=', ['42'], result[0])
+ include = result[1]
+ assert len(include) == 1
+ assert include.get('included', '') == 'foo'
+ validate_op('B', '=', ['23'], result[2])
+
+def test_load():
+ result = parse_file(_tests_path + '/data/load.pro')
+ assert len(result) == 3
+ validate_op('A', '=', ['42'], result[0])
+ load = result[1]
+ assert len(load) == 1
+ assert load.get('loaded', '') == 'foo'
+ validate_op('B', '=', ['23'], result[2])
+
+def test_definetest():
+ result = parse_file(_tests_path + '/data/definetest.pro')
+ assert len(result) == 1
+ assert result[0] == []
+
+def test_unset():
+ result = parse_file(_tests_path + '/data/unset.pro')
+ assert len(result) == 1
+ assert result[0] == []
+
+def test_quoted():
+ result = parse_file(_tests_path + '/data/quoted.pro')
+ assert len(result) == 1
+
+def test_complex_values():
+ result = parse_file(_tests_path + '/data/complex_values.pro')
+ assert len(result) == 1
+
+def test_function_if():
+ result = parse_file(_tests_path + '/data/function_if.pro')
+ assert len(result) == 1