diff --git a/util/cmake/.gitignore b/util/cmake/.gitignore deleted file mode 100644 index 68b9c10b642..00000000000 --- a/util/cmake/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.pro2cmake_cache/ diff --git a/util/cmake/Makefile b/util/cmake/Makefile deleted file mode 100644 index f8fe4adf4ef..00000000000 --- a/util/cmake/Makefile +++ /dev/null @@ -1,20 +0,0 @@ - -test: flake8 mypy pytest black_format_check - -coverage: - pytest --cov . - -format: - black *.py --line-length 100 - -black_format_check: - black *.py --line-length 100 --check - -flake8: - flake8 *.py --ignore=E501,E266,E203,W503,F541 - -pytest: - pytest - -mypy: - mypy --pretty *.py diff --git a/util/cmake/Pipfile b/util/cmake/Pipfile deleted file mode 100644 index 21c18f47433..00000000000 --- a/util/cmake/Pipfile +++ /dev/null @@ -1,18 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -pyparsing = "*" -sympy = "*" -mypy = "*" -pytest = "*" -pytest-cov = "*" -flake8 = "*" -portalocker = "*" - -[dev-packages] - -[requires] -python_version = "3.7" diff --git a/util/cmake/README.md b/util/cmake/README.md deleted file mode 100644 index 0d80fbcdce4..00000000000 --- a/util/cmake/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# CMake Utils - -This directory holds scripts to help the porting process from `qmake` to `cmake` for Qt6. - -If you're looking to port your own Qt-based project from `qmake` to `cmake`, please use -[qmake2cmake](https://wiki.qt.io/Qmake2cmake). - -# Requirements - -* [Python 3.7](https://www.python.org/downloads/), -* `pipenv` or `pip` to manage the modules. - -## Python modules - -Since Python has many ways of handling projects, you have a couple of options to -install the dependencies of the scripts: - -### Using `pipenv` - -The dependencies are specified on the `Pipfile`, so you just need to run -`pipenv install` and that will automatically create a virtual environment -that you can activate with a `pipenv shell`. - -### Using `pip` - -It's highly recommended to use a [virtualenvironment](https://virtualenv.pypa.io/en/latest/) -to avoid conflict with other packages that are already installed: `pip install virtualenv`. - -* Create an environment: `virtualenv env`, -* Activate the environment: `source env/bin/activate` - (on Windows: `source env\Scripts\activate.bat`) -* Install the requirements: `pip install -r requirements.txt` - -If the `pip install` command above doesn't work, try: - -``` -python3.7 -m pip install -r requirements.txt -``` - -# Contributing to the scripts - -You can verify if the styling of a script is compliant with PEP8, with a couple of exceptions: - -Install [flake8](http://flake8.pycqa.org/en/latest/) (`pip install flake8`) and run it -on all python source files: - -``` -make flake8 -``` - -You can also modify the file with an automatic formatter, -like [black](https://black.readthedocs.io/en/stable/) (`pip install black`), -and execute it: - -``` -make format -``` diff --git a/util/cmake/cmakeconversionrate.py b/util/cmake/cmakeconversionrate.py deleted file mode 100755 index 012ef1ee2d7..00000000000 --- a/util/cmake/cmakeconversionrate.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2018 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -from argparse import ArgumentParser - -import os -import re -import subprocess -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="", - type=str, - help="The Qt module source directory", - ) - parser.add_argument( - "binary_directory", - metavar="", - 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(f'Scanning "{source_directory}" for qmake-based tests.') - 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(f'Running cmake in "{binary_directory}"') - result = subprocess.run(["/usr/bin/cmake", "-GNinja", abs_source], cwd=binary_directory) - if debug: - print(f"CMake return code: {result.returncode}.") - - assert result.returncode == 0 - - if debug: - print(f'Running ninja in "{binary_directory}".') - result = subprocess.run("/usr/bin/ninja", cwd=binary_directory) - if debug: - print(f"Ninja return code: {result.returncode}.") - - assert result.returncode == 0 - - -def test(binary_directory: str, *, debug=False) -> typing.Tuple[int, int]: - if debug: - print(f'Running ctest in "{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(f"Test summary: {summary} ({result.returncode}).") - - matches = re.fullmatch(r"\d+% tests passed, (\d+) tests failed out of (\d+)", summary) - if matches: - if debug: - print(f"Matches: failed {matches.group(1)}, total {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(f"Could not find the qmake baseline in {args.source_directory}.") - return 1 - - if args.debug: - print(f"qmake baseline: {base_line} test binaries.") - - 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(f"\n\n\nCMake test conversion rate: {cmake_total/base_line:.2f}.") - print(f"CMake test success rate : {cmake_success/base_line:.2f}.") - return 0 - - -if __name__ == "__main__": - main() diff --git a/util/cmake/condition_simplifier.py b/util/cmake/condition_simplifier.py deleted file mode 100755 index a540ee0519a..00000000000 --- a/util/cmake/condition_simplifier.py +++ /dev/null @@ -1,211 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2021 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - - -import re -from sympy import simplify_logic, And, Or, Not, SympifyError # type: ignore -from condition_simplifier_cache import simplify_condition_memoize - - -def _iterate_expr_tree(expr, op, matches): - assert expr.func == op - keepers = () - for arg in expr.args: - if arg in matches: - matches = tuple(x for x in matches if x != arg) - elif arg == op: - (matches, extra_keepers) = _iterate_expr_tree(arg, op, matches) - keepers = (*keepers, *extra_keepers) - else: - keepers = (*keepers, arg) - return matches, keepers - - -def _simplify_expressions(expr, op, matches, replacement): - for arg in expr.args: - expr = expr.subs(arg, _simplify_expressions(arg, op, matches, replacement)) - - if expr.func == op: - (to_match, keepers) = tuple(_iterate_expr_tree(expr, op, matches)) - if len(to_match) == 0: - # build expression with keepers and replacement: - if keepers: - start = replacement - current_expr = None - last_expr = keepers[-1] - for repl_arg in keepers[:-1]: - current_expr = op(start, repl_arg) - start = current_expr - top_expr = op(start, last_expr) - else: - top_expr = replacement - - expr = expr.subs(expr, top_expr) - - return expr - - -def _simplify_flavors_in_condition(base: str, flavors, expr): - """Simplify conditions based on the knowledge of which flavors - belong to which OS.""" - base_expr = simplify_logic(base) - false_expr = simplify_logic("false") - for flavor in flavors: - flavor_expr = simplify_logic(flavor) - expr = _simplify_expressions(expr, And, (base_expr, flavor_expr), flavor_expr) - expr = _simplify_expressions(expr, Or, (base_expr, flavor_expr), base_expr) - expr = _simplify_expressions(expr, And, (Not(base_expr), flavor_expr), false_expr) - return expr - - -def _simplify_os_families(expr, family_members, other_family_members): - for family in family_members: - for other in other_family_members: - if other in family_members: - continue # skip those in the sub-family - - f_expr = simplify_logic(family) - o_expr = simplify_logic(other) - - expr = _simplify_expressions(expr, And, (f_expr, Not(o_expr)), f_expr) - expr = _simplify_expressions(expr, And, (Not(f_expr), o_expr), o_expr) - expr = _simplify_expressions(expr, And, (f_expr, o_expr), simplify_logic("false")) - return expr - - -def _recursive_simplify(expr): - """Simplify the expression as much as possible based on - domain knowledge.""" - input_expr = expr - - # Simplify even further, based on domain knowledge: - # windowses = ('WIN32', 'WINRT') - apples = ("MACOS", "UIKIT", "IOS", "TVOS", "WATCHOS") - bsds = ("FREEBSD", "OPENBSD", "NETBSD") - androids = ("ANDROID",) - unixes = ( - "APPLE", - *apples, - "BSD", - *bsds, - "LINUX", - *androids, - "HAIKU", - "INTEGRITY", - "VXWORKS", - "QNX", - "WASM", - ) - - unix_expr = simplify_logic("UNIX") - win_expr = simplify_logic("WIN32") - false_expr = simplify_logic("false") - true_expr = simplify_logic("true") - - expr = expr.subs(Not(unix_expr), win_expr) # NOT UNIX -> WIN32 - expr = expr.subs(Not(win_expr), unix_expr) # NOT WIN32 -> UNIX - - # UNIX [OR foo ]OR WIN32 -> ON [OR foo] - expr = _simplify_expressions(expr, Or, (unix_expr, win_expr), true_expr) - # UNIX [AND foo ]AND WIN32 -> OFF [AND foo] - expr = _simplify_expressions(expr, And, (unix_expr, win_expr), false_expr) - - expr = _simplify_flavors_in_condition("WIN32", ("WINRT",), expr) - expr = _simplify_flavors_in_condition("APPLE", apples, expr) - expr = _simplify_flavors_in_condition("BSD", bsds, expr) - expr = _simplify_flavors_in_condition("UNIX", unixes, expr) - - # Simplify families of OSes against other families: - expr = _simplify_os_families(expr, ("WIN32", "WINRT"), unixes) - expr = _simplify_os_families(expr, androids, unixes) - expr = _simplify_os_families(expr, ("BSD", *bsds), unixes) - - for family in ("HAIKU", "QNX", "INTEGRITY", "LINUX", "VXWORKS"): - expr = _simplify_os_families(expr, (family,), unixes) - - # Now simplify further: - expr = simplify_logic(expr) - - while expr != input_expr: - input_expr = expr - expr = _recursive_simplify(expr) - - return expr - - -@simplify_condition_memoize -def simplify_condition(condition: str) -> str: - input_condition = condition.strip() - - # Map to sympy syntax: - condition = " " + input_condition + " " - condition = condition.replace("(", " ( ") - condition = condition.replace(")", " ) ") - - tmp = "" - while tmp != condition: - tmp = condition - - condition = condition.replace(" NOT ", " ~ ") - condition = condition.replace(" AND ", " & ") - condition = condition.replace(" OR ", " | ") - condition = condition.replace(" ON ", " true ") - condition = condition.replace(" OFF ", " false ") - # Replace dashes with a token - condition = condition.replace("-", "_dash_") - - # SymPy chokes on expressions that contain two tokens one next to - # the other delimited by a space, which are not an operation. - # So a CMake condition like "TARGET Foo::Bar" fails the whole - # expression simplifying process. - # Turn these conditions into a single token so that SymPy can parse - # the expression, and thus simplify it. - # Do this by replacing and keeping a map of conditions to single - # token symbols. - # Support both target names without double colons, and with double - # colons. - pattern = re.compile(r"(TARGET [a-zA-Z]+(?:::[a-zA-Z]+)?)") - target_symbol_mapping = {} - all_target_conditions = re.findall(pattern, condition) - for target_condition in all_target_conditions: - # Replace spaces and colons with underscores. - target_condition_symbol_name = re.sub("[ :]", "_", target_condition) - target_symbol_mapping[target_condition_symbol_name] = target_condition - condition = re.sub(target_condition, target_condition_symbol_name, condition) - - # Do similar token mapping for comparison operators. - pattern = re.compile(r"([a-zA-Z_0-9]+ (?:STRLESS|STREQUAL|STRGREATER) [a-zA-Z_0-9]+)") - comparison_symbol_mapping = {} - all_comparisons = re.findall(pattern, condition) - for comparison in all_comparisons: - # Replace spaces and colons with underscores. - comparison_symbol_name = re.sub("[ ]", "_", comparison) - comparison_symbol_mapping[comparison_symbol_name] = comparison - condition = re.sub(comparison, comparison_symbol_name, condition) - - try: - # Generate and simplify condition using sympy: - condition_expr = simplify_logic(condition) - condition = str(_recursive_simplify(condition_expr)) - - # Restore the target conditions. - for symbol_name in target_symbol_mapping: - condition = re.sub(symbol_name, target_symbol_mapping[symbol_name], condition) - - # Restore comparisons. - for comparison in comparison_symbol_mapping: - condition = re.sub(comparison, comparison_symbol_mapping[comparison], condition) - - # Map back to CMake syntax: - condition = condition.replace("~", "NOT ") - condition = condition.replace("&", "AND") - condition = condition.replace("|", "OR") - condition = condition.replace("True", "ON") - condition = condition.replace("False", "OFF") - condition = condition.replace("_dash_", "-") - except (SympifyError, TypeError, AttributeError): - # sympy did not like our input, so leave this condition alone: - condition = input_condition - - return condition or "ON" diff --git a/util/cmake/condition_simplifier_cache.py b/util/cmake/condition_simplifier_cache.py deleted file mode 100755 index 5995c5bb814..00000000000 --- a/util/cmake/condition_simplifier_cache.py +++ /dev/null @@ -1,154 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2018 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - - -import atexit -import hashlib -import json -import os -import sys -import time - -from typing import Any, Callable, Dict - -condition_simplifier_cache_enabled = True - - -def set_condition_simplified_cache_enabled(value: bool): - global condition_simplifier_cache_enabled - condition_simplifier_cache_enabled = value - - -def get_current_file_path() -> str: - try: - this_file = __file__ - except NameError: - this_file = sys.argv[0] - this_file = os.path.abspath(this_file) - return this_file - - -def get_cache_location() -> str: - this_file = get_current_file_path() - dir_path = os.path.dirname(this_file) - cache_path = os.path.join(dir_path, ".pro2cmake_cache", "cache.json") - return cache_path - - -def get_file_checksum(file_path: str) -> str: - try: - with open(file_path, "r") as content_file: - content = content_file.read() - except IOError: - content = str(time.time()) - checksum = hashlib.md5(content.encode("utf-8")).hexdigest() - return checksum - - -def get_condition_simplifier_checksum() -> str: - current_file_path = get_current_file_path() - dir_name = os.path.dirname(current_file_path) - condition_simplifier_path = os.path.join(dir_name, "condition_simplifier.py") - return get_file_checksum(condition_simplifier_path) - - -def init_cache_dict(): - return { - "checksum": get_condition_simplifier_checksum(), - "schema_version": "1", - "cache": {"conditions": {}}, - } - - -def merge_dicts_recursive(a: Dict[str, Any], other: Dict[str, Any]) -> Dict[str, Any]: - """Merges values of "other" into "a", mutates a.""" - for key in other: - if key in a: - if isinstance(a[key], dict) and isinstance(other[key], dict): - merge_dicts_recursive(a[key], other[key]) - elif a[key] == other[key]: - pass - else: - a[key] = other[key] - return a - - -def open_file_safe(file_path: str, mode: str = "r+"): - # Use portalocker package for file locking if available, - # otherwise print a message to install the package. - try: - import portalocker # type: ignore - - return portalocker.Lock(file_path, mode=mode, flags=portalocker.LOCK_EX) - except ImportError: - print( - "The conversion script is missing a required package: portalocker. Please run " - "python -m pip install -r requirements.txt to install the missing dependency." - ) - exit(1) - - -def simplify_condition_memoize(f: Callable[[str], str]): - cache_path = get_cache_location() - cache_file_content: Dict[str, Any] = {} - - if os.path.exists(cache_path): - try: - with open_file_safe(cache_path, mode="r") as cache_file: - cache_file_content = json.load(cache_file) - except (IOError, ValueError): - print(f"Invalid pro2cmake cache file found at: {cache_path}. Removing it.") - os.remove(cache_path) - - if not cache_file_content: - cache_file_content = init_cache_dict() - - current_checksum = get_condition_simplifier_checksum() - if cache_file_content["checksum"] != current_checksum: - cache_file_content = init_cache_dict() - - def update_cache_file(): - if not os.path.exists(cache_path): - os.makedirs(os.path.dirname(cache_path), exist_ok=True) - # Create the file if it doesn't exist, but don't override - # it. - with open(cache_path, "a"): - pass - - updated_cache = cache_file_content - - with open_file_safe(cache_path, "r+") as cache_file_write_handle: - # Read any existing cache content, and truncate the file. - cache_file_existing_content = cache_file_write_handle.read() - cache_file_write_handle.seek(0) - cache_file_write_handle.truncate() - - # Merge the new cache into the old cache if it exists. - if cache_file_existing_content: - possible_cache = json.loads(cache_file_existing_content) - if ( - "checksum" in possible_cache - and "schema_version" in possible_cache - and possible_cache["checksum"] == cache_file_content["checksum"] - and possible_cache["schema_version"] == cache_file_content["schema_version"] - ): - updated_cache = merge_dicts_recursive(dict(possible_cache), updated_cache) - - json.dump(updated_cache, cache_file_write_handle, indent=4) - - # Flush any buffered writes. - cache_file_write_handle.flush() - os.fsync(cache_file_write_handle.fileno()) - - atexit.register(update_cache_file) - - def helper(condition: str) -> str: - if ( - condition not in cache_file_content["cache"]["conditions"] - or not condition_simplifier_cache_enabled - ): - cache_file_content["cache"]["conditions"][condition] = f(condition) - return cache_file_content["cache"]["conditions"][condition] - - return helper diff --git a/util/cmake/configurejson2cmake.py b/util/cmake/configurejson2cmake.py deleted file mode 100755 index 50a40f61121..00000000000 --- a/util/cmake/configurejson2cmake.py +++ /dev/null @@ -1,1559 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2018 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -import json_parser -import posixpath -import re -import sys -from typing import Optional, Set -from textwrap import dedent -import os - -from special_case_helper import SpecialCaseHandler -from helper import ( - map_qt_library, - featureName, - map_platform, - find_3rd_party_library_mapping, - generate_find_package_info, - get_compile_test_dependent_library_mapping, -) - -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_tests(test: str) -> Optional[str]: - testmap = { - "c99": "c_std_99 IN_LIST CMAKE_C_COMPILE_FEATURES", - "c11": "c_std_11 IN_LIST CMAKE_C_COMPILE_FEATURES", - "x86SimdAlways": "ON", # FIXME: Make this actually do a compile test. - "aesni": "TEST_subarch_aesni", - "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_f16c", - "fma": "TEST_subarch_fma", - "fma4": "TEST_subarch_fma4", - "fsgsbase": "TEST_subarch_fsgsbase", - "gfni": "TEST_subarch_gfni", - "ibt": "TEST_subarch_ibt", - "libclang": "TEST_libclang", - "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_rdrnd", - "rtm": "TEST_subarch_rtm", - "shani": "TEST_subarch_shani", - "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", - "openssl11": '(OPENSSL_VERSION VERSION_GREATER_EQUAL "1.1.0")', - "libinput_axis_api": "ON", - "xlib": "X11_FOUND", - "wayland-scanner": "WaylandScanner_FOUND", - "3rdparty-hunspell": "VKB_HAVE_3RDPARTY_HUNSPELL", - "t9write-alphabetic": "VKB_HAVE_T9WRITE_ALPHA", - "t9write-cjk": "VKB_HAVE_T9WRITE_CJK", - } - if test in testmap: - return testmap.get(test, None) - if test in knownTests: - return f"TEST_{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(path: str) -> str: - path = posixpath.join(path, "configure.json") - - print(f"Reading {path}...") - assert posixpath.exists(path) - - parser = json_parser.QMakeSpecificJSONParser() - return parser.parse(path) - - -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): - newlib = find_3rd_party_library_mapping(lib) - if not newlib: - print(f' XXXX Unknown library "{lib}".') - return - - if newlib.packageName is None: - print(f' **** Skipping library "{lib}" -- was masked.') - return - - print(f" mapped library {lib} to {newlib.targetName}.") - - # Avoid duplicate find_package calls. - if newlib.targetName in cmake_find_packages_set: - return - - # If certain libraries are used within a feature, but the feature - # is only emitted conditionally with a simple condition (like - # 'on Windows' or 'on Linux'), we should enclose the find_package - # call for the library into the same condition. - emit_if = newlib.emit_if - - # Only look through features if a custom emit_if wasn't provided. - if not emit_if: - for feature in data["features"]: - feature_data = data["features"][feature] - if ( - "condition" in feature_data - and f"libs.{lib}" in feature_data["condition"] - and "emitIf" in feature_data - and "config." in feature_data["emitIf"] - ): - emit_if = feature_data["emitIf"] - break - - if emit_if: - emit_if = map_condition(emit_if) - - cmake_find_packages_set.add(newlib.targetName) - - find_package_kwargs = {"emit_if": emit_if} - if newlib.is_bundled_with_qt: - # If a library is bundled with Qt, it has 2 FindFoo.cmake - # modules: WrapFoo and WrapSystemFoo. - # FindWrapSystemFoo.cmake will try to find the 'Foo' library in - # the usual CMake locations, and will create a - # WrapSystemFoo::WrapSystemFoo target pointing to the library. - # - # FindWrapFoo.cmake will create a WrapFoo::WrapFoo target which - # will link either against the WrapSystemFoo or QtBundledFoo - # target depending on certain feature values. - # - # Because the following qt_find_package call is for - # configure.cmake consumption, we make the assumption that - # configure.cmake is interested in finding the system library - # for the purpose of enabling or disabling a system_foo feature. - find_package_kwargs["use_system_package_name"] = True - find_package_kwargs["module"] = ctx["module"] - - cm_fh.write(generate_find_package_info(newlib, **find_package_kwargs)) - - if "use" in data["libraries"][lib]: - use_entry = data["libraries"][lib]["use"] - if isinstance(use_entry, str): - print(f"1use: {use_entry}") - cm_fh.write(f"qt_add_qmake_lib_dependency({newlib.soName} {use_entry})\n") - else: - for use in use_entry: - print(f"2use: {use}") - indentation = "" - has_condition = False - if "condition" in use: - has_condition = True - indentation = " " - condition = map_condition(use["condition"]) - cm_fh.write(f"if({condition})\n") - cm_fh.write( - f"{indentation}qt_add_qmake_lib_dependency({newlib.soName} {use['lib']})\n" - ) - if has_condition: - cm_fh.write("endif()\n") - - run_library_test = False - mapped_library = find_3rd_party_library_mapping(lib) - if mapped_library: - run_library_test = mapped_library.run_library_test - - if run_library_test and "test" in data["libraries"][lib]: - test = data["libraries"][lib]["test"] - write_compile_test( - ctx, lib, test, data, cm_fh, manual_library_list=[lib], is_library_test=True - ) - - -def lineify(label, value, quote=True): - if value: - if quote: - escaped_value = value.replace('"', '\\"') - return f' {label} "{escaped_value}"\n' - return f" {label} {value}\n" - 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 = {"gbm": "gbm_FOUND"} - - # Turn foo != "bar" into (NOT foo STREQUAL 'bar') - condition = re.sub(r"([^ ]+)\s*!=\s*('.*?')", "(! \\1 == \\2)", condition) - # Turn foo != 156 into (NOT foo EQUAL 156) - condition = re.sub(r"([^ ]+)\s*!=\s*([0-9]?)", "(! \\1 EQUAL \\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": - libmapping = find_3rd_party_library_mapping(match.group(2)) - - if libmapping and libmapping.packageName: - substitution = libmapping.packageName - if libmapping.resultVariable: - substitution = libmapping.resultVariable - if libmapping.appendFoundSuffix: - substitution += "_FOUND" - - # Assume that feature conditions are interested whether - # a system library is found, rather than the bundled one - # which we always know we can build. - if libmapping.is_bundled_with_qt: - substitution = substitution.replace("Wrap", "WrapSystem") - - elif match.group(1) == "features": - feature = match.group(2) - if feature in mapped_features: - substitution = mapped_features.get(feature) - else: - substitution = f"QT_FEATURE_{featureName(match.group(2))}" - - elif match.group(1) == "subarch": - substitution = f"TEST_arch_{'${TEST_architecture_arch}'}_subarch_{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 = f"INPUT_{featureName(match.group(2))}" - - elif match.group(1) == "config": - substitution = map_platform(match.group(2)) - elif match.group(1) == "module": - substitution = f"TARGET {map_qt_library(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(f' XXXX Unknown condition "{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() - - # Special case for WrapLibClang in qttools - condition = condition.replace("TEST_libclang.has_clangcpp", "TEST_libclang") - - if has_failed: - condition += " OR FIXME" - - return condition - - -def parseInput(ctx, sinput, 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", - "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", - "eventfd", - "glib", - "icu", - "inotify", - "journald", - "pcre", - "posix-ipc", - "pps", - "slog2", - "syslog", - } - - if sinput in skip_inputs: - print(f" **** Skipping input {sinput}: masked.") - return - - dtype = data - if isinstance(data, dict): - dtype = data["type"] - - if dtype == "boolean": - print(f" **** Skipping boolean input {sinput}: masked.") - return - - if dtype == "enum": - values_line = " ".join(data["values"]) - cm_fh.write(f"# input {sinput}\n") - cm_fh.write(f'set(INPUT_{featureName(sinput)} "undefined" CACHE STRING "")\n') - cm_fh.write( - f"set_property(CACHE INPUT_{featureName(sinput)} PROPERTY STRINGS undefined {values_line})\n\n" - ) - return - - print(f" XXXX UNHANDLED INPUT TYPE {dtype} in input description") - return - - -def get_library_usage_for_compile_test(library): - result = {} - mapped_library = find_3rd_party_library_mapping(library) - if not mapped_library: - result["fixme"] = f"# FIXME: use: unmapped library: {library}\n" - return result - - if mapped_library.test_library_overwrite: - target_name = mapped_library.test_library_overwrite - else: - target_name = mapped_library.targetName - result["target_name"] = target_name - result["package_name"] = mapped_library.packageName - result["extra"] = mapped_library.extra - return result - - -# Handles config.test/foo/foo.pro projects. -def write_standalone_compile_test(cm_fh, ctx, data, config_test_name, is_library_test): - rel_test_project_path = f"{ctx['test_dir']}/{config_test_name}" - if posixpath.exists(f"{ctx['project_dir']}/{rel_test_project_path}/CMakeLists.txt"): - label = "" - libraries = [] - packages = [] - - if "label" in data: - label = data["label"] - - if is_library_test and config_test_name in data["libraries"]: - if "label" in data["libraries"][config_test_name]: - label = data["libraries"][config_test_name]["label"] - - # If a library entry in configure.json has a test, and - # the test uses a config.tests standalone project, we - # need to get the package and target info for the - # library, and pass it to the test so compiling and - # linking succeeds. - library_usage = get_library_usage_for_compile_test(config_test_name) - if "target_name" in library_usage: - libraries.append(library_usage["target_name"]) - if "package_name" in library_usage: - find_package_arguments = [] - find_package_arguments.append(library_usage["package_name"]) - if "extra" in library_usage: - find_package_arguments.extend(library_usage["extra"]) - package_line = "PACKAGE " + " ".join(find_package_arguments) - packages.append(package_line) - - cm_fh.write( - f""" -qt_config_compile_test("{config_test_name}" - LABEL "{label}" - PROJECT_PATH "${{CMAKE_CURRENT_SOURCE_DIR}}/{rel_test_project_path}" -""" - ) - if libraries: - libraries_string = " ".join(libraries) - cm_fh.write(f" LIBRARIES {libraries_string}\n") - if packages: - packages_string = " ".join(packages) - cm_fh.write(f" PACKAGES {packages_string}") - cm_fh.write(")\n") - - -def write_compile_test( - ctx, name, details, data, cm_fh, manual_library_list=None, is_library_test=False -): - - if manual_library_list is None: - manual_library_list = [] - - inherited_test_name = details["inherit"] if "inherit" in details else None - inherit_details = None - if inherited_test_name and is_library_test: - inherit_details = data["libraries"][inherited_test_name]["test"] - if not inherit_details: - print(f" XXXX Failed to locate inherited library test {inherited_test_name}") - - if isinstance(details, str): - write_standalone_compile_test(cm_fh, ctx, data, details, is_library_test) - return - - def resolve_head(detail): - head = detail.get("head") - if isinstance(head, list): - head = "\n".join(head) - return head + "\n" if head else "" - - head = "" - if inherit_details: - head += resolve_head(inherit_details) - head += resolve_head(details) - - sourceCode = head - - def resolve_include(detail, keyword): - include = detail.get(keyword, "") - if isinstance(include, list): - include = "#include <" + ">\n#include <".join(include) + ">\n" - elif include: - include = f"#include <{include}>\n" - return include - - include = "" - if is_library_test: - if inherit_details: - inherited_lib_data = data["libraries"][inherited_test_name] - include += resolve_include(inherited_lib_data, "headers") - this_lib_data = data["libraries"][name] - include += resolve_include(this_lib_data, "headers") - else: - if inherit_details: - include += resolve_include(inherit_details, "include") - include += resolve_include(details, "include") - - sourceCode += include - - def resolve_tail(detail): - tail = detail.get("tail") - if isinstance(tail, list): - tail = "\n".join(tail) - return tail + "\n" if tail else "" - - tail = "" - if inherit_details: - tail += resolve_tail(inherit_details) - tail += resolve_tail(details) - - sourceCode += tail - - if sourceCode: # blank line before main - sourceCode += "\n" - sourceCode += "int main(void)\n" - sourceCode += "{\n" - sourceCode += " /* BEGIN TEST: */\n" - - def resolve_main(detail): - main = detail.get("main") - if isinstance(main, list): - main = "\n".join(main) - return main + "\n" if main else "" - - main = "" - if inherit_details: - main += resolve_main(inherit_details) - main += resolve_main(details) - - sourceCode += main - - sourceCode += " /* END TEST: */\n" - sourceCode += " return 0;\n" - sourceCode += "}\n" - - sourceCode = sourceCode.replace('"', '\\"') - - librariesCmakeName = "" - languageStandard = "" - compileOptions = "" - qmakeFixme = "" - - cm_fh.write(f"# {name}\n") - - if "qmake" in details: # We don't really have many so we can just enumerate them all - if details["qmake"] == "unix:LIBS += -lpthread": - librariesCmakeName = format(featureName(name)) + "_TEST_LIBRARIES" - cm_fh.write("if (UNIX)\n") - cm_fh.write(" set(" + librariesCmakeName + " pthread)\n") - cm_fh.write("endif()\n") - elif details["qmake"] == "linux: LIBS += -lpthread -lrt": - librariesCmakeName = format(featureName(name)) + "_TEST_LIBRARIES" - cm_fh.write("if (LINUX)\n") - cm_fh.write(" set(" + librariesCmakeName + " pthread rt)\n") - cm_fh.write("endif()\n") - elif details["qmake"] == "!winrt: LIBS += runtimeobject.lib": - librariesCmakeName = format(featureName(name)) + "_TEST_LIBRARIES" - cm_fh.write("if (NOT WINRT)\n") - cm_fh.write(" set(" + librariesCmakeName + " runtimeobject)\n") - cm_fh.write("endif()\n") - elif details["qmake"] == "CONFIG += c++11": - # do nothing we're always in c++11 mode - pass - elif details["qmake"] == "CONFIG += c++11 c++14": - languageStandard = "CXX_STANDARD 14" - elif details["qmake"] == "CONFIG += c++11 c++14 c++17": - languageStandard = "CXX_STANDARD 17" - elif details["qmake"] == "CONFIG += c++11 c++14 c++17 c++20": - languageStandard = "CXX_STANDARD 20" - elif details["qmake"] == "QMAKE_CXXFLAGS += -fstack-protector-strong": - compileOptions = details["qmake"][18:] - else: - qmakeFixme = f"# FIXME: qmake: {details['qmake']}\n" - - library_list = [] - test_libraries = manual_library_list - - if "use" in data: - test_libraries += data["use"].split(" ") - - for library in test_libraries: - if len(library) == 0: - continue - - adjusted_library = get_compile_test_dependent_library_mapping(name, library) - library_usage = get_library_usage_for_compile_test(adjusted_library) - if "fixme" in library_usage: - qmakeFixme += library_usage["fixme"] - continue - else: - library_list.append(library_usage["target_name"]) - - cm_fh.write(f"qt_config_compile_test({featureName(name)}\n") - cm_fh.write(lineify("LABEL", data.get("label", ""))) - if librariesCmakeName != "" or len(library_list) != 0: - cm_fh.write(" LIBRARIES\n") - if librariesCmakeName != "": - cm_fh.write(lineify("", "${" + librariesCmakeName + "}")) - if len(library_list) != 0: - cm_fh.write(" ") - cm_fh.write("\n ".join(library_list)) - cm_fh.write("\n") - if compileOptions != "": - cm_fh.write(f" COMPILE_OPTIONS {compileOptions}\n") - cm_fh.write(" CODE\n") - cm_fh.write('"' + sourceCode + '"') - if qmakeFixme != "": - cm_fh.write(qmakeFixme) - if languageStandard != "": - cm_fh.write(f"\n {languageStandard}\n") - cm_fh.write(")\n\n") - - -# "tests": { -# "cxx11_future": { -# "label": "C++11 ", -# "type": "compile", -# "test": { -# "include": "future", -# "main": [ -# "std::future f = std::async([]() { return 42; });", -# "(void)f.get();" -# ], -# "qmake": "unix:LIBS += -lpthread" -# } -# }, - - -def write_compiler_supports_flag_test( - ctx, name, details, data, cm_fh, manual_library_list=None, is_library_test=False -): - cm_fh.write(f"qt_config_compiler_supports_flag_test({featureName(name)}\n") - cm_fh.write(lineify("LABEL", data.get("label", ""))) - cm_fh.write(lineify("FLAG", data.get("flag", ""))) - cm_fh.write(")\n\n") - - -def write_linker_supports_flag_test( - ctx, name, details, data, cm_fh, manual_library_list=None, is_library_test=False -): - cm_fh.write(f"qt_config_linker_supports_flag_test({featureName(name)}\n") - cm_fh.write(lineify("LABEL", data.get("label", ""))) - cm_fh.write(lineify("FLAG", data.get("flag", ""))) - cm_fh.write(")\n\n") - - -def parseTest(ctx, test, data, cm_fh): - skip_tests = { - "c11", - "c99", - "gc_binaries", - "precomile_header", - "reduce_exports", - "gc_binaries", - "libinput_axis_api", - "wayland-scanner", - "xlib", - } - - if test in skip_tests: - print(f" **** Skipping features {test}: masked.") - return - - if data["type"] == "compile": - knownTests.add(test) - - if "test" in data: - details = data["test"] - else: - details = test - - write_compile_test(ctx, test, details, data, cm_fh) - - if data["type"] == "compilerSupportsFlag": - knownTests.add(test) - - if "test" in data: - details = data["test"] - else: - details = test - - write_compiler_supports_flag_test(ctx, test, details, data, cm_fh) - - if data["type"] == "linkerSupportsFlag": - knownTests.add(test) - - if "test" in data: - details = data["test"] - else: - details = test - - write_linker_supports_flag_test(ctx, test, details, data, cm_fh) - - elif data["type"] == "libclang": - knownTests.add(test) - - cm_fh.write(f"# {test}\n") - lib_clang_lib = find_3rd_party_library_mapping("libclang") - cm_fh.write(generate_find_package_info(lib_clang_lib)) - cm_fh.write( - dedent( - """ - if(TARGET WrapLibClang::WrapLibClang) - set(TEST_libclang "ON" CACHE BOOL "Required libclang version found." FORCE) - endif() - """ - ) - ) - cm_fh.write("\n") - - elif data["type"] == "x86Simd": - knownTests.add(test) - - label = data["label"] - - cm_fh.write(f"# {test}\n") - cm_fh.write(f'qt_config_compile_test_x86simd({test} "{label}")\n') - cm_fh.write("\n") - - elif data["type"] == "machineTuple": - knownTests.add(test) - - label = data["label"] - - cm_fh.write(f"# {test}\n") - cm_fh.write(f'qt_config_compile_test_machine_tuple("{label}")\n') - 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(f" XXXX UNHANDLED TEST TYPE {data['type']} in test description") - - -def get_feature_mapping(): - # This is *before* the feature name gets normalized! So keep - and + chars, etc. - feature_mapping = { - "alloc_h": None, # handled by alloc target - "alloc_malloc_h": None, - "alloc_stdlib_h": None, - "build_all": None, - "ccache": {"autoDetect": "1", "condition": "QT_USE_CCACHE"}, - "compiler-flags": None, - "cross_compile": {"condition": "CMAKE_CROSSCOMPILING"}, - "debug_and_release": { - "autoDetect": "1", # Setting this to None has weird effects... - "condition": "QT_GENERATOR_IS_MULTI_CONFIG", - }, - "debug": { - "autoDetect": "ON", - "condition": "CMAKE_BUILD_TYPE STREQUAL Debug OR Debug IN_LIST CMAKE_CONFIGURATION_TYPES", - }, - "dlopen": {"condition": "UNIX"}, - "force_debug_info": { - "autoDetect": "CMAKE_BUILD_TYPE STREQUAL RelWithDebInfo OR RelWithDebInfo IN_LIST CMAKE_CONFIGURATION_TYPES" - }, - "framework": { - "condition": "APPLE AND BUILD_SHARED_LIBS AND NOT CMAKE_BUILD_TYPE STREQUAL Debug" - }, - "gc_binaries": {"condition": "NOT QT_FEATURE_shared"}, - "gcc-sysroot": None, - "gcov": None, - "GNUmake": None, - "host-dbus": None, - "iconv": { - "condition": "NOT QT_FEATURE_icu AND QT_FEATURE_textcodec AND NOT WIN32 AND NOT QNX AND NOT ANDROID AND NOT APPLE AND WrapIconv_FOUND", - }, - "incredibuild_xge": None, - "ltcg": { - "autoDetect": "ON", - "cmakePrelude": """set(__qt_ltcg_detected FALSE) -if(CMAKE_INTERPROCEDURAL_OPTIMIZATION) - set(__qt_ltcg_detected TRUE) -else() - foreach(config ${CMAKE_BUILD_TYPE} ${CMAKE_CONFIGURATION_TYPES}) - string(TOUPPER "${config}" __qt_uc_config) - if(CMAKE_INTERPROCEDURAL_OPTIMIZATION_${__qt_uc_config}) - set(__qt_ltcg_detected TRUE) - break() - endif() - endforeach() - unset(__qt_uc_config) -endif()""", - "condition": "__qt_ltcg_detected", - }, - "msvc_mp": None, - "simulator_and_device": {"condition": "UIKIT AND NOT QT_APPLE_SDK"}, - "pkg-config": {"condition": "PKG_CONFIG_FOUND"}, - "precompile_header": {"condition": "BUILD_WITH_PCH"}, - "profile": None, - "qmakeargs": None, - "qpa_default_platform": None, # Not a bool! - "qreal": { - "condition": 'DEFINED QT_COORD_TYPE AND NOT QT_COORD_TYPE STREQUAL "double"', - "output": [ - { - "type": "define", - "name": "QT_COORD_TYPE", - "value": "${QT_COORD_TYPE}", - }, - { - "type": "define", - "name": "QT_COORD_TYPE_STRING", - "value": '\\"${QT_COORD_TYPE}\\"', - }, - ], - }, - "reduce_exports": { - "condition": "NOT MSVC", - }, - "release": None, - "release_tools": None, - "rpath": { - "autoDetect": "1", - "condition": "BUILD_SHARED_LIBS AND UNIX AND NOT WIN32 AND NOT ANDROID", - }, - "shared": { - "condition": "BUILD_SHARED_LIBS", - "output": [ - "publicFeature", - "publicQtConfig", - "publicConfig", - { - "type": "define", - "name": "QT_STATIC", - "prerequisite": "!defined(QT_SHARED) && !defined(QT_STATIC)", - "negative": True, - }, - ], - }, - "silent": None, - "sql-sqlite": {"condition": "QT_FEATURE_datestring"}, - "stl": None, # Do we really need to test for this in 2018?! - "strip": None, - "verifyspec": None, # qmake specific... - "warnings_are_errors": None, # FIXME: Do we need these? - "xkbcommon-system": None, # another system library, just named a bit different from the rest - } - return feature_mapping - - -def parseFeature(ctx, feature, data, cm_fh): - feature_mapping = get_feature_mapping() - mapping = feature_mapping.get(feature, {}) - - if mapping is None: - print(f" **** Skipping features {feature}: masked.") - return - - handled = { - "autoDetect", - "comment", - "condition", - "description", - "disable", - "emitIf", - "enable", - "label", - "output", - "purpose", - "section", - } - label = mapping.get("label", data.get("label", "")) - purpose = mapping.get("purpose", data.get("purpose", data.get("description", label))) - autoDetect = map_condition(mapping.get("autoDetect", data.get("autoDetect", ""))) - condition = map_condition(mapping.get("condition", data.get("condition", ""))) - output = mapping.get("output", data.get("output", [])) - comment = mapping.get("comment", data.get("comment", "")) - section = mapping.get("section", data.get("section", "")) - enable = map_condition(mapping.get("enable", data.get("enable", ""))) - disable = map_condition(mapping.get("disable", data.get("disable", ""))) - emitIf = map_condition(mapping.get("emitIf", data.get("emitIf", ""))) - cmakePrelude = mapping.get("cmakePrelude", None) - cmakeEpilogue = mapping.get("cmakeEpilogue", None) - - for k in [k for k in data.keys() if k not in handled]: - print(f" XXXX UNHANDLED KEY {k} in feature description") - - if not output: - # feature that is only used in the conditions of other features - output = ["internalFeature"] - - publicFeature = False # #define QT_FEATURE_featurename in public header - privateFeature = False # #define QT_FEATURE_featurename in private header - negativeFeature = False # #define QT_NO_featurename in public header - internalFeature = False # No custom or QT_FEATURE_ defines - publicDefine = False # #define MY_CUSTOM_DEFINE in public header - publicConfig = False # add to CONFIG in public pri file - privateConfig = False # add to CONFIG in private pri file - publicQtConfig = False # add to QT_CONFIG in public pri file - - for o in output: - outputType = o - if isinstance(o, dict): - outputType = o["type"] - - if outputType in [ - "varAssign", - "varAppend", - "varRemove", - "useBFDLinker", - "useGoldLinker", - "useLLDLinker", - ]: - continue - elif outputType == "define": - publicDefine = True - elif outputType == "feature": - negativeFeature = True - elif outputType == "publicFeature": - publicFeature = True - elif outputType == "privateFeature": - privateFeature = True - elif outputType == "internalFeature": - internalFeature = True - elif outputType == "publicConfig": - publicConfig = True - elif outputType == "privateConfig": - privateConfig = True - elif outputType == "publicQtConfig": - publicQtConfig = True - else: - print(f" XXXX UNHANDLED OUTPUT TYPE {outputType} in feature {feature}.") - continue - - if not any( - [ - publicFeature, - privateFeature, - internalFeature, - publicDefine, - negativeFeature, - publicConfig, - privateConfig, - publicQtConfig, - ] - ): - print(f" **** Skipping feature {feature}: Not relevant for C++.") - return - - normalized_feature_name = featureName(feature) - - def writeFeature( - name, - publicFeature=False, - privateFeature=False, - labelAppend="", - superFeature=None, - autoDetect="", - cmakePrelude=None, - cmakeEpilogue=None, - ): - if comment: - cm_fh.write(f"# {comment}\n") - - if cmakePrelude is not None: - cm_fh.write(cmakePrelude) - cm_fh.write("\n") - - cm_fh.write(f'qt_feature("{name}"') - if publicFeature: - cm_fh.write(" PUBLIC") - if privateFeature: - cm_fh.write(" PRIVATE") - cm_fh.write("\n") - - cm_fh.write(lineify("SECTION", section)) - cm_fh.write(lineify("LABEL", label + labelAppend)) - if purpose != label: - cm_fh.write(lineify("PURPOSE", purpose)) - cm_fh.write(lineify("AUTODETECT", autoDetect, quote=False)) - if superFeature: - feature_condition = f"QT_FEATURE_{superFeature}" - else: - feature_condition = condition - cm_fh.write(lineify("CONDITION", feature_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") - - if cmakeEpilogue is not None: - cm_fh.write(cmakeEpilogue) - cm_fh.write("\n") - - # Write qt_feature() calls before any qt_feature_definition() calls - - # Default internal feature case. - featureCalls = {} - featureCalls[feature] = { - "name": feature, - "labelAppend": "", - "autoDetect": autoDetect, - "cmakePrelude": cmakePrelude, - "cmakeEpilogue": cmakeEpilogue, - } - - # Go over all outputs to compute the number of features that have to be declared - for o in output: - outputType = o - name = feature - - # The label append is to provide a unique label for features that have more than one output - # with different names. - labelAppend = "" - - if isinstance(o, dict): - outputType = o["type"] - if "name" in o: - name = o["name"] - labelAppend = f": {o['name']}" - - if outputType not in ["feature", "publicFeature", "privateFeature"]: - continue - if name not in featureCalls: - featureCalls[name] = {"name": name, "labelAppend": labelAppend} - - if name != feature: - featureCalls[name]["superFeature"] = normalized_feature_name - - if outputType in ["feature", "publicFeature"]: - featureCalls[name]["publicFeature"] = True - elif outputType == "privateFeature": - featureCalls[name]["privateFeature"] = True - elif outputType == "publicConfig": - featureCalls[name]["publicConfig"] = True - elif outputType == "privateConfig": - featureCalls[name]["privateConfig"] = True - elif outputType == "publicQtConfig": - featureCalls[name]["publicQtConfig"] = True - - # Write the qt_feature() calls from the computed feature map - for _, args in featureCalls.items(): - writeFeature(**args) - - # Write qt_feature_definition() calls - for o in output: - outputType = o - outputArgs = {} - if isinstance(o, dict): - outputType = o["type"] - outputArgs = o - - # Map negative feature to define: - if outputType == "feature": - outputType = "define" - outputArgs = { - "name": f"QT_NO_{normalized_feature_name.upper()}", - "negative": True, - "value": 1, - "type": "define", - } - - if outputType != "define": - continue - - if outputArgs.get("name") is None: - print(f" XXXX DEFINE output without name in feature {feature}.") - continue - - out_name = outputArgs.get("name") - cm_fh.write(f'qt_feature_definition("{feature}" "{out_name}"') - if outputArgs.get("negative", False): - cm_fh.write(" NEGATE") - if outputArgs.get("value") is not None: - cm_fh.write(f' VALUE "{outputArgs.get("value")}"') - if outputArgs.get("prerequisite") is not None: - cm_fh.write(f' PREREQUISITE "{outputArgs.get("prerequisite")}"') - cm_fh.write(")\n") - - # Write qt_feature_config() calls - for o in output: - outputType = o - name = feature - modified_name = name - - outputArgs = {} - if isinstance(o, dict): - outputType = o["type"] - outputArgs = o - if "name" in o: - modified_name = o["name"] - - if outputType not in ["publicConfig", "privateConfig", "publicQtConfig"]: - continue - - config_type = "" - if outputType == "publicConfig": - config_type = "QMAKE_PUBLIC_CONFIG" - elif outputType == "privateConfig": - config_type = "QMAKE_PRIVATE_CONFIG" - elif outputType == "publicQtConfig": - config_type = "QMAKE_PUBLIC_QT_CONFIG" - - if not config_type: - print(" XXXX config output without type in feature {}.".format(feature)) - continue - - cm_fh.write('qt_feature_config("{}" {}'.format(name, config_type)) - if outputArgs.get("negative", False): - cm_fh.write("\n NEGATE") - if modified_name != name: - cm_fh.write("\n") - cm_fh.write(lineify("NAME", modified_name, quote=True)) - - cm_fh.write(")\n") - - -def processSummaryHelper(ctx, entries, cm_fh): - for entry in entries: - if isinstance(entry, str): - name = entry - cm_fh.write(f'qt_configure_add_summary_entry(ARGS "{name}")\n') - elif "type" in entry and entry["type"] in [ - "feature", - "firstAvailableFeature", - "featureList", - ]: - function_args = [] - entry_type = entry["type"] - - if entry_type in ["firstAvailableFeature", "featureList"]: - feature_mapping = get_feature_mapping() - unhandled_feature = False - for feature_name, value in feature_mapping.items(): - # Skip entries that mention a feature which is - # skipped by configurejson2cmake in the feature - # mapping. This is not ideal, but prevents errors at - # CMake configuration time. - if not value and f"{feature_name}" in entry["args"]: - unhandled_feature = True - break - - if unhandled_feature: - print(f" XXXX UNHANDLED FEATURE in SUMMARY TYPE {entry}.") - continue - - if entry_type != "feature": - function_args.append(lineify("TYPE", entry_type)) - if "args" in entry: - args = entry["args"] - function_args.append(lineify("ARGS", args)) - if "message" in entry: - message = entry["message"] - function_args.append(lineify("MESSAGE", message)) - if "condition" in entry: - condition = map_condition(entry["condition"]) - function_args.append(lineify("CONDITION", condition, quote=False)) - entry_args_string = "".join(function_args) - cm_fh.write(f"qt_configure_add_summary_entry(\n{entry_args_string})\n") - elif "type" in entry and entry["type"] == "buildTypeAndConfig": - cm_fh.write("qt_configure_add_summary_build_type_and_config()\n") - elif "type" in entry and entry["type"] == "buildMode": - message = entry["message"] - cm_fh.write(f"qt_configure_add_summary_build_mode({message})\n") - elif "type" in entry and entry["type"] == "buildParts": - message = entry["message"] - cm_fh.write(f'qt_configure_add_summary_build_parts("{message}")\n') - elif "section" in entry: - section = entry["section"] - cm_fh.write(f'qt_configure_add_summary_section(NAME "{section}")\n') - processSummaryHelper(ctx, entry["entries"], cm_fh) - cm_fh.write(f'qt_configure_end_summary_section() # end of "{section}" section\n') - else: - print(f" XXXX UNHANDLED SUMMARY TYPE {entry}.") - - -report_condition_mapping = { - "(features.rpath || features.rpath_dir) && !features.shared": "(features.rpath || QT_EXTRA_RPATHS) && !features.shared", - "(features.rpath || features.rpath_dir) && var.QMAKE_LFLAGS_RPATH == ''": None, -} - - -def processReportHelper(ctx, entries, cm_fh): - feature_mapping = get_feature_mapping() - - for entry in entries: - if isinstance(entry, dict): - entry_args = [] - if "type" not in entry: - print(f" XXXX UNHANDLED REPORT TYPE missing type in {entry}.") - continue - - report_type = entry["type"] - if report_type not in ["note", "warning", "error"]: - print(f" XXXX UNHANDLED REPORT TYPE unknown type in {entry}.") - continue - - report_type = report_type.upper() - entry_args.append(lineify("TYPE", report_type, quote=False)) - message = entry["message"] - - # Replace semicolons, qt_parse_all_arguments can't handle - # them due to an escaping bug in CMake regarding escaping - # macro arguments. - # https://gitlab.kitware.com/cmake/cmake/issues/19972 - message = message.replace(";", ",") - - entry_args.append(lineify("MESSAGE", message)) - # Need to overhaul everything to fix conditions. - if "condition" in entry: - condition = entry["condition"] - - unhandled_condition = False - for feature_name, value in feature_mapping.items(): - # Skip reports that mention a feature which is - # skipped by configurejson2cmake in the feature - # mapping. This is not ideal, but prevents errors at - # CMake configuration time. - if not value and f"features.{feature_name}" in condition: - unhandled_condition = True - break - - if unhandled_condition: - print(f" XXXX UNHANDLED CONDITION in REPORT TYPE {entry}.") - continue - - if isinstance(condition, str) and condition in report_condition_mapping: - new_condition = report_condition_mapping[condition] - if new_condition is None: - continue - else: - condition = new_condition - condition = map_condition(condition) - entry_args.append(lineify("CONDITION", condition, quote=False)) - entry_args_string = "".join(entry_args) - cm_fh.write(f"qt_configure_add_report_entry(\n{entry_args_string})\n") - else: - print(f" XXXX UNHANDLED REPORT TYPE {entry}.") - - -def parseCommandLineCustomHandler(ctx, data, cm_fh): - cm_fh.write(f"qt_commandline_custom({data})\n") - - -def parseCommandLineOptions(ctx, data, cm_fh): - for key in data: - args = [key] - option = data[key] - if isinstance(option, str): - args += ["TYPE", option] - else: - if "type" in option: - args += ["TYPE", option["type"]] - if "name" in option: - args += ["NAME", option["name"]] - if "value" in option: - args += ["VALUE", option["value"]] - if "values" in option: - values = option["values"] - if isinstance(values, list): - args += ["VALUES", " ".join(option["values"])] - else: - args += ["MAPPING"] - for lhs in values: - args += [lhs, values[lhs]] - - cm_fh.write(f"qt_commandline_option({' '.join(args)})\n") - - -def parseCommandLinePrefixes(ctx, data, cm_fh): - for key in data: - cm_fh.write(f"qt_commandline_prefix({key} {data[key]})\n") - - -def processCommandLine(ctx, data, cm_fh): - print(" commandline:") - - if "subconfigs" in data: - for subconf in data["subconfigs"]: - cm_fh.write(f"qt_commandline_subconfig({subconf})\n") - - if "commandline" not in data: - return - - commandLine = data["commandline"] - if "custom" in commandLine: - print(" custom:") - parseCommandLineCustomHandler(ctx, commandLine["custom"], cm_fh) - if "options" in commandLine: - print(" options:") - parseCommandLineOptions(ctx, commandLine["options"], cm_fh) - if "prefix" in commandLine: - print(" prefix:") - parseCommandLinePrefixes(ctx, commandLine["prefix"], cm_fh) - if "assignments" in commandLine: - print(" assignments are ignored") - - -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_option in commandLine["options"]: - parseInput(ctx, input_option, commandLine["options"][input_option], 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, cm_fh, cmake_find_packages_set) - - -def processReports(ctx, data, cm_fh): - if "summary" in data: - print(" summary:") - processSummaryHelper(ctx, data["summary"], cm_fh) - if "report" in data: - print(" report:") - processReportHelper(ctx, data["report"], cm_fh) - if "earlyReport" in data: - print(" earlyReport:") - processReportHelper(ctx, data["earlyReport"], cm_fh) - - -def processSubconfigs(path, ctx, data): - assert ctx is not None - if "subconfigs" in data: - for subconf in data["subconfigs"]: - subconfDir = posixpath.join(path, subconf) - subconfData = readJsonFromDir(subconfDir) - subconfCtx = ctx - processJson(subconfDir, subconfCtx, subconfData) - - -class special_cased_file: - def __init__(self, base_dir: str, file_name: str, skip_special_case_preservation: bool): - self.base_dir = base_dir - self.file_path = posixpath.join(base_dir, file_name) - self.gen_file_path = self.file_path + ".gen" - self.preserve_special_cases = not skip_special_case_preservation - - def __enter__(self): - self.file = open(self.gen_file_path, "w") - if self.preserve_special_cases: - self.sc_handler = SpecialCaseHandler( - os.path.abspath(self.file_path), - os.path.abspath(self.gen_file_path), - os.path.abspath(self.base_dir), - debug=False, - ) - return self.file - - def __exit__(self, type, value, trace_back): - self.file.close() - if self.preserve_special_cases: - self.sc_handler.handle_special_cases() - os.replace(self.gen_file_path, self.file_path) - - -def processJson(path, ctx, data, skip_special_case_preservation=False): - ctx["project_dir"] = path - ctx["module"] = data.get("module", "global") - ctx["test_dir"] = data.get("testDir", "config.tests") - - ctx = processFiles(ctx, data) - - with special_cased_file(path, "qt_cmdline.cmake", skip_special_case_preservation) as cm_fh: - processCommandLine(ctx, data, cm_fh) - - with special_cased_file(path, "configure.cmake", skip_special_case_preservation) 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) - - processReports(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') - - # do this late: - processSubconfigs(path, ctx, data) - - -def main(): - if len(sys.argv) < 2: - print("This scripts needs one directory to process!") - quit(1) - - directory = sys.argv[1] - skip_special_case_preservation = "-s" in sys.argv[2:] - - print(f"Processing: {directory}.") - - data = readJsonFromDir(directory) - processJson(directory, {}, data, skip_special_case_preservation=skip_special_case_preservation) - - -if __name__ == "__main__": - main() diff --git a/util/cmake/generate_module_map.sh b/util/cmake/generate_module_map.sh deleted file mode 100755 index 8cca1c0aa3c..00000000000 --- a/util/cmake/generate_module_map.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/bash -# Copyright (C) 2018 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -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 deleted file mode 100644 index 3e9f4d73b25..00000000000 --- a/util/cmake/helper.py +++ /dev/null @@ -1,868 +0,0 @@ -# Copyright (C) 2021 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -import re -import typing - - -class LibraryMapping: - def __init__( - self, - soName: str, - packageName: typing.Optional[str], - targetName: typing.Optional[str], - *, - resultVariable: typing.Optional[str] = None, - extra: typing.List[str] = [], - components: typing.Optional[typing.List[str]] = None, - appendFoundSuffix: bool = True, - emit_if: str = "", - is_bundled_with_qt: bool = False, - test_library_overwrite: str = "", - run_library_test: bool = False, - no_link_so_name: str = "", - ) -> None: - self.soName = soName - self.packageName = packageName - self.resultVariable = resultVariable - self.appendFoundSuffix = appendFoundSuffix - # Allows passing addiitonal arguments to the generated find_package call. - self.extra = extra - self.components = components - self.targetName = targetName - - # True if qt bundles the library sources as part of Qt. - self.is_bundled_with_qt = is_bundled_with_qt - - # if emit_if is non-empty, the generated find_package call - # for a library will be surrounded by this condition. - self.emit_if = emit_if - - # Allow overwriting library name when used with tests. E.g.: _nolink - # targets do not exist when used during compile tests - self.test_library_overwrite = test_library_overwrite - - # Run the library compile test of configure.json - self.run_library_test = run_library_test - - # The custom nolink library mapping associated with this one. - self.no_link_so_name = no_link_so_name - - def is_qt(self) -> bool: - return self.packageName == "Qt" or self.packageName == "Qt5" or self.packageName == "Qt6" - - -_qt_library_map = [ - # Qt: - LibraryMapping("androidextras", "Qt6", "Qt::AndroidExtras", components=["AndroidExtras"]), - LibraryMapping("3danimation", "Qt6", "Qt::3DAnimation", components=["3DAnimation"]), - LibraryMapping("3dcore", "Qt6", "Qt::3DCore", components=["3DCore"]), - LibraryMapping("3dcoretest", "Qt6", "Qt::3DCoreTest", components=["3DCoreTest"]), - LibraryMapping("3dextras", "Qt6", "Qt::3DExtras", components=["3DExtras"]), - LibraryMapping("3dinput", "Qt6", "Qt::3DInput", components=["3DInput"]), - LibraryMapping("3dlogic", "Qt6", "Qt::3DLogic", components=["3DLogic"]), - LibraryMapping("3dquick", "Qt6", "Qt::3DQuick", components=["3DQuick"]), - LibraryMapping("3dquickextras", "Qt6", "Qt::3DQuickExtras", components=["3DQuickExtras"]), - LibraryMapping("3dquickinput", "Qt6", "Qt::3DQuickInput", components=["3DQuickInput"]), - LibraryMapping("3dquickrender", "Qt6", "Qt::3DQuickRender", components=["3DQuickRender"]), - LibraryMapping("3drender", "Qt6", "Qt::3DRender", components=["3DRender"]), - LibraryMapping( - "application-lib", "Qt6", "Qt::AppManApplication", components=["AppManApplication"] - ), - LibraryMapping("axbase", "Qt6", "Qt::AxBasePrivate", components=["AxBasePrivate"]), - LibraryMapping("axcontainer", "Qt6", "Qt::AxContainer", components=["AxContainer"]), - LibraryMapping("axserver", "Qt6", "Qt::AxServer", components=["AxServer"]), - LibraryMapping("bluetooth", "Qt6", "Qt::Bluetooth", components=["Bluetooth"]), - LibraryMapping("bootstrap", "Qt6", "Qt::Bootstrap", components=["Bootstrap"]), - # bootstrap-dbus: Not needed in Qt6! - LibraryMapping("client", "Qt6", "Qt::WaylandClient", components=["WaylandClient"]), - LibraryMapping("coap", "Qt6", "Qt::Coap", components=["Coap"]), - LibraryMapping("common-lib", "Qt6", "Qt::AppManCommon", components=["AppManCommon"]), - LibraryMapping("compositor", "Qt6", "Qt::WaylandCompositor", components=["WaylandCompositor"]), - LibraryMapping("concurrent", "Qt6", "Qt::Concurrent", components=["Concurrent"]), - LibraryMapping("container", "Qt6", "Qt::AxContainer", components=["AxContainer"]), - LibraryMapping("control", "Qt6", "Qt::AxServer", components=["AxServer"]), - LibraryMapping("core_headers", "Qt6", "Qt::WebEngineCore", components=["WebEngineCore"]), - LibraryMapping("core", "Qt6", "Qt::Core", components=["Core"]), - LibraryMapping("crypto-lib", "Qt6", "Qt::AppManCrypto", components=["AppManCrypto"]), - LibraryMapping("dbus", "Qt6", "Qt::DBus", components=["DBus"]), - LibraryMapping("designer", "Qt6", "Qt::Designer", components=["Designer"]), - LibraryMapping( - "designercomponents", - "Qt6", - "Qt::DesignerComponentsPrivate", - components=["DesignerComponentsPrivate"], - ), - LibraryMapping( - "devicediscovery", - "Qt6", - "Qt::DeviceDiscoverySupportPrivate", - components=["DeviceDiscoverySupportPrivate"], - ), - LibraryMapping( - "devicediscovery_support", - "Qt6", - "Qt::DeviceDiscoverySupportPrivate", - components=["DeviceDiscoverySupportPrivate"], - ), - LibraryMapping("edid", "Qt6", "Qt::EdidSupport", components=["EdidSupport"]), - LibraryMapping("edid_support", "Qt6", "Qt::EdidSupport", components=["EdidSupport"]), - LibraryMapping("eglconvenience", "Qt6", "Qt::EglSupport", components=["EglSupport"]), - LibraryMapping( - "eglfsdeviceintegration", - "Qt6", - "Qt::EglFSDeviceIntegrationPrivate", - components=["EglFSDeviceIntegrationPrivate"], - ), - LibraryMapping( - "eglfs_kms_support", - "Qt6", - "Qt::EglFsKmsSupportPrivate", - components=["EglFsKmsSupportPrivate"], - ), - LibraryMapping( - "eglfs_kms_gbm_support", - "Qt6", - "Qt::EglFsKmsGbmSupportPrivate", - components=["EglFsKmsGbmSupportPrivate"], - ), - LibraryMapping("egl_support", "Qt6", "Qt::EglSupport", components=["EglSupport"]), - # enginio: Not needed in Qt6! - LibraryMapping( - "eventdispatchers", - "Qt6", - "Qt::EventDispatcherSupport", - components=["EventDispatcherSupport"], - ), - LibraryMapping( - "eventdispatcher_support", - "Qt6", - "Qt::EventDispatcherSupport", - components=["EventDispatcherSupport"], - ), - LibraryMapping("fbconvenience", "Qt6", "Qt::FbSupportPrivate", components=["FbSupportPrivate"]), - LibraryMapping("fb_support", "Qt6", "Qt::FbSupportPrivate", components=["FbSupportPrivate"]), - LibraryMapping( - "fontdatabase_support", - "Qt6", - "Qt::FontDatabaseSupport", - components=["FontDatabaseSupport"], - ), - LibraryMapping("gamepad", "Qt6", "Qt::Gamepad", components=["Gamepad"]), - LibraryMapping("geniviextras", "Qt6", "Qt::GeniviExtras", components=["GeniviExtras"]), - LibraryMapping("global", "Qt6", "Qt::Core", components=["Core"]), # manually added special case - LibraryMapping("glx_support", "Qt6", "Qt::GlxSupport", components=["GlxSupport"]), - LibraryMapping("gsttools", "Qt6", "Qt::MultimediaGstTools", components=["MultimediaGstTools"]), - LibraryMapping("gui", "Qt6", "Qt::Gui", components=["Gui"]), - LibraryMapping("help", "Qt6", "Qt::Help", components=["Help"]), - LibraryMapping( - "hunspellinputmethod", - "Qt6", - "Qt::HunspellInputMethodPrivate", - components=["HunspellInputMethodPrivate"], - ), - LibraryMapping("input", "Qt6", "Qt::InputSupportPrivate", components=["InputSupportPrivate"]), - LibraryMapping( - "input_support", - "Qt6", - "Qt::InputSupportPrivate", - components=["InputSupportPrivate"], - ), - LibraryMapping("installer-lib", "Qt6", "Qt::AppManInstaller", components=["AppManInstaller"]), - LibraryMapping("ivi", "Qt6", "Qt::Ivi", components=["Ivi"]), - LibraryMapping("ivicore", "Qt6", "Qt::IviCore", components=["IviCore"]), - LibraryMapping("ivimedia", "Qt6", "Qt::IviMedia", components=["IviMedia"]), - LibraryMapping("knx", "Qt6", "Qt::Knx", components=["Knx"]), - LibraryMapping( - "kmsconvenience", "Qt6", "Qt::KmsSupportPrivate", components=["KmsSupportPrivate"] - ), - LibraryMapping("kms_support", "Qt6", "Qt::KmsSupportPrivate", components=["KmsSupportPrivate"]), - LibraryMapping("launcher-lib", "Qt6", "Qt::AppManLauncher", components=["AppManLauncher"]), - LibraryMapping("lib", "Qt6", "Qt::Designer", components=["Designer"]), - LibraryMapping( - "linuxaccessibility_support", - "Qt6", - "Qt::LinuxAccessibilitySupport", - components=["LinuxAccessibilitySupport"], - ), - LibraryMapping("location", "Qt6", "Qt::Location", components=["Location"]), - LibraryMapping("macextras", "Qt6", "Qt::MacExtras", components=["MacExtras"]), - LibraryMapping("main-lib", "Qt6", "Qt::AppManMain", components=["AppManMain"]), - LibraryMapping("manager-lib", "Qt6", "Qt::AppManManager", components=["AppManManager"]), - LibraryMapping("monitor-lib", "Qt6", "Qt::AppManMonitor", components=["AppManMonitor"]), - LibraryMapping("mqtt", "Qt6", "Qt::Mqtt", components=["Mqtt"]), - LibraryMapping("multimedia", "Qt6", "Qt::Multimedia", components=["Multimedia"]), - LibraryMapping( - "multimediawidgets", - "Qt6", - "Qt::MultimediaWidgets", - components=["MultimediaWidgets"], - ), - LibraryMapping("network", "Qt6", "Qt::Network", components=["Network"]), - LibraryMapping("networkauth", "Qt6", "Qt::NetworkAuth", components=["NetworkAuth"]), - LibraryMapping("nfc", "Qt6", "Qt::Nfc", components=["Nfc"]), - LibraryMapping("oauth", "Qt6", "Qt::NetworkAuth", components=["NetworkAuth"]), - LibraryMapping("opcua", "Qt6", "Qt::OpcUa", components=["OpcUa"]), - LibraryMapping("opcua_private", "Qt6", "Qt::OpcUaPrivate", components=["OpcUaPrivate"]), - LibraryMapping("opengl", "Qt6", "Qt::OpenGL", components=["OpenGL"]), - LibraryMapping("openglwidgets", "Qt6", "Qt::OpenGLWidgets", components=["OpenGLWidgets"]), - LibraryMapping("package-lib", "Qt6", "Qt::AppManPackage", components=["AppManPackage"]), - LibraryMapping( - "packetprotocol", - "Qt6", - "Qt::PacketProtocolPrivate", - components=["PacketProtocolPrivate"], - ), - LibraryMapping( - "particles", - "Qt6", - "Qt::QuickParticlesPrivate", - components=["QuickParticlesPrivate"], - ), - LibraryMapping( - "plugin-interfaces", - "Qt6", - "Qt::AppManPluginInterfaces", - components=["AppManPluginInterfaces"], - ), - LibraryMapping("positioning", "Qt6", "Qt::Positioning", components=["Positioning"]), - LibraryMapping( - "positioningquick", "Qt6", "Qt::PositioningQuick", components=["PositioningQuick"] - ), - LibraryMapping("printsupport", "Qt6", "Qt::PrintSupport", components=["PrintSupport"]), - LibraryMapping("purchasing", "Qt6", "Qt::Purchasing", components=["Purchasing"]), - LibraryMapping("qmldebug", "Qt6", "Qt::QmlDebugPrivate", components=["QmlDebugPrivate"]), - LibraryMapping( - "qmldevtools", "Qt6", "Qt::QmlDevToolsPrivate", components=["QmlDevToolsPrivate"] - ), - LibraryMapping( - "qmlcompiler", "Qt6", "Qt::QmlCompilerPrivate", components=["QmlCompilerPrivate"] - ), - LibraryMapping("qml", "Qt6", "Qt::Qml", components=["Qml"]), - LibraryMapping("qmldom", "Qt6", "Qt::QmlDomPrivate", components=["QmlDomPrivate"]), - LibraryMapping("qmlmodels", "Qt6", "Qt::QmlModels", components=["QmlModels"]), - LibraryMapping("qmltest", "Qt6", "Qt::QuickTest", components=["QuickTest"]), - LibraryMapping( - "qtmultimediaquicktools", - "Qt6", - "Qt::MultimediaQuickPrivate", - components=["MultimediaQuickPrivate"], - ), - LibraryMapping( - "quick3dassetimport", - "Qt6", - "Qt::Quick3DAssetImport", - components=["Quick3DAssetImport"], - ), - LibraryMapping("core5compat", "Qt6", "Qt::Core5Compat", components=["Core5Compat"]), - LibraryMapping("quick3d", "Qt6", "Qt::Quick3D", components=["Quick3D"]), - LibraryMapping("quick3drender", "Qt6", "Qt::Quick3DRender", components=["Quick3DRender"]), - LibraryMapping( - "quick3druntimerender", - "Qt6", - "Qt::Quick3DRuntimeRender", - components=["Quick3DRuntimeRender"], - ), - LibraryMapping("quick3dutils", "Qt6", "Qt::Quick3DUtils", components=["Quick3DUtils"]), - LibraryMapping("quickcontrols2", "Qt6", "Qt::QuickControls2", components=["QuickControls2"]), - LibraryMapping( - "quickcontrols2impl", - "Qt6", - "Qt::QuickControls2Impl", - components=["QuickControls2Impl"], - ), - LibraryMapping("quick", "Qt6", "Qt::Quick", components=["Quick"]), - LibraryMapping( - "quickshapes", "Qt6", "Qt::QuickShapesPrivate", components=["QuickShapesPrivate"] - ), - LibraryMapping("quicktemplates2", "Qt6", "Qt::QuickTemplates2", components=["QuickTemplates2"]), - LibraryMapping("quickwidgets", "Qt6", "Qt::QuickWidgets", components=["QuickWidgets"]), - LibraryMapping("remoteobjects", "Qt6", "Qt::RemoteObjects", components=["RemoteObjects"]), - LibraryMapping("script", "Qt6", "Qt::Script", components=["Script"]), - LibraryMapping("scripttools", "Qt6", "Qt::ScriptTools", components=["ScriptTools"]), - LibraryMapping("scxml", "Qt6", "Qt::Scxml", components=["Scxml"]), - LibraryMapping("sensors", "Qt6", "Qt::Sensors", components=["Sensors"]), - LibraryMapping("serialport", "Qt6", "Qt::SerialPort", components=["SerialPort"]), - LibraryMapping("serialbus", "Qt6", "Qt::SerialBus", components=["SerialBus"]), - LibraryMapping("services", "Qt6", "Qt::ServiceSupport", components=["ServiceSupport"]), - LibraryMapping("service_support", "Qt6", "Qt::ServiceSupport", components=["ServiceSupport"]), - LibraryMapping("shadertools", "Qt6", "Qt::ShaderTools", components=["ShaderTools"]), - LibraryMapping("statemachine", "Qt6", "Qt::StateMachine", components=["StateMachine"]), - LibraryMapping("sql", "Qt6", "Qt::Sql", components=["Sql"]), - LibraryMapping("svg", "Qt6", "Qt::Svg", components=["Svg"]), - LibraryMapping("svgwidgets", "Qt6", "Qt::SvgWidgets", components=["SvgWidgets"]), - LibraryMapping("charts", "Qt6", "Qt::Charts", components=["Charts"]), - LibraryMapping("testlib", "Qt6", "Qt::Test", components=["Test"]), - LibraryMapping("texttospeech", "Qt6", "Qt::TextToSpeech", components=["TextToSpeech"]), - LibraryMapping("theme_support", "Qt6", "Qt::ThemeSupport", components=["ThemeSupport"]), - LibraryMapping("tts", "Qt6", "Qt::TextToSpeech", components=["TextToSpeech"]), - LibraryMapping("uiplugin", "Qt6", "Qt::UiPlugin", components=["UiPlugin"]), - LibraryMapping("uitools", "Qt6", "Qt::UiTools", components=["UiTools"]), - LibraryMapping("virtualkeyboard", "Qt6", "Qt::VirtualKeyboard", components=["VirtualKeyboard"]), - LibraryMapping("waylandclient", "Qt6", "Qt::WaylandClient", components=["WaylandClient"]), - LibraryMapping( - "waylandcompositor", - "Qt6", - "Qt::WaylandCompositor", - components=["WaylandCompositor"], - ), - LibraryMapping("webchannel", "Qt6", "Qt::WebChannel", components=["WebChannel"]), - LibraryMapping("webengine", "Qt6", "Qt::WebEngine", components=["WebEngine"]), - LibraryMapping( - "webenginewidgets", "Qt6", "Qt::WebEngineWidgets", components=["WebEngineWidgets"] - ), - LibraryMapping("websockets", "Qt6", "Qt::WebSockets", components=["WebSockets"]), - LibraryMapping("webview", "Qt6", "Qt::WebView", components=["WebView"]), - LibraryMapping("widgets", "Qt6", "Qt::Widgets", components=["Widgets"]), - LibraryMapping("window-lib", "Qt6", "Qt::AppManWindow", components=["AppManWindow"]), - LibraryMapping("winextras", "Qt6", "Qt::WinExtras", components=["WinExtras"]), - LibraryMapping("x11extras", "Qt6", "Qt::X11Extras", components=["X11Extras"]), - LibraryMapping("xcb_qpa_lib", "Qt6", "Qt::XcbQpaPrivate", components=["XcbQpaPrivate"]), - LibraryMapping( - "xkbcommon_support", "Qt6", "Qt::XkbCommonSupport", components=["XkbCommonSupport"] - ), - LibraryMapping("xmlpatterns", "Qt6", "Qt::XmlPatterns", components=["XmlPatterns"]), - LibraryMapping("xml", "Qt6", "Qt::Xml", components=["Xml"]), - LibraryMapping("qmlworkerscript", "Qt6", "Qt::QmlWorkerScript", components=["QmlWorkerScript"]), - LibraryMapping( - "quickparticles", - "Qt6", - "Qt::QuickParticlesPrivate", - components=["QuickParticlesPrivate"], - ), - LibraryMapping( - "linuxofono_support", - "Qt6", - "Qt::LinuxOfonoSupport", - components=["LinuxOfonoSupport"], - ), - LibraryMapping( - "linuxofono_support_private", - "Qt6", - "Qt::LinuxOfonoSupportPrivate", - components=["LinuxOfonoSupportPrivate"], - ), - LibraryMapping("tools", "Qt6", "Qt::Tools", components=["Tools"]), - LibraryMapping("axcontainer", "Qt6", "Qt::AxContainer", components=["AxContainer"]), - LibraryMapping("webkitwidgets", "Qt6", "Qt::WebKitWidgets", components=["WebKitWidgets"]), - LibraryMapping("zlib", "Qt6", "Qt::Zlib", components=["Zlib"]), - LibraryMapping("httpserver", "Qt6", "Qt::HttpServer", components=["HttpServer"]), - LibraryMapping("sslserver", "Qt6", "Qt::SslServer", components=["HttpServer"]), -] - -# Note that the library map is adjusted dynamically further down. -_library_map = [ - # 3rd party: - LibraryMapping("atspi", "ATSPI2", "PkgConfig::ATSPI2"), - LibraryMapping( - "backtrace", "WrapBacktrace", "WrapBacktrace::WrapBacktrace", emit_if="config.unix" - ), - LibraryMapping("bluez", "BlueZ", "PkgConfig::BlueZ"), - LibraryMapping("brotli", "WrapBrotli", "WrapBrotli::WrapBrotliDec"), - LibraryMapping("corewlan", None, None), - LibraryMapping("cups", "Cups", "Cups::Cups"), - LibraryMapping("directfb", "DirectFB", "PkgConfig::DirectFB"), - LibraryMapping("db2", "DB2", "DB2::DB2"), - LibraryMapping("dbus", "WrapDBus1", "dbus-1", resultVariable="DBus1", extra=["1.2"]), - LibraryMapping( - "doubleconversion", "WrapSystemDoubleConversion", - "WrapSystemDoubleConversion::WrapSystemDoubleConversion" - ), - LibraryMapping("dlt", "DLT", "DLT::DLT"), - LibraryMapping("drm", "Libdrm", "Libdrm::Libdrm"), - LibraryMapping("egl", "EGL", "EGL::EGL"), - LibraryMapping("flite", "Flite", "Flite::Flite"), - LibraryMapping("flite_alsa", "ALSA", "ALSA::ALSA"), - LibraryMapping( - "fontconfig", "Fontconfig", "Fontconfig::Fontconfig", resultVariable="FONTCONFIG" - ), - LibraryMapping( - "freetype", - "WrapFreetype", - "WrapFreetype::WrapFreetype", - extra=["2.2.0", "REQUIRED"], - is_bundled_with_qt=True, - ), - LibraryMapping("gbm", "gbm", "gbm::gbm"), - LibraryMapping("glib", "GLIB2", "GLIB2::GLIB2"), - LibraryMapping("iconv", "WrapIconv", "WrapIconv::WrapIconv"), - LibraryMapping("gtk3", "GTK3", "PkgConfig::GTK3", extra=["3.6"]), - LibraryMapping("gssapi", "GSSAPI", "GSSAPI::GSSAPI"), - LibraryMapping( - "harfbuzz", - "WrapHarfbuzz", - "WrapHarfbuzz::WrapHarfbuzz", - is_bundled_with_qt=True, - extra=["2.6.0"], - ), - LibraryMapping("host_dbus", None, None), - LibraryMapping("icu", "ICU", "ICU::i18n ICU::uc ICU::data", components=["i18n", "uc", "data"]), - LibraryMapping("journald", "Libsystemd", "PkgConfig::Libsystemd"), - LibraryMapping("jpeg", "JPEG", "JPEG::JPEG"), # see also libjpeg - LibraryMapping("libatomic", "WrapAtomic", "WrapAtomic::WrapAtomic"), - LibraryMapping("libb2", "Libb2", "Libb2::Libb2"), - LibraryMapping("libclang", "WrapLibClang", "WrapLibClang::WrapLibClang"), - LibraryMapping("libdl", None, "${CMAKE_DL_LIBS}"), - LibraryMapping("libinput", "Libinput", "Libinput::Libinput"), - LibraryMapping("libjpeg", "JPEG", "JPEG::JPEG"), # see also jpeg - LibraryMapping("libpng", "WrapPNG", "WrapPNG::WrapPNG", is_bundled_with_qt=True), - LibraryMapping("libproxy", "Libproxy", "PkgConfig::Libproxy"), - LibraryMapping("librt", "WrapRt", "WrapRt::WrapRt"), - LibraryMapping("libudev", "Libudev", "PkgConfig::Libudev"), - LibraryMapping("lttng-ust", "LTTngUST", "LTTng::UST", resultVariable="LTTNGUST"), - LibraryMapping("libmd4c", "WrapMd4c", "WrapMd4c::WrapMd4c", is_bundled_with_qt=True), - LibraryMapping("mtdev", "Mtdev", "PkgConfig::Mtdev"), - LibraryMapping("mysql", "MySQL", "MySQL::MySQL"), - LibraryMapping("odbc", "ODBC", "ODBC::ODBC"), - LibraryMapping("opengl_es2", "GLESv2", "GLESv2::GLESv2"), - LibraryMapping("opengl", "WrapOpenGL", "WrapOpenGL::WrapOpenGL", resultVariable="WrapOpenGL"), - LibraryMapping( - "openssl_headers", - "WrapOpenSSLHeaders", - "WrapOpenSSLHeaders::WrapOpenSSLHeaders", - resultVariable="TEST_openssl_headers", - appendFoundSuffix=False, - test_library_overwrite="WrapOpenSSLHeaders::WrapOpenSSLHeaders", - run_library_test=True, - ), - LibraryMapping( - "openssl", - "WrapOpenSSL", - "WrapOpenSSL::WrapOpenSSL", - resultVariable="TEST_openssl", - appendFoundSuffix=False, - run_library_test=True, - no_link_so_name="openssl_headers", - ), - LibraryMapping("oci", "Oracle", "Oracle::OCI"), - LibraryMapping( - "pcre2", - "WrapPCRE2", - "WrapPCRE2::WrapPCRE2", - extra=["10.20", "REQUIRED"], - is_bundled_with_qt=True, - ), - LibraryMapping("pps", "PPS", "PPS::PPS"), - LibraryMapping("psql", "PostgreSQL", "PostgreSQL::PostgreSQL"), - LibraryMapping("slog2", "Slog2", "Slog2::Slog2"), - LibraryMapping("speechd", "SpeechDispatcher", "SpeechDispatcher::SpeechDispatcher"), - LibraryMapping("sqlite2", None, None), # No more sqlite2 support in Qt6! - LibraryMapping("sqlite3", "SQLite3", "SQLite::SQLite3"), - LibraryMapping("sqlite", "SQLite3", "SQLite::SQLite3"), - LibraryMapping( - "taglib", "WrapTagLib", "WrapTagLib::WrapTagLib", is_bundled_with_qt=True - ), # used in qtivi - LibraryMapping("tslib", "Tslib", "PkgConfig::Tslib"), - LibraryMapping("udev", "Libudev", "PkgConfig::Libudev"), - LibraryMapping("udev", "Libudev", "PkgConfig::Libudev"), # see also libudev! - LibraryMapping("vulkan", "WrapVulkanHeaders", "WrapVulkanHeaders::WrapVulkanHeaders"), - LibraryMapping("wayland_server", "Wayland", "Wayland::Server"), # used in qtbase/src/gui - LibraryMapping("wayland-server", "Wayland", "Wayland::Server"), # used in qtwayland - LibraryMapping("wayland-client", "Wayland", "Wayland::Client"), - LibraryMapping("wayland-cursor", "Wayland", "Wayland::Cursor"), - LibraryMapping("wayland-egl", "Wayland", "Wayland::Egl"), - LibraryMapping( - "wayland-kms", "Waylandkms", "PkgConfig::Waylandkms" - ), # TODO: check if this actually works - LibraryMapping("x11", "X11", "X11::X11"), - LibraryMapping("x11sm", "X11", "${X11_SM_LIB} ${X11_ICE_LIB}", resultVariable="X11_SM"), - LibraryMapping( - "xcb", - "XCB", - "XCB::XCB", - extra=["1.11"], - resultVariable="TARGET XCB::XCB", - appendFoundSuffix=False, - ), - LibraryMapping("xcb_glx", "XCB", "XCB::GLX", components=["GLX"], resultVariable="XCB_GLX"), - LibraryMapping( - "xcb_cursor", - "XCB", - "XCB::CURSOR", - extra=["0.1.1", "COMPONENTS", "CURSOR"], - resultVariable="XCB_CURSOR", - ), - LibraryMapping( - "xcb_icccm", - "XCB", - "XCB::ICCCM", - extra=["0.3.9"], - components=["ICCCM"], - resultVariable="XCB_ICCCM", - ), - LibraryMapping( - "xcb_image", - "XCB", - "XCB::IMAGE", - extra=["0.3.9"], - components=["IMAGE"], - resultVariable="XCB_IMAGE", - ), - LibraryMapping( - "xcb_keysyms", - "XCB", - "XCB::KEYSYMS", - extra=["0.3.9"], - components=["KEYSYMS"], - resultVariable="XCB_KEYSYMS", - ), - LibraryMapping( - "xcb_randr", "XCB", "XCB::RANDR", components=["RANDR"], resultVariable="XCB_RANDR" - ), - LibraryMapping( - "xcb_render", - "XCB", - "XCB::RENDER", - components=["RENDER"], - resultVariable="XCB_RENDER", - ), - LibraryMapping( - "xcb_renderutil", - "XCB", - "XCB::RENDERUTIL", - extra=["0.3.9"], - components=["RENDERUTIL"], - resultVariable="XCB_RENDERUTIL", - ), - LibraryMapping( - "xcb_shape", "XCB", "XCB::SHAPE", components=["SHAPE"], resultVariable="XCB_SHAPE" - ), - LibraryMapping("xcb_shm", "XCB", "XCB::SHM", components=["SHM"], resultVariable="XCB_SHM"), - LibraryMapping("xcb_sync", "XCB", "XCB::SYNC", components=["SYNC"], resultVariable="XCB_SYNC"), - LibraryMapping( - "xcb_xfixes", - "XCB", - "XCB::XFIXES", - components=["XFIXES"], - resultVariable="TARGET XCB::XFIXES", - appendFoundSuffix=False, - ), - LibraryMapping( - "xcb-xfixes", - "XCB", - "XCB::XFIXES", - components=["XFIXES"], - resultVariable="TARGET XCB::XFIXES", - appendFoundSuffix=False, - ), - LibraryMapping( - "xcb_xinput", - "XCB", - "XCB::XINPUT", - extra=["1.12"], - components=["XINPUT"], - resultVariable="XCB_XINPUT", - ), - LibraryMapping("xcb_xkb", "XCB", "XCB::XKB", components=["XKB"], resultVariable="XCB_XKB"), - LibraryMapping("xcb_xlib", "X11_XCB", "X11::XCB"), - LibraryMapping("xcomposite", "XComposite", "PkgConfig::XComposite"), - LibraryMapping("xkbcommon_evdev", "XKB", "XKB::XKB", extra=["0.5.0"]), # see also xkbcommon - LibraryMapping("xkbcommon_x11", "XKB_COMMON_X11", "PkgConfig::XKB_COMMON_X11", extra=["0.5.0"]), - LibraryMapping("xkbcommon", "XKB", "XKB::XKB", extra=["0.5.0"]), - LibraryMapping("xlib", "X11", "X11::X11"), - LibraryMapping("xrender", "XRender", "PkgConfig::XRender", extra=["0.6"]), - LibraryMapping("zlib", "WrapZLIB", "WrapZLIB::WrapZLIB", extra=["1.0.8"]), - LibraryMapping("zstd", "WrapZSTD", "WrapZSTD::WrapZSTD", extra=["1.3"]), - LibraryMapping("tiff", "TIFF", "TIFF::TIFF"), - LibraryMapping("webp", "WrapWebP", "WrapWebP::WrapWebP"), - LibraryMapping("jasper", "WrapJasper", "WrapJasper::WrapJasper"), - LibraryMapping("mng", "Libmng", "Libmng::Libmng"), - LibraryMapping("sdl2", "WrapSDL2", "WrapSDL2::WrapSDL2"), - LibraryMapping("hunspell", "Hunspell", "Hunspell::Hunspell"), - LibraryMapping( - "qt3d-assimp", - "WrapQt3DAssimp", - "WrapQt3DAssimp::WrapQt3DAssimp", - extra=["5"], - run_library_test=True, - resultVariable="TEST_assimp", - appendFoundSuffix=False, - ), - LibraryMapping( - "quick3d_assimp", - "WrapQuick3DAssimp", - "WrapQuick3DAssimp::WrapQuick3DAssimp", - extra=["5"], - run_library_test=True, - resultVariable="TEST_quick3d_assimp", - appendFoundSuffix=False, - ), -] - - -def _adjust_library_map(): - # Assign a Linux condition on all wayland related packages. - # Assign platforms that have X11 condition on all X11 related packages. - # We don't want to get pages of package not found messages on - # Windows and macOS, and this also improves configure time on - # those platforms. - linux_package_prefixes = ["wayland"] - x11_package_prefixes = ["xcb", "x11", "xkb", "xrender", "xlib"] - for i, _ in enumerate(_library_map): - if any([_library_map[i].soName.startswith(p) for p in linux_package_prefixes]): - _library_map[i].emit_if = "config.linux" - if any([_library_map[i].soName.startswith(p) for p in x11_package_prefixes]): - _library_map[i].emit_if = "X11_SUPPORTED" - - -_adjust_library_map() - - -def find_3rd_party_library_mapping(soName: str) -> typing.Optional[LibraryMapping]: - for i in _library_map: - if i.soName == soName: - return i - return None - - -def find_qt_library_mapping(soName: str) -> typing.Optional[LibraryMapping]: - for i in _qt_library_map: - if i.soName == soName: - return i - return None - - -def find_library_info_for_target(targetName: str) -> typing.Optional[LibraryMapping]: - qt_target = targetName - if targetName.endswith("Private"): - qt_target = qt_target[:-7] - - for i in _qt_library_map: - if i.targetName == qt_target: - return i - - for i in _library_map: - if i.targetName == targetName: - return i - - return None - - -# For a given qmake library (e.g. 'openssl_headers'), check whether this is a fake library used -# for the /nolink annotation, and return the actual annotated qmake library ('openssl/nolink'). -def find_annotated_qmake_lib_name(lib: str) -> str: - for entry in _library_map: - if entry.no_link_so_name == lib: - return entry.soName + "/nolink" - return lib - - -def featureName(name: str) -> str: - replacement_char = "_" - if name.startswith("c++"): - replacement_char = "x" - return re.sub(r"[^a-zA-Z0-9_]", replacement_char, name) - - -def map_qt_library(lib: str) -> str: - private = False - if lib.endswith("-private"): - private = True - lib = lib[:-8] - mapped = find_qt_library_mapping(lib) - qt_name = lib - if mapped: - assert mapped.targetName # Qt libs must have a target name set - qt_name = mapped.targetName - if private: - qt_name += "Private" - return qt_name - - -platform_mapping = { - "win32": "WIN32", - "win": "WIN32", - "unix": "UNIX", - "darwin": "APPLE", - "linux": "LINUX", - "integrity": "INTEGRITY", - "qnx": "QNX", - "vxworks": "VXWORKS", - "hpux": "HPUX", - "android": "ANDROID", - "uikit": "UIKIT", - "tvos": "TVOS", - "watchos": "WATCHOS", - "winrt": "WINRT", - "wasm": "WASM", - "emscripten": "EMSCRIPTEN", - "msvc": "MSVC", - "clang": "CLANG", - "gcc": "GCC", - "icc": "ICC", - "intel_icc": "ICC", - "osx": "MACOS", - "ios": "IOS", - "freebsd": "FREEBSD", - "openbsd": "OPENBSD", - "mingw": "MINGW", - "netbsd": "NETBSD", - "haiku": "HAIKU", - "mac": "APPLE", - "macx": "MACOS", - "macos": "MACOS", - "macx-icc": "(MACOS AND ICC)", -} - - -def map_platform(platform: str) -> str: - """Return the qmake platform as cmake platform or the unchanged string.""" - return platform_mapping.get(platform, platform) - - -def is_known_3rd_party_library(lib: str) -> bool: - handling_no_link = False - if lib.endswith("/nolink") or lib.endswith("_nolink"): - lib = lib[:-7] - handling_no_link = True - mapping = find_3rd_party_library_mapping(lib) - if handling_no_link and mapping and mapping.no_link_so_name: - no_link_mapping = find_3rd_party_library_mapping(mapping.no_link_so_name) - if no_link_mapping: - mapping = no_link_mapping - - return mapping is not None - - -def map_3rd_party_library(lib: str) -> str: - handling_no_link = False - libpostfix = "" - if lib.endswith("/nolink"): - lib = lib[:-7] - libpostfix = "_nolink" - handling_no_link = True - - mapping = find_3rd_party_library_mapping(lib) - - if handling_no_link and mapping and mapping.no_link_so_name: - no_link_mapping = find_3rd_party_library_mapping(mapping.no_link_so_name) - if no_link_mapping: - mapping = no_link_mapping - libpostfix = "" - - if not mapping or not mapping.targetName: - return lib - - return mapping.targetName + libpostfix - - -compile_test_dependent_library_mapping = { - "dtls": {"openssl": "openssl_headers"}, - "ocsp": {"openssl": "openssl_headers"}, -} - - -def get_compile_test_dependent_library_mapping(compile_test_name: str, dependency_name: str): - if compile_test_name in compile_test_dependent_library_mapping: - mapping = compile_test_dependent_library_mapping[compile_test_name] - if dependency_name in mapping: - return mapping[dependency_name] - - return dependency_name - - -def generate_find_package_info( - lib: LibraryMapping, - use_qt_find_package: bool = True, - *, - indent: int = 0, - emit_if: str = "", - use_system_package_name: bool = False, - remove_REQUIRED_from_extra: bool = True, - components_required: bool = True, - module: str = "", -) -> str: - isRequired = False - - extra = lib.extra.copy() - if lib.components: - extra.append("COMPONENTS" if components_required else "OPTIONAL_COMPONENTS") - extra += lib.components - - if "REQUIRED" in extra and use_qt_find_package: - isRequired = True - if remove_REQUIRED_from_extra: - extra.remove("REQUIRED") - - cmake_target_name = lib.targetName - assert cmake_target_name - - # _nolink or not does not matter at this point: - if cmake_target_name.endswith("_nolink") or cmake_target_name.endswith("/nolink"): - cmake_target_name = cmake_target_name[:-7] - - initial_package_name: str = lib.packageName if lib.packageName else "" - package_name: str = initial_package_name - if use_system_package_name: - replace_args = ["Wrap", "WrapSystem"] - package_name = package_name.replace(*replace_args) # type: ignore - cmake_target_name = cmake_target_name.replace(*replace_args) # type: ignore - - if use_qt_find_package: - if cmake_target_name: - extra += ["PROVIDED_TARGETS", cmake_target_name] - if module: - extra += ["MODULE_NAME", module] - extra += ["QMAKE_LIB", find_annotated_qmake_lib_name(lib.soName)] - - result = "" - one_ind = " " - ind = one_ind * indent - - if use_qt_find_package: - if extra: - result = f"{ind}qt_find_package({package_name} {' '.join(extra)})\n" - else: - result = f"{ind}qt_find_package({package_name})\n" - - if isRequired: - result += ( - f"{ind}set_package_properties({initial_package_name} PROPERTIES TYPE REQUIRED)\n" - ) - else: - if extra: - result = f"{ind}find_package({package_name} {' '.join(extra)})\n" - else: - result = f"{ind}find_package({package_name})\n" - - # If a package should be found only in certain conditions, wrap - # the find_package call within that condition. - if emit_if: - result = f"if(({emit_if}) OR QT_FIND_ALL_PACKAGES_ALWAYS)\n{one_ind}{result}endif()\n" - - return result - - -def _set_up_py_parsing_nicer_debug_output(pp): - indent = -1 - - def increase_indent(fn): - def wrapper_function(*args): - nonlocal indent - indent += 1 - print("> " * indent, end="") - return fn(*args) - - return wrapper_function - - def decrease_indent(fn): - def wrapper_function(*args): - nonlocal indent - print("> " * indent, end="") - indent -= 1 - return fn(*args) - - return wrapper_function - - if hasattr(pp, "_defaultStartDebugAction"): - pp._defaultStartDebugAction = increase_indent(pp._defaultStartDebugAction) - pp._defaultSuccessDebugAction = decrease_indent(pp._defaultSuccessDebugAction) - pp._defaultExceptionDebugAction = decrease_indent(pp._defaultExceptionDebugAction) - elif hasattr(pp.core, "_default_start_debug_action"): - pp.core._default_start_debug_action = increase_indent(pp.core._default_start_debug_action) - pp.core._default_success_debug_action = decrease_indent( - pp.core._default_success_debug_action - ) - pp.core._default_exception_debug_action = decrease_indent( - pp.core._default_exception_debug_action - ) diff --git a/util/cmake/json_parser.py b/util/cmake/json_parser.py deleted file mode 100644 index f8e0fa6017f..00000000000 --- a/util/cmake/json_parser.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2019 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -import pyparsing as pp # type: ignore -import json -import re -from helper import _set_up_py_parsing_nicer_debug_output - -_set_up_py_parsing_nicer_debug_output(pp) - - -class QMakeSpecificJSONParser: - def __init__(self, *, debug: bool = False) -> None: - self.debug = debug - self.grammar = self.create_py_parsing_grammar() - - def create_py_parsing_grammar(self): - # Keep around all whitespace. - pp.ParserElement.setDefaultWhitespaceChars("") - - def add_element(name: str, value: pp.ParserElement): - nonlocal self - if self.debug: - value.setName(name) - value.setDebug() - return value - - # Our grammar is pretty simple. We want to remove all newlines - # inside quoted strings, to make the quoted strings JSON - # compliant. So our grammar should skip to the first quote while - # keeping everything before it as-is, process the quoted string - # skip to the next quote, and repeat that until the end of the - # file. - - EOF = add_element("EOF", pp.StringEnd()) - SkipToQuote = add_element("SkipToQuote", pp.SkipTo('"')) - SkipToEOF = add_element("SkipToEOF", pp.SkipTo(EOF)) - - def remove_newlines_and_whitespace_in_quoted_string(tokens): - first_string = tokens[0] - replaced_string = re.sub(r"\n[ ]*", " ", first_string) - return replaced_string - - QuotedString = add_element( - "QuotedString", pp.QuotedString(quoteChar='"', multiline=True, unquoteResults=False) - ) - QuotedString.setParseAction(remove_newlines_and_whitespace_in_quoted_string) - - QuotedTerm = add_element("QuotedTerm", pp.Optional(SkipToQuote) + QuotedString) - Grammar = add_element("Grammar", pp.OneOrMore(QuotedTerm) + SkipToEOF) - - return Grammar - - def parse_file_using_py_parsing(self, file: str): - print(f'Pre processing "{file}" using py parsing to remove incorrect newlines.') - try: - with open(file, "r") as file_fd: - contents = file_fd.read() - - parser_result = self.grammar.parseString(contents, parseAll=True) - token_list = parser_result.asList() - joined_string = "".join(token_list) - - return joined_string - except pp.ParseException as pe: - print(pe.line) - print(" " * (pe.col - 1) + "^") - print(pe) - raise pe - - def parse(self, file: str): - pre_processed_string = self.parse_file_using_py_parsing(file) - print(f'Parsing "{file}" using json.loads().') - json_parsed = json.loads(pre_processed_string) - return json_parsed diff --git a/util/cmake/pro2cmake.py b/util/cmake/pro2cmake.py deleted file mode 100755 index 4d6ca50d670..00000000000 --- a/util/cmake/pro2cmake.py +++ /dev/null @@ -1,5094 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2018 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - - -# Requires Python 3.7. The import statement needs to be the first line of code -# so it's not possible to conditionally check the version and raise an -# exception. -from __future__ import annotations - -import copy -import os.path -import posixpath -import sys -import re -import io -import itertools -import glob -import fnmatch - -from condition_simplifier import simplify_condition -from condition_simplifier_cache import set_condition_simplified_cache_enabled - -import pyparsing as pp # type: ignore -import xml.etree.ElementTree as ET - -from argparse import ArgumentParser -from textwrap import dedent -from functools import lru_cache -from shutil import copyfile -from collections import defaultdict -from typing import ( - List, - Optional, - Dict, - Set, - IO, - Union, - Any, - Callable, - FrozenSet, - Tuple, - Match, - Type, -) - -from qmake_parser import parseProFile -from special_case_helper import SpecialCaseHandler -from helper import ( - map_qt_library, - map_3rd_party_library, - is_known_3rd_party_library, - featureName, - map_platform, - find_library_info_for_target, - generate_find_package_info, - LibraryMapping, -) - -cmake_version_string = "3.16" -cmake_api_version = 3 - - -def _parse_commandline(): - parser = ArgumentParser( - description="Generate CMakeLists.txt files from ." "pro files.", - epilog="Requirements: pip install -r requirements.txt", - ) - 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( - "--debug-special-case-preservation", - dest="debug_special_case_preservation", - action="store_true", - help="Show all git commands and file copies.", - ) - - parser.add_argument( - "--is-example", - action="store_true", - dest="is_example", - help="Treat the input .pro file as a Qt example.", - ) - parser.add_argument( - "--is-user-project", - action="store_true", - dest="is_user_project", - help="Treat the input .pro file as a user project.", - ) - parser.add_argument( - "-s", - "--skip-special-case-preservation", - dest="skip_special_case_preservation", - action="store_true", - help="Skips behavior to reapply " "special case modifications (requires git in PATH)", - ) - parser.add_argument( - "-k", - "--keep-temporary-files", - dest="keep_temporary_files", - action="store_true", - help="Don't automatically remove CMakeLists.gen.txt and other " "intermediate files.", - ) - - parser.add_argument( - "-e", - "--skip-condition-cache", - dest="skip_condition_cache", - action="store_true", - help="Don't use condition simplifier cache (conversion speed may decrease).", - ) - - parser.add_argument( - "--skip-subdirs-project", - dest="skip_subdirs_project", - action="store_true", - help="Skip converting project if it ends up being a TEMPLATE=subdirs project.", - ) - - parser.add_argument( - "-i", - "--ignore-skip-marker", - dest="ignore_skip_marker", - action="store_true", - help="If set, pro file will be converted even if skip marker is found in CMakeLists.txt.", - ) - - parser.add_argument( - "--api-version", - dest="api_version", - type=int, - help="Specify which cmake api version should be generated. 1, 2 or 3, 3 is latest.", - ) - - parser.add_argument( - "-o", - "--output-file", - dest="output_file", - type=str, - help="Specify a file path where the generated content should be written to. " - "Default is to write to CMakeLists.txt in the same directory as the .pro file.", - ) - - parser.add_argument( - "files", - metavar="<.pro/.pri file>", - type=str, - nargs="+", - help="The .pro/.pri file to process", - ) - return parser.parse_args() - - -def get_top_level_repo_project_path(project_file_path: str = "") -> str: - qmake_or_cmake_conf_path = find_qmake_or_cmake_conf(project_file_path) - qmake_or_cmake_conf_dir_path = os.path.dirname(qmake_or_cmake_conf_path) - return qmake_or_cmake_conf_dir_path - - -def is_top_level_repo_project(project_file_path: str = "") -> bool: - qmake_or_cmake_conf_path = find_qmake_or_cmake_conf(project_file_path) - qmake_or_cmake_conf_dir_path = os.path.dirname(qmake_or_cmake_conf_path) - project_dir_path = os.path.dirname(project_file_path) - return qmake_or_cmake_conf_dir_path == project_dir_path - - -def is_top_level_repo_tests_project(project_file_path: str = "") -> bool: - qmake_or_cmake_conf_path = find_qmake_or_cmake_conf(project_file_path) - qmake_or_cmake_conf_dir_path = os.path.dirname(qmake_or_cmake_conf_path) - project_dir_path = os.path.dirname(project_file_path) - project_dir_name = os.path.basename(project_dir_path) - maybe_same_level_dir_path = os.path.join(project_dir_path, "..") - normalized_maybe_same_level_dir_path = os.path.normpath(maybe_same_level_dir_path) - return ( - qmake_or_cmake_conf_dir_path == normalized_maybe_same_level_dir_path - and project_dir_name == "tests" - ) - - -def is_top_level_repo_examples_project(project_file_path: str = "") -> bool: - qmake_or_cmake_conf_path = find_qmake_or_cmake_conf(project_file_path) - qmake_or_cmake_conf_dir_path = os.path.dirname(qmake_or_cmake_conf_path) - project_dir_path = os.path.dirname(project_file_path) - project_dir_name = os.path.basename(project_dir_path) - maybe_same_level_dir_path = os.path.join(project_dir_path, "..") - normalized_maybe_same_level_dir_path = os.path.normpath(maybe_same_level_dir_path) - return ( - qmake_or_cmake_conf_dir_path == normalized_maybe_same_level_dir_path - and project_dir_name == "examples" - ) - - -def is_example_project(project_file_path: str = "") -> bool: - # If there's a .qmake.conf or .cmake.conf file in the parent - # directories of the given project path, it is likely that the - # project is an internal Qt project that uses private Qt CMake - # API. - found_qt_repo_version = False - qmake_conf = find_qmake_conf(project_file_path) - if qmake_conf: - repo_version = parse_qt_repo_module_version_from_qmake_conf(qmake_conf) - if repo_version: - found_qt_repo_version = True - - cmake_conf = find_cmake_conf(project_file_path) - if cmake_conf: - repo_version = parse_qt_repo_module_version_from_cmake_conf(cmake_conf) - if repo_version: - found_qt_repo_version = True - - # If we haven't found a conf file, we assume this is an example - # project and not a project under a qt source repository. - if not found_qt_repo_version: - return True - - # If the project file is found in a subdir called 'examples' - # relative to the repo source dir, then it must be an example, but - # some examples contain 3rdparty libraries that do not need to be - # built as examples. - qmake_or_cmake_conf_path = find_qmake_or_cmake_conf(project_file_path) - qmake_or_cmake_conf_dir_path = os.path.dirname(qmake_or_cmake_conf_path) - project_relative_path = os.path.relpath(project_file_path, qmake_or_cmake_conf_dir_path) - - is_example_under_repo_sources = ( - project_relative_path.startswith("examples") and "3rdparty" not in project_relative_path - ) - return is_example_under_repo_sources - - -def is_config_test_project(project_file_path: str = "") -> bool: - qmake_or_cmake_conf_path = find_qmake_or_cmake_conf(project_file_path) - qmake_or_cmake_conf_dir_path = os.path.dirname(qmake_or_cmake_conf_path) - dir_name_with_qmake_or_cmake_conf = os.path.basename(qmake_or_cmake_conf_dir_path) - - project_relative_path = os.path.relpath(project_file_path, qmake_or_cmake_conf_dir_path) - # If the project file is found in a subdir called 'config.tests' - # relative to the repo source dir, then it's probably a config test. - # Also if the .qmake.conf is found within config.tests dir (like in qtbase) - # then the project is probably a config .test - return ( - project_relative_path.startswith("config.tests") - or dir_name_with_qmake_or_cmake_conf == "config.tests" - ) - - -def is_benchmark_project(project_file_path: str = "") -> bool: - qmake_or_cmake_conf_path = find_qmake_or_cmake_conf(project_file_path) - qmake_or_cmake_conf_dir_path = os.path.dirname(qmake_or_cmake_conf_path) - - project_relative_path = os.path.relpath(project_file_path, qmake_or_cmake_conf_dir_path) - # If the project file is found in a subdir called 'tests/benchmarks' - # relative to the repo source dir, then it must be a benchmark - return project_relative_path.startswith("tests/benchmarks") - - -def is_manual_test_project(project_file_path: str = "") -> bool: - qmake_or_cmake_conf_path = find_qmake_or_cmake_conf(project_file_path) - qmake_or_cmake_conf_dir_path = os.path.dirname(qmake_or_cmake_conf_path) - - project_relative_path = os.path.relpath(project_file_path, qmake_or_cmake_conf_dir_path) - # If the project file is found in a subdir called 'tests/manual' - # relative to the repo source dir, then it must be a manual test - return project_relative_path.startswith("tests/manual") - - -@lru_cache(maxsize=None) -def find_file_walking_parent_dirs(file_name: str, project_file_path: str = "") -> str: - assert file_name - if not os.path.isabs(project_file_path): - print( - f"Warning: could not find {file_name} file, given path is not an " - f"absolute path: {project_file_path}" - ) - return "" - - cwd = os.path.dirname(project_file_path) - - while os.path.isdir(cwd): - maybe_file = posixpath.join(cwd, file_name) - if os.path.isfile(maybe_file): - return maybe_file - else: - last_cwd = cwd - cwd = os.path.dirname(cwd) - if last_cwd == cwd: - # reached the top level directory, stop looking - break - - return "" - - -def find_qmake_conf(project_file_path: str = "") -> str: - return find_file_walking_parent_dirs(".qmake.conf", project_file_path) - - -def find_cmake_conf(project_file_path: str = "") -> str: - return find_file_walking_parent_dirs(".cmake.conf", project_file_path) - - -def find_qmake_or_cmake_conf(project_file_path: str = "") -> str: - qmake_conf = find_qmake_conf(project_file_path) - if qmake_conf: - return qmake_conf - cmake_conf = find_cmake_conf(project_file_path) - return cmake_conf - - -def parse_qt_repo_module_version_from_qmake_conf(qmake_conf_path: str = "") -> str: - with open(qmake_conf_path) as f: - file_contents = f.read() - m = re.search(r"MODULE_VERSION\s*=\s*([0-9.]+)", file_contents) - return m.group(1) if m else "" - - -def parse_qt_repo_module_version_from_cmake_conf(cmake_conf_path: str = "") -> str: - with open(cmake_conf_path) as f: - file_contents = f.read() - m = re.search(r'set\(QT_REPO_MODULE_VERSION\s*"([0-9.]+)"\)', file_contents) - return m.group(1) if m else "" - - -def set_up_cmake_api_calls(): - def nested_dict(): - return defaultdict(nested_dict) - - api = nested_dict() - - api[1]["qt_extend_target"] = "extend_target" - api[1]["qt_add_module"] = "add_qt_module" - api[1]["qt_add_plugin"] = "add_qt_plugin" - api[1]["qt_add_tool"] = "add_qt_tool" - api[2]["qt_internal_add_app"] = "qt_internal_add_app" - api[1]["qt_add_test"] = "add_qt_test" - api[1]["qt_add_test_helper"] = "add_qt_test_helper" - api[1]["qt_add_manual_test"] = "add_qt_manual_test" - api[1]["qt_add_benchmark"] = "add_qt_benchmark" - api[1]["qt_add_executable"] = "add_qt_executable" - api[1]["qt_add_simd_part"] = "add_qt_simd_part" - api[1]["qt_add_docs"] = "add_qt_docs" - api[1]["qt_add_resource"] = "add_qt_resource" - api[1]["qt_add_qml_module"] = "add_qml_module" - api[1]["qt_add_cmake_library"] = "add_cmake_library" - api[1]["qt_add_3rdparty_library"] = "qt_add_3rdparty_library" - api[1]["qt_create_tracepoints"] = "qt_create_tracepoints" - - api[2]["qt_extend_target"] = "qt_extend_target" - api[2]["qt_add_module"] = "qt_add_module" - api[2]["qt_add_plugin"] = "qt_internal_add_plugin" - api[2]["qt_add_tool"] = "qt_add_tool" - api[2]["qt_internal_add_app"] = "qt_internal_add_app" - api[2]["qt_add_test"] = "qt_add_test" - api[2]["qt_add_test_helper"] = "qt_add_test_helper" - api[2]["qt_add_manual_test"] = "qt_add_manual_test" - api[2]["qt_add_benchmark"] = "qt_add_benchmark" - api[2]["qt_add_executable"] = "qt_add_executable" - api[2]["qt_add_simd_part"] = "qt_add_simd_part" - api[2]["qt_add_docs"] = "qt_add_docs" - api[2]["qt_add_resource"] = "qt_add_resource" - api[2]["qt_add_qml_module"] = "qt_add_qml_module" - api[2]["qt_add_cmake_library"] = "qt_add_cmake_library" - api[2]["qt_add_3rdparty_library"] = "qt_add_3rdparty_library" - api[2]["qt_create_tracepoints"] = "qt_create_tracepoints" - - api[3]["qt_extend_target"] = "qt_internal_extend_target" - api[3]["qt_add_module"] = "qt_internal_add_module" - api[3]["qt_add_plugin"] = "qt_internal_add_plugin" - api[3]["qt_add_tool"] = "qt_internal_add_tool" - api[3]["qt_internal_add_app"] = "qt_internal_add_app" - api[3]["qt_add_test"] = "qt_internal_add_test" - api[3]["qt_add_test_helper"] = "qt_internal_add_test_helper" - api[3]["qt_add_manual_test"] = "qt_internal_add_manual_test" - api[3]["qt_add_benchmark"] = "qt_internal_add_benchmark" - api[3]["qt_add_executable"] = "qt_internal_add_executable" - api[3]["qt_add_simd_part"] = "qt_internal_add_simd_part" - api[3]["qt_add_docs"] = "qt_internal_add_docs" - api[3]["qt_add_resource"] = "qt_internal_add_resource" - api[3]["qt_add_qml_module"] = "qt_internal_add_qml_module" - api[3]["qt_add_cmake_library"] = "qt_internal_add_cmake_library" - api[3]["qt_add_3rdparty_library"] = "qt_internal_add_3rdparty_library" - api[3]["qt_create_tracepoints"] = "qt_internal_create_tracepoints" - - return api - - -cmake_api_calls = set_up_cmake_api_calls() - - -def detect_cmake_api_version_used_in_file_content(project_file_path: str) -> Optional[int]: - dir_path = os.path.dirname(project_file_path) - cmake_project_path = os.path.join(dir_path, "CMakeLists.txt") - - # If file doesn't exist, None implies default version selected by - # script. - if not os.path.exists(cmake_project_path): - return None - - with open(cmake_project_path, "r") as file_fd: - contents = file_fd.read() - - api_call_versions = [version for version in cmake_api_calls] - api_call_versions = sorted(api_call_versions, reverse=True) - api_call_version_matches = {} - for version in api_call_versions: - versioned_api_calls = [ - cmake_api_calls[version][api_call] for api_call in cmake_api_calls[version] - ] - versioned_api_calls_alternatives = "|".join(versioned_api_calls) - api_call_version_matches[version] = re.search( - versioned_api_calls_alternatives, contents - ) - - # If new style found, return latest api version. Otherwise - # return the current version. - for version in api_call_version_matches: - if api_call_version_matches[version]: - return version - - return 1 - - -def get_cmake_api_call(api_name: str, api_version: Optional[int] = None) -> str: - if not api_version: - global cmake_api_version - api_version = cmake_api_version - if not cmake_api_calls[api_version][api_name]: - raise RuntimeError(f"No CMake API call {api_name} of version {api_version} found.") - - return cmake_api_calls[api_version][api_name] - - -class QtResource: - def __init__( - self, - name: str = "", - prefix: str = "", - base_dir: str = "", - files: Dict[str, str] = {}, - lang: str = None, - generated: bool = False, - skip_qtquick_compiler: bool = False, - ) -> None: - self.name = name - self.prefix = prefix - self.base_dir = base_dir - self.files = files - self.lang = lang - self.generated = generated - self.skip_qtquick_compiler = skip_qtquick_compiler - - -def read_qrc_file( - filepath: str, - base_dir: str = "", - project_file_path: str = "", - skip_qtquick_compiler: bool = False, -) -> List[QtResource]: - # Hack to handle QT_SOURCE_TREE. Assume currently that it's the same - # as the qtbase source path. - qt_source_tree_literal = "${QT_SOURCE_TREE}" - if qt_source_tree_literal in filepath: - qmake_or_cmake_conf = find_qmake_or_cmake_conf(project_file_path) - - if qmake_or_cmake_conf: - qt_source_tree = os.path.dirname(qmake_or_cmake_conf) - filepath = filepath.replace(qt_source_tree_literal, qt_source_tree) - else: - print( - f"Warning, could not determine QT_SOURCE_TREE location while trying " - f"to find: {filepath}" - ) - - resource_name = os.path.splitext(os.path.basename(filepath))[0] - dir_name = os.path.dirname(filepath) - base_dir = posixpath.join("" if base_dir == "." else base_dir, dir_name) - - # Small not very thorough check to see if this a shared qrc resource - # pattern is mostly used by the tests. - if not os.path.isfile(filepath): - raise RuntimeError(f"Invalid file path given to process_qrc_file: {filepath}") - - tree = ET.parse(filepath) - root = tree.getroot() - assert root.tag == "RCC" - - result: List[QtResource] = [] - for resource in root: - assert resource.tag == "qresource" - r = QtResource( - name=resource_name, - prefix=resource.get("prefix", "/"), - base_dir=base_dir, - lang=resource.get("lang", ""), - skip_qtquick_compiler=skip_qtquick_compiler, - ) - - if len(result) > 0: - r.name += str(len(result)) - - if not r.prefix.startswith("/"): - r.prefix = f"/{r.prefix}" - - for file in resource: - path = file.text - assert path - - # Get alias: - alias = file.get("alias", "") - r.files[path] = alias - - result.append(r) - - return result - - -def write_resource_source_file_properties( - sorted_files: List[str], files: Dict[str, str], base_dir: str, skip_qtquick_compiler: bool -) -> str: - output = "" - source_file_properties = defaultdict(list) - - for source in sorted_files: - alias = files[source] - if alias: - source_file_properties[source].append(f'QT_RESOURCE_ALIAS "{alias}"') - # If a base dir is given, we have to write the source file property - # assignments that disable the quick compiler per file. - if base_dir and skip_qtquick_compiler: - source_file_properties[source].append("QT_SKIP_QUICKCOMPILER 1") - - for full_source in source_file_properties: - per_file_props = source_file_properties[full_source] - if per_file_props: - prop_spaces = " " - per_file_props_joined = f"\n{prop_spaces}".join(per_file_props) - output += dedent( - f"""\ - set_source_files_properties("{full_source}" - PROPERTIES {per_file_props_joined} - ) - """ - ) - - return output - - -def write_add_qt_resource_call( - target: str, - scope: Scope, - resource_name: str, - prefix: Optional[str], - base_dir: str, - lang: Optional[str], - files: Dict[str, str], - skip_qtquick_compiler: bool, - is_example: bool, -) -> str: - output = "" - - if base_dir: - base_dir_expanded = scope.expandString(base_dir) - if base_dir_expanded: - base_dir = base_dir_expanded - new_files = {} - for file_path, alias in files.items(): - full_file_path = posixpath.join(base_dir, file_path) - new_files[full_file_path] = alias - files = new_files - - sorted_files = sorted(files.keys()) - assert sorted_files - - output += write_resource_source_file_properties( - sorted_files, files, base_dir, skip_qtquick_compiler - ) - - # Quote file paths in case there are spaces. - sorted_files_backup = sorted_files - sorted_files = [] - for source in sorted_files_backup: - if source.startswith("${"): - sorted_files.append(source) - else: - sorted_files.append(f'"{source}"') - - file_list = "\n ".join(sorted_files) - output += dedent( - f"""\ - set({resource_name}_resource_files - {file_list} - )\n - """ - ) - file_list = f"${{{resource_name}_resource_files}}" - if skip_qtquick_compiler and not base_dir: - output += ( - f"set_source_files_properties(${{{resource_name}_resource_files}}" - " PROPERTIES QT_SKIP_QUICKCOMPILER 1)\n\n" - ) - - prefix_expanded = scope.expandString(str(prefix)) - if prefix_expanded: - prefix = prefix_expanded - params = "" - if lang: - params += f'{spaces(1)}LANG\n{spaces(2)}"{lang}"\n' - params += f'{spaces(1)}PREFIX\n{spaces(2)}"{prefix}"\n' - if base_dir: - params += f'{spaces(1)}BASE\n{spaces(2)}"{base_dir}"\n' - if is_example: - add_resource_command = "qt6_add_resources" - else: - add_resource_command = get_cmake_api_call("qt_add_resource") - output += ( - f'{add_resource_command}({target} "{resource_name}"\n{params}{spaces(1)}FILES\n' - f"{spaces(2)}{file_list}\n)\n" - ) - - return output - - -class QmlDirFileInfo: - def __init__(self, file_path: str, type_name: str) -> None: - self.file_path = file_path - self.versions = "" - self.type_name = type_name - self.internal = False - self.singleton = False - self.path = "" - - -class QmlDir: - def __init__(self) -> None: - self.module = "" - self.plugin_name = "" - self.plugin_optional = False - self.plugin_path = "" - self.classname = "" - self.imports: List[str] = [] - self.optional_imports: List[str] = [] - self.type_names: Dict[str, QmlDirFileInfo] = {} - self.type_infos: List[str] = [] - self.depends: List[Tuple[str, str]] = [] - self.designer_supported = False - - def __str__(self) -> str: - type_infos_line = " \n".join(self.type_infos) - imports_line = " \n".join(self.imports) - optional_imports_line = " \n".join(self.optional_imports) - string = f"""\ - module: {self.module} - plugin: {self.plugin_optional} {self.plugin_name} {self.plugin_path} - classname: {self.classname} - type_infos:{type_infos_line} - imports:{imports_line} - optional_imports:{optional_imports_line} - dependends: - """ - for dep in self.depends: - string += f" {dep[0]} {dep[1]}\n" - string += f"designer supported: {self.designer_supported}\n" - string += "type_names:\n" - for key in self.type_names: - file_info = self.type_names[key] - string += ( - f" type:{file_info.type_name} " - f"versions:{file_info.versions} " - f"path:{file_info.file_path} " - f"internal:{file_info.internal} " - f"singleton:{file_info.singleton}\n" - ) - return string - - def get_or_create_file_info(self, path: str, type_name: str) -> QmlDirFileInfo: - if path not in self.type_names: - self.type_names[path] = QmlDirFileInfo(path, type_name) - qmldir_file = self.type_names[path] - if qmldir_file.type_name != type_name: - raise RuntimeError("Registered qmldir file type_name does not match.") - return qmldir_file - - def handle_file_internal(self, type_name: str, path: str): - qmldir_file = self.get_or_create_file_info(path, type_name) - qmldir_file.internal = True - - def handle_file_singleton(self, type_name: str, version: str, path: str): - qmldir_file = self.handle_file(type_name, version, path) - qmldir_file.singleton = True - - def handle_file(self, type_name: str, version: str, path: str) -> QmlDirFileInfo: - qmldir_file = self.get_or_create_file_info(path, type_name) - # If this is not the first version we've found, - # append ';' to delineate the next version; e.g.: "2.0;2.6" - if qmldir_file.versions: - qmldir_file.versions += ";" - qmldir_file.versions += version - qmldir_file.type_name = type_name - qmldir_file.path = path - return qmldir_file - - def from_lines(self, lines: List[str]): - for line in lines: - self.handle_line(line) - - def from_file(self, path: str): - f = open(path, "r") - if not f: - raise RuntimeError(f"Failed to open qmldir file at: {path}") - for line in f: - self.handle_line(line) - - def handle_line(self, line: str): - if line.startswith("#"): - return - line = line.strip().replace("\n", "") - if len(line) == 0: - return - - entries = line.split(" ") - if len(entries) == 0: - raise RuntimeError("Unexpected QmlDir file line entry") - if entries[0] == "module": - self.module = entries[1] - elif entries[0] == "singleton": - self.handle_file_singleton(entries[1], entries[2], entries[3]) - elif entries[0] == "internal": - self.handle_file_internal(entries[1], entries[2]) - elif entries[0] == "plugin": - self.plugin_name = entries[1] - if len(entries) > 2: - self.plugin_path = entries[2] - elif entries[0] == "optional": - if entries[1] == "plugin": - self.plugin_name = entries[2] - self.plugin_optional = True - if len(entries) > 3: - self.plugin_path = entries[3] - elif entries[1] == "import": - if len(entries) == 4: - self.optional_imports.append(entries[2] + "/" + entries[3]) - else: - self.optional_imports.append(entries[2]) - else: - raise RuntimeError("Only plugins and imports can be optional in qmldir files") - elif entries[0] == "classname": - self.classname = entries[1] - elif entries[0] == "typeinfo": - self.type_infos.append(entries[1]) - elif entries[0] == "depends": - self.depends.append((entries[1], entries[2])) - elif entries[0] == "designersupported": - self.designer_supported = True - elif entries[0] == "import": - if len(entries) == 3: - self.imports.append(entries[1] + "/" + entries[2]) - else: - self.imports.append(entries[1]) - elif len(entries) == 3: - self.handle_file(entries[0], entries[1], entries[2]) - else: - raise RuntimeError(f"Uhandled qmldir entry {line}") - - -def spaces(indent: int) -> str: - return " " * indent - - -def trim_leading_dot(file: str) -> str: - while file.startswith("./"): - file = file[2:] - return file - - -def map_to_file(f: str, scope: Scope, *, is_include: bool = False) -> str: - assert "$$" not in f - - if f.startswith("${"): # Some cmake variable is prepended - return f - - base_dir = scope.currentdir if is_include else scope.basedir - f = posixpath.join(base_dir, f) - - return trim_leading_dot(f) - - -def handle_vpath(source: str, base_dir: str, vpath: List[str]) -> str: - assert "$$" not in source - - if not source: - return "" - - if not vpath: - return source - - if os.path.exists(os.path.join(base_dir, source)): - return source - - variable_pattern = re.compile(r"\$\{[A-Za-z0-9_]+\}") - match = re.match(variable_pattern, source) - if match: - # a complex, variable based path, skipping validation - # or resolving - return source - - for v in vpath: - fullpath = posixpath.join(v, source) - if os.path.exists(fullpath): - return trim_leading_dot(posixpath.relpath(fullpath, base_dir)) - - print(f" XXXX: Source {source}: Not found.") - return f"{source}-NOTFOUND" - - -class Operation: - def __init__(self, value: Union[List[str], str], line_no: int = -1) -> None: - if isinstance(value, list): - self._value = value - else: - self._value = [str(value)] - self._line_no = line_no - - def process( - self, key: str, sinput: List[str], transformer: Callable[[List[str]], List[str]] - ) -> List[str]: - assert False - - def __repr__(self): - assert False - - def _dump(self): - if not self._value: - return "" - - if not isinstance(self._value, list): - return "" - - result = [] - for i in self._value: - if not i: - result.append("") - else: - result.append(str(i)) - return '"' + '", "'.join(result) + '"' - - -class AddOperation(Operation): - def process( - self, key: str, sinput: List[str], transformer: Callable[[List[str]], List[str]] - ) -> List[str]: - return sinput + transformer(self._value) - - def __repr__(self): - return f"+({self._dump()})" - - -class UniqueAddOperation(Operation): - def process( - self, key: str, sinput: List[str], transformer: Callable[[List[str]], List[str]] - ) -> List[str]: - result = sinput - for v in transformer(self._value): - if v not in result: - result.append(v) - return result - - def __repr__(self): - return f"*({self._dump()})" - - -class ReplaceOperation(Operation): - def process( - self, key: str, sinput: List[str], transformer: Callable[[List[str]], List[str]] - ) -> List[str]: - result = [] - for s in sinput: - for v in transformer(self._value): - pattern, replacement = self.split_rex(v) - result.append(re.sub(pattern, replacement, s)) - return result - - def split_rex(self, s): - pattern = "" - replacement = "" - if len(s) < 4: - return pattern, replacement - sep = s[1] - s = s[2:] - rex = re.compile(f"[^\\\\]{sep}") - m = rex.search(s) - if not m: - return pattern, replacement - pattern = s[: m.start() + 1] - replacement = s[m.end() :] - m = rex.search(replacement) - if m: - replacement = replacement[: m.start() + 1] - return pattern, replacement - - def __repr__(self): - return f"*({self._dump()})" - - -class SetOperation(Operation): - def process( - self, key: str, sinput: List[str], transformer: Callable[[List[str]], List[str]] - ) -> List[str]: - values = [] # List[str] - for v in self._value: - if v != f"$${key}": - values.append(v) - else: - values += sinput - - if transformer: - return list(transformer(values)) - else: - return values - - def __repr__(self): - return f"=({self._dump()})" - - -class RemoveOperation(Operation): - def process( - self, key: str, sinput: List[str], transformer: Callable[[List[str]], List[str]] - ) -> List[str]: - sinput_set = set(sinput) - value_set = set(self._value) - result: List[str] = [] - - # Add everything that is not going to get removed: - for v in sinput: - if v not in value_set: - result += [v] - - # Add everything else with removal marker: - for v in transformer(self._value): - if v not in sinput_set: - result += [f"-{v}"] - - return result - - def __repr__(self): - return f"-({self._dump()})" - - -# Helper class that stores a list of tuples, representing a scope id and -# a line number within that scope's project file. The whole list -# represents the full path location for a certain operation while -# traversing include()'d scopes. Used for sorting when determining -# operation order when evaluating operations. -class OperationLocation(object): - def __init__(self): - self.list_of_scope_ids_and_line_numbers = [] - - def clone_and_append(self, scope_id: int, line_number: int) -> OperationLocation: - new_location = OperationLocation() - new_location.list_of_scope_ids_and_line_numbers = list( - self.list_of_scope_ids_and_line_numbers - ) - new_location.list_of_scope_ids_and_line_numbers.append((scope_id, line_number)) - return new_location - - def __lt__(self, other: OperationLocation) -> Any: - return self.list_of_scope_ids_and_line_numbers < other.list_of_scope_ids_and_line_numbers - - def __repr__(self) -> str: - s = "" - for t in self.list_of_scope_ids_and_line_numbers: - s += f"s{t[0]}:{t[1]} " - s = s.strip(" ") - return s - - -class Scope(object): - - SCOPE_ID: int = 1 - - def __init__( - self, - *, - parent_scope: Optional[Scope], - qmake_file: str, - condition: str = "", - base_dir: str = "", - operations: Union[Dict[str, List[Operation]], None] = None, - parent_include_line_no: int = -1, - ) -> None: - if not operations: - operations = { - "QT_SOURCE_TREE": [SetOperation(["${QT_SOURCE_TREE}"])], - "QT_BUILD_TREE": [SetOperation(["${PROJECT_BINARY_DIR}"])], - "QTRO_SOURCE_TREE": [SetOperation(["${CMAKE_SOURCE_DIR}"])], - } - - self._operations: Dict[str, List[Operation]] = copy.deepcopy(operations) - if parent_scope: - parent_scope._add_child(self) - else: - self._parent = None # type: Optional[Scope] - # Only add the "QT = core gui" Set operation once, on the - # very top-level .pro scope, aka it's basedir is empty. - if not base_dir: - self._operations["QT"] = [SetOperation(["core", "gui"])] - - self._basedir = base_dir - if qmake_file: - self._currentdir = os.path.dirname(qmake_file) or "." - if not self._basedir: - self._basedir = self._currentdir - - self._scope_id = Scope.SCOPE_ID - Scope.SCOPE_ID += 1 - self._file = qmake_file - self._file_absolute_path = os.path.abspath(qmake_file) - self._condition = map_condition(condition) - self._children = [] # type: List[Scope] - self._included_children = [] # type: List[Scope] - self._including_scope = None # type: Optional[Scope] - self._visited_keys = set() # type: Set[str] - self._total_condition = None # type: Optional[str] - self._parent_include_line_no = parent_include_line_no - self._is_public_module = False - self._has_private_module = False - self._is_internal_qt_app = False - - def __repr__(self): - return ( - f"{self._scope_id}:{self._basedir}:{self._currentdir}:{self._file}:" - f"{self._condition or ''}" - ) - - def reset_visited_keys(self): - self._visited_keys = set() - - def merge(self, other: "Scope") -> None: - assert self != other - other._including_scope = self - self._included_children.append(other) - - @property - def scope_debug(self) -> bool: - merge = self.get_string("PRO2CMAKE_SCOPE_DEBUG").lower() - return merge == "1" or merge == "on" or merge == "yes" or merge == "true" - - @property - def parent(self) -> Optional[Scope]: - return self._parent - - @property - def including_scope(self) -> Optional[Scope]: - return self._including_scope - - @property - def basedir(self) -> str: - return self._basedir - - @property - def currentdir(self) -> str: - return self._currentdir - - @property - def is_public_module(self) -> bool: - return self._is_public_module - - @property - def has_private_module(self) -> bool: - return self._has_private_module - - @property - def is_internal_qt_app(self) -> bool: - is_app = self._is_internal_qt_app - current_scope = self - while not is_app and current_scope.parent: - current_scope = current_scope.parent - is_app = current_scope.is_internal_qt_app - - return is_app - - def can_merge_condition(self): - if self._condition == "else": - return False - if self._operations: - return False - - child_count = len(self._children) - if child_count == 0 or child_count > 2: - return False - assert child_count != 1 or self._children[0]._condition != "else" - return child_count == 1 or self._children[1]._condition == "else" - - def settle_condition(self): - new_children: List[Scope] = [] - for c in self._children: - c.settle_condition() - - if c.can_merge_condition(): - child = c._children[0] - child._condition = "({c._condition}) AND ({child._condition})" - new_children += c._children - else: - new_children.append(c) - self._children = new_children - - @staticmethod - def FromDict( - parent_scope: Optional["Scope"], - file: str, - statements, - cond: str = "", - base_dir: str = "", - project_file_content: str = "", - parent_include_line_no: int = -1, - ) -> Scope: - scope = Scope( - parent_scope=parent_scope, - qmake_file=file, - condition=cond, - base_dir=base_dir, - parent_include_line_no=parent_include_line_no, - ) - 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 != "" - - op_location_start = operation["locn_start"] - operation = operation["value"] - op_line_no = pp.lineno(op_location_start, project_file_content) - - if operation == "=": - scope._append_operation(key, SetOperation(value, line_no=op_line_no)) - elif operation == "-=": - scope._append_operation(key, RemoveOperation(value, line_no=op_line_no)) - elif operation == "+=": - scope._append_operation(key, AddOperation(value, line_no=op_line_no)) - elif operation == "*=": - scope._append_operation(key, UniqueAddOperation(value, line_no=op_line_no)) - elif operation == "~=": - scope._append_operation(key, ReplaceOperation(value, line_no=op_line_no)) - else: - print(f'Unexpected operation "{operation}" in scope "{scope}".') - assert False - - continue - - condition = statement.get("condition", None) - if condition: - Scope.FromDict(scope, file, statement.get("statements"), condition, scope.basedir) - - else_statements = statement.get("else_statements") - if else_statements: - Scope.FromDict(scope, file, else_statements, "else", scope.basedir) - continue - - loaded = statement.get("loaded") - if loaded: - scope._append_operation("_LOADED", UniqueAddOperation(loaded)) - continue - - option = statement.get("option", None) - if option: - scope._append_operation("_OPTION", UniqueAddOperation(option)) - continue - - included = statement.get("included", None) - if included: - included_location_start = included["locn_start"] - included = included["value"] - included_line_no = pp.lineno(included_location_start, project_file_content) - scope._append_operation( - "_INCLUDED", UniqueAddOperation(included, line_no=included_line_no) - ) - continue - - project_required_condition = statement.get("project_required_condition") - if project_required_condition: - scope._append_operation("_REQUIREMENTS", AddOperation(project_required_condition)) - - qt_no_make_tools = statement.get("qt_no_make_tools_arguments") - if qt_no_make_tools: - qt_no_make_tools = qt_no_make_tools.strip("()").strip() - qt_no_make_tools = qt_no_make_tools.split() - for entry in qt_no_make_tools: - scope._append_operation("_QT_NO_MAKE_TOOLS", AddOperation(entry)) - - scope.settle_condition() - - if scope.scope_debug: - print(f"..... [SCOPE_DEBUG]: Created scope {scope}:") - scope.dump(indent=1) - print("..... [SCOPE_DEBUG]: <>") - return scope - - def _append_operation(self, key: str, op: Operation) -> None: - if key in self._operations: - self._operations[key].append(op) - else: - self._operations[key] = [op] - - @property - def file(self) -> str: - return self._file or "" - - @property - def file_absolute_path(self) -> str: - return self._file_absolute_path or "" - - @property - def generated_cmake_lists_path(self) -> str: - assert self.basedir - return os.path.join(self.basedir, "CMakeLists.gen.txt") - - @property - def original_cmake_lists_path(self) -> str: - assert self.basedir - return os.path.join(self.basedir, "CMakeLists.txt") - - @property - def condition(self) -> str: - return self._condition - - @property - def total_condition(self) -> Optional[str]: - return self._total_condition - - @total_condition.setter - def total_condition(self, condition: str) -> None: - self._total_condition = condition - - def _add_child(self, scope: "Scope") -> None: - scope._parent = self - self._children.append(scope) - - @property - def children(self) -> List["Scope"]: - result = list(self._children) - for include_scope in self._included_children: - result += include_scope.children - return result - - def dump(self, *, indent: int = 0) -> None: - ind = spaces(indent) - print(f'{ind}Scope "{self}":') - if self.total_condition: - print(f"{ind} Total condition = {self.total_condition}") - print(f"{ind} Keys:") - keys = self._operations.keys() - if not keys: - print(f"{ind} -- NONE --") - else: - for k in sorted(keys): - print(f'{ind} {k} = "{self._operations.get(k, [])}"') - print(f"{ind} Children:") - if not self._children: - print(f"{ind} -- NONE --") - else: - for c in self._children: - c.dump(indent=indent + 1) - print(f"{ind} Includes:") - if not self._included_children: - print(f"{ind} -- NONE --") - else: - for c in self._included_children: - c.dump(indent=indent + 1) - - def dump_structure(self, *, structure_type: str = "ROOT", indent: int = 0) -> None: - print(f"{spaces(indent)}{structure_type}: {self}") - for i in self._included_children: - i.dump_structure(structure_type="INCL", indent=indent + 1) - for i in self._children: - i.dump_structure(structure_type="CHLD", indent=indent + 1) - - @property - def keys(self): - return self._operations.keys() - - @property - def visited_keys(self): - return self._visited_keys - - # Traverses a scope and its children, and collects operations - # that need to be processed for a certain key. - def _gather_operations_from_scope( - self, - operations_result: List[Dict[str, Any]], - current_scope: Scope, - op_key: str, - current_location: OperationLocation, - ): - for op in current_scope._operations.get(op_key, []): - new_op_location = current_location.clone_and_append( - current_scope._scope_id, op._line_no - ) - op_info: Dict[str, Any] = {} - op_info["op"] = op - op_info["scope"] = current_scope - op_info["location"] = new_op_location - operations_result.append(op_info) - - for included_child in current_scope._included_children: - new_scope_location = current_location.clone_and_append( - current_scope._scope_id, included_child._parent_include_line_no - ) - self._gather_operations_from_scope( - operations_result, included_child, op_key, new_scope_location - ) - - # Partially applies a scope argument to a given transformer. - @staticmethod - def _create_transformer_for_operation( - given_transformer: Optional[Callable[[Scope, List[str]], List[str]]], - transformer_scope: Scope, - ) -> Callable[[List[str]], List[str]]: - if given_transformer: - - def wrapped_transformer(values): - return given_transformer(transformer_scope, values) - - else: - - def wrapped_transformer(values): - return values - - return wrapped_transformer - - def _evalOps( - self, - key: str, - transformer: Optional[Callable[[Scope, List[str]], List[str]]], - result: List[str], - *, - inherit: bool = False, - ) -> List[str]: - self._visited_keys.add(key) - - # Inherit values from parent scope. - # This is a strange edge case which is wrong in principle, because - # .pro files are imperative and not declarative. Nevertheless - # this fixes certain mappings (e.g. for handling - # VERSIONTAGGING_SOURCES in src/corelib/global/global.pri). - if self._parent and inherit: - result = self._parent._evalOps(key, transformer, result) - - operations_to_run: List[Dict[str, Any]] = [] - starting_location = OperationLocation() - starting_scope = self - self._gather_operations_from_scope( - operations_to_run, starting_scope, key, starting_location - ) - - # Sorts the operations based on the location of each operation. Technically compares two - # lists of tuples. - operations_to_run = sorted(operations_to_run, key=lambda o: o["location"]) - - # Process the operations. - for op_info in operations_to_run: - op_transformer = self._create_transformer_for_operation(transformer, op_info["scope"]) - result = op_info["op"].process(key, result, op_transformer) - return result - - def get(self, key: str, *, ignore_includes: bool = False, inherit: bool = False) -> List[str]: - is_same_path = self.currentdir == self.basedir - if not is_same_path: - relative_path = posixpath.relpath(self.currentdir, self.basedir) - - if key == "QQC2_SOURCE_TREE": - qmake_or_cmake_conf_path = find_qmake_or_cmake_conf(os.path.abspath(self.currentdir)) - qmake_or_cmake_conf_dir_path = os.path.dirname(qmake_or_cmake_conf_path) - project_relative_path = os.path.relpath(qmake_or_cmake_conf_dir_path, self.currentdir) - return ["${CMAKE_CURRENT_SOURCE_DIR}/" + project_relative_path] - - if key == "QT_ARCH": - return ["${CMAKE_SYSTEM_PROCESSOR}"] - - if key == "_PRO_FILE_PWD_": - return ["${CMAKE_CURRENT_SOURCE_DIR}"] - if key == "PWD": - if is_same_path: - return ["${CMAKE_CURRENT_SOURCE_DIR}"] - else: - return [f"${{CMAKE_CURRENT_SOURCE_DIR}}/{relative_path}"] - if key == "OUT_PWD": - if is_same_path: - return ["${CMAKE_CURRENT_BINARY_DIR}"] - else: - return [f"${{CMAKE_CURRENT_BINARY_DIR}}/{relative_path}"] - - # Horrible hack. If we're returning the values for some key - # that looks like source or header files, make sure to use a - # map_files transformer, so that $$PWD values are evaluated - # in the transformer scope, otherwise relative paths will be - # broken. - # Looking at you qmltyperegistrar.pro. - eval_ops_transformer = None - if key.endswith("SOURCES") or key.endswith("HEADERS"): - - def file_transformer(scope, files): - return scope._map_files(files) - - eval_ops_transformer = file_transformer - return self._evalOps(key, eval_ops_transformer, [], inherit=inherit) - - def get_string(self, key: str, default: str = "", inherit: bool = False) -> str: - v = self.get(key, inherit=inherit) - if len(v) == 0: - return default - if len(v) > 1: - return " ".join(v) - return v[0] - - def _map_files( - self, files: List[str], *, use_vpath: bool = True, is_include: bool = False - ) -> List[str]: - - expanded_files = [] # type: List[str] - for f in files: - r = self._expand_value(f) - expanded_files += r - - mapped_files = list( - map(lambda f: map_to_file(f, self, is_include=is_include), expanded_files) - ) - - if use_vpath: - result = list( - map( - lambda f: handle_vpath(f, self.basedir, self.get("VPATH", inherit=True)), - mapped_files, - ) - ) - else: - result = mapped_files - - # strip ${CMAKE_CURRENT_SOURCE_DIR}: - result = list( - map(lambda f: f[28:] if f.startswith("${CMAKE_CURRENT_SOURCE_DIR}/") else f, result) - ) - - # strip leading ./: - result = list(map(lambda f: trim_leading_dot(f), result)) - - return result - - def get_files( - self, key: str, *, use_vpath: bool = False, is_include: bool = False - ) -> List[str]: - def transformer(scope, files): - return scope._map_files(files, use_vpath=use_vpath, is_include=is_include) - - return list(self._evalOps(key, transformer, [])) - - @staticmethod - def _replace_env_var_value(value: Any) -> Any: - if not isinstance(value, str): - return value - - pattern = re.compile(r"\$\$\(([A-Za-z_][A-Za-z0-9_]*)\)") - match = re.search(pattern, value) - if match: - value = re.sub(pattern, r"$ENV{\1}", value) - - return value - - def _expand_value(self, value: str) -> List[str]: - result = value - pattern = re.compile(r"\$\$\{?([A-Za-z_][A-Za-z0-9_]*)\}?") - match = re.search(pattern, result) - while match: - old_result = result - match_group_0 = match.group(0) - if match_group_0 == value: - get_result = self.get(match.group(1), inherit=True) - if len(get_result) == 1: - result = get_result[0] - result = self._replace_env_var_value(result) - else: - # Recursively expand each value from the result list - # returned from self.get(). - result_list: List[str] = [] - for entry_value in get_result: - result_list += self._expand_value(self._replace_env_var_value(entry_value)) - return result_list - else: - replacement = self.get(match.group(1), inherit=True) - replacement_str = replacement[0] if replacement else "" - if replacement_str == value: - # we have recursed - replacement_str = "" - result = result[: match.start()] + replacement_str + result[match.end() :] - result = self._replace_env_var_value(result) - - if result == old_result: - return [result] # Do not go into infinite loop - - match = re.search(pattern, result) - - result = self._replace_env_var_value(result) - return [result] - - def expand(self, key: str) -> List[str]: - value = self.get(key) - result: List[str] = [] - assert isinstance(value, list) - for v in value: - result += self._expand_value(v) - return result - - def expandString(self, key: str) -> str: - result = self._expand_value(self.get_string(key)) - assert len(result) == 1 - return result[0] - - def _get_operation_at_index(self, key, index): - return self._operations[key][index] - - @property - def TEMPLATE(self) -> str: - return self.get_string("TEMPLATE", "app") - - def _rawTemplate(self) -> str: - return self.get_string("TEMPLATE") - - @property - def TARGET(self) -> str: - target = self.expandString("TARGET") or os.path.splitext(os.path.basename(self.file))[0] - return re.sub(r"\.\./", "", target) - - @property - def TARGET_ORIGINAL(self) -> str: - return self.expandString("TARGET") or os.path.splitext(os.path.basename(self.file))[0] - - @property - def _INCLUDED(self) -> List[str]: - return self.get("_INCLUDED") - - -# Given "if(a|b):c" returns "(a|b):c". Uses pyparsing to keep the parentheses -# balanced. -def unwrap_if(input_string): - # Compute the grammar only once. - if not hasattr(unwrap_if, "if_grammar"): - - def handle_expr_with_parentheses(s, l_unused, t): - # The following expression unwraps the condition via the - # additional info set by originalTextFor, thus returning the - # condition without parentheses. - condition_without_parentheses = s[t._original_start + 1 : t._original_end - 1] - - # Re-add the parentheses, but with spaces in-between. This - # fixes map_condition -> map_platform to apply properly. - condition_with_parentheses = "( " + condition_without_parentheses + " )" - return condition_with_parentheses - - expr_with_parentheses = pp.originalTextFor(pp.nestedExpr()) - expr_with_parentheses.setParseAction(handle_expr_with_parentheses) - - if_keyword = pp.Suppress(pp.Keyword("if")) - unwrap_if.if_grammar = if_keyword + expr_with_parentheses - - output_string = unwrap_if.if_grammar.transformString(input_string) - return output_string - - -def map_condition(condition: str) -> str: - # Some hardcoded cases that are too bothersome to generalize. - condition = re.sub( - r"qtConfig\(opengles\.\)", - r"(QT_FEATURE_opengles2 OR QT_FEATURE_opengles3 OR QT_FEATURE_opengles31 OR QT_FEATURE_opengles32)", - condition, - ) - condition = re.sub( - r"qtConfig\(opengl\(es1\|es2\)\?\)", - r"(QT_FEATURE_opengl OR QT_FEATURE_opengles2 OR QT_FEATURE_opengles3)", - condition, - ) - condition = re.sub(r"qtConfig\(opengl\.\*\)", r"QT_FEATURE_opengl", condition) - condition = re.sub(r"^win\*$", r"win", condition) - condition = re.sub(r"^no-png$", r"NOT QT_FEATURE_png", condition) - condition = re.sub(r"contains\(CONFIG, static\)", r"NOT QT_BUILD_SHARED_LIBS", condition) - condition = re.sub(r"contains\(QT_CONFIG,\w*shared\)", r"QT_BUILD_SHARED_LIBS", condition) - condition = re.sub(r"CONFIG\(osx\)", r"MACOS", condition) - - def gcc_version_handler(match_obj: Match): - operator = match_obj.group(1) - version_type = match_obj.group(2) - if operator == "equals": - operator = "STREQUAL" - elif operator == "greaterThan": - operator = "STRGREATER" - elif operator == "lessThan": - operator = "STRLESS" - - version = match_obj.group(3) - return f"(QT_COMPILER_VERSION_{version_type} {operator} {version})" - - # TODO: Possibly fix for other compilers. - pattern = r"(equals|greaterThan|lessThan)\(QT_GCC_([A-Z]+)_VERSION,[ ]*([0-9]+)\)" - condition = re.sub(pattern, gcc_version_handler, condition) - - def windows_sdk_version_handler(match_obj: Match): - operator = match_obj.group(1) - if operator == "equals": - operator = "STREQUAL" - elif operator == "greaterThan": - operator = "STRGREATER" - elif operator == "lessThan": - operator = "STRLESS" - - version = match_obj.group(2) - return f"(QT_WINDOWS_SDK_VERSION {operator} {version})" - - pattern = r"(equals|greaterThan|lessThan)\(WINDOWS_SDK_VERSION,[ ]*([0-9]+)\)" - condition = re.sub(pattern, windows_sdk_version_handler, condition) - - def qt_version_handler(match_obj: Match): - operator = match_obj.group(1) - if operator == "equals": - operator = "EQUAL" - elif operator == "greaterThan": - operator = "GREATER" - elif operator == "lessThan": - operator = "LESS" - - operator_prefix = "VERSION_" - version_variable = "QT_VERSION" - version_flavor = match_obj.group(2) - if version_flavor: - version_variable += "_" + version_flavor[:-1] - operator_prefix = "" - - version = match_obj.group(3) - return f"({version_variable} {operator_prefix}{operator} {version})" - - pattern = r"(equals|greaterThan|lessThan)\(QT_(MAJOR_|MINOR_|PATCH_)?VERSION,[ ]*([0-9.]+)\)" - condition = re.sub(pattern, qt_version_handler, condition) - - # Generic lessThan|equals|lessThan() - - def generic_version_handler(match_obj: Match): - operator = match_obj.group(1) - if operator == "equals": - operator = "EQUAL" - elif operator == "greaterThan": - operator = "GREATER" - elif operator == "lessThan": - operator = "LESS" - - variable = match_obj.group(2) - version = match_obj.group(3) - return f"({variable} {operator} {version})" - - pattern = r"(equals|greaterThan|lessThan)\(([^,]+?),[ ]*([0-9]+)\)" - condition = re.sub(pattern, generic_version_handler, condition) - - # Handle if(...) conditions. - condition = unwrap_if(condition) - - condition = re.sub(r"\bisEmpty\s*\((.*?)\)", r"\1_ISEMPTY", condition) - condition = re.sub( - r"\bcontains\s*\(\s*(?:QT_)?CONFIG\s*,\s*c\+\+(\d+)\)", - r"cxx_std_\1 IN_LIST CMAKE_CXX_COMPILE_FEATURES", - condition, - ) - condition = re.sub(r'\bcontains\s*\((.*?),\s*"?(.*?)"?\)', r"\1___contains___\2", condition) - condition = re.sub(r'\bequals\s*\((.*?),\s*"?(.*?)"?\)', r"\1___equals___\2", condition) - condition = re.sub(r'\bisEqual\s*\((.*?),\s*"?(.*?)"?\)', r"\1___equals___\2", condition) - condition = re.sub(r"\s*==\s*", "___STREQUAL___", condition) - condition = re.sub(r"\bexists\s*\((.*?)\)", r"EXISTS \1", condition) - - # checking mkspec, predating gcc scope in qmake, will then be replaced by platform_mapping in helper.py - condition = condition.replace("*-g++*", "GCC") - condition = condition.replace("*g++*", "GCC") - condition = condition.replace("aix-g++*", "AIX") - condition = condition.replace("*-icc*", "ICC") - condition = condition.replace("*-clang*", "CLANG") - condition = condition.replace("*-llvm", "CLANG") - condition = condition.replace("win32-*", "WIN32") - - pattern = r"CONFIG\((debug|release),debug\|release\)" - match_result = re.match(pattern, condition) - if match_result: - build_type = match_result.group(1) - if build_type == "debug": - build_type = "Debug" - elif build_type == "release": - build_type = "Release" - condition = re.sub(pattern, f"(CMAKE_BUILD_TYPE STREQUAL {build_type})", condition) - - condition = condition.replace("*", "_x_") - condition = condition.replace(".$$", "__ss_") - condition = condition.replace("$$", "_ss_") - - condition = condition.replace("!", "NOT ") - condition = condition.replace("&&", " AND ") - condition = condition.replace("|", " OR ") - - # new conditions added by the android multi arch qmake build - condition = re.sub(r"(^| )x86((?=[^\w])|$)", "TEST_architecture_arch STREQUAL i386", condition) - condition = re.sub(r"(^| )x86_64", " TEST_architecture_arch STREQUAL x86_64", condition) - condition = re.sub(r"(^| )arm64-v8a", "TEST_architecture_arch STREQUAL arm64", condition) - condition = re.sub(r"(^| )armeabi-v7a", "TEST_architecture_arch STREQUAL arm", condition) - - # some defines replacements - condition = re.sub(r"DEFINES___contains___QT_NO_CURSOR", r"(NOT QT_FEATURE_cursor)", condition) - condition = re.sub( - r"DEFINES___contains___QT_NO_TRANSLATION", r"(NOT QT_FEATURE_translation)", condition - ) - condition = re.sub(r"styles___contains___fusion", r"QT_FEATURE_style_fusion", condition) - condition = re.sub(r"CONFIG___contains___largefile", r"QT_FEATURE_largefile", condition) - - condition = condition.replace("cross_compile", "CMAKE_CROSSCOMPILING") - - 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: - if feature.group(1) == "qtHaveModule": - part = f"TARGET {map_qt_library(feature.group(2))}" - else: - feature_name = featureName(feature.group(2)) - if ( - feature_name.startswith("system_") - and is_known_3rd_party_library(feature_name[7:]) - and not feature_name.startswith("system_jpeg") - and not feature_name.startswith("system_zlib") - and not feature_name.startswith("system_tiff") - and not feature_name.startswith("system_assimp") - and not feature_name.startswith("system_doubleconversion") - and not feature_name.startswith("system_sqlite") - and not feature_name.startswith("system_hunspell") - and not feature_name.startswith("system_libb2") - and not feature_name.startswith("system_webp") - ): - part = "ON" - elif feature == "dlopen": - part = "ON" - else: - part = "QT_FEATURE_" + feature_name - else: - part = map_platform(part) - - part = part.replace("true", "ON") - part = part.replace("false", "OFF") - cmake_condition += " " + part - return cmake_condition.strip() - - -_path_replacements = { - "$$[QT_INSTALL_PREFIX]": "${INSTALL_DIRECTORY}", - "$$[QT_INSTALL_EXAMPLES]": "${INSTALL_EXAMPLESDIR}", - "$$[QT_INSTALL_TESTS]": "${INSTALL_TESTSDIR}", - "$$OUT_PWD": "${CMAKE_CURRENT_BINARY_DIR}", - "$$MODULE_BASE_OUTDIR": "${QT_BUILD_DIR}", -} - - -def replace_path_constants(path: str, scope: Scope) -> str: - """Clean up DESTDIR and target.path""" - if path.startswith("./"): - path = f"${{CMAKE_CURRENT_BINARY_DIR}}/{path[2:]}" - elif path.startswith("../"): - path = f"${{CMAKE_CURRENT_BINARY_DIR}}/{path}" - for original, replacement in _path_replacements.items(): - path = path.replace(original, replacement) - path = path.replace("$$TARGET", scope.TARGET) - return path - - -def handle_subdir( - scope: Scope, - cm_fh: IO[str], - *, - indent: int = 0, - is_example: bool = False, - is_user_project: bool = False, -) -> None: - - # Global nested dictionary that will contain sub_dir assignments and their conditions. - # Declared as a global in order not to pollute the nested function signatures with giant - # type hints. - sub_dirs: Dict[str, Dict[str, Set[FrozenSet[str]]]] = {} - - # Collects assignment conditions into global sub_dirs dict. - def collect_subdir_info(sub_dir_assignment: str, *, current_conditions: FrozenSet[str] = None): - subtraction = sub_dir_assignment.startswith("-") - if subtraction: - subdir_name = sub_dir_assignment[1:] - else: - subdir_name = sub_dir_assignment - if subdir_name not in sub_dirs: - sub_dirs[subdir_name] = {} - additions = sub_dirs[subdir_name].get("additions", set()) - subtractions = sub_dirs[subdir_name].get("subtractions", set()) - if current_conditions: - if subtraction: - subtractions.add(current_conditions) - else: - additions.add(current_conditions) - if additions: - sub_dirs[subdir_name]["additions"] = additions - if subtractions: - sub_dirs[subdir_name]["subtractions"] = subtractions - - # Recursive helper that collects subdir info for given scope, - # and the children of the given scope. - def handle_subdir_helper( - scope: Scope, - cm_fh: IO[str], - *, - indent: int = 0, - current_conditions: FrozenSet[str] = frozenset(), - is_example: bool = False, - ): - for sd in scope.get_files("SUBDIRS"): - # Collect info about conditions and SUBDIR assignments in the - # current scope. - if os.path.isdir(sd) or sd.startswith("-"): - collect_subdir_info(sd, current_conditions=current_conditions) - # For the file case, directly write into the file handle. - elif os.path.isfile(sd): - # Handle cases with SUBDIRS += Foo/bar/z.pro. We want to be able - # to generate add_subdirectory(Foo/bar) instead of parsing the full - # .pro file in the current CMakeLists.txt. This causes issues - # with relative paths in certain projects otherwise. - dirname = os.path.dirname(sd) - if dirname: - collect_subdir_info(dirname, current_conditions=current_conditions) - else: - subdir_result, project_file_content = parseProFile(sd, debug=False) - subdir_scope = Scope.FromDict( - scope, - sd, - subdir_result.asDict().get("statements"), - "", - scope.basedir, - project_file_content=project_file_content, - ) - - do_include(subdir_scope) - cmakeify_scope( - subdir_scope, - cm_fh, - indent=indent, - is_example=is_example, - is_user_project=is_user_project, - ) - else: - print(f" XXXX: SUBDIR {sd} in {scope}: Not found.") - - # Collect info about conditions and SUBDIR assignments in child - # scopes, aka recursively call the same function, but with an - # updated current_conditions frozen set. - for c in scope.children: - # Use total_condition for 'else' conditions, otherwise just use the regular value to - # simplify the logic. - child_conditions = current_conditions - child_condition = c.total_condition if c.condition == "else" else c.condition - if child_condition: - child_conditions = frozenset((*child_conditions, child_condition)) - - handle_subdir_helper( - c, - cm_fh, - indent=indent + 1, - current_conditions=child_conditions, - is_example=is_example, - ) - - def group_and_print_sub_dirs(scope: Scope, indent: int = 0) -> None: - # Simplify conditions, and group - # subdirectories with the same conditions. - grouped_sub_dirs: Dict[str, List[str]] = {} - - # Wraps each element in the given interable with parentheses, - # to make sure boolean simplification happens correctly. - def wrap_in_parenthesis(iterable): - return [f"({c})" for c in iterable] - - def join_all_conditions(set_of_alternatives): - # Elements within one frozen set represent one single - # alternative whose pieces are ANDed together. - # This is repeated for each alternative that would - # enable a subdir, and are thus ORed together. - final_str = "" - if set_of_alternatives: - wrapped_set_of_alternatives = [ - wrap_in_parenthesis(alternative) for alternative in set_of_alternatives - ] - alternatives = [ - f'({" AND ".join(alternative)})' for alternative in wrapped_set_of_alternatives - ] - final_str = " OR ".join(sorted(alternatives)) - return final_str - - for subdir_name in sub_dirs: - additions = sub_dirs[subdir_name].get("additions", set()) - subtractions = sub_dirs[subdir_name].get("subtractions", set()) - - # An empty condition key represents the group of sub dirs - # that should be added unconditionally. - condition_key = "" - if additions or subtractions: - addition_str = join_all_conditions(additions) - if addition_str: - addition_str = f"({addition_str})" - subtraction_str = join_all_conditions(subtractions) - if subtraction_str: - subtraction_str = f"NOT ({subtraction_str})" - - condition_str = addition_str - if condition_str and subtraction_str: - condition_str += " AND " - condition_str += subtraction_str - if not condition_str.rstrip("()").strip(): - continue - condition_simplified = simplify_condition(condition_str) - condition_key = condition_simplified - - sub_dir_list_by_key: List[str] = grouped_sub_dirs.get(condition_key, []) - sub_dir_list_by_key.append(subdir_name) - grouped_sub_dirs[condition_key] = sub_dir_list_by_key - - # Print any requires() blocks. - cm_fh.write(expand_project_requirements(scope, skip_message=True)) - - # Print the groups. - ind = spaces(indent) - for condition_key in grouped_sub_dirs: - cond_ind = ind - if condition_key: - cm_fh.write(f"{ind}if({condition_key})\n") - cond_ind += " " - - sub_dir_list_by_key = grouped_sub_dirs.get(condition_key, []) - for subdir_name in sub_dir_list_by_key: - cm_fh.write(f"{cond_ind}add_subdirectory({subdir_name})\n") - if condition_key: - cm_fh.write(f"{ind}endif()\n") - - # A set of conditions which will be ANDed together. The set is recreated with more conditions - # as the scope deepens. - current_conditions: FrozenSet[str] = frozenset() - - # Compute the total condition for scopes. Needed for scopes that - # have 'else' as a condition. - recursive_evaluate_scope(scope) - - # Do the work. - handle_subdir_helper( - scope, cm_fh, indent=indent, current_conditions=current_conditions, is_example=is_example - ) - - # Make sure to exclude targets within subdirectories first. - qt_no_make_tools = scope.get("_QT_NO_MAKE_TOOLS") - if qt_no_make_tools: - ind = spaces(indent + 1) - directories_string = "" - for directory in qt_no_make_tools: - directories_string += f"{ind}{directory}\n" - cm_fh.write( - f"\nqt_exclude_tool_directories_from_default_target(\n{directories_string})\n\n" - ) - - # Then write the subdirectories. - group_and_print_sub_dirs(scope, indent=indent) - - -def sort_sources(sources: List[str]) -> List[str]: - to_sort = {} # type: Dict[str, List[str]] - for s in sources: - if s is None: - continue - - path = os.path.dirname(s) - base = os.path.splitext(os.path.basename(s))[0] - if base.endswith("_p"): - base = base[:-2] - sort_name = posixpath.join(path, 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 _map_libraries_to_cmake( - libraries: List[str], known_libraries: Set[str], is_example: bool = False -) -> List[str]: - result = [] # type: List[str] - is_framework = False - - for lib in libraries: - if lib == "-framework": - is_framework = True - continue - if is_framework: - if is_example: - lib = f'"-framework {lib}"' - else: - lib = f"${{FW{lib}}}" - if lib.startswith("-l"): - lib = lib[2:] - - if lib.startswith("-"): - lib = f"# Remove: {lib[1:]}" - else: - lib = map_3rd_party_library(lib) - - if not lib or lib in result or lib in known_libraries: - continue - - result.append(lib) - is_framework = False - - return result - - -def extract_cmake_libraries( - scope: Scope, *, known_libraries: Optional[Set[str]] = None, is_example: bool = False -) -> Tuple[List[str], List[str]]: - if known_libraries is None: - known_libraries = set() - public_dependencies = [] # type: List[str] - private_dependencies = [] # type: List[str] - - for key in ["QMAKE_USE", "LIBS"]: - public_dependencies += scope.expand(key) - for key in ["QMAKE_USE_PRIVATE", "QMAKE_USE_FOR_PRIVATE", "LIBS_PRIVATE"]: - private_dependencies += scope.expand(key) - - for key in ["QT_FOR_PRIVATE", "QT_PRIVATE"]: - private_dependencies += [map_qt_library(q) for q in scope.expand(key)] - - for key in ["QT"]: - for lib in scope.expand(key): - mapped_lib = map_qt_library(lib) - public_dependencies.append(mapped_lib) - - return ( - _map_libraries_to_cmake(public_dependencies, known_libraries, is_example=is_example), - _map_libraries_to_cmake(private_dependencies, known_libraries, is_example=is_example), - ) - - -def write_header(cm_fh: IO[str], name: str, typename: str, *, indent: int = 0): - ind = spaces(indent) - comment_line = "#" * 69 - cm_fh.write(f"{ind}{comment_line}\n") - cm_fh.write(f"{ind}## {name} {typename}:\n") - cm_fh.write(f"{ind}{comment_line}\n\n") - - -def write_scope_header(cm_fh: IO[str], *, indent: int = 0): - ind = spaces(indent) - comment_line = "#" * 69 - cm_fh.write(f"\n{ind}## Scopes:\n") - cm_fh.write(f"{ind}{comment_line}\n") - - -def write_list( - cm_fh: IO[str], - entries: List[str], - cmake_parameter: str, - indent: int = 0, - *, - header: str = "", - footer: str = "", - prefix: str = "", -): - if not entries: - return - - ind = spaces(indent) - extra_indent = "" - - if header: - cm_fh.write(f"{ind}{header}") - extra_indent += " " - if cmake_parameter: - cm_fh.write(f"{ind}{extra_indent}{cmake_parameter}\n") - extra_indent += " " - for s in sort_sources(entries): - cm_fh.write(f"{ind}{extra_indent}{prefix}{s}\n") - if footer: - cm_fh.write(f"{ind}{footer}\n") - - -def write_source_file_list( - cm_fh: IO[str], - scope, - cmake_parameter: str, - keys: List[str], - indent: int = 0, - *, - header: str = "", - footer: str = "", -): - # collect sources - sources: List[str] = [] - for key in keys: - sources += scope.get_files(key, use_vpath=True) - - # Remove duplicates, like in the case when NO_PCH_SOURCES ends up - # adding the file to SOURCES, but SOURCES might have already - # contained it before. Preserves order in Python 3.7+ because - # dict keys are ordered. - sources = list(dict.fromkeys(sources)) - - write_list(cm_fh, sources, cmake_parameter, indent, header=header, footer=footer) - - -def write_all_source_file_lists( - cm_fh: IO[str], - scope: Scope, - header: str, - *, - indent: int = 0, - footer: str = "", - extra_keys: Optional[List[str]] = None, -): - if extra_keys is None: - extra_keys = [] - write_source_file_list( - cm_fh, - scope, - header, - ["SOURCES", "HEADERS", "OBJECTIVE_SOURCES", "OBJECTIVE_HEADERS", "NO_PCH_SOURCES", "FORMS"] - + extra_keys, - indent, - footer=footer, - ) - - -def write_defines( - cm_fh: IO[str], scope: Scope, cmake_parameter: str, *, indent: int = 0, footer: str = "" -): - defines = scope.expand("DEFINES") - defines += [d[2:] for d in scope.expand("QMAKE_CXXFLAGS") if d.startswith("-D")] - defines = [ - d.replace('=\\\\\\"$$PWD/\\\\\\"', '="${CMAKE_CURRENT_SOURCE_DIR}/"') for d in defines - ] - - # Handle LIBS_SUFFIX='\\"_$${QT_ARCH}.so\\"'. - # The escaping of backslashes is still needed even if it's a raw - # string, because backslashes have a special meaning for regular - # expressions (escape next char). So we actually expect to match - # 2 backslashes in the input string. - pattern = r"""([^ ]+)='\\\\"([^ ]*)\\\\"'""" - - # Replace with regular quotes, CMake will escape the quotes when - # passing the define to the compiler. - replacement = r'\1="\2"' - defines = [re.sub(pattern, replacement, d) for d in defines] - - if "qml_debug" in scope.get("CONFIG"): - defines.append("QT_QML_DEBUG") - - write_list(cm_fh, defines, cmake_parameter, indent, footer=footer) - - -def write_3rd_party_defines( - cm_fh: IO[str], scope: Scope, cmake_parameter: str, *, indent: int = 0, footer: str = "" -): - defines = scope.expand("MODULE_DEFINES") - write_list(cm_fh, defines, cmake_parameter, indent, footer=footer) - - -def get_include_paths_helper(scope: Scope, include_var_name: str) -> List[str]: - includes = [i.rstrip("/") or ("/") for i in scope.get_files(include_var_name)] - return includes - - -def write_include_paths( - cm_fh: IO[str], scope: Scope, cmake_parameter: str, *, indent: int = 0, footer: str = "" -): - includes = get_include_paths_helper(scope, "INCLUDEPATH") - write_list(cm_fh, includes, cmake_parameter, indent, footer=footer) - - -def write_3rd_party_include_paths( - cm_fh: IO[str], scope: Scope, cmake_parameter: str, *, indent: int = 0, footer: str = "" -): - # Used in qt_helper_lib.prf. - includes = get_include_paths_helper(scope, "MODULE_INCLUDEPATH") - - # Wrap the includes in BUILD_INTERFACE generator expression, because - # the include paths point to a source dir, and CMake will error out - # when trying to create consumable exported targets. - processed_includes = [] - for i in includes: - # CMake generator expressions don't seem to like relative paths. - # Make them absolute relative to the source dir. - if is_path_relative_ish(i): - i = f"${{CMAKE_CURRENT_SOURCE_DIR}}/{i}" - i = f"$" - processed_includes.append(i) - - write_list(cm_fh, processed_includes, cmake_parameter, indent, footer=footer) - - -def write_compile_options( - cm_fh: IO[str], scope: Scope, cmake_parameter: str, *, indent: int = 0, footer: str = "" -): - compile_options = [d for d in scope.expand("QMAKE_CXXFLAGS") if not d.startswith("-D")] - - write_list(cm_fh, compile_options, cmake_parameter, indent, footer=footer) - - -# Return True if given scope belongs to a public module. -# First, traverse the parent/child hierarchy. Then, traverse the include hierarchy. -def recursive_is_public_module(scope: Scope): - if scope.is_public_module: - return True - if scope.parent: - return recursive_is_public_module(scope.parent) - if scope.including_scope: - return recursive_is_public_module(scope.including_scope) - return False - - -def write_library_section( - cm_fh: IO[str], scope: Scope, *, indent: int = 0, known_libraries: Optional[Set[str]] = None -): - if known_libraries is None: - known_libraries = set() - public_dependencies, private_dependencies = extract_cmake_libraries( - scope, known_libraries=known_libraries - ) - - is_public_module = recursive_is_public_module(scope) - - # When handling module dependencies, handle QT += foo-private magic. - # This implies: - # target_link_libraries(Module PUBLIC Qt::Foo) - # target_link_libraries(Module PRIVATE Qt::FooPrivate) - # target_link_libraries(ModulePrivate INTERFACE Qt::FooPrivate) - if is_public_module: - private_module_dep_pattern = re.compile(r"^(Qt::(.+))Private$") - - public_module_public_deps = [] - public_module_private_deps = private_dependencies - private_module_interface_deps = [] - - for dep in public_dependencies: - match = re.match(private_module_dep_pattern, dep) - if match: - if match[1] not in public_module_public_deps: - public_module_public_deps.append(match[1]) - private_module_interface_deps.append(dep) - if dep not in public_module_private_deps: - public_module_private_deps.append(dep) - else: - if dep not in public_module_public_deps: - public_module_public_deps.append(dep) - - private_module_interface_deps.extend( - [map_qt_library(q) for q in scope.expand("QT_FOR_PRIVATE")] - ) - private_module_interface_deps.extend( - _map_libraries_to_cmake(scope.expand("QMAKE_USE_FOR_PRIVATE"), known_libraries) - ) - - write_list(cm_fh, public_module_private_deps, "LIBRARIES", indent + 1) - write_list(cm_fh, public_module_public_deps, "PUBLIC_LIBRARIES", indent + 1) - write_list(cm_fh, private_module_interface_deps, "PRIVATE_MODULE_INTERFACE", indent + 1) - else: - write_list(cm_fh, private_dependencies, "LIBRARIES", indent + 1) - write_list(cm_fh, public_dependencies, "PUBLIC_LIBRARIES", indent + 1) - - -def write_autogen_section(cm_fh: IO[str], scope: Scope, *, indent: int = 0): - forms = scope.get_files("FORMS") - if forms: - write_list(cm_fh, ["uic"], "ENABLE_AUTOGEN_TOOLS", indent) - - -def write_sources_section( - cm_fh: IO[str], scope: Scope, *, indent: int = 0, known_libraries: Optional[Set[str]] = None -): - if known_libraries is None: - known_libraries = set() - ind = spaces(indent) - - # mark RESOURCES as visited: - scope.get("RESOURCES") - - write_all_source_file_lists(cm_fh, scope, "SOURCES", indent=indent + 1) - - write_source_file_list(cm_fh, scope, "DBUS_ADAPTOR_SOURCES", ["DBUS_ADAPTORS"], indent + 1) - dbus_adaptor_flags = scope.expand("QDBUSXML2CPP_ADAPTOR_HEADER_FLAGS") - if dbus_adaptor_flags: - dbus_adaptor_flags_line = '" "'.join(dbus_adaptor_flags) - cm_fh.write(f"{ind} DBUS_ADAPTOR_FLAGS\n") - cm_fh.write(f'{ind} "{dbus_adaptor_flags_line}"\n') - - write_source_file_list(cm_fh, scope, "DBUS_INTERFACE_SOURCES", ["DBUS_INTERFACES"], indent + 1) - dbus_interface_flags = scope.expand("QDBUSXML2CPP_INTERFACE_HEADER_FLAGS") - if dbus_interface_flags: - dbus_interface_flags_line = '" "'.join(dbus_interface_flags) - cm_fh.write(f"{ind} DBUS_INTERFACE_FLAGS\n") - cm_fh.write(f'{ind} "{dbus_interface_flags_line}"\n') - - write_defines(cm_fh, scope, "DEFINES", indent=indent + 1) - - write_3rd_party_defines(cm_fh, scope, "PUBLIC_DEFINES", indent=indent + 1) - - write_include_paths(cm_fh, scope, "INCLUDE_DIRECTORIES", indent=indent + 1) - - write_3rd_party_include_paths(cm_fh, scope, "PUBLIC_INCLUDE_DIRECTORIES", indent=indent + 1) - - write_library_section(cm_fh, scope, indent=indent, known_libraries=known_libraries) - - write_compile_options(cm_fh, scope, "COMPILE_OPTIONS", indent=indent + 1) - - write_autogen_section(cm_fh, scope, indent=indent + 1) - - link_options = scope.get("QMAKE_LFLAGS") - if link_options: - cm_fh.write(f"{ind} LINK_OPTIONS\n") - for lo in link_options: - cm_fh.write(f'{ind} "{lo}"\n') - - moc_options = scope.get("QMAKE_MOC_OPTIONS") - if moc_options: - cm_fh.write(f"{ind} MOC_OPTIONS\n") - for mo in moc_options: - cm_fh.write(f'{ind} "{mo}"\n') - - precompiled_header = scope.get("PRECOMPILED_HEADER") - if precompiled_header: - cm_fh.write(f"{ind} PRECOMPILED_HEADER\n") - for header in precompiled_header: - cm_fh.write(f'{ind} "{header}"\n') - - no_pch_sources = scope.get("NO_PCH_SOURCES") - if no_pch_sources: - cm_fh.write(f"{ind} NO_PCH_SOURCES\n") - for source in no_pch_sources: - cm_fh.write(f'{ind} "{source}"\n') - - -def is_simple_condition(condition: str) -> bool: - return " " not in condition or (condition.startswith("NOT ") and " " not in condition[4:]) - - -def write_ignored_keys(scope: Scope, indent: str) -> str: - result = "" - ignored_keys = scope.keys - scope.visited_keys - for k in sorted(ignored_keys): - if k in { - "_INCLUDED", - "_LOADED", - "TARGET", - "QMAKE_DOCS", - "QT_SOURCE_TREE", - "QT_BUILD_TREE", - "QTRO_SOURCE_TREE", - "TRACEPOINT_PROVIDER", - "PLUGIN_TYPE", - "PLUGIN_CLASS_NAME", - "CLASS_NAME", - "MODULE_PLUGIN_TYPES", - }: - # All these keys are actually reported already - continue - values = scope.get(k) - value_string = "" if not values else '"' + '" "'.join(scope.get(k)) + '"' - result += f"{indent}# {k} = {value_string}\n" - - if result: - result = f"\n#### Keys ignored in scope {scope}:\n{result}" - - return result - - -def recursive_evaluate_scope( - scope: Scope, parent_condition: str = "", previous_condition: str = "" -) -> str: - current_condition = scope.condition - total_condition = current_condition - if total_condition == "else": - assert previous_condition, f"Else branch without previous condition in: {scope.file}" - total_condition = f"NOT ({previous_condition})" - if parent_condition: - if not total_condition: - total_condition = parent_condition - else: - total_condition = f"({parent_condition}) AND ({total_condition})" - - scope.total_condition = simplify_condition(total_condition) - - prev_condition = "" - for c in scope.children: - prev_condition = recursive_evaluate_scope(c, total_condition, prev_condition) - - return current_condition - - -def map_to_cmake_condition(condition: str = "") -> str: - condition = condition.replace("QTDIR_build", "QT_BUILDING_QT") - condition = re.sub( - r"\bQT_ARCH___equals___([a-zA-Z_0-9]*)", - r'(TEST_architecture_arch STREQUAL "\1")', - condition or "", - ) - condition = re.sub( - r"\bQT_ARCH___contains___([a-zA-Z_0-9]*)", - r'(TEST_architecture_arch STREQUAL "\1")', - condition or "", - ) - condition = condition.replace("QT___contains___opengl", "QT_FEATURE_opengl") - condition = condition.replace("QT___contains___widgets", "QT_FEATURE_widgets") - condition = condition.replace( - "DEFINES___contains___QT_NO_PRINTER", "(QT_FEATURE_printer EQUAL FALSE)" - ) - return condition - - -resource_file_expansion_counter = 0 - - -def expand_resource_glob(cm_fh: IO[str], expression: str) -> str: - global resource_file_expansion_counter - r = expression.replace('"', "") - - cm_fh.write( - dedent( - f""" - file(GLOB resource_glob_{resource_file_expansion_counter} RELATIVE "${{CMAKE_CURRENT_SOURCE_DIR}}" "{r}") - foreach(file IN LISTS resource_glob_{resource_file_expansion_counter}) - set_source_files_properties("${{CMAKE_CURRENT_SOURCE_DIR}}/${{file}}" PROPERTIES QT_RESOURCE_ALIAS "${{file}}") - endforeach() - """ - ) - ) - - expanded_var = f"${{resource_glob_{resource_file_expansion_counter}}}" - resource_file_expansion_counter += 1 - return expanded_var - - -def extract_resources( - target: str, - scope: Scope, -) -> Tuple[List[QtResource], List[str]]: - """Read the resources of the given scope. - Return a tuple: - - list of QtResource objects - - list of standalone sources files that are marked as QTQUICK_COMPILER_SKIPPED_RESOURCES""" - - resource_infos: List[QtResource] = [] - skipped_standalone_files: List[str] = [] - - resources = scope.get_files("RESOURCES") - qtquickcompiler_skipped = scope.get_files("QTQUICK_COMPILER_SKIPPED_RESOURCES") - if resources: - standalone_files: List[str] = [] - for r in resources: - skip_qtquick_compiler = r in qtquickcompiler_skipped - if r.endswith(".qrc"): - if "${CMAKE_CURRENT_BINARY_DIR}" in r: - resource_infos.append( - QtResource( - name=r, generated=True, skip_qtquick_compiler=skip_qtquick_compiler - ) - ) - continue - resource_infos += read_qrc_file( - r, - scope.basedir, - scope.file_absolute_path, - skip_qtquick_compiler=skip_qtquick_compiler, - ) - else: - immediate_files = {f: "" for f in scope.get_files(f"{r}.files")} - if immediate_files: - immediate_files_filtered = [] - for f in immediate_files: - immediate_files_filtered.append(f) - immediate_files = {f: "" for f in immediate_files_filtered} - scope_prefix = scope.get(f"{r}.prefix") - if scope_prefix: - immediate_prefix = scope_prefix[0] - else: - immediate_prefix = "/" - immediate_base_list = scope.get(f"{r}.base") - assert ( - len(immediate_base_list) < 2 - ), "immediate base directory must be at most one entry" - immediate_base = replace_path_constants("".join(immediate_base_list), scope) - immediate_lang = None - immediate_name = f"qmake_{r}" - resource_infos.append( - QtResource( - name=immediate_name, - prefix=immediate_prefix, - base_dir=immediate_base, - lang=immediate_lang, - files=immediate_files, - skip_qtquick_compiler=skip_qtquick_compiler, - ) - ) - else: - standalone_files.append(r) - if not ("*" in r) and skip_qtquick_compiler: - skipped_standalone_files.append(r) - - if standalone_files: - resource_infos.append( - QtResource( - name="qmake_immediate", - prefix="/", - base_dir="", - files={f: "" for f in standalone_files}, - ) - ) - - return (resource_infos, skipped_standalone_files) - - -def write_resources( - cm_fh: IO[str], - target: str, - scope: Scope, - indent: int = 0, - is_example=False, - target_ref: str = None, - resources: List[QtResource] = None, - skipped_standalone_files: List[str] = None, -): - if resources is None: - (resources, skipped_standalone_files) = extract_resources(target, scope) - if target_ref is None: - target_ref = target - - qrc_output = "" - for r in resources: - name = r.name - if "*" in name: - name = expand_resource_glob(cm_fh, name) - qrc_output += write_add_qt_resource_call( - target=target_ref, - scope=scope, - resource_name=name, - prefix=r.prefix, - base_dir=r.base_dir, - lang=r.lang, - files=r.files, - skip_qtquick_compiler=r.skip_qtquick_compiler, - is_example=is_example, - ) - - if skipped_standalone_files: - for f in skipped_standalone_files: - qrc_output += ( - f'set_source_files_properties("{f}" PROPERTIES ' f"QT_SKIP_QUICKCOMPILER 1)\n\n" - ) - - if qrc_output: - str_indent = spaces(indent) - cm_fh.write(f"\n{str_indent}# Resources:\n") - for line in qrc_output.split("\n"): - if line: - cm_fh.write(f"{str_indent}{line}\n") - else: - # do not add spaces to empty lines - cm_fh.write("\n") - - -def write_statecharts(cm_fh: IO[str], target: str, scope: Scope, indent: int = 0, is_example=False): - sources = scope.get_files("STATECHARTS", use_vpath=True) - if not sources: - return - cm_fh.write("\n# Statecharts:\n") - if is_example: - cm_fh.write(f"qt6_add_statecharts({target}\n") - else: - cm_fh.write(f"add_qt_statecharts({target} FILES\n") - indent += 1 - for f in sources: - cm_fh.write(f"{spaces(indent)}{f}\n") - cm_fh.write(")\n") - - -def write_qlalrsources(cm_fh: IO[str], target: str, scope: Scope, indent: int = 0): - sources = scope.get_files("QLALRSOURCES", use_vpath=True) - if not sources: - return - cm_fh.write("\n# QLALR Grammars:\n") - cm_fh.write("qt_process_qlalr(\n") - indent += 1 - cm_fh.write(f"{spaces(indent)}{target}\n") - cm_fh.write(f"{spaces(indent)}{';'.join(sources)}\n") - cm_fh.write(f'{spaces(indent)}""\n') - cm_fh.write(")\n") - - -def write_repc_files(cm_fh: IO[str], target: str, scope: Scope, indent: int = 0): - for t in ["SOURCE", "REPLICA", "MERGED"]: - sources = scope.get_files("REPC_" + t, use_vpath=True) - if not sources: - continue - cm_fh.write(f"qt6_add_repc_{t.lower()}({target}\n") - indent += 1 - for f in sources: - cm_fh.write(f"{spaces(indent)}{f}\n") - cm_fh.write(")\n") - - -def write_generic_cmake_command( - cm_fh: IO[str], command_name: str, arguments: List[str], indent: int = 0 -): - ind = spaces(indent) - arguments_str = " ".join(arguments) - cm_fh.write(f"{ind}{command_name}({arguments_str})\n") - - -def write_set_target_properties( - cm_fh: IO[str], targets: List[str], properties: List[str], indent: int = 0 -): - ind = spaces(indent) - command_name = "set_target_properties" - arguments_ind = spaces(indent + 1) - - prop_pairs = [(properties[i] + " " + properties[i + 1]) for i in range(0, len(properties), 2)] - properties_str = f"\n{arguments_ind}" + f"\n{arguments_ind}".join(prop_pairs) - - if len(targets) == 1: - targets_str = targets[0] + " " - else: - targets_str = ( - f"\n{arguments_ind}" + f"\n{arguments_ind}".join(targets) + f"\n{arguments_ind}" - ) - - cm_fh.write(f"{ind}{command_name}({targets_str}PROPERTIES{properties_str}\n{ind})\n") - - -def write_set_source_files_properties( - cm_fh: IO[str], files: List[str], properties: List[str], indent: int = 0 -): - ind = spaces(indent) - command_name = "set_source_files_properties" - arguments_ind = spaces(indent + 1) - - prop_pairs = [(properties[i] + " " + properties[i + 1]) for i in range(0, len(properties), 2)] - properties_str = f"\n{arguments_ind}" + f"\n{arguments_ind}".join(prop_pairs) - - if len(files) == 1: - targets_str = files[0] + " " - else: - targets_str = f"\n{arguments_ind}" + f"\n{arguments_ind}".join(files) + f"\n{arguments_ind}" - - cm_fh.write(f"{ind}{command_name}({targets_str}PROPERTIES{properties_str}\n{ind})\n") - - -def write_target_sources( - cm_fh: IO[str], target: str, sources: List[str], visibility: str = "PRIVATE", indent: int = 0 -): - command_name = "target_sources" - header = f"{command_name}({target} {visibility}\n" - write_list(cm_fh, sources, "", indent, footer=")", header=header) - - -def expand_project_requirements(scope: Scope, skip_message: bool = False) -> str: - requirements = "" - for requirement in scope.get("_REQUIREMENTS"): - original_condition = simplify_condition(map_condition(requirement)) - inverted_requirement = simplify_condition(f"NOT ({map_condition(requirement)})") - if not skip_message: - message = f""" -{spaces(7)}message(NOTICE "Skipping the build as the condition \\"{original_condition}\\" is not met.")""" - else: - message = "" - requirements += dedent( - f"""\ - if({inverted_requirement}){message} - return() - endif() -""" - ) - return requirements - - -def write_extend_target( - cm_fh: IO[str], target: str, scope: Scope, indent: int = 0, target_ref: str = None -): - if target_ref is None: - target_ref = target - ind = spaces(indent) - extend_qt_io_string = io.StringIO() - write_sources_section(extend_qt_io_string, scope) - extend_qt_string = extend_qt_io_string.getvalue() - - assert scope.total_condition, "Cannot write CONDITION when scope.condition is None" - - condition = map_to_cmake_condition(scope.total_condition) - - cmake_api_call = get_cmake_api_call("qt_extend_target") - extend_scope = ( - f"\n{ind}{cmake_api_call}({target_ref} CONDITION" - f" {condition}\n" - f"{extend_qt_string}{ind})\n" - ) - - if not extend_qt_string: - extend_scope = "" # Nothing to report, so don't! - - cm_fh.write(extend_scope) - - io_string = io.StringIO() - write_resources(io_string, target, scope, indent + 1, target_ref=target_ref) - resource_string = io_string.getvalue() - if len(resource_string) != 0: - resource_string = resource_string.strip("\n").rstrip(f"\n{spaces(indent + 1)}") - cm_fh.write(f"\n{spaces(indent)}if({condition})\n{resource_string}") - cm_fh.write(f"\n{spaces(indent)}endif()\n") - - -def flatten_scopes(scope: Scope) -> List[Scope]: - result = [scope] # type: List[Scope] - for c in scope.children: - result += flatten_scopes(c) - return result - - -def merge_scopes(scopes: List[Scope]) -> List[Scope]: - result = [] # type: List[Scope] - - # Merge scopes with their parents: - known_scopes = {} # type: Dict[str, Scope] - for scope in scopes: - total_condition = scope.total_condition - assert total_condition - if total_condition == "OFF": - # ignore this scope entirely! - pass - elif total_condition in known_scopes: - known_scopes[total_condition].merge(scope) - else: - # Keep everything else: - result.append(scope) - known_scopes[total_condition] = scope - - return result - - -def write_simd_part(cm_fh: IO[str], target: str, scope: Scope, indent: int = 0): - simd_options = [ - "sse2", - "sse3", - "ssse3", - "sse4_1", - "sse4_2", - "aesni", - "shani", - "avx", - "avx2", - "avx512f", - "avx512cd", - "avx512er", - "avx512pf", - "avx512dq", - "avx512bw", - "avx512vl", - "avx512ifma", - "avx512vbmi", - "f16c", - "rdrnd", - "neon", - "mips_dsp", - "mips_dspr2", - "arch_haswell", - "avx512common", - "avx512core", - ] - - simd_io_string = io.StringIO() - - condition = "ON" - if scope.total_condition: - condition = map_to_cmake_condition(scope.total_condition) - - if condition != "ON": - indent += 1 - - for simd in simd_options: - SIMD = simd.upper() - write_source_file_list( - simd_io_string, - scope, - "SOURCES", - [f"{SIMD}_HEADERS", f"{SIMD}_SOURCES", f"{SIMD}_C_SOURCES", f"{SIMD}_ASM"], - indent=indent, - header=f"{get_cmake_api_call('qt_add_simd_part')}({target} SIMD {simd}\n", - footer=")\n", - ) - - simd_string = simd_io_string.getvalue() - if simd_string: - simd_string = simd_string.rstrip("\n") - cond_start = "" - cond_end = "" - if condition != "ON": - cond_start = f"{spaces(indent - 1)}if({condition})" - cond_end = f"{spaces(indent - 1)}endif()" - - extend_scope = f"\n{cond_start}\n" f"{simd_string}" f"\n{cond_end}\n" - cm_fh.write(extend_scope) - - -def write_reduce_relocations_part(cm_fh: IO[str], target: str, scope: Scope, indent: int = 0): - ind = spaces(indent) - dynlist_file = scope.get_files("QMAKE_DYNAMIC_LIST_FILE") - if dynlist_file: - dynlist_path = "${CMAKE_CURRENT_LIST_DIR}/" + dynlist_file[0] - cm_fh.write(f"{ind}if(QT_FEATURE_reduce_relocations AND UNIX AND GCC)\n") - ind = spaces(indent + 1) - cm_fh.write(f"{ind}target_link_options({target} PRIVATE\n") - cm_fh.write(f'{ind} "LINKER:--dynamic-list={dynlist_path}")\n') - ind = spaces(indent) - cm_fh.write(f"{ind}endif()\n") - - -def write_android_part(cm_fh: IO[str], target: str, scope: Scope, indent: int = 0): - keys = [ - "ANDROID_BUNDLED_JAR_DEPENDENCIES", - "ANDROID_LIB_DEPENDENCIES", - "ANDROID_JAR_DEPENDENCIES", - "ANDROID_LIB_DEPENDENCY_REPLACEMENTS", - "ANDROID_BUNDLED_FILES", - "ANDROID_PERMISSIONS", - "ANDROID_PACKAGE_SOURCE_DIR", - ] - - has_no_values = True - for key in keys: - value = scope.expand(key) - if len(value) != 0: - if has_no_values: - if scope.condition: - cm_fh.write(f"\n{spaces(indent)}if(ANDROID AND ({scope.condition}))\n") - else: - cm_fh.write(f"\n{spaces(indent)}if(ANDROID)\n") - indent += 1 - has_no_values = False - cm_fh.write(f"{spaces(indent)}set_property(TARGET {target} APPEND PROPERTY QT_{key}\n") - write_list(cm_fh, value, "", indent + 1) - cm_fh.write(f"{spaces(indent)})\n") - indent -= 1 - - if not has_no_values: - cm_fh.write(f"{spaces(indent)}endif()\n") - - -def write_wayland_part(cm_fh: IO[str], target: str, scope: Scope, indent: int = 0): - client_sources = scope.get_files("WAYLANDCLIENTSOURCES", use_vpath=True) - server_sources = scope.get_files("WAYLANDSERVERSOURCES", use_vpath=True) - if len(client_sources) == 0 and len(server_sources) == 0: - return - - condition, indent = write_scope_condition_begin(cm_fh, scope, indent=indent) - - if len(client_sources) != 0: - cm_fh.write(f"\n{spaces(indent)}qt6_generate_wayland_protocol_client_sources({target}\n") - write_list( - cm_fh, client_sources, "FILES", indent + 1, prefix="${CMAKE_CURRENT_SOURCE_DIR}/" - ) - cm_fh.write(f"{spaces(indent)})\n") - - if len(server_sources) != 0: - cm_fh.write(f"\n{spaces(indent)}qt6_generate_wayland_protocol_server_sources({target}\n") - write_list( - cm_fh, server_sources, "FILES", indent + 1, prefix="${CMAKE_CURRENT_SOURCE_DIR}/" - ) - cm_fh.write(f"{spaces(indent)})\n") - - write_scope_condition_end(cm_fh, condition, indent=indent) - - -def write_scope_condition_begin(cm_fh: IO[str], scope: Scope, indent: int = 0) -> Tuple[str, int]: - condition = "ON" - if scope.total_condition: - condition = map_to_cmake_condition(scope.total_condition) - - if condition != "ON": - cm_fh.write(f"\n{spaces(indent)}if({condition})\n") - indent += 1 - - return condition, indent - - -def write_scope_condition_end(cm_fh: IO[str], condition: str, indent: int = 0) -> int: - if condition != "ON": - indent -= 1 - cm_fh.write(f"{spaces(indent)}endif()\n") - return indent - - -def is_path_relative_ish(path: str) -> bool: - if not os.path.isabs(path) and not path.startswith("$"): - return True - return False - - -def absolutify_path(path: str, base_dir: str = "${CMAKE_CURRENT_SOURCE_DIR}") -> str: - if not path: - return path - if is_path_relative_ish(path): - path = posixpath.join(base_dir, path) - return path - - -def write_version_part(cm_fh: IO[str], target: str, scope: Scope, indent: int = 0): - if scope.is_internal_qt_app: - version_value = scope.get_string("VERSION") - if version_value: - version_value = re.sub(r"\$\${QT_VERSION\}", "${PROJECT_VERSION}", version_value) - target_description = scope.expandString("QMAKE_TARGET_DESCRIPTION") - - if version_value or target_description: - condition, indent = write_scope_condition_begin(cm_fh, scope, indent=indent) - - properties = [] - if version_value: - properties.extend(["QT_TARGET_VERSION", f'"{version_value}"']) - if target_description: - properties.extend(["QT_TARGET_DESCRIPTION", f'"{target_description}"']) - - if properties: - write_set_target_properties(cm_fh, [target], properties, indent=indent) - - write_scope_condition_end(cm_fh, condition, indent=indent) - - -def write_darwin_part( - cm_fh: IO[str], target: str, scope: Scope, main_scope_target_name: str = "", indent: int = 0 -): - if scope.is_internal_qt_app: - # Embed custom provided Info.plist file. - info_plist = scope.expandString("QMAKE_INFO_PLIST") - info_plist = absolutify_path(info_plist) - - icon_path = scope.expandString("ICON") - icon_basename = "" - - new_output_name = None - current_scope_output_name = scope.TARGET - if current_scope_output_name != main_scope_target_name: - new_output_name = current_scope_output_name - - if icon_path: - icon_basename = os.path.basename(icon_path) - - if info_plist or icon_path or new_output_name: - condition, indent = write_scope_condition_begin(cm_fh, scope, indent=indent) - - properties = [] - if info_plist: - properties.extend(["MACOSX_BUNDLE_INFO_PLIST", f'"{info_plist}"']) - properties.extend(["MACOSX_BUNDLE", "TRUE"]) - if icon_path: - properties.extend(["MACOSX_BUNDLE_ICON_FILE", f'"{icon_basename}"']) - - if new_output_name: - properties.extend(["OUTPUT_NAME", f'"{new_output_name}"']) - - if properties: - write_set_target_properties(cm_fh, [target], properties, indent=indent) - if icon_path: - source_properties = ["MACOSX_PACKAGE_LOCATION", "Resources"] - write_set_source_files_properties( - cm_fh, [icon_path], source_properties, indent=indent - ) - write_target_sources(cm_fh, target, [icon_path], indent=indent) - - write_scope_condition_end(cm_fh, condition, indent=indent) - - -def write_windows_part(cm_fh: IO[str], target: str, scope: Scope, indent: int = 0): - if scope.is_internal_qt_app: - # Handle CONFIG += console assignments. - is_console = "console" in scope.get("CONFIG") - rc_file = scope.expandString("RC_FILE") - rc_file = absolutify_path(rc_file) - rc_icons = scope.expandString("RC_ICONS") - rc_icons = absolutify_path(rc_icons) - - if is_console or rc_file or rc_icons: - condition, indent = write_scope_condition_begin(cm_fh, scope, indent=indent) - - properties = [] - if is_console: - properties.extend(["WIN32_EXECUTABLE", "FALSE"]) - - if rc_file: - properties.extend(["QT_TARGET_WINDOWS_RC_FILE", f'"{rc_file}"']) - - if rc_icons: - properties.extend(["QT_TARGET_RC_ICONS", f'"{rc_icons}"']) - - if properties: - write_set_target_properties(cm_fh, [target], properties, indent=indent) - - write_scope_condition_end(cm_fh, condition, indent=indent) - - -def write_aux_qml_file_install_call(cm_fh: IO[str], file_list: List[str], indent: int = 0): - cm_fh.write(f"\n{spaces(indent)}qt_copy_or_install(\n") - write_list(cm_fh, file_list, "FILES", indent + 1) - - destination_option = 'DESTINATION "${__aux_qml_files_install_dir}"' - cm_fh.write(f"{spaces(indent + 1)}{destination_option})\n") - - -def write_aux_qml_path_setup(cm_fh: IO[str], base_dir: str, indent: int = 0): - path_join_args = f'__aux_qml_files_install_dir "${{__aux_qml_files_install_base}}" "{base_dir}"' - cm_fh.write(f"\n{spaces(indent)}qt_path_join({path_join_args})\n") - - -def write_aux_qml_files_part(cm_fh: IO[str], target: str, scope: Scope, indent: int = 0): - aux_files = scope.get_files("AUX_QML_FILES") - if aux_files and isinstance(aux_files, list): - aux_files_per_dir = defaultdict(list) - aux_files_globs = [] - - # Handle globs differently from regular paths. - # For regular paths, group by base dir. Each base dir will get - # its own install call. - for path in aux_files: - if "*" in path: - aux_files_globs.append(path) - else: - base_dir = os.path.dirname(path) - aux_files_per_dir[base_dir].append(path) - - condition, indent = write_scope_condition_begin(cm_fh, scope, indent=indent) - - # Extract the location of $prefix/qml, where we want to install - # files. - get_prop_args = f"__aux_qml_files_install_base {target} QT_QML_MODULE_INSTALL_DIR" - cm_fh.write(f"{spaces(indent)}get_target_property({get_prop_args})\n") - - # Handle glob installs. - for path in aux_files_globs: - cm_fh.write( - f""" -{spaces(indent)}file(GLOB_RECURSE __aux_qml_glob_files -{spaces(indent + 1)}RELATIVE "${{CMAKE_CURRENT_SOURCE_DIR}}" -{spaces(indent + 1)}"{path}")""" - ) - file_list = ["${__aux_qml_glob_files}"] - - # Extract base dir. Hopes that the globs only appear in the - # file name part. - base_dir = os.path.dirname(path) - write_aux_qml_path_setup(cm_fh, base_dir, indent=indent) - write_aux_qml_file_install_call(cm_fh, file_list, indent=indent) - - # Handle regular per base-dir installs. - for base_dir in aux_files_per_dir: - file_list = aux_files_per_dir[base_dir] - write_aux_qml_path_setup(cm_fh, base_dir, indent=indent) - write_aux_qml_file_install_call(cm_fh, file_list, indent=indent) - write_scope_condition_end(cm_fh, condition, indent=indent) - - -def handle_source_subtractions(scopes: List[Scope]): - """ - Handles source subtractions like SOURCES -= painting/qdrawhelper.cpp - by creating a new scope with a new condition containing all addition - and subtraction conditions. - - Algorithm is as follows: - - Go through each scope and find files in SOURCES starting with "-" - - Save that file and the scope condition in modified_sources dict. - - Remove the file from the found scope (optionally remove the - NO_PCH_SOURCES entry for that file as well). - - Go through each file in modified_sources dict. - - Find scopes where the file is added, remove the file from that - scope and save the condition. - - Create a new scope just for that file with a new simplified - condition that takes all the other conditions into account. - """ - - def remove_file_from_operation( - scope: Scope, ops_key: str, file: str, op_type: Type[Operation] - ) -> bool: - """ - Remove a source file from an operation in a scope. - Example: remove foo.cpp from any operations that have - ops_key="SOURCES" in "scope", where the operation is of - type "op_type". - - The implementation is very rudimentary and might not work in - all cases. - - Returns True if a file was found and removed in any operation. - """ - file_removed = False - ops = scope._operations.get(ops_key, list()) - for op in ops: - if not isinstance(op, op_type): - continue - if file in op._value: - op._value.remove(file) - file_removed = True - for include_child_scope in scope._included_children: - file_removed = file_removed or remove_file_from_operation( - include_child_scope, ops_key, file, op_type - ) - return file_removed - - def join_all_conditions(set_of_alternatives: Set[str]): - final_str = "" - if set_of_alternatives: - alternatives = [f"({alternative})" for alternative in set_of_alternatives] - final_str = " OR ".join(sorted(alternatives)) - return final_str - - modified_sources: Dict[str, Dict[str, Union[Set[str], bool]]] = {} - - new_scopes = [] - top_most_scope = scopes[0] - - for scope in scopes: - sources = scope.get_files("SOURCES") - for file in sources: - # Find subtractions. - if file.startswith("-"): - file_without_minus = file[1:] - - if file_without_minus not in modified_sources: - modified_sources[file_without_minus] = {} - - subtractions = modified_sources[file_without_minus].get("subtractions", set()) - assert isinstance(subtractions, set) - - # Add the condition to the set of conditions and remove - # the file subtraction from the processed scope, which - # will be later re-added in a new scope. - if scope.condition: - assert scope.total_condition - subtractions.add(scope.total_condition) - remove_file_from_operation(scope, "SOURCES", file_without_minus, RemoveOperation) - if subtractions: - modified_sources[file_without_minus]["subtractions"] = subtractions - - # In case if the source is also listed in a - # NO_PCH_SOURCES operation, remove it from there as - # well, and add it back later. - no_pch_source_removed = remove_file_from_operation( - scope, "NO_PCH_SOURCES", file_without_minus, AddOperation - ) - if no_pch_source_removed: - modified_sources[file_without_minus]["add_to_no_pch_sources"] = True - - for modified_source in modified_sources: - additions = modified_sources[modified_source].get("additions", set()) - assert isinstance(additions, set), f"Additions must be a set, got {additions} instead." - subtractions = modified_sources[modified_source].get("subtractions", set()) - assert isinstance( - subtractions, set - ), f"Subtractions must be a set, got {additions} instead." - add_to_no_pch_sources = modified_sources[modified_source].get( - "add_to_no_pch_sources", False - ) - - for scope in scopes: - sources = scope.get_files("SOURCES") - if modified_source in sources: - # Remove the source file from any addition operations - # that mention it. - remove_file_from_operation(scope, "SOURCES", modified_source, AddOperation) - if scope.total_condition: - additions.add(scope.total_condition) - - # Construct a condition that takes into account all addition - # and subtraction conditions. - addition_str = join_all_conditions(additions) - if addition_str: - addition_str = f"({addition_str})" - subtraction_str = join_all_conditions(subtractions) - if subtraction_str: - subtraction_str = f"NOT ({subtraction_str})" - - condition_str = addition_str - if condition_str and subtraction_str: - condition_str += " AND " - condition_str += subtraction_str - condition_simplified = simplify_condition(condition_str) - - # Create a new scope with that condition and add the source - # operations. - new_scope = Scope( - parent_scope=top_most_scope, - qmake_file=top_most_scope.file, - condition=condition_simplified, - base_dir=top_most_scope.basedir, - ) - new_scope.total_condition = condition_simplified - new_scope._append_operation("SOURCES", AddOperation([modified_source])) - if add_to_no_pch_sources: - new_scope._append_operation("NO_PCH_SOURCES", AddOperation([modified_source])) - - new_scopes.append(new_scope) - - # Add all the newly created scopes. - scopes += new_scopes - - -def write_main_part( - cm_fh: IO[str], - name: str, - typename: str, - cmake_function: str, - scope: Scope, - *, - extra_lines: Optional[List[str]] = None, - indent: int = 0, - extra_keys: List[str], - **kwargs: Any, -): - # Evaluate total condition of all scopes: - if extra_lines is None: - extra_lines = [] - recursive_evaluate_scope(scope) - - if "exceptions" in scope.get("CONFIG"): - extra_lines.append("EXCEPTIONS") - - # Get a flat list of all scopes but the main one: - scopes = flatten_scopes(scope) - # total_scopes = len(scopes) - # Merge scopes based on their conditions: - scopes = merge_scopes(scopes) - - # Handle SOURCES -= foo calls, and merge scopes one more time - # because there might have been several files removed with the same - # scope condition. - handle_source_subtractions(scopes) - scopes = merge_scopes(scopes) - - assert len(scopes) - assert scopes[0].total_condition == "ON" - - scopes[0].reset_visited_keys() - for k in extra_keys: - scopes[0].get(k) - - # Now write out the scopes: - write_header(cm_fh, name, typename, indent=indent) - - # collect all testdata and insert globbing commands - has_test_data = False - if typename == "Test": - cm_fh.write(f"{spaces(indent)}if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)\n") - cm_fh.write(f"{spaces(indent+1)}cmake_minimum_required(VERSION 3.16)\n") - cm_fh.write(f"{spaces(indent+1)}project({name} LANGUAGES C CXX ASM)\n") - cm_fh.write( - f"{spaces(indent+1)}find_package(Qt6BuildInternals COMPONENTS STANDALONE_TEST)\n" - ) - cm_fh.write(f"{spaces(indent)}endif()\n\n") - - test_data = scope.expand("TESTDATA") - if test_data: - has_test_data = True - cm_fh.write("# Collect test data\n") - for data in test_data: - if "*" in data: - cm_fh.write( - dedent( - f"""\ - {spaces(indent)}file(GLOB_RECURSE test_data_glob - {spaces(indent+1)}RELATIVE ${{CMAKE_CURRENT_SOURCE_DIR}} - {spaces(indent+1)}{data}) - """ - ) - ) - cm_fh.write(f"{spaces(indent)}list(APPEND test_data ${{test_data_glob}})\n") - else: - cm_fh.write(f'{spaces(indent)}list(APPEND test_data "{data}")\n') - cm_fh.write("\n") - - target_ref = name - if typename == "Tool": - target_ref = "${target_name}" - cm_fh.write(f"{spaces(indent)}qt_get_tool_target_name(target_name {name})\n") - - # Check for DESTDIR override - destdir = scope.get_string("DESTDIR") - if destdir: - already_added = False - for line in extra_lines: - if line.startswith("OUTPUT_DIRECTORY"): - already_added = True - break - if not already_added: - destdir = replace_path_constants(destdir, scope) - extra_lines.append(f'OUTPUT_DIRECTORY "{destdir}"') - - cm_fh.write(f"{spaces(indent)}{cmake_function}({target_ref}\n") - for extra_line in extra_lines: - cm_fh.write(f"{spaces(indent)} {extra_line}\n") - - write_sources_section(cm_fh, scopes[0], indent=indent, **kwargs) - - if has_test_data: - cm_fh.write(f"{spaces(indent)} TESTDATA ${{test_data}}\n") - # Footer: - cm_fh.write(f"{spaces(indent)})\n") - - if typename == "Tool": - cm_fh.write(f"{spaces(indent)}qt_internal_return_unless_building_tools()\n") - - write_resources(cm_fh, name, scope, indent, target_ref=target_ref) - - write_statecharts(cm_fh, name, scope, indent) - - write_qlalrsources(cm_fh, name, scope, indent) - - write_repc_files(cm_fh, name, scope, indent) - - write_simd_part(cm_fh, name, scope, indent) - - write_reduce_relocations_part(cm_fh, name, scope, indent) - - write_android_part(cm_fh, name, scopes[0], indent) - - write_wayland_part(cm_fh, name, scopes[0], indent) - - write_windows_part(cm_fh, name, scopes[0], indent) - - write_darwin_part(cm_fh, name, scopes[0], main_scope_target_name=name, indent=indent) - - write_version_part(cm_fh, name, scopes[0], indent) - - write_aux_qml_files_part(cm_fh, name, scopes[0], indent) - - if "warn_off" in scope.get("CONFIG"): - write_generic_cmake_command(cm_fh, "qt_disable_warnings", [name]) - - if "hide_symbols" in scope.get("CONFIG"): - write_generic_cmake_command(cm_fh, "qt_set_symbol_visibility_hidden", [name]) - - ignored_keys_report = write_ignored_keys(scopes[0], spaces(indent)) - if ignored_keys_report: - cm_fh.write(ignored_keys_report) - - # Scopes: - if len(scopes) == 1: - return - - write_scope_header(cm_fh, indent=indent) - - for c in scopes[1:]: - c.reset_visited_keys() - write_android_part(cm_fh, name, c, indent=indent) - write_wayland_part(cm_fh, name, c, indent=indent) - write_windows_part(cm_fh, name, c, indent=indent) - write_darwin_part(cm_fh, name, c, main_scope_target_name=name, indent=indent) - write_version_part(cm_fh, name, c, indent=indent) - write_aux_qml_files_part(cm_fh, name, c, indent=indent) - write_extend_target(cm_fh, name, c, target_ref=target_ref, indent=indent) - write_simd_part(cm_fh, name, c, indent=indent) - - ignored_keys_report = write_ignored_keys(c, spaces(indent)) - if ignored_keys_report: - cm_fh.write(ignored_keys_report) - - -def write_3rdparty_library(cm_fh: IO[str], scope: Scope, *, indent: int = 0) -> str: - # Remove default QT libs. - scope._append_operation("QT", RemoveOperation(["core", "gui"])) - - target_name = re.sub(r"^qt", "", scope.TARGET) - target_name = target_name.replace("-", "_") - qmake_lib_name = target_name - - # Capitalize the first letter for a nicer name. - target_name = target_name.title() - - # Prefix with Bundled, to avoid possible duplicate target names - # e.g. "BundledFreetype" instead of "freetype". - target_name = f"Bundled{target_name}" - - if "dll" in scope.get("CONFIG"): - library_type = "SHARED" - else: - library_type = "STATIC" - - extra_lines = [f"QMAKE_LIB_NAME {qmake_lib_name}"] - - if library_type: - extra_lines.append(library_type) - - if "installed" in scope.get("CONFIG"): - extra_lines.append("INSTALL") - - write_main_part( - cm_fh, - target_name, - "Generic Library", - get_cmake_api_call("qt_add_3rdparty_library"), - scope, - extra_lines=extra_lines, - indent=indent, - known_libraries={}, - extra_keys=[], - ) - - return target_name - - -def write_generic_library(cm_fh: IO[str], scope: Scope, *, indent: int = 0) -> str: - - target_name = scope.TARGET - - library_type = "" - - if "dll" in scope.get("CONFIG"): - library_type = "SHARED" - - is_plugin = False - if "plugin" in scope.get("CONFIG"): - library_type = "MODULE" - is_plugin = True - - # static after plugin in order to handle static library plugins - if "static" in scope.get("CONFIG"): - library_type = "STATIC" - - extra_lines = [] - - if library_type: - extra_lines.append(library_type) - - target_path = scope.expandString("target.path") - target_path = replace_path_constants(target_path, scope) - if target_path: - extra_lines.append(f'INSTALL_DIRECTORY "{target_path}"') - - write_main_part( - cm_fh, - target_name, - "Generic Library", - get_cmake_api_call("qt_add_cmake_library"), - scope, - extra_lines=extra_lines, - indent=indent, - known_libraries={}, - extra_keys=[], - ) - - if is_plugin: - # Plugins need to be able to run auto moc - cm_fh.write(f"\nqt_autogen_tools_initial_setup({target_name})\n") - - if library_type == "STATIC": - cm_fh.write(f"\ntarget_compile_definitions({target_name} PRIVATE QT_STATICPLUGIN)\n") - - return target_name - - -def forward_target_info(scope: Scope, extra: List[str], skip: Optional[Dict[str, bool]] = None): - s = scope.get_string("QMAKE_TARGET_PRODUCT") - if s: - extra.append(f'TARGET_PRODUCT "{s}"') - s = scope.get_string("QMAKE_TARGET_DESCRIPTION") - if s and (not skip or "QMAKE_TARGET_DESCRIPTION" not in skip): - extra.append(f'TARGET_DESCRIPTION "{s}"') - s = scope.get_string("QMAKE_TARGET_COMPANY") - if s: - extra.append(f'TARGET_COMPANY "{s}"') - s = scope.get_string("QMAKE_TARGET_COPYRIGHT") - if s: - extra.append(f'TARGET_COPYRIGHT "{s}"') - - -def write_module(cm_fh: IO[str], scope: Scope, *, indent: int = 0) -> str: - # e.g. QtCore - qt_module_name = scope.TARGET - if not qt_module_name.startswith("Qt"): - print(f"XXXXXX Module name {qt_module_name} does not start with Qt!") - - extra = [] - - # A module should be static when 'static' is in CONFIG - # or when option(host_build) is used, as described in qt_module.prf. - is_static = "static" in scope.get("CONFIG") or "host_build" in scope.get("_OPTION") - - is_public_module = True - - # CMake target name as passed to qt_internal_add_module() - # e.g. Core - cmake_target_name = qt_module_name[2:] - - # MODULE is used for the name of the generated .pri file. - # If MODULE is not explicitly set, qmake computes its value in - # mkspecs/features/qt_build_config.prf - module_name_for_pri = scope.expandString("MODULE") - if not module_name_for_pri: - module_name_for_pri_as_qmake_computes_it = scope.file[:-4] - module_name_for_pri = module_name_for_pri_as_qmake_computes_it - - # Given 'qt_internal_add_module(Core)', computes 'core'. - module_name_for_pri_as_cmake_computes_it = cmake_target_name.lower() - - if module_name_for_pri != module_name_for_pri_as_cmake_computes_it: - extra.append(f"CONFIG_MODULE_NAME {module_name_for_pri}") - - if is_static: - extra.append("STATIC") - if "internal_module" in scope.get("CONFIG"): - is_public_module = False - cmake_target_name += "Private" # Assume all internal modules have the 'Private' suffix - extra.append("INTERNAL_MODULE") - if "no_module_headers" in scope.get("CONFIG"): - extra.append("NO_MODULE_HEADERS") - if "minimal_syncqt" in scope.get("CONFIG"): - extra.append("NO_SYNC_QT") - if "no_private_module" in scope.get("CONFIG"): - extra.append("NO_PRIVATE_MODULE") - else: - scope._has_private_module = True - if "header_module" in scope.get("CONFIG"): - extra.append("HEADER_MODULE") - if not("metatypes" in scope.get("CONFIG") or "qmltypes" in scope.get("CONFIG")): - extra.append("NO_GENERATE_METATYPES") - - module_config = scope.get("MODULE_CONFIG") - if len(module_config): - extra.append(f'QMAKE_MODULE_CONFIG {" ".join(module_config)}') - - module_plugin_types = scope.get_files("MODULE_PLUGIN_TYPES") - if module_plugin_types: - extra.append(f"PLUGIN_TYPES {' '.join(module_plugin_types)}") - - scope._is_public_module = is_public_module - - forward_target_info(scope, extra) - write_main_part( - cm_fh, - cmake_target_name, - "Module", - f"{get_cmake_api_call('qt_add_module')}", - scope, - extra_lines=extra, - indent=indent, - known_libraries={}, - extra_keys=[], - ) - - if "qt_tracepoints" in scope.get("CONFIG"): - tracepoints = scope.get_files("TRACEPOINT_PROVIDER") - create_trace_points = get_cmake_api_call("qt_create_tracepoints") - cm_fh.write( - f"\n\n{spaces(indent)}{create_trace_points}({cmake_target_name} {' '.join(tracepoints)})\n" - ) - - return cmake_target_name - - -def write_tool(cm_fh: IO[str], scope: Scope, *, indent: int = 0) -> Tuple[str, str]: - tool_name = scope.TARGET - - if "force_bootstrap" in scope.get("CONFIG"): - extra = ["BOOTSTRAP"] - - # Remove default QT libs. - scope._append_operation("QT", RemoveOperation(["core", "gui"])) - else: - extra = [] - - forward_target_info(scope, extra) - - write_main_part( - cm_fh, - tool_name, - "Tool", - get_cmake_api_call("qt_add_tool"), - scope, - indent=indent, - known_libraries={"Qt::Core"}, - extra_lines=extra, - extra_keys=["CONFIG"], - ) - - return tool_name, "${target_name}" - - -def write_qt_app(cm_fh: IO[str], scope: Scope, *, indent: int = 0) -> str: - app_name = scope.TARGET - - extra: List[str] = [] - - target_info_skip = {} - target_info_skip["QMAKE_TARGET_DESCRIPTION"] = True - forward_target_info(scope, extra, target_info_skip) - - write_main_part( - cm_fh, - app_name, - "App", - get_cmake_api_call("qt_internal_add_app"), - scope, - indent=indent, - known_libraries={"Qt::Core"}, - extra_lines=extra, - extra_keys=["CONFIG"], - ) - - return app_name - - -def write_test(cm_fh: IO[str], scope: Scope, gui: bool = False, *, indent: int = 0) -> str: - test_name = scope.TARGET - assert test_name - - extra = ["GUI"] if gui else [] - libraries = {"Qt::Core", "Qt::Test"} - - if "qmltestcase" in scope.get("CONFIG"): - libraries.add("Qt::QmlTest") - extra.append("QMLTEST") - importpath = scope.expand("IMPORTPATH") - if importpath: - extra.append("QML_IMPORTPATH") - for path in importpath: - extra.append(f' "{path}"') - - target_original = scope.TARGET_ORIGINAL - if target_original and target_original.startswith("../"): - extra.append('OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/../"') - - requires_content = expand_project_requirements(scope, skip_message=True) - if requires_content: - requires_content += "\n" - cm_fh.write(requires_content) - - write_main_part( - cm_fh, - test_name, - "Test", - get_cmake_api_call("qt_add_test"), - scope, - indent=indent, - known_libraries=libraries, - extra_lines=extra, - extra_keys=[], - ) - - return test_name - - -def write_binary(cm_fh: IO[str], scope: Scope, gui: bool = False, *, indent: int = 0) -> str: - binary_name = scope.TARGET - assert binary_name - - is_benchmark = is_benchmark_project(scope.file_absolute_path) - is_manual_test = is_manual_test_project(scope.file_absolute_path) - - is_qt_test_helper = "qt_test_helper" in scope.get("_LOADED") - - extra = ["GUI"] if gui and not is_qt_test_helper else [] - cmake_function_call = get_cmake_api_call("qt_add_executable") - extra_keys: List[str] = [] - - if is_qt_test_helper: - binary_name += "_helper" - cmake_function_call = get_cmake_api_call("qt_add_test_helper") - - if is_benchmark: - cmake_function_call = get_cmake_api_call("qt_add_benchmark") - elif is_manual_test: - cmake_function_call = get_cmake_api_call("qt_add_manual_test") - else: - extra_keys = ["target.path", "INSTALLS"] - target_path = scope.get_string("target.path") - if target_path: - target_path = replace_path_constants(target_path, scope) - if not scope.get("DESTDIR"): - extra.append(f'OUTPUT_DIRECTORY "{target_path}"') - if "target" in scope.get("INSTALLS"): - extra.append(f'INSTALL_DIRECTORY "{target_path}"') - - write_main_part( - cm_fh, - binary_name, - "Binary", - cmake_function_call, - scope, - extra_lines=extra, - indent=indent, - known_libraries={"Qt::Core"}, - extra_keys=extra_keys, - ) - - return binary_name - - -def write_find_package_section( - cm_fh: IO[str], - public_libs: List[str], - private_libs: List[str], - *, - indent: int = 0, - is_required: bool = True, - end_with_extra_newline: bool = True, - qt_package_name: str = "Qt6", -): - packages = [] # type: List[LibraryMapping] - all_libs = public_libs + private_libs - - for one_lib in all_libs: - info = find_library_info_for_target(one_lib) - if info and info not in packages: - packages.append(info) - - qt_components: List[str] = [] - for p in filter(LibraryMapping.is_qt, packages): - if p.components is not None: - qt_components += p.components - if qt_components: - if "Core" in qt_components: - qt_components.remove("Core") - qt_components = sorted(qt_components) - qt_package = LibraryMapping("unknown", qt_package_name, "unknown", components=qt_components) - if is_required: - qt_package.extra = ["REQUIRED"] - cm_fh.write( - generate_find_package_info( - qt_package, - use_qt_find_package=False, - remove_REQUIRED_from_extra=False, - components_required=is_required, - indent=indent, - ) - ) - - for p in itertools.filterfalse(LibraryMapping.is_qt, packages): - cm_fh.write(generate_find_package_info(p, use_qt_find_package=False, indent=indent)) - - if packages and end_with_extra_newline: - cm_fh.write("\n") - - -def write_jar(cm_fh: IO[str], scope: Scope, *, indent: int = 0) -> str: - - target = scope.TARGET - - install_dir = scope.expandString("target.path") - if not install_dir: - raise RuntimeError("Could not locate jar install path") - install_dir = install_dir.replace("$$[QT_INSTALL_PREFIX]/", "") - - write_source_file_list( - cm_fh, scope, "", ["JAVASOURCES"], indent=indent, header="set(java_sources\n", footer=")\n" - ) - - cm_fh.write(f"{spaces(indent)}qt_internal_add_jar({target}\n") - cm_fh.write(f"{spaces(indent+1)}INCLUDE_JARS ${{QT_ANDROID_JAR}}\n") - cm_fh.write(f"{spaces(indent+1)}SOURCES ${{java_sources}}\n") - cm_fh.write(f'{spaces(indent+1)}OUTPUT_DIR "${{QT_BUILD_DIR}}/{install_dir}"\n') - cm_fh.write(f"{spaces(indent)})\n\n") - - cm_fh.write(f"{spaces(indent)}install_jar({target}\n") - cm_fh.write(f"{spaces(indent+1)}DESTINATION {install_dir}\n") - cm_fh.write(f"{spaces(indent+1)}COMPONENT Devel\n") - cm_fh.write(f"{spaces(indent)})\n\n") - - return target - - -def get_win32_and_mac_bundle_properties(scope: Scope) -> tuple: - config = scope.get("CONFIG") - win32 = all(val not in config for val in ["cmdline", "console"]) - mac_bundle = all(val not in config for val in ["cmdline", "-app_bundle"]) - - return win32, mac_bundle - - -def write_win32_and_mac_bundle_properties( - cm_fh: IO[str], scope: Scope, target: str, *, handling_first_scope=False, indent: int = 0 -): - win32, mac_bundle = get_win32_and_mac_bundle_properties(scope) - - true_value = "TRUE" - false_value = "FALSE" - - properties_mapping = { - "WIN32_EXECUTABLE": true_value if win32 else false_value, - "MACOSX_BUNDLE": true_value if mac_bundle else false_value, - } - - properties = [] - - # Always write the properties for the first scope. - # For conditional scopes, only write them if the value is different - # from the default value (aka different from TRUE). - # This is a heurestic that should cover 90% of the example projects - # without creating excess noise of setting the properties in every - # single scope. - for name, value in properties_mapping.items(): - if not handling_first_scope and value != true_value: - properties.extend([name, value]) - - if properties: - write_set_target_properties(cm_fh, [target], properties, indent=indent) - - -def is_qtquick_source_file(filename: str): - return filename.endswith(".qml") or filename.endswith(".js") or filename.endswith(".mjs") - - -def looks_like_qml_resource(resource: QtResource): - if resource.generated or "*" in resource.name: - return False - for f in resource.files: - if is_qtquick_source_file(f): - return True - return False - - -def find_qml_resource(resources: List[QtResource]): - """Return the resource object that's most likely the one that should be used for - qt_add_qml_module. Return None if there's no such resource.""" - return next(filter(looks_like_qml_resource, resources), None) - - -def write_example( - cm_fh: IO[str], - scope: Scope, - gui: bool = False, - *, - indent: int = 0, - is_plugin: bool = False, - is_user_project: bool = False, -) -> str: - binary_name = scope.TARGET - assert binary_name - config = scope.get("CONFIG") - is_qml_plugin = ("qml" in scope.get("QT")) or "qmltypes" in config - - if not is_user_project: - example_install_dir = scope.expandString("target.path") - if not example_install_dir: - example_install_dir = "${INSTALL_EXAMPLESDIR}" - example_install_dir = example_install_dir.replace( - "$$[QT_INSTALL_EXAMPLES]", "${INSTALL_EXAMPLESDIR}" - ) - - project_version = scope.get_string("VERSION", "1.0") - cm_fh.write( - f"cmake_minimum_required(VERSION {cmake_version_string})\n" - f"project({binary_name} VERSION {project_version} LANGUAGES CXX)\n\n" - "set(CMAKE_INCLUDE_CURRENT_DIR ON)\n\n" - "set(CMAKE_AUTOMOC ON)\n" - ) - if scope.get_files("FORMS"): - cm_fh.write("set(CMAKE_AUTOUIC ON)\n") - cm_fh.write("\n") - if not is_user_project: - cm_fh.write( - "if(NOT DEFINED INSTALL_EXAMPLESDIR)\n" - ' set(INSTALL_EXAMPLESDIR "examples")\n' - "endif()\n\n" - f'set(INSTALL_EXAMPLEDIR "{example_install_dir}")\n\n' - ) - - recursive_evaluate_scope(scope) - - # Get a flat list of all scopes but the main one: - scopes = flatten_scopes(scope) - # Merge scopes based on their conditions: - scopes = merge_scopes(scopes) - # Handle SOURCES -= foo calls, and merge scopes one more time - # because there might have been several files removed with the same - # scope condition. - handle_source_subtractions(scopes) - scopes = merge_scopes(scopes) - - # Write find_package call for Qt5/Qt6 and make it available as package QT. - cm_fh.write("find_package(QT NAMES Qt5 Qt6 REQUIRED COMPONENTS Core)\n") - - # Write find_package calls for required packages. - # We consider packages as required if they appear at the top-level scope. - (public_libs, private_libs) = extract_cmake_libraries(scope, is_example=True) - write_find_package_section( - cm_fh, - public_libs, - private_libs, - indent=indent, - end_with_extra_newline=False, - qt_package_name="Qt${QT_VERSION_MAJOR}", - ) - - # Write find_package calls for optional packages. - # We consider packages inside scopes other than the top-level one as optional. - optional_public_libs: List[str] = [] - optional_private_libs: List[str] = [] - handling_first_scope = True - for inner_scope in scopes: - if handling_first_scope: - handling_first_scope = False - continue - (public_libs, private_libs) = extract_cmake_libraries(inner_scope, is_example=True) - optional_public_libs += public_libs - optional_private_libs += private_libs - write_find_package_section( - cm_fh, - optional_public_libs, - optional_private_libs, - indent=indent, - is_required=False, - end_with_extra_newline=False, - qt_package_name="Qt${QT_VERSION_MAJOR}", - ) - - cm_fh.write("\n") - - (resources, standalone_qtquick_compiler_skipped_files) = extract_resources(binary_name, scope) - qml_resource = find_qml_resource(resources) if is_qml_plugin else None - - add_target = "" - - if is_plugin: - if is_qml_plugin: - extra_args = [f"PLUGIN_TARGET {binary_name}"] - io_string = io.StringIO() - write_qml_module( - io_string, - binary_name, - scope, - scopes, - indent=indent, - resource=qml_resource, - extra_add_qml_module_args=extra_args, - ) - add_target += io_string.getvalue() - else: - add_target = f"qt_add_plugin({binary_name}" - if "static" in scope.get("CONFIG"): - add_target += " STATIC" - add_target += ")\n" - add_target += f"target_sources({binary_name} PRIVATE" - else: - add_target = f"qt_add_executable({binary_name}" - - property_win32, property_mac_bundle = get_win32_and_mac_bundle_properties(scope) - - if property_win32: - add_target += " " + "WIN32" - if property_mac_bundle: - add_target += " " + "MACOSX_BUNDLE" - - write_all_source_file_lists(cm_fh, scope, add_target, indent=0) - cm_fh.write(")\n") - - if is_qml_plugin and not is_plugin: - write_qml_module(cm_fh, binary_name, scope, scopes, indent=indent, resource=qml_resource) - - handling_first_scope = True - - for scope in scopes: - # write wayland already has condition scope handling - write_wayland_part(cm_fh, binary_name, scope, indent=0) - - # The following options do not - io_string = io.StringIO() - condition_str = "" - condition = "ON" - if scope.total_condition: - condition = map_to_cmake_condition(scope.total_condition) - - if condition != "ON": - condition_str = f"\n{spaces(indent)}if({condition})\n" - indent += 1 - - if not handling_first_scope: - target_sources = f"target_sources({binary_name} PUBLIC" - write_all_source_file_lists( - io_string, scope, target_sources, indent=indent, footer=")\n" - ) - - write_win32_and_mac_bundle_properties( - io_string, scope, binary_name, handling_first_scope=handling_first_scope, indent=indent - ) - - write_include_paths( - io_string, - scope, - f"target_include_directories({binary_name} PUBLIC", - indent=indent, - footer=")\n", - ) - write_defines( - io_string, - scope, - f"target_compile_definitions({binary_name} PUBLIC", - indent=indent, - footer=")\n", - ) - - (scope_public_libs, scope_private_libs) = extract_cmake_libraries(scope, is_example=True) - - write_list( - io_string, - scope_private_libs, - "", - indent=indent, - header=f"target_link_libraries({binary_name} PRIVATE\n", - footer=")\n", - ) - write_list( - io_string, - scope_public_libs, - "", - indent=indent, - header=f"target_link_libraries({binary_name} PUBLIC\n", - footer=")\n", - ) - write_compile_options( - io_string, scope, f"target_compile_options({binary_name}", indent=indent, footer=")\n" - ) - - (resources, standalone_qtquick_compiler_skipped_files) = extract_resources( - binary_name, scope - ) - - # Remove the QML resource, because we've handled it in write_qml_module. - if qml_resource is not None: - resources = list(filter(lambda r: r.name != qml_resource.name, resources)) - - write_resources( - io_string, - binary_name, - scope, - indent=indent, - is_example=True, - resources=resources, - skipped_standalone_files=standalone_qtquick_compiler_skipped_files, - ) - write_statecharts(io_string, binary_name, scope, indent=indent, is_example=True) - write_repc_files(io_string, binary_name, scope, indent=indent) - - if condition != "ON": - indent -= 1 - string = io_string.getvalue() - if len(string) != 0: - string = string.rstrip("\n") - cm_fh.write(f"{condition_str}{string}\n") - if condition != "ON": - cm_fh.write(f"{spaces(indent)}endif()\n") - - handling_first_scope = False - - if not is_user_project: - cm_fh.write( - f"\ninstall(TARGETS {binary_name}\n" - f' RUNTIME DESTINATION "${{INSTALL_EXAMPLEDIR}}"\n' - f' BUNDLE DESTINATION "${{INSTALL_EXAMPLEDIR}}"\n' - f' LIBRARY DESTINATION "${{INSTALL_EXAMPLEDIR}}"\n' - f")\n" - ) - - return binary_name - - -def write_plugin(cm_fh, scope, *, indent: int = 0) -> str: - extra = [] - is_qml_plugin = any("qml_plugin" == s for s in scope.get("_LOADED")) - qmake_target_name = scope.TARGET - - # Forward the original Qt5 plugin target name, to correctly name the - # final library file name, and also for .prl generation. - if qmake_target_name and not is_qml_plugin: - extra.append(f"OUTPUT_NAME {qmake_target_name}") - - # In Qt 6 CMake, the CMake target name for a plugin should be the - # same as it is in Qt5. qmake in Qt 5 derived the CMake target name - # from the "plugin class name", so use that. - # If the class name isn't empty, use that as the target name. - # Otherwise use the of value qmake TARGET - plugin_class_name = scope.get_string("PLUGIN_CLASS_NAME") - if plugin_class_name: - plugin_name = plugin_class_name - else: - plugin_name = qmake_target_name - assert plugin_name - - # If the target name is derived from the class name, no need to - # forward the class name. - if plugin_class_name and plugin_class_name != plugin_name: - extra.append(f"CLASS_NAME {plugin_class_name}") - - qmldir = None - plugin_type = scope.get_string("PLUGIN_TYPE") - plugin_function_name = get_cmake_api_call("qt_add_plugin") - if plugin_type: - extra.append(f"TYPE {plugin_type}") - elif is_qml_plugin: - plugin_function_name = get_cmake_api_call("qt_add_qml_module") - qmldir = write_qml_plugin(cm_fh, plugin_name, scope, indent=indent, extra_lines=extra) - else: - target_path = scope.expandString("target.path") - target_path = replace_path_constants(target_path, scope) - if target_path: - extra.append(f'INSTALL_DIRECTORY "{target_path}"') - else: - extra.append("SKIP_INSTALL") - - past_major_versions = scope.expandString("QML_PAST_MAJOR_VERSIONS") - if past_major_versions: - extra.append(f"PAST_MAJOR_VERSIONS {past_major_versions}") - - if "qmltypes" in scope.get("CONFIG"): - extra.append("GENERATE_QMLTYPES") - - if "install_qmltypes" in scope.get("CONFIG"): - extra.append("INSTALL_QMLTYPES") - - if "static" in scope.get("CONFIG"): - extra.append("STATIC") - - plugin_extends = scope.get_string("PLUGIN_EXTENDS") - if plugin_type != "platform" and plugin_extends == "-": - extra.append("DEFAULT_IF FALSE") - - forward_target_info(scope, extra) - - write_main_part( - cm_fh, - plugin_name, - "Plugin", - plugin_function_name, - scope, - indent=indent, - extra_lines=extra, - known_libraries={}, - extra_keys=[], - ) - - if qmldir: - write_qml_plugin_epilogue(cm_fh, plugin_name, scope, qmldir, indent) - - return plugin_name - - -def get_qml_import_version(scope: Scope, target: str) -> str: - import_version = scope.get_string("IMPORT_VERSION") - if not import_version: - import_version = scope.get_string("QML_IMPORT_VERSION") - if not import_version: - import_major_version = scope.get_string("QML_IMPORT_MAJOR_VERSION") - import_minor_version = scope.get_string("QML_IMPORT_MINOR_VERSION") - - if not import_major_version and not import_minor_version: - raise RuntimeError(f"No QML_IMPORT_VERSION info found for target {target}.") - - if not import_minor_version: - import_minor_version = str(0) - import_version = f"{import_major_version}.{import_minor_version}" - - if import_version: - replacements = [ - ("$$QT_MINOR_VERSION", "${PROJECT_VERSION_MINOR}"), - ("$$QT_VERSION", "${PROJECT_VERSION}"), - ] - for needle, replacement in replacements: - import_version = import_version.replace(needle, replacement) - return import_version - - -def write_qml_module( - cm_fh: IO[str], - target: str, - scope: Scope, - scopes: List[Scope], - resource: QtResource, - extra_add_qml_module_args: List[str] = [], - indent: int = 0, -): - uri = scope.get_string("QML_IMPORT_NAME") - if not uri: - uri = target - - try: - version = get_qml_import_version(scope, target) - except RuntimeError: - version = "${PROJECT_VERSION}" - - dest_dir = scope.expandString("DESTDIR") - if dest_dir: - dest_dir = f"${{CMAKE_CURRENT_BINARY_DIR}}/{dest_dir}" - - content = "" - - qml_dir = None - qml_dir_dynamic_imports = False - - qmldir_file_path_list = scope.get_files("qmldir.files") - assert len(qmldir_file_path_list) < 2, "File path must only contain one path" - qmldir_file_path = qmldir_file_path_list[0] if qmldir_file_path_list else "qmldir" - qmldir_file_path = os.path.join(os.getcwd(), qmldir_file_path[0]) - - dynamic_qmldir = scope.get("DYNAMIC_QMLDIR") - if os.path.exists(qmldir_file_path): - qml_dir = QmlDir() - qml_dir.from_file(qmldir_file_path) - elif dynamic_qmldir: - qml_dir = QmlDir() - qml_dir.from_lines(dynamic_qmldir) - qml_dir_dynamic_imports = True - - content += "set(module_dynamic_qml_imports\n " - if len(qml_dir.imports) != 0: - content += "\n ".join(qml_dir.imports) - content += "\n)\n\n" - - for sc in scopes[1:]: - import_list = [] - qml_imports = sc.get("DYNAMIC_QMLDIR") - for qml_import in qml_imports: - if not qml_import.startswith("import "): - raise RuntimeError( - "Only qmldir import statements expected in conditional scope!" - ) - import_list.append(qml_import[len("import ") :].replace(" ", "/")) - if len(import_list) == 0: - continue - - assert sc.condition - - content += f"if ({sc.condition})\n" - content += " list(APPEND module_dynamic_qml_imports\n " - content += "\n ".join(import_list) - content += "\n )\nendif()\n\n" - - content += dedent( - f"""\ - qt_add_qml_module({target} - URI {uri} - VERSION {version} - """ - ) - - if resource is not None: - qml_files = list(filter(is_qtquick_source_file, resource.files.keys())) - if qml_files: - content += " QML_FILES\n" - for file in qml_files: - content += f" {file}\n" - other_files = list(itertools.filterfalse(is_qtquick_source_file, resource.files.keys())) - if other_files: - content += " RESOURCES\n" - for file in other_files: - content += f" {file}\n" - if resource.prefix != "/": - content += f" RESOURCE_PREFIX {resource.prefix}\n" - if scope.TEMPLATE == "app": - content += " NO_RESOURCE_TARGET_PATH\n" - if dest_dir: - content += f" OUTPUT_DIRECTORY {dest_dir}\n" - - if qml_dir is not None: - if qml_dir.designer_supported: - content += " DESIGNER_SUPPORTED\n" - if len(qml_dir.classname) != 0: - content += f" CLASSNAME {qml_dir.classname}\n" - if len(qml_dir.depends) != 0: - content += " DEPENDENCIES\n" - for dep in qml_dir.depends: - content += f" {dep[0]}/{dep[1]}\n" - if len(qml_dir.type_names) == 0: - content += " SKIP_TYPE_REGISTRATION\n" - if len(qml_dir.imports) != 0 and not qml_dir_dynamic_imports: - qml_dir_imports_line = " \n".join(qml_dir.imports) - content += f" IMPORTS\n{qml_dir_imports_line}" - if qml_dir_dynamic_imports: - content += " IMPORTS ${module_dynamic_qml_imports}\n" - if len(qml_dir.optional_imports) != 0: - qml_dir_optional_imports_line = " \n".join(qml_dir.optional_imports) - content += f" OPTIONAL_IMPORTS\n{qml_dir_optional_imports_line}" - if qml_dir.plugin_optional: - content += " PLUGIN_OPTIONAL\n" - - for arg in extra_add_qml_module_args: - content += " " - content += arg - content += "\n" - content += ")\n" - - if resource: - content += write_resource_source_file_properties( - sorted(resource.files.keys()), - resource.files, - resource.base_dir, - resource.skip_qtquick_compiler, - ) - - content += "\n" - cm_fh.write(content) - - -def write_qml_plugin( - cm_fh: IO[str], - target: str, - scope: Scope, - *, - extra_lines: Optional[List[str]] = None, - indent: int = 0, - **kwargs: Any, -) -> Optional[QmlDir]: - # Collect other args if available - if extra_lines is None: - extra_lines = [] - indent += 2 - - target_path = scope.get_string("TARGETPATH") - if target_path: - uri = target_path.replace("/", ".") - import_name = scope.get_string("IMPORT_NAME") - # Catch special cases such as foo.QtQuick.2.bar, which when converted - # into a target path via cmake will result in foo/QtQuick/2/bar, which is - # not what we want. So we supply the target path override. - target_path_from_uri = uri.replace(".", "/") - if target_path != target_path_from_uri: - extra_lines.append(f'TARGET_PATH "{target_path}"') - if import_name: - extra_lines.append(f'URI "{import_name}"') - else: - uri = re.sub("\\.\\d+", "", uri) - extra_lines.append(f'URI "{uri}"') - - import_version = get_qml_import_version(scope, target) - if import_version: - extra_lines.append(f'VERSION "{import_version}"') - - plugindump_dep = scope.get_string("QML_PLUGINDUMP_DEPENDENCIES") - - if plugindump_dep: - extra_lines.append(f'QML_PLUGINDUMP_DEPENDENCIES "{plugindump_dep}"') - - qml_dir = None - qmldir_file_path = os.path.join(os.getcwd(), "qmldir") - qml_dir_dynamic_imports = False - if os.path.exists(qmldir_file_path): - qml_dir = QmlDir() - qml_dir.from_file(qmldir_file_path) - else: - dynamic_qmldir = scope.get("DYNAMIC_QMLDIR") - if not dynamic_qmldir: - return None - qml_dir = QmlDir() - qml_dir.from_lines(dynamic_qmldir) - qml_dir_dynamic_imports = True - - # Check scopes for conditional entries - scopes = flatten_scopes(scope) - cm_fh.write("set(module_dynamic_qml_imports\n ") - if len(qml_dir.imports) != 0: - cm_fh.write("\n ".join(qml_dir.imports)) - cm_fh.write("\n)\n\n") - - for sc in scopes[1:]: - import_list = [] - qml_imports = sc.get("DYNAMIC_QMLDIR") - for qml_import in qml_imports: - if not qml_import.startswith("import "): - raise RuntimeError( - "Only qmldir import statements expected in conditional scope!" - ) - import_list.append(qml_import[len("import ") :].replace(" ", "/")) - if len(import_list) == 0: - continue - - assert sc.condition - - cm_fh.write(f"if ({sc.condition})\n") - cm_fh.write(" list(APPEND module_dynamic_qml_imports\n ") - cm_fh.write("\n ".join(import_list)) - cm_fh.write("\n )\nendif()\n\n") - - if qml_dir is not None: - if qml_dir.designer_supported: - extra_lines.append("DESIGNER_SUPPORTED") - if len(qml_dir.classname) != 0: - extra_lines.append(f"CLASSNAME {qml_dir.classname}") - if len(qml_dir.depends) != 0: - extra_lines.append("DEPENDENCIES") - for dep in qml_dir.depends: - extra_lines.append(f" {dep[0]}/{dep[1]}") - if len(qml_dir.type_names) == 0: - extra_lines.append("SKIP_TYPE_REGISTRATION") - if len(qml_dir.imports) != 0 and not qml_dir_dynamic_imports: - qml_dir_imports_line = "\n ".join(qml_dir.imports) - extra_lines.append("IMPORTS\n " f"{qml_dir_imports_line}") - if qml_dir_dynamic_imports: - extra_lines.append("IMPORTS ${module_dynamic_qml_imports}") - if len(qml_dir.optional_imports): - qml_dir_optional_imports_line = "\n ".join(qml_dir.optional_imports) - extra_lines.append("OPTIONAL_IMPORTS\n " f"{qml_dir_optional_imports_line}") - if qml_dir.plugin_optional: - extra_lines.append("PLUGIN_OPTIONAL") - - return qml_dir - - -def write_qml_plugin_epilogue( - cm_fh: IO[str], target: str, scope: Scope, qmldir: QmlDir, indent: int = 0 -): - - qml_files = scope.get_files("QML_FILES", use_vpath=True) - if qml_files: - - indent_0 = spaces(indent) - indent_1 = spaces(indent + 1) - # Quote file paths in case there are spaces. - qml_files_quoted = [f'"{qf}"' for qf in qml_files] - - indented_qml_files = f"\n{indent_1}".join(qml_files_quoted) - cm_fh.write(f"\n{indent_0}set(qml_files\n{indent_1}" f"{indented_qml_files}\n)\n") - - for qml_file in qml_files: - if qml_file in qmldir.type_names: - qmldir_file_info = qmldir.type_names[qml_file] - cm_fh.write(f"{indent_0}set_source_files_properties({qml_file} PROPERTIES\n") - cm_fh.write(f'{indent_1}QT_QML_SOURCE_VERSION "{qmldir_file_info.versions}"\n') - # Only write typename if they are different, CMake will infer - # the name by default - if ( - os.path.splitext(os.path.basename(qmldir_file_info.path))[0] - != qmldir_file_info.type_name - ): - cm_fh.write(f"{indent_1}QT_QML_SOURCE_TYPENAME {qmldir_file_info.type_name}\n") - if qmldir_file_info.singleton: - cm_fh.write(f"{indent_1}QT_QML_SINGLETON_TYPE TRUE\n") - if qmldir_file_info.internal: - cm_fh.write(f"{indent_1}QT_QML_INTERNAL_TYPE TRUE\n") - cm_fh.write(f"{indent_0})\n") - else: - cm_fh.write( - f"{indent_0}set_source_files_properties({qml_file} PROPERTIES\n" - f"{indent_1}QT_QML_SKIP_QMLDIR_ENTRY TRUE\n" - f"{indent_0})\n" - ) - - cm_fh.write( - f"\n{indent_0}qt6_target_qml_files({target}\n{indent_1}FILES\n" - f"{spaces(indent+2)}${{qml_files}}\n)\n" - ) - - -def handle_app_or_lib( - scope: Scope, - cm_fh: IO[str], - *, - indent: int = 0, - is_example: bool = False, - is_user_project=False, -) -> None: - assert scope.TEMPLATE in ("app", "lib") - - config = scope.get("CONFIG") - is_jar = "java" in config - is_lib = scope.TEMPLATE == "lib" - is_qml_plugin = any("qml_plugin" == s for s in scope.get("_LOADED")) - is_plugin = "plugin" in config - is_qt_plugin = any("qt_plugin" == s for s in scope.get("_LOADED")) or is_qml_plugin - target = "" - target_ref = None - gui = all(val not in config for val in ["console", "cmdline", "-app_bundle"]) and all( - val not in scope.expand("QT") for val in ["testlib", "testlib-private"] - ) - - if is_jar: - write_jar(cm_fh, scope, indent=indent) - elif "qt_helper_lib" in scope.get("_LOADED"): - assert not is_example - target = write_3rdparty_library(cm_fh, scope, indent=indent) - elif is_example: - target = write_example( - cm_fh, scope, gui, indent=indent, is_plugin=is_plugin, is_user_project=is_user_project - ) - elif is_qt_plugin: - assert not is_example - target = write_plugin(cm_fh, scope, indent=indent) - elif (is_lib and "qt_module" not in scope.get("_LOADED")) or is_plugin: - assert not is_example - target = write_generic_library(cm_fh, scope, indent=indent) - elif is_lib or "qt_module" in scope.get("_LOADED"): - assert not is_example - target = write_module(cm_fh, scope, indent=indent) - elif "qt_tool" in scope.get("_LOADED"): - assert not is_example - target, target_ref = write_tool(cm_fh, scope, indent=indent) - elif "qt_app" in scope.get("_LOADED"): - assert not is_example - scope._is_internal_qt_app = True - target = write_qt_app(cm_fh, scope, indent=indent) - else: - if "testcase" in config or "testlib" in config or "qmltestcase" in config: - assert not is_example - target = write_test(cm_fh, scope, gui, indent=indent) - else: - target = write_binary(cm_fh, scope, gui, indent=indent) - - if target_ref is None: - target_ref = target - - # ind = spaces(indent) - cmake_api_call = get_cmake_api_call("qt_add_docs") - write_source_file_list( - cm_fh, - scope, - "", - ["QMAKE_DOCS"], - indent, - header=f"{cmake_api_call}({target_ref}\n", - footer=")\n", - ) - - # Generate qmltypes instruction for anything that may have CONFIG += qmltypes - # that is not a qml plugin - if ( - not is_example - and "qmltypes" in scope.get("CONFIG") - and "qml_plugin" not in scope.get("_LOADED") - ): - cm_fh.write(f"\n{spaces(indent)}set_target_properties({target_ref} PROPERTIES\n") - - install_dir = scope.expandString("QMLTYPES_INSTALL_DIR") - if install_dir: - cm_fh.write(f"{spaces(indent+1)}QT_QML_MODULE_INSTALL_QMLTYPES TRUE\n") - - import_version = get_qml_import_version(scope, target) - if import_version: - cm_fh.write(f"{spaces(indent+1)}QT_QML_MODULE_VERSION {import_version}\n") - - past_major_versions = scope.expandString("QML_PAST_MAJOR_VERSIONS") - if past_major_versions: - cm_fh.write(f"{spaces(indent+1)}QT_QML_PAST_MAJOR_VERSIONS {past_major_versions}\n") - - import_name = scope.expandString("QML_IMPORT_NAME") - if import_name: - cm_fh.write(f"{spaces(indent+1)}QT_QML_MODULE_URI {import_name}\n") - - json_output_filename = scope.expandString("QMLTYPES_FILENAME") - if json_output_filename: - cm_fh.write(f"{spaces(indent+1)}QT_QMLTYPES_FILENAME {json_output_filename}\n") - - target_path = scope.get("TARGETPATH") - if target_path: - cm_fh.write(f"{spaces(indent+1)}QT_QML_MODULE_TARGET_PATH {target_path}\n") - - if install_dir: - install_dir = install_dir.replace("$$[QT_INSTALL_QML]", "${INSTALL_QMLDIR}") - cm_fh.write(f'{spaces(indent+1)}QT_QML_MODULE_INSTALL_DIR "{install_dir}"\n') - - cm_fh.write(f"{spaces(indent)})\n\n") - cm_fh.write(f"qt6_qml_type_registration({target_ref})\n") - - -def handle_top_level_repo_project(scope: Scope, cm_fh: IO[str]): - # qtdeclarative - project_file_name = os.path.splitext(os.path.basename(scope.file_absolute_path))[0] - - # declarative - file_name_without_qt_prefix = project_file_name[2:] - - # Qt::Declarative - qt_lib = map_qt_library(file_name_without_qt_prefix) - - # Found a mapping, adjust name. - if qt_lib != file_name_without_qt_prefix: - # QtDeclarative - qt_lib = re.sub(r":", r"", qt_lib) - - # Declarative - qt_lib_no_prefix = qt_lib[2:] - else: - qt_lib += "_FIXME" - qt_lib_no_prefix = qt_lib - - header = dedent( - f"""\ - cmake_minimum_required(VERSION {cmake_version_string}) - - include(.cmake.conf) - project({qt_lib} - VERSION "${{QT_REPO_MODULE_VERSION}}" - DESCRIPTION "Qt {qt_lib_no_prefix} Libraries" - HOMEPAGE_URL "https://qt.io/" - LANGUAGES CXX C - ) - - find_package(Qt6 ${{PROJECT_VERSION}} CONFIG REQUIRED COMPONENTS BuildInternals Core SET_ME_TO_SOMETHING_USEFUL) - find_package(Qt6 ${{PROJECT_VERSION}} CONFIG OPTIONAL_COMPONENTS SET_ME_TO_SOMETHING_USEFUL) - - """ - ) - - build_repo = dedent( - """\ - qt_build_repo() - """ - ) - - cm_fh.write(f"{header}{expand_project_requirements(scope)}{build_repo}") - - -def create_top_level_cmake_conf(): - conf_file_name = ".cmake.conf" - try: - with open(conf_file_name, "x") as file: - file.write('set(QT_REPO_MODULE_VERSION "6.10.0")\n') - except FileExistsError: - pass - - -def find_top_level_repo_project_file(project_file_path: str = "") -> Optional[str]: - qmake_or_cmake_conf_path = find_qmake_or_cmake_conf(project_file_path) - qmake_or_cmake_dir = os.path.dirname(qmake_or_cmake_conf_path) - - # Hope to a programming god that there's only one .pro file at the - # top level directory of repository. - glob_result = glob.glob(os.path.join(qmake_or_cmake_dir, "*.pro")) - if len(glob_result) > 0: - return glob_result[0] - return None - - -def handle_top_level_repo_tests_project(scope: Scope, cm_fh: IO[str]): - - content = dedent( - """\ - if(QT_BUILD_STANDALONE_TESTS) - # Add qt_find_package calls for extra dependencies that need to be found when building - # the standalone tests here. - endif() - qt_build_tests() -""" - ) - - cm_fh.write(f"{content}") - - -def write_regular_cmake_target_scope_section( - scope: Scope, cm_fh: IO[str], indent: int = 0, skip_sources: bool = False -): - if not skip_sources: - target_sources = "target_sources(${PROJECT_NAME} PUBLIC" - write_all_source_file_lists(cm_fh, scope, target_sources, indent=indent, footer=")") - - write_include_paths( - cm_fh, - scope, - "target_include_directories(${{PROJECT_NAME}} PUBLIC", - indent=indent, - footer=")", - ) - write_defines( - cm_fh, - scope, - "target_compile_definitions(${{PROJECT_NAME}} PUBLIC", - indent=indent, - footer=")", - ) - (public_libs, private_libs) = extract_cmake_libraries(scope) - write_list( - cm_fh, - private_libs, - "", - indent=indent, - header="target_link_libraries(${{PROJECT_NAME}} PRIVATE\n", - footer=")", - ) - write_list( - cm_fh, - public_libs, - "", - indent=indent, - header="target_link_libraries(${{PROJECT_NAME}} PUBLIC\n", - footer=")", - ) - write_compile_options( - cm_fh, scope, "target_compile_options(${{PROJECT_NAME}}", indent=indent, footer=")" - ) - - -def handle_config_test_project(scope: Scope, cm_fh: IO[str]): - project_name = os.path.splitext(os.path.basename(scope.file_absolute_path))[0] - content = ( - f"cmake_minimum_required(VERSION 3.16)\n" - f"project(config_test_{project_name} LANGUAGES C CXX)\n" - """ -if(DEFINED QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_PREFIX_PATH) - set(CMAKE_SYSTEM_PREFIX_PATH "${QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_PREFIX_PATH}") -endif() -if(DEFINED QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_FRAMEWORK_PATH) - set(CMAKE_SYSTEM_FRAMEWORK_PATH "${QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_FRAMEWORK_PATH}") -endif() - -foreach(p ${QT_CONFIG_COMPILE_TEST_PACKAGES}) - find_package(${p}) -endforeach() - -if(QT_CONFIG_COMPILE_TEST_LIBRARIES) - link_libraries(${QT_CONFIG_COMPILE_TEST_LIBRARIES}) -endif() -if(QT_CONFIG_COMPILE_TEST_LIBRARY_TARGETS) - foreach(lib ${QT_CONFIG_COMPILE_TEST_LIBRARY_TARGETS}) - if(TARGET ${lib}) - link_libraries(${lib}) - endif() - endforeach() -endif() -""" - ) - cm_fh.write(f"{content}\n") - - # Remove default QT libs. - scope._append_operation("QT", RemoveOperation(["core", "gui"])) - - add_target = "add_executable(${{PROJECT_NAME}}" - - temp_buffer = io.StringIO() - write_all_source_file_lists(temp_buffer, scope, add_target, indent=0) - buffer_value = temp_buffer.getvalue() - - if buffer_value: - cm_fh.write(buffer_value) - else: - cm_fh.write(add_target) - cm_fh.write(")\n") - - indent = 0 - write_regular_cmake_target_scope_section(scope, cm_fh, indent, skip_sources=True) - - recursive_evaluate_scope(scope) - scopes = flatten_scopes(scope) - scopes = merge_scopes(scopes) - - assert len(scopes) - assert scopes[0].total_condition == "ON" - - for c in scopes[1:]: - extend_scope_io_string = io.StringIO() - write_regular_cmake_target_scope_section(c, extend_scope_io_string, indent=indent + 1) - extend_string = extend_scope_io_string.getvalue() - - if extend_string: - assert c.total_condition, "Cannot write if with empty condition" - extend_scope = ( - f"\nif({map_to_cmake_condition(c.total_condition)})\n" - f"{extend_string}" - f"endif()\n" - ) - cm_fh.write(extend_scope) - - -def cmakeify_scope( - scope: Scope, - cm_fh: IO[str], - *, - indent: int = 0, - is_example: bool = False, - is_user_project: bool = False, -) -> None: - template = scope.TEMPLATE - - if is_user_project: - if template == "subdirs": - handle_subdir(scope, cm_fh, indent=indent, is_example=True, is_user_project=True) - elif template in ("app", "lib"): - handle_app_or_lib(scope, cm_fh, indent=indent, is_example=True, is_user_project=True) - else: - temp_buffer = io.StringIO() - - # Handle top level repo project in a special way. - if is_top_level_repo_project(scope.file_absolute_path): - create_top_level_cmake_conf() - handle_top_level_repo_project(scope, temp_buffer) - # Same for top-level tests. - elif is_top_level_repo_tests_project(scope.file_absolute_path): - handle_top_level_repo_tests_project(scope, temp_buffer) - elif is_config_test_project(scope.file_absolute_path): - handle_config_test_project(scope, temp_buffer) - elif template == "subdirs": - handle_subdir(scope, temp_buffer, indent=indent, is_example=is_example) - elif template in ("app", "lib"): - handle_app_or_lib(scope, temp_buffer, indent=indent, is_example=is_example) - else: - print(f" XXXX: {scope.file}: Template type {template} not yet supported.") - - buffer_value = temp_buffer.getvalue() - - if is_top_level_repo_examples_project(scope.file_absolute_path): - # Wrap top level examples project with some commands which - # are necessary to build examples as part of the overall - # build. - buffer_value = f"qt_examples_build_begin()\n\n{buffer_value}\nqt_examples_build_end()\n" - - cm_fh.write(buffer_value) - - -def generate_new_cmakelists( - scope: Scope, *, is_example: bool = False, is_user_project: bool = True, debug: bool = False -) -> None: - if debug: - print("Generating CMakeLists.gen.txt") - with open(scope.generated_cmake_lists_path, "w") as cm_fh: - assert scope.file - cm_fh.write(f"# Generated from {os.path.basename(scope.file)}.\n\n") - - is_example_heuristic = is_example_project(scope.file_absolute_path) - final_is_example_decision = is_example or is_example_heuristic - cmakeify_scope( - scope, cm_fh, is_example=final_is_example_decision, is_user_project=is_user_project - ) - - -def do_include(scope: Scope, *, debug: bool = False) -> None: - for c in scope.children: - do_include(c) - - for include_index, include_file in enumerate(scope.get_files("_INCLUDED", is_include=True)): - if not include_file: - continue - # Ignore selfcover.pri as this generates too many incompatible flags - # need to be removed with special cases - if include_file.endswith("selfcover.pri"): - continue - if include_file.startswith("${QT_SOURCE_TREE}"): - root_source_dir = get_top_level_repo_project_path(scope.file_absolute_path) - include_file = include_file.replace("${QT_SOURCE_TREE}", root_source_dir) - if not os.path.isfile(include_file): - generated_config_pri_pattern = re.compile(r"qt.+?-config\.pri$") - match_result = re.search(generated_config_pri_pattern, include_file) - if not match_result: - print(f" XXXX: Failed to include {include_file}.") - continue - - include_op = scope._get_operation_at_index("_INCLUDED", include_index) - include_line_no = include_op._line_no - - include_result, project_file_content = parseProFile(include_file, debug=debug) - include_scope = Scope.FromDict( - None, - include_file, - include_result.asDict().get("statements"), - "", - scope.basedir, - project_file_content=project_file_content, - parent_include_line_no=include_line_no, - ) # This scope will be merged into scope! - - do_include(include_scope) - - scope.merge(include_scope) - - -def copy_generated_file_to_final_location( - scope: Scope, output_file: str, keep_temporary_files=False, debug: bool = False -) -> None: - if debug: - print(f"Copying {scope.generated_cmake_lists_path} to {output_file}") - - base_dir = os.path.dirname(output_file) - base_dir_abs = os.path.realpath(base_dir) - os.makedirs(base_dir_abs, exist_ok=True) - - copyfile(scope.generated_cmake_lists_path, output_file) - if not keep_temporary_files: - os.remove(scope.generated_cmake_lists_path) - - -def cmake_project_has_skip_marker(project_file_path: str = "") -> bool: - dir_path = os.path.dirname(project_file_path) - cmake_project_path = os.path.join(dir_path, "CMakeLists.txt") - if not os.path.exists(cmake_project_path): - return False - - with open(cmake_project_path, "r") as file_fd: - contents = file_fd.read() - - if "# special case skip regeneration" in contents: - return True - - return False - - -def should_convert_project(project_file_path: str = "", ignore_skip_marker: bool = False) -> bool: - qmake_or_cmake_conf_path = find_qmake_or_cmake_conf(project_file_path) - qmake_or_cmake_conf_dir_path = os.path.dirname(qmake_or_cmake_conf_path) - - project_relative_path = os.path.relpath(project_file_path, qmake_or_cmake_conf_dir_path) - - # Skip cmake auto tests, they should not be converted. - if project_relative_path.startswith("tests/auto/cmake"): - return False - if project_relative_path.startswith("tests/auto/installed_cmake"): - return False - - # Skip qmake testdata projects. - if project_relative_path.startswith("tests/auto/tools/qmake/testdata"): - return False - - # Skip doc snippets. - if fnmatch.fnmatch(project_relative_path, "src/*/doc/snippets/*"): - return False - - # Skip certain config tests. - config_tests = [ - # Relative to qtbase/config.tests - "arch/arch.pro", - "avx512/avx512.pro", - "stl/stl.pro", - "verifyspec/verifyspec.pro", - "x86_simd/x86_simd.pro", - # Relative to repo src dir - "config.tests/hostcompiler/hostcompiler.pro", - ] - skip_certain_tests = any(project_relative_path.startswith(c) for c in config_tests) - if skip_certain_tests: - return False - - # Skip if CMakeLists.txt in the same path as project_file_path has a - # special skip marker. - if not ignore_skip_marker and cmake_project_has_skip_marker(project_file_path): - return False - - return True - - -def should_convert_project_after_parsing( - file_scope: Scope, skip_subdirs_project: bool = False -) -> bool: - template = file_scope.TEMPLATE - if template == "subdirs" and skip_subdirs_project: - return False - return True - - -def main() -> None: - # Be sure of proper Python version - assert sys.version_info >= (3, 7) - - args = _parse_commandline() - - debug_parsing = args.debug_parser or args.debug - if args.skip_condition_cache: - set_condition_simplified_cache_enabled(False) - - backup_current_dir = os.getcwd() - - for file in args.files: - new_current_dir = os.path.dirname(file) - file_relative_path = os.path.basename(file) - if new_current_dir: - os.chdir(new_current_dir) - - project_file_absolute_path = os.path.abspath(file_relative_path) - if not should_convert_project(project_file_absolute_path, args.ignore_skip_marker): - print(f'Skipping conversion of project: "{project_file_absolute_path}"') - continue - - parseresult, project_file_content = parseProFile(file_relative_path, debug=debug_parsing) - - # If CMake api version is given on command line, that means the - # user wants to force use that api version. - global cmake_api_version - if args.api_version: - cmake_api_version = args.api_version - else: - # Otherwise detect the api version in the old CMakeLists.txt - # if it exists. - detected_cmake_api_version = detect_cmake_api_version_used_in_file_content( - file_relative_path - ) - if detected_cmake_api_version: - cmake_api_version = detected_cmake_api_version - - 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( - None, - file_relative_path, - parseresult.asDict().get("statements"), - project_file_content=project_file_content, - ) - - if args.debug_pro_structure or args.debug: - print("\n\n#### .pro/.pri file structure:") - file_scope.dump() - print("\n#### End of .pro/.pri file structure.\n") - - do_include(file_scope, debug=debug_parsing) - - if args.debug_full_pro_structure or args.debug: - print("\n\n#### Full .pro/.pri file structure:") - file_scope.dump() - print("\n#### End of full .pro/.pri file structure.\n") - - if not should_convert_project_after_parsing(file_scope, args.skip_subdirs_project): - print(f'Skipping conversion of project: "{project_file_absolute_path}"') - continue - - generate_new_cmakelists( - file_scope, - is_example=args.is_example, - is_user_project=args.is_user_project, - debug=args.debug, - ) - - copy_generated_file = True - - output_file = file_scope.original_cmake_lists_path - if args.output_file: - output_file = args.output_file - - if not args.skip_special_case_preservation: - debug_special_case = args.debug_special_case_preservation or args.debug - handler = SpecialCaseHandler( - output_file, - file_scope.generated_cmake_lists_path, - file_scope.basedir, - keep_temporary_files=args.keep_temporary_files, - debug=debug_special_case, - ) - - copy_generated_file = handler.handle_special_cases() - - if copy_generated_file: - copy_generated_file_to_final_location( - file_scope, output_file, keep_temporary_files=args.keep_temporary_files - ) - os.chdir(backup_current_dir) - - -if __name__ == "__main__": - main() diff --git a/util/cmake/pro_conversion_rate.py b/util/cmake/pro_conversion_rate.py deleted file mode 100755 index 30aae95b065..00000000000 --- a/util/cmake/pro_conversion_rate.py +++ /dev/null @@ -1,210 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2019 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -from __future__ import annotations - -""" -This utility script shows statistics about -converted .pro -> CMakeLists.txt files. - -To execute: python3 pro_conversion_rate.py -where can be any qt source directory. For better statistics, -specify a module root source dir (like ./qtbase or ./qtsvg). - -""" - -from argparse import ArgumentParser - -import os -import typing -from typing import Dict, Union -from timeit import default_timer - - -def _parse_commandline(): - parser = ArgumentParser(description="Find pro files for which there are no CMakeLists.txt.") - parser.add_argument( - "source_directory", metavar="", type=str, help="The source directory" - ) - - return parser.parse_args() - - -class Blacklist: - """Class to check if a certain dir_name / dir_path is blacklisted""" - - def __init__(self, names: typing.List[str], path_parts: typing.List[str]): - self.names = names - self.path_parts = path_parts - - # The lookup algorithm - self.lookup = self.is_blacklisted_part - self.tree = None - - try: - # If package is available, use Aho-Corasick algorithm, - from ahocorapy.keywordtree import KeywordTree # type: ignore - - self.tree = KeywordTree(case_insensitive=True) - - for p in self.path_parts: - self.tree.add(p) - self.tree.finalize() - - self.lookup = self.is_blacklisted_part_aho - except ImportError: - pass - - def is_blacklisted(self, dir_name: str, dir_path: str) -> bool: - # First check if exact dir name is blacklisted. - if dir_name in self.names: - return True - - # Check if a path part is blacklisted (e.g. util/cmake) - return self.lookup(dir_path) - - def is_blacklisted_part(self, dir_path: str) -> bool: - if any(part in dir_path for part in self.path_parts): - return True - return False - - def is_blacklisted_part_aho(self, dir_path: str) -> bool: - return self.tree.search(dir_path) is not None # type: ignore - - -def recursive_scan(path: str, extension: str, result_paths: typing.List[str], blacklist: Blacklist): - """Find files ending with a certain extension, filtering out blacklisted entries""" - try: - for entry in os.scandir(path): - if entry.is_file() and entry.path.endswith(extension): - result_paths.append(entry.path) - elif entry.is_dir(): - if blacklist.is_blacklisted(entry.name, entry.path): - continue - recursive_scan(entry.path, extension, result_paths, blacklist) - except Exception as e: - print(e) - - -def check_for_cmake_project(pro_path: str) -> bool: - pro_dir_name = os.path.dirname(pro_path) - cmake_project_path = os.path.join(pro_dir_name, "CMakeLists.txt") - return os.path.exists(cmake_project_path) - - -def compute_stats( - src_path: str, - pros_with_missing_project: typing.List[str], - total_pros: int, - existing_pros: int, - missing_pros: int, -) -> dict: - stats: Dict[str, Dict[str, Union[str, int, float]]] = {} - stats["total projects"] = {"label": "Total pro files found", "value": total_pros} - stats["existing projects"] = { - "label": "Existing CMakeLists.txt files found", - "value": existing_pros, - } - stats["missing projects"] = { - "label": "Missing CMakeLists.txt files found", - "value": missing_pros, - } - stats["missing examples"] = {"label": "Missing examples", "value": 0} - stats["missing tests"] = {"label": "Missing tests", "value": 0} - stats["missing src"] = {"label": "Missing src/**/**", "value": 0} - stats["missing plugins"] = {"label": "Missing plugins", "value": 0} - - for p in pros_with_missing_project: - rel_path = os.path.relpath(p, src_path) - if rel_path.startswith("examples"): - assert isinstance(stats["missing examples"]["value"], int) - stats["missing examples"]["value"] += 1 - elif rel_path.startswith("tests"): - assert isinstance(stats["missing tests"]["value"], int) - stats["missing tests"]["value"] += 1 - elif rel_path.startswith(os.path.join("src", "plugins")): - assert isinstance(stats["missing plugins"]["value"], int) - stats["missing plugins"]["value"] += 1 - elif rel_path.startswith("src"): - assert isinstance(stats["missing src"]["value"], int) - stats["missing src"]["value"] += 1 - - for stat in stats: - if int(stats[stat]["value"]) > 0: - stats[stat]["percentage"] = round(float(stats[stat]["value"]) * 100 / total_pros, 2) - return stats - - -def print_stats( - src_path: str, - pros_with_missing_project: typing.List[str], - stats: dict, - scan_time: float, - script_time: float, -): - - if stats["total projects"]["value"] == 0: - print("No .pro files found. Did you specify a correct source path?") - return - - if stats["total projects"]["value"] == stats["existing projects"]["value"]: - print("All projects were converted.") - else: - print("Missing CMakeLists.txt files for the following projects: \n") - - for p in pros_with_missing_project: - rel_path = os.path.relpath(p, src_path) - print(rel_path) - - print("\nStatistics: \n") - - for stat in stats: - if stats[stat]["value"] > 0: - print( - f"{stats[stat]['label']:<40}: {stats[stat]['value']} ({stats[stat]['percentage']}%)" - ) - - print(f"\n{'Scan time':<40}: {scan_time:.10f} seconds") - print(f"{'Total script time':<40}: {script_time:.10f} seconds") - - -def main(): - args = _parse_commandline() - src_path = os.path.abspath(args.source_directory) - pro_paths = [] - - extension = ".pro" - - blacklist_names = ["config.tests", "doc", "3rdparty", "angle"] - blacklist_path_parts = [os.path.join("util", "cmake")] - - script_start_time = default_timer() - blacklist = Blacklist(blacklist_names, blacklist_path_parts) - - scan_time_start = default_timer() - recursive_scan(src_path, extension, pro_paths, blacklist) - scan_time_end = default_timer() - scan_time = scan_time_end - scan_time_start - - total_pros = len(pro_paths) - - pros_with_missing_project = [] - for pro_path in pro_paths: - if not check_for_cmake_project(pro_path): - pros_with_missing_project.append(pro_path) - - missing_pros = len(pros_with_missing_project) - existing_pros = total_pros - missing_pros - - stats = compute_stats( - src_path, pros_with_missing_project, total_pros, existing_pros, missing_pros - ) - script_end_time = default_timer() - script_time = script_end_time - script_start_time - - print_stats(src_path, pros_with_missing_project, stats, scan_time, script_time) - - -if __name__ == "__main__": - main() diff --git a/util/cmake/qmake_parser.py b/util/cmake/qmake_parser.py deleted file mode 100755 index 8cf7b1c46e4..00000000000 --- a/util/cmake/qmake_parser.py +++ /dev/null @@ -1,422 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2018 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -import collections -import os -import re -from itertools import chain -from typing import Tuple - -import pyparsing as pp # type: ignore - -from helper import _set_up_py_parsing_nicer_debug_output - -_set_up_py_parsing_nicer_debug_output(pp) - - -def fixup_linecontinuation(contents: str) -> str: - # Remove all line continuations, aka a backslash followed by - # a newline character with an arbitrary amount of whitespace - # between the backslash and the newline. - # This greatly simplifies the qmake parsing grammar. - contents = re.sub(r"([^\t ])\\[ \t]*\n", "\\1 ", contents) - contents = re.sub(r"\\[ \t]*\n", "", contents) - return contents - - -def fixup_comments(contents: str) -> str: - # Get rid of completely commented out lines. - # So any line which starts with a '#' char and ends with a new line - # will be replaced by a single new line. - # The # may be preceded by any number of spaces or tabs. - # - # This is needed because qmake syntax is weird. In a multi line - # assignment (separated by backslashes and newlines aka - # # \\\n ), if any of the lines are completely commented out, in - # principle the assignment should fail. - # - # It should fail because you would have a new line separating - # the previous value from the next value, and the next value would - # not be interpreted as a value, but as a new token / operation. - # qmake is lenient though, and accepts that, so we need to take - # care of it as well, as if the commented line didn't exist in the - # first place. - - contents = re.sub(r"(^|\n)[ \t]*#[^\n]*?\n", "\n", contents, re.DOTALL) - return contents - - -def flatten_list(input_list): - """Flattens an irregular nested list into a simple list.""" - for el in input_list: - if isinstance(el, collections.abc.Iterable) and not isinstance(el, (str, bytes)): - yield from flatten_list(el) - else: - yield el - - -def handle_function_value(group: pp.ParseResults): - function_name = group[0] - function_args = group[1] - if function_name == "qtLibraryTarget": - if len(function_args) > 1: - raise RuntimeError( - "Don't know what to with more than one function argument " - "for $$qtLibraryTarget()." - ) - return str(function_args[0]) - - if function_name == "quote": - # Do nothing, just return a string result - return str(group) - - if function_name == "files": - return str(function_args[0]) - - if function_name == "basename": - if len(function_args) != 1: - print(f"XXXX basename with more than one argument") - if function_args[0] == "_PRO_FILE_PWD_": - return os.path.basename(os.getcwd()) - print(f"XXXX basename with value other than _PRO_FILE_PWD_") - return os.path.basename(str(function_args[0])) - - if isinstance(function_args, pp.ParseResults): - function_args = list(flatten_list(function_args.asList())) - - # For other functions, return the whole expression as a string. - return f"$${function_name}({' '.join(function_args)})" - - -class QmakeParser: - def __init__(self, *, debug: bool = False) -> None: - self.debug = debug - self._Grammar = self._generate_grammar() - - def _generate_grammar(self): - # Define grammar: - pp.ParserElement.setDefaultWhitespaceChars(" \t") - - def add_element(name: str, value: pp.ParserElement): - nonlocal self - if self.debug: - value.setName(name) - value.setDebug() - return value - - EOL = add_element("EOL", pp.Suppress(pp.LineEnd())) - Else = add_element("Else", pp.Keyword("else")) - Identifier = add_element( - "Identifier", pp.Word(f"{pp.alphas}_", bodyChars=pp.alphanums + "_-./") - ) - BracedValue = add_element( - "BracedValue", - pp.nestedExpr( - ignoreExpr=pp.quotedString - | pp.QuotedString( - quoteChar="$(", endQuoteChar=")", escQuote="\\", unquoteResults=False - ) - ).setParseAction(lambda s, l, t: ["(", *t[0], ")"]), - ) - - Substitution = add_element( - "Substitution", - pp.Combine( - pp.Literal("$") - + ( - ( - (pp.Literal("$") + Identifier + pp.Optional(pp.nestedExpr())) - | (pp.Literal("(") + Identifier + pp.Literal(")")) - | (pp.Literal("{") + Identifier + pp.Literal("}")) - | ( - pp.Literal("$") - + pp.Literal("{") - + Identifier - + pp.Optional(pp.nestedExpr()) - + pp.Literal("}") - ) - | (pp.Literal("$") + pp.Literal("[") + Identifier + pp.Literal("]")) - ) - ) - ), - ) - LiteralValuePart = add_element( - "LiteralValuePart", pp.Word(pp.printables, excludeChars="$#{}()") - ) - SubstitutionValue = add_element( - "SubstitutionValue", - pp.Combine(pp.OneOrMore(Substitution | LiteralValuePart | pp.Literal("$"))), - ) - FunctionValue = add_element( - "FunctionValue", - pp.Group( - pp.Suppress(pp.Literal("$") + pp.Literal("$")) - + Identifier - + pp.nestedExpr() # .setParseAction(lambda s, l, t: ['(', *t[0], ')']) - ).setParseAction(lambda s, l, t: handle_function_value(*t)), - ) - Value = add_element( - "Value", - pp.NotAny(Else | pp.Literal("}") | EOL) - + ( - pp.QuotedString(quoteChar='"', escChar="\\") - | FunctionValue - | SubstitutionValue - | BracedValue - ), - ) - - Values = add_element("Values", pp.ZeroOrMore(Value)("value")) - - Op = add_element( - "OP", - pp.Literal("=") - | pp.Literal("-=") - | pp.Literal("+=") - | pp.Literal("*=") - | pp.Literal("~="), - ) - - Key = add_element("Key", Identifier) - - Operation = add_element( - "Operation", Key("key") + pp.locatedExpr(Op)("operation") + Values("value") - ) - CallArgs = add_element("CallArgs", pp.nestedExpr()) - - def parse_call_args(results): - out = "" - for item in chain(*results): - if isinstance(item, str): - out += item - else: - out += "(" + parse_call_args(item) + ")" - return out - - CallArgs.setParseAction(parse_call_args) - - Load = add_element("Load", pp.Keyword("load") + CallArgs("loaded")) - Include = add_element( - "Include", pp.Keyword("include") + pp.locatedExpr(CallArgs)("included") - ) - Option = add_element("Option", pp.Keyword("option") + CallArgs("option")) - RequiresCondition = add_element("RequiresCondition", pp.originalTextFor(pp.nestedExpr())) - - def parse_requires_condition(s, l_unused, t): - # The following expression unwraps the condition via the additional info - # set by originalTextFor. - condition_without_parentheses = s[t._original_start + 1 : t._original_end - 1] - - # And this replaces the colons with '&&' similar how it's done for 'Condition'. - condition_without_parentheses = ( - condition_without_parentheses.strip().replace(":", " && ").strip(" && ") - ) - return condition_without_parentheses - - RequiresCondition.setParseAction(parse_requires_condition) - Requires = add_element( - "Requires", pp.Keyword("requires") + RequiresCondition("project_required_condition") - ) - - FunctionArgumentsAsString = add_element( - "FunctionArgumentsAsString", pp.originalTextFor(pp.nestedExpr()) - ) - QtNoMakeTools = add_element( - "QtNoMakeTools", - pp.Keyword("qtNomakeTools") + FunctionArgumentsAsString("qt_no_make_tools_arguments"), - ) - - # ignore the whole thing... - DefineTestDefinition = add_element( - "DefineTestDefinition", - pp.Suppress( - pp.Keyword("defineTest") - + CallArgs - + pp.nestedExpr(opener="{", closer="}", ignoreExpr=pp.LineEnd()) - ), - ) - - # ignore the whole thing... - ForLoop = add_element( - "ForLoop", - pp.Suppress( - pp.Keyword("for") - + CallArgs - + pp.nestedExpr(opener="{", closer="}", ignoreExpr=pp.LineEnd()) - ), - ) - - # ignore the whole thing... - ForLoopSingleLine = add_element( - "ForLoopSingleLine", - pp.Suppress(pp.Keyword("for") + CallArgs + pp.Literal(":") + pp.SkipTo(EOL)), - ) - - # ignore the whole thing... - FunctionCall = add_element("FunctionCall", pp.Suppress(Identifier + pp.nestedExpr())) - - Scope = add_element("Scope", pp.Forward()) - - Statement = add_element( - "Statement", - pp.Group( - Load - | Include - | Option - | Requires - | QtNoMakeTools - | ForLoop - | ForLoopSingleLine - | DefineTestDefinition - | FunctionCall - | Operation - ), - ) - StatementLine = add_element("StatementLine", Statement + (EOL | pp.FollowedBy("}"))) - StatementGroup = add_element( - "StatementGroup", pp.ZeroOrMore(StatementLine | Scope | pp.Suppress(EOL)) - ) - - Block = add_element( - "Block", - pp.Suppress("{") - + pp.Optional(EOL) - + StatementGroup - + pp.Optional(EOL) - + pp.Suppress("}") - + pp.Optional(EOL), - ) - - ConditionEnd = add_element( - "ConditionEnd", - pp.FollowedBy( - (pp.Optional(pp.White()) + (pp.Literal(":") | pp.Literal("{") | pp.Literal("|"))) - ), - ) - - ConditionPart1 = add_element( - "ConditionPart1", (pp.Optional("!") + Identifier + pp.Optional(BracedValue)) - ) - ConditionPart2 = add_element("ConditionPart2", pp.CharsNotIn("#{}|:=\\\n")) - ConditionPart = add_element( - "ConditionPart", (ConditionPart1 ^ ConditionPart2) + ConditionEnd - ) - - ConditionOp = add_element("ConditionOp", pp.Literal("|") ^ pp.Literal(":")) - ConditionWhiteSpace = add_element( - "ConditionWhiteSpace", pp.Suppress(pp.Optional(pp.White(" "))) - ) - - # Unfortunately qmake condition operators have no precedence, - # and are simply evaluated left to right. To emulate that, wrap - # each condition sub-expression in parentheses. - # So c1|c2:c3 is evaluated by qmake as (c1|c2):c3. - # The following variable keeps count on how many parentheses - # should be added to the beginning of the condition. Each - # condition sub-expression always gets an ")", and in the - # end the whole condition gets many "(". Note that instead - # inserting the actual parentheses, we insert special markers - # which get replaced in the end. - condition_parts_count = 0 - # Whitespace in the markers is important. Assumes the markers - # never appear in .pro files. - l_paren_marker = "_(_ " - r_paren_marker = " _)_" - - def handle_condition_part(condition_part_parse_result: pp.ParseResults) -> str: - condition_part_list = [*condition_part_parse_result] - nonlocal condition_parts_count - condition_parts_count += 1 - condition_part_joined = "".join(condition_part_list) - # Add ending parenthesis marker. The counterpart is added - # in handle_condition. - return f"{condition_part_joined}{r_paren_marker}" - - ConditionPart.setParseAction(handle_condition_part) - ConditionRepeated = add_element( - "ConditionRepeated", pp.ZeroOrMore(ConditionOp + ConditionWhiteSpace + ConditionPart) - ) - - def handle_condition(condition_parse_results: pp.ParseResults) -> str: - nonlocal condition_parts_count - prepended_parentheses = l_paren_marker * condition_parts_count - result = prepended_parentheses + " ".join(condition_parse_results).strip().replace( - ":", " && " - ).strip(" && ") - # If there are only 2 condition sub-expressions, there is no - # need for parentheses. - if condition_parts_count < 3: - result = result.replace(l_paren_marker, "") - result = result.replace(r_paren_marker, "") - result = result.strip(" ") - else: - result = result.replace(l_paren_marker, "( ") - result = result.replace(r_paren_marker, " )") - # Strip parentheses and spaces around the final - # condition. - result = result[1:-1] - result = result.strip(" ") - # Reset the parenthesis count for the next condition. - condition_parts_count = 0 - return result - - Condition = add_element("Condition", pp.Combine(ConditionPart + ConditionRepeated)) - Condition.setParseAction(handle_condition) - - # Weird thing like write_file(a)|error() where error() is the alternative condition - # which happens to be a function call. In this case there is no scope, but our code expects - # a scope with a list of statements, so create a fake empty statement. - ConditionEndingInFunctionCall = add_element( - "ConditionEndingInFunctionCall", - pp.Suppress(ConditionOp) - + FunctionCall - + pp.Empty().setParseAction(lambda x: [[]]).setResultsName("statements"), - ) - - SingleLineScope = add_element( - "SingleLineScope", - pp.Suppress(pp.Literal(":")) + pp.Group(Block | (Statement + EOL))("statements"), - ) - MultiLineScope = add_element("MultiLineScope", Block("statements")) - - SingleLineElse = add_element( - "SingleLineElse", - pp.Suppress(pp.Literal(":")) + (Scope | Block | (Statement + pp.Optional(EOL))), - ) - MultiLineElse = add_element("MultiLineElse", Block) - ElseBranch = add_element("ElseBranch", pp.Suppress(Else) + (SingleLineElse | MultiLineElse)) - - # Scope is already add_element'ed in the forward declaration above. - Scope <<= pp.Group( - Condition("condition") - + (SingleLineScope | MultiLineScope | ConditionEndingInFunctionCall) - + pp.Optional(ElseBranch)("else_statements") - ) - - Grammar = StatementGroup("statements") - Grammar.ignore(pp.pythonStyleComment()) - - return Grammar - - def parseFile(self, file: str) -> Tuple[pp.ParseResults, str]: - print(f'Parsing "{file}"...') - try: - with open(file, "r") as file_fd: - contents = file_fd.read() - - # old_contents = contents - contents = fixup_comments(contents) - contents = fixup_linecontinuation(contents) - result = self._Grammar.parseString(contents, parseAll=True) - except pp.ParseException as pe: - print(pe.line) - print(f"{' ' * (pe.col-1)}^") - print(pe) - raise pe - return result, contents - - -def parseProFile(file: str, *, debug=False) -> Tuple[pp.ParseResults, str]: - parser = QmakeParser(debug=debug) - return parser.parseFile(file) diff --git a/util/cmake/requirements.txt b/util/cmake/requirements.txt deleted file mode 100644 index 16fb99a08cc..00000000000 --- a/util/cmake/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -pytest; python_version >= '3.7' -pytest-cov; python_version >= '3.7' -mypy; python_version >= '3.7' -pyparsing; python_version >= '3.7' -sympy; python_version >= '3.7' -portalocker; python_version >= '3.7' -black; python_version >= '3.7' - diff --git a/util/cmake/run_pro2cmake.py b/util/cmake/run_pro2cmake.py deleted file mode 100755 index 3e860e90b29..00000000000 --- a/util/cmake/run_pro2cmake.py +++ /dev/null @@ -1,221 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2018 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -import glob -import os -import subprocess -import concurrent.futures -import sys -import typing -import argparse -from argparse import ArgumentParser - - -def parse_command_line() -> argparse.Namespace: - parser = ArgumentParser( - description="Run pro2cmake on all .pro files recursively in given path. " - "You can pass additional arguments to the pro2cmake calls by appending " - "-- --foo --bar" - ) - parser.add_argument( - "--only-existing", - dest="only_existing", - action="store_true", - help="Run pro2cmake only on .pro files that already have a CMakeLists.txt.", - ) - parser.add_argument( - "--only-missing", - dest="only_missing", - action="store_true", - help="Run pro2cmake only on .pro files that do not have a CMakeLists.txt.", - ) - parser.add_argument( - "--only-qtbase-main-modules", - dest="only_qtbase_main_modules", - action="store_true", - help="Run pro2cmake only on the main modules in qtbase.", - ) - parser.add_argument( - "--skip-subdirs-projects", - dest="skip_subdirs_projects", - action="store_true", - help="Don't run pro2cmake on TEMPLATE=subdirs projects.", - ) - parser.add_argument( - "--is-example", - dest="is_example", - action="store_true", - help="Run pro2cmake with --is-example flag.", - ) - parser.add_argument( - "--count", dest="count", help="How many projects should be converted.", type=int - ) - parser.add_argument( - "--offset", - dest="offset", - help="From the list of found projects, from which project should conversion begin.", - type=int, - ) - parser.add_argument( - "path", metavar="", type=str, help="The path where to look for .pro files." - ) - - args, unknown = parser.parse_known_args() - - # Error out when the unknown arguments do not start with a "--", - # which implies passing through arguments to pro2cmake. - if len(unknown) > 0 and unknown[0] != "--": - parser.error("unrecognized arguments: {}".format(" ".join(unknown))) - else: - args.pro2cmake_args = unknown[1:] - - return args - - -def find_all_pro_files(base_path: str, args: argparse.Namespace): - def sorter(pro_file: str) -> str: - """Sorter that tries to prioritize main pro files in a directory.""" - pro_file_without_suffix = pro_file.rsplit("/", 1)[-1][:-4] - dir_name = os.path.dirname(pro_file) - if dir_name == ".": - dir_name = os.path.basename(os.getcwd()) - if dir_name.endswith(pro_file_without_suffix): - return dir_name - return dir_name + "/__" + pro_file - - all_files = [] - previous_dir_name: typing.Optional[str] = None - - print("Finding .pro files.") - glob_result = glob.glob(os.path.join(base_path, "**/*.pro"), recursive=True) - - def cmake_lists_exists_filter(path): - path_dir_name = os.path.dirname(path) - if os.path.exists(os.path.join(path_dir_name, "CMakeLists.txt")): - return True - return False - - def cmake_lists_missing_filter(path): - return not cmake_lists_exists_filter(path) - - def qtbase_main_modules_filter(path): - main_modules = [ - "corelib", - "network", - "gui", - "widgets", - "testlib", - "printsupport", - "opengl", - "sql", - "dbus", - "concurrent", - "xml", - ] - path_suffixes = [f"src/{m}/{m}.pro" for m in main_modules] - - for path_suffix in path_suffixes: - if path.endswith(path_suffix): - return True - return False - - filter_result = glob_result - filter_func = None - if args.only_existing: - filter_func = cmake_lists_exists_filter - elif args.only_missing: - filter_func = cmake_lists_missing_filter - elif args.only_qtbase_main_modules: - filter_func = qtbase_main_modules_filter - - if filter_func: - print("Filtering.") - filter_result = [p for p in filter_result if filter_func(p)] - - for pro_file in sorted(filter_result, key=sorter): - dir_name = os.path.dirname(pro_file) - if dir_name == previous_dir_name: - print("Skipping:", pro_file) - else: - all_files.append(pro_file) - previous_dir_name = dir_name - return all_files - - -def run(all_files: typing.List[str], pro2cmake: str, args: argparse.Namespace) -> typing.List[str]: - failed_files = [] - files_count = len(all_files) - workers = os.cpu_count() or 1 - - if args.only_qtbase_main_modules: - # qtbase main modules take longer than usual to process. - workers = 2 - - with concurrent.futures.ThreadPoolExecutor(max_workers=workers, initargs=(10,)) as pool: - print("Firing up thread pool executor.") - - def _process_a_file(data: typing.Tuple[str, int, int]) -> typing.Tuple[int, str, str]: - filename, index, total = data - pro2cmake_args = [] - if sys.platform == "win32": - pro2cmake_args.append(sys.executable) - pro2cmake_args.append(pro2cmake) - if args.is_example: - pro2cmake_args.append("--is-example") - if args.skip_subdirs_projects: - pro2cmake_args.append("--skip-subdirs-project") - pro2cmake_args.append(os.path.basename(filename)) - - if args.pro2cmake_args: - pro2cmake_args += args.pro2cmake_args - - result = subprocess.run( - pro2cmake_args, - cwd=os.path.dirname(filename), - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) - stdout = f"Converted[{index}/{total}]: {filename}\n" - return result.returncode, filename, stdout + result.stdout.decode() - - for return_code, filename, stdout in pool.map( - _process_a_file, - zip(all_files, range(1, files_count + 1), (files_count for _ in all_files)), - ): - if return_code: - failed_files.append(filename) - print(stdout) - - return failed_files - - -def main() -> None: - args = parse_command_line() - - script_path = os.path.dirname(os.path.abspath(__file__)) - pro2cmake = os.path.join(script_path, "pro2cmake.py") - base_path = args.path - - all_files = find_all_pro_files(base_path, args) - if args.offset: - all_files = all_files[args.offset :] - if args.count: - all_files = all_files[: args.count] - files_count = len(all_files) - - failed_files = run(all_files, pro2cmake, args) - if len(all_files) == 0: - print("No files found.") - - if failed_files: - print( - f"The following files were not successfully " - f"converted ({len(failed_files)} of {files_count}):" - ) - for f in failed_files: - print(f' "{f}"') - - -if __name__ == "__main__": - main() diff --git a/util/cmake/special_case_helper.py b/util/cmake/special_case_helper.py deleted file mode 100644 index a7343d32c36..00000000000 --- a/util/cmake/special_case_helper.py +++ /dev/null @@ -1,397 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2019 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -""" -This is a helper script that takes care of reapplying special case -modifications when regenerating a CMakeLists.txt file using -pro2cmake.py or configure.cmake with configurejson2cmake.py. - -It has two modes of operation: -1) Dumb "special case" block removal and re-application. -2) Smart "special case" diff application, using a previously generated - "clean" CMakeLists.txt/configure.cmake as a source. "clean" in this - case means a generated file which has no "special case" modifications. - -Both modes use a temporary git repository to compute and reapply -"special case" diffs. - -For the first mode to work, the developer has to mark changes -with "# special case" markers on every line they want to keep. Or -enclose blocks of code they want to keep between "# special case begin" -and "# special case end" markers. - -For example: - -SOURCES - foo.cpp - bar.cpp # special case - -SOURCES - foo1.cpp - foo2.cpp - # special case begin - foo3.cpp - foo4.cpp - # special case end - -The second mode, as mentioned, requires a previous "clean" -CMakeLists.txt/configure.cmake file. - -The script can then compute the exact diff between -a "clean" and "modified" (with special cases) file, and reapply that -diff to a newly generated "CMakeLists.txt"/"configure.cmake" file. - -This implies that we always have to keep a "clean" file alongside the -"modified" project file for each project (corelib, gui, etc.) So we -have to commit both files to the repository. - -If there is no such "clean" file, we can use the first operation mode -to generate one. After that, we only have to use the second operation -mode for the project file in question. - -When the script is used, the developer only has to take care of fixing -the newly generated "modified" file. The "clean" file is automatically -handled and git add'ed by the script, and will be committed together -with the "modified" file. - - -""" - -import re -import os -import subprocess -import filecmp -import time -import typing -import stat - -from shutil import copyfile -from shutil import rmtree -from textwrap import dedent - - -def remove_special_cases(original: str) -> str: - # Remove content between the following markers - # '# special case begin' and '# special case end'. - # This also remove the markers. - replaced = re.sub( - r"\n[^#\n]*?#[^\n]*?special case begin.*?#[^\n]*special case end[^\n]*?\n", - "\n", - original, - 0, - re.DOTALL, - ) - - # Remove individual lines that have the "# special case" marker. - replaced = re.sub(r"\n.*#.*special case[^\n]*\n", "\n", replaced) - return replaced - - -def read_content_from_file(file_path: str) -> str: - with open(file_path, "r") as file_fd: - content = file_fd.read() - return content - - -def write_content_to_file(file_path: str, content: str) -> None: - with open(file_path, "w") as file_fd: - file_fd.write(content) - - -def resolve_simple_git_conflicts(file_path: str, debug=False) -> None: - content = read_content_from_file(file_path) - # If the conflict represents the addition of a new content hunk, - # keep the content and remove the conflict markers. - if debug: - print("Resolving simple conflicts automatically.") - replaced = re.sub(r"\n<<<<<<< HEAD\n=======(.+?)>>>>>>> master\n", r"\1", content, 0, re.DOTALL) - write_content_to_file(file_path, replaced) - - -def copyfile_log(src: str, dst: str, debug=False): - if debug: - print(f"Copying {src} to {dst}.") - copyfile(src, dst) - - -def check_if_git_in_path() -> bool: - is_win = os.name == "nt" - for path in os.environ["PATH"].split(os.pathsep): - git_path = os.path.join(path, "git") - if is_win: - git_path += ".exe" - if os.path.isfile(git_path) and os.access(git_path, os.X_OK): - return True - return False - - -def run_process_quiet(args_string: str, debug=False) -> bool: - if debug: - print(f'Running command: "{args_string}"') - args_list = args_string.split() - try: - subprocess.run(args_list, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - except subprocess.CalledProcessError as e: - # git merge with conflicts returns with exit code 1, but that's not - # an error for us. - if "git merge" not in args_string: - if debug: - print( - dedent( - f"""\ - Error while running: "{args_string}" - {e.stdout}""" - ) - ) - return False - return True - - -def does_file_have_conflict_markers(file_path: str, debug=False) -> bool: - if debug: - print(f"Checking if {file_path} has no leftover conflict markers.") - content_actual = read_content_from_file(file_path) - if "<<<<<<< HEAD" in content_actual: - print(f"Conflict markers found in {file_path}. " "Please remove or solve them first.") - return True - return False - - -def create_file_with_no_special_cases( - original_file_path: str, no_special_cases_file_path: str, debug=False -): - """ - Reads content of original CMakeLists.txt/configure.cmake, removes all content - between "# special case" markers or lines, saves the result into a - new file. - """ - content_actual = read_content_from_file(original_file_path) - if debug: - print(f"Removing special case blocks from {original_file_path}.") - content_no_special_cases = remove_special_cases(content_actual) - - if debug: - print( - f"Saving original contents of {original_file_path} " - f"with removed special case blocks to {no_special_cases_file_path}" - ) - write_content_to_file(no_special_cases_file_path, content_no_special_cases) - - -def rm_tree_on_error_handler(func: typing.Callable[..., None], path: str, exception_info: tuple): - # If the path is read only, try to make it writable, and try - # to remove the path again. - if not os.access(path, os.W_OK): - os.chmod(path, stat.S_IWRITE) - func(path) - else: - print(f"Error while trying to remove path: {path}. Exception: {exception_info}") - - -class SpecialCaseHandler(object): - def __init__( - self, - original_file_path: str, - generated_file_path: str, - base_dir: str, - keep_temporary_files=False, - debug=False, - ) -> None: - self.base_dir = base_dir - self.original_file_path = original_file_path - self.generated_file_path = generated_file_path - self.keep_temporary_files = keep_temporary_files - self.use_heuristic = False - self.debug = debug - - @property - def prev_file_path(self) -> str: - filename = ".prev_" + os.path.basename(self.original_file_path) - return os.path.join(self.base_dir, filename) - - @property - def post_merge_file_path(self) -> str: - original_file_name = os.path.basename(self.original_file_path) - (original_file_basename, original_file_ext) = os.path.splitext(original_file_name) - filename = original_file_basename + "-post-merge" + original_file_ext - return os.path.join(self.base_dir, filename) - - @property - def no_special_file_path(self) -> str: - original_file_name = os.path.basename(self.original_file_path) - (original_file_basename, original_file_ext) = os.path.splitext(original_file_name) - filename = original_file_basename + ".no-special" + original_file_ext - return os.path.join(self.base_dir, filename) - - def apply_git_merge_magic(self, no_special_cases_file_path: str) -> None: - # Create new folder for temporary repo, and ch dir into it. - repo = os.path.join(self.base_dir, "tmp_repo") - repo_absolute_path = os.path.abspath(repo) - txt = os.path.basename(self.original_file_path) - - try: - os.mkdir(repo) - current_dir = os.getcwd() - os.chdir(repo) - except Exception as e: - print(f"Failed to create temporary directory for temporary git repo. Exception: {e}") - raise e - - generated_file_path = os.path.join("..", self.generated_file_path) - original_file_path = os.path.join("..", self.original_file_path) - no_special_cases_file_path = os.path.join("..", no_special_cases_file_path) - post_merge_file_path = os.path.join("..", self.post_merge_file_path) - - try: - # Create new repo with the "clean" CMakeLists.txt/configure.cmake file. - run_process_quiet("git init .", debug=self.debug) - run_process_quiet("git config user.name fake", debug=self.debug) - run_process_quiet("git config user.email fake@fake", debug=self.debug) - copyfile_log(no_special_cases_file_path, txt, debug=self.debug) - run_process_quiet(f"git add {txt}", debug=self.debug) - run_process_quiet("git commit -m no_special", debug=self.debug) - run_process_quiet("git checkout -b no_special", debug=self.debug) - - # Copy the original "modified" file (with the special cases) - # and make a new commit. - run_process_quiet("git checkout -b original", debug=self.debug) - copyfile_log(original_file_path, txt, debug=self.debug) - run_process_quiet(f"git add {txt}", debug=self.debug) - run_process_quiet("git commit -m original", debug=self.debug) - - # Checkout the commit with "clean" file again, and create a - # new branch. - run_process_quiet("git checkout no_special", debug=self.debug) - run_process_quiet("git checkout -b newly_generated", debug=self.debug) - - # Copy the new "modified" file and make a commit. - copyfile_log(generated_file_path, txt, debug=self.debug) - run_process_quiet(f"git add {txt}", debug=self.debug) - run_process_quiet("git commit -m newly_generated", debug=self.debug) - - # Merge the "old" branch with modifications into the "new" - # branch with the newly generated file. - run_process_quiet("git merge original", debug=self.debug) - - # Resolve some simple conflicts (just remove the markers) - # for cases that don't need intervention. - resolve_simple_git_conflicts(txt, debug=self.debug) - - # Copy the resulting file from the merge. - copyfile_log(txt, post_merge_file_path) - except Exception as e: - print(f"Git merge conflict resolution process failed. Exception: {e}") - raise e - finally: - os.chdir(current_dir) - - # Remove the temporary repo. - try: - if not self.keep_temporary_files: - rmtree(repo_absolute_path, onerror=rm_tree_on_error_handler) - except Exception as e: - print(f"Error removing temporary repo. Exception: {e}") - - def save_next_clean_file(self): - files_are_equivalent = filecmp.cmp(self.generated_file_path, self.post_merge_file_path) - - if not files_are_equivalent: - # Before overriding the generated file with the post - # merge result, save the new "clean" file for future - # regenerations. - copyfile_log(self.generated_file_path, self.prev_file_path, debug=self.debug) - - # Attempt to git add until we succeed. It can fail when - # run_pro2cmake executes pro2cmake in multiple threads, and git - # has acquired the index lock. - success = False - failed_once = False - i = 0 - while not success and i < 20: - success = run_process_quiet(f"git add {self.prev_file_path}", debug=self.debug) - if not success: - failed_once = True - i += 1 - time.sleep(0.1) - - if failed_once and not success: - if self.debug: - print("Retrying git add, the index.lock was probably acquired.") - if failed_once and success: - if self.debug: - print("git add succeeded.") - elif failed_once and not success: - print(f"git add failed. Make sure to git add {self.prev_file_path} yourself.") - - def handle_special_cases_helper(self) -> bool: - """ - Uses git to reapply special case modifications to the "new" - generated CMakeLists.gen.txt/configure.cmake.gen file. - - If use_heuristic is True, a new file is created from the - original file, with special cases removed. - - If use_heuristic is False, an existing "clean" file with no - special cases is used from a previous conversion. The "clean" - file is expected to be in the same folder as the original one. - """ - try: - if does_file_have_conflict_markers(self.original_file_path): - return False - - if self.use_heuristic: - create_file_with_no_special_cases( - self.original_file_path, self.no_special_file_path - ) - no_special_cases_file_path = self.no_special_file_path - else: - no_special_cases_file_path = self.prev_file_path - - if self.debug: - print( - f"Using git to reapply special case modifications to newly " - f"generated {self.generated_file_path} file" - ) - - self.apply_git_merge_magic(no_special_cases_file_path) - self.save_next_clean_file() - - copyfile_log(self.post_merge_file_path, self.generated_file_path) - if not self.keep_temporary_files: - os.remove(self.post_merge_file_path) - if self.debug: - print( - "Special case reapplication using git is complete. " - "Make sure to fix remaining conflict markers." - ) - - except Exception as e: - print(f"Error occurred while trying to reapply special case modifications: {e}") - return False - finally: - if not self.keep_temporary_files and self.use_heuristic: - os.remove(self.no_special_file_path) - - return True - - def handle_special_cases(self) -> bool: - original_file_exists = os.path.isfile(self.original_file_path) - prev_file_exists = os.path.isfile(self.prev_file_path) - self.use_heuristic = not prev_file_exists - - git_available = check_if_git_in_path() - keep_special_cases = original_file_exists and git_available - - if not git_available: - print( - "You need to have git in PATH in order to reapply the special " - "case modifications." - ) - - copy_generated_file = True - - if keep_special_cases: - copy_generated_file = self.handle_special_cases_helper() - - return copy_generated_file diff --git a/util/cmake/tests/__init__.py b/util/cmake/tests/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/util/cmake/tests/data/comment_scope.pro b/util/cmake/tests/data/comment_scope.pro deleted file mode 100644 index be43cad37db..00000000000 --- a/util/cmake/tests/data/comment_scope.pro +++ /dev/null @@ -1,6 +0,0 @@ -# QtCore can't be compiled with -Wl,-no-undefined because it uses the "environ" -# variable and on FreeBSD and OpenBSD, this variable is in the final executable itself. -# OpenBSD 6.0 will include environ in libc. -freebsd|openbsd: QMAKE_LFLAGS_NOUNDEF = - -include(animation/animation.pri) diff --git a/util/cmake/tests/data/complex_assign.pro b/util/cmake/tests/data/complex_assign.pro deleted file mode 100644 index d251afcdd58..00000000000 --- a/util/cmake/tests/data/complex_assign.pro +++ /dev/null @@ -1,2 +0,0 @@ -qmake-clean.commands += (cd qmake && $(MAKE) clean ":-(==)-:" '(Foo)' ) - diff --git a/util/cmake/tests/data/complex_condition.pro b/util/cmake/tests/data/complex_condition.pro deleted file mode 100644 index bc3369bd632..00000000000 --- a/util/cmake/tests/data/complex_condition.pro +++ /dev/null @@ -1,4 +0,0 @@ -!system("dbus-send --session --type=signal / local.AutotestCheck.Hello >$$QMAKE_SYSTEM_NULL_DEVICE 2>&1") { - SOURCES = dbus.cpp -} - diff --git a/util/cmake/tests/data/complex_values.pro b/util/cmake/tests/data/complex_values.pro deleted file mode 100644 index 4d747c1dd73..00000000000 --- a/util/cmake/tests/data/complex_values.pro +++ /dev/null @@ -1,22 +0,0 @@ -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/condition_operator_precedence.pro b/util/cmake/tests/data/condition_operator_precedence.pro deleted file mode 100644 index 8af628404d7..00000000000 --- a/util/cmake/tests/data/condition_operator_precedence.pro +++ /dev/null @@ -1,11 +0,0 @@ -a1|a2 { - DEFINES += d -} - -b1|b2:b3 { - DEFINES += d -} - -c1|c2:c3|c4 { - DEFINES += d -} diff --git a/util/cmake/tests/data/condition_without_scope.pro b/util/cmake/tests/data/condition_without_scope.pro deleted file mode 100644 index 2aa1237c12b..00000000000 --- a/util/cmake/tests/data/condition_without_scope.pro +++ /dev/null @@ -1,2 +0,0 @@ -write_file("a", contents)|error() - diff --git a/util/cmake/tests/data/contains_scope.pro b/util/cmake/tests/data/contains_scope.pro deleted file mode 100644 index 0f51350a45d..00000000000 --- a/util/cmake/tests/data/contains_scope.pro +++ /dev/null @@ -1,4 +0,0 @@ -contains(DEFINES,QT_EVAL):include(eval.pri) - -HOST_BINS = $$[QT_HOST_BINS] - diff --git a/util/cmake/tests/data/conversion/optional_qt_modules.pro b/util/cmake/tests/data/conversion/optional_qt_modules.pro deleted file mode 100644 index b9522169fc3..00000000000 --- a/util/cmake/tests/data/conversion/optional_qt_modules.pro +++ /dev/null @@ -1,4 +0,0 @@ -TARGET = myapp -QT = core network widgets -win32: QT += opengl -SOURCES = main.cpp diff --git a/util/cmake/tests/data/conversion/qt_version_check.pro b/util/cmake/tests/data/conversion/qt_version_check.pro deleted file mode 100644 index cf3697bb64f..00000000000 --- a/util/cmake/tests/data/conversion/qt_version_check.pro +++ /dev/null @@ -1,8 +0,0 @@ -QT += core gui -SOURCES += main.cpp -greaterThan(QT_MAJOR_VERSION, 5):lessThan(QT_MINOR_VERSION, 1):equals(QT_PATCH_VERSION, 0) { - DEFINES += SUPER_FRESH_MAJOR_QT_RELEASE -} -greaterThan(QT_VERSION, 6.6.5):lessThan(QT_VERSION, 6.6.7):equals(QT_VERSION, 6.6.6): { - DEFINES += QT_VERSION_OF_THE_BEAST -} diff --git a/util/cmake/tests/data/conversion/required_qt_modules.pro b/util/cmake/tests/data/conversion/required_qt_modules.pro deleted file mode 100644 index 287bb468318..00000000000 --- a/util/cmake/tests/data/conversion/required_qt_modules.pro +++ /dev/null @@ -1,3 +0,0 @@ -TARGET = myapp -QT = core network widgets -SOURCES = main.cpp diff --git a/util/cmake/tests/data/definetest.pro b/util/cmake/tests/data/definetest.pro deleted file mode 100644 index 76b63d239fb..00000000000 --- a/util/cmake/tests/data/definetest.pro +++ /dev/null @@ -1,6 +0,0 @@ -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 deleted file mode 100644 index bbf9c5ac9f9..00000000000 --- a/util/cmake/tests/data/else.pro +++ /dev/null @@ -1,6 +0,0 @@ - -linux { - SOURCES += a.cpp -} else { - SOURCES += b.cpp -} diff --git a/util/cmake/tests/data/else2.pro b/util/cmake/tests/data/else2.pro deleted file mode 100644 index f2ef36ec288..00000000000 --- a/util/cmake/tests/data/else2.pro +++ /dev/null @@ -1,4 +0,0 @@ - -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 deleted file mode 100644 index 0de9c2c1d99..00000000000 --- a/util/cmake/tests/data/else3.pro +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index 9ed676ccfae..00000000000 --- a/util/cmake/tests/data/else4.pro +++ /dev/null @@ -1,6 +0,0 @@ -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 deleted file mode 100644 index 3de76af50ad..00000000000 --- a/util/cmake/tests/data/else5.pro +++ /dev/null @@ -1,10 +0,0 @@ -# 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 deleted file mode 100644 index 9eaa834a193..00000000000 --- a/util/cmake/tests/data/else6.pro +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index e663b1c05e9..00000000000 --- a/util/cmake/tests/data/else7.pro +++ /dev/null @@ -1,2 +0,0 @@ -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 deleted file mode 100644 index 6d4d5f01edf..00000000000 --- a/util/cmake/tests/data/else8.pro +++ /dev/null @@ -1,5 +0,0 @@ -qtConfig(timezone) { A = 1 } else:win32: {\ -B = 2 \ -} else: \ - C \ -= 3 \ diff --git a/util/cmake/tests/data/escaped_value.pro b/util/cmake/tests/data/escaped_value.pro deleted file mode 100644 index 7c95b1fc309..00000000000 --- a/util/cmake/tests/data/escaped_value.pro +++ /dev/null @@ -1,2 +0,0 @@ -MODULE_AUX_INCLUDES = \ - \$\$QT_MODULE_INCLUDE_BASE/QtANGLE diff --git a/util/cmake/tests/data/for.pro b/util/cmake/tests/data/for.pro deleted file mode 100644 index 5751432980e..00000000000 --- a/util/cmake/tests/data/for.pro +++ /dev/null @@ -1,11 +0,0 @@ -SOURCES = main.cpp -for (config, SIMD) { - uc = $$upper($$config) - DEFINES += QT_COMPILER_SUPPORTS_$${uc} - - add_cflags { - cflags = QMAKE_CFLAGS_$${uc} - !defined($$cflags, var): error("This compiler does not support $${uc}") - QMAKE_CXXFLAGS += $$eval($$cflags) - } -} diff --git a/util/cmake/tests/data/function_if.pro b/util/cmake/tests/data/function_if.pro deleted file mode 100644 index 9af018f864d..00000000000 --- a/util/cmake/tests/data/function_if.pro +++ /dev/null @@ -1,4 +0,0 @@ -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 deleted file mode 100644 index 22d8a40919c..00000000000 --- a/util/cmake/tests/data/include.pro +++ /dev/null @@ -1,3 +0,0 @@ -A = 42 -include(foo) # load foo -B=23 diff --git a/util/cmake/tests/data/lc.pro b/util/cmake/tests/data/lc.pro deleted file mode 100644 index def80e7c959..00000000000 --- a/util/cmake/tests/data/lc.pro +++ /dev/null @@ -1,10 +0,0 @@ -TEMPLATE=subdirs -SUBDIRS=\ - qmacstyle \ - qstyle \ - qstyleoption \ - qstylesheetstyle \ - -!qtConfig(private_tests): SUBDIRS -= \ - qstylesheetstyle \ - diff --git a/util/cmake/tests/data/lc_with_comment.pro b/util/cmake/tests/data/lc_with_comment.pro deleted file mode 100644 index 176913dfc8a..00000000000 --- a/util/cmake/tests/data/lc_with_comment.pro +++ /dev/null @@ -1,22 +0,0 @@ -SUBDIRS = \ -# dds \ - tga \ - wbmp - -MYVAR = foo # comment -MYVAR = foo2# comment -MYVAR = foo3# comment # - -MYVAR = foo4# comment # - -## -# -# -## - - # - # -# - # # - -MYVAR = foo5# comment # # diff --git a/util/cmake/tests/data/load.pro b/util/cmake/tests/data/load.pro deleted file mode 100644 index c9717e98326..00000000000 --- a/util/cmake/tests/data/load.pro +++ /dev/null @@ -1,3 +0,0 @@ -A = 42 -load(foo)# load foo -B=23 diff --git a/util/cmake/tests/data/multi_condition_divided_by_lc.pro b/util/cmake/tests/data/multi_condition_divided_by_lc.pro deleted file mode 100644 index 23254231df5..00000000000 --- a/util/cmake/tests/data/multi_condition_divided_by_lc.pro +++ /dev/null @@ -1,3 +0,0 @@ -equals(a): \ - greaterThan(a):flags += 1 - diff --git a/util/cmake/tests/data/multiline_assign.pro b/util/cmake/tests/data/multiline_assign.pro deleted file mode 100644 index 42a3d0a6745..00000000000 --- a/util/cmake/tests/data/multiline_assign.pro +++ /dev/null @@ -1,4 +0,0 @@ -A = 42 \ - 43 \ - 44 -B=23 diff --git a/util/cmake/tests/data/nested_function_calls.pro b/util/cmake/tests/data/nested_function_calls.pro deleted file mode 100644 index 5ecc53f1ccc..00000000000 --- a/util/cmake/tests/data/nested_function_calls.pro +++ /dev/null @@ -1,2 +0,0 @@ -requires(qtConfig(dlopen)) - diff --git a/util/cmake/tests/data/quoted.pro b/util/cmake/tests/data/quoted.pro deleted file mode 100644 index 61682aa0d01..00000000000 --- a/util/cmake/tests/data/quoted.pro +++ /dev/null @@ -1,5 +0,0 @@ -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/single_line_for.pro b/util/cmake/tests/data/single_line_for.pro deleted file mode 100644 index 806d08a49cf..00000000000 --- a/util/cmake/tests/data/single_line_for.pro +++ /dev/null @@ -1,4 +0,0 @@ -for(d, sd): \ - exists($$d/$${d}.pro): \ - SUBDIRS += $$d - diff --git a/util/cmake/tests/data/sql.pro b/util/cmake/tests/data/sql.pro deleted file mode 100644 index a9d7fc7c5a4..00000000000 --- a/util/cmake/tests/data/sql.pro +++ /dev/null @@ -1,3 +0,0 @@ -TEMPLATE = subdirs -SUBDIRS = \ - kernel \ diff --git a/util/cmake/tests/data/standardpaths.pro b/util/cmake/tests/data/standardpaths.pro deleted file mode 100644 index b9896b8e294..00000000000 --- a/util/cmake/tests/data/standardpaths.pro +++ /dev/null @@ -1,17 +0,0 @@ -win32 { - !winrt { - SOURCES +=io/qstandardpaths_win.cpp - } else { - SOURCES +=io/qstandardpaths_winrt.cpp - } -} else:unix { - mac { - OBJECTIVE_SOURCES += io/qstandardpaths_mac.mm - } else:android { - SOURCES += io/qstandardpaths_android.cpp - } else:haiku { - SOURCES += io/qstandardpaths_haiku.cpp - } else { - SOURCES += io/qstandardpaths_unix.cpp - } -} diff --git a/util/cmake/tests/data/unset.pro b/util/cmake/tests/data/unset.pro deleted file mode 100644 index 7ffb0582f1e..00000000000 --- a/util/cmake/tests/data/unset.pro +++ /dev/null @@ -1,2 +0,0 @@ -unset(f16c_cxx) - diff --git a/util/cmake/tests/data/value_function.pro b/util/cmake/tests/data/value_function.pro deleted file mode 100644 index 598e4fadbd4..00000000000 --- a/util/cmake/tests/data/value_function.pro +++ /dev/null @@ -1,2 +0,0 @@ -TARGET = Dummy -TARGET = $$qtLibraryTarget($$TARGET) diff --git a/util/cmake/tests/test_conversion.py b/util/cmake/tests/test_conversion.py deleted file mode 100755 index 0cdfb51976d..00000000000 --- a/util/cmake/tests/test_conversion.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -from pro2cmake import Scope, SetOperation, merge_scopes, recursive_evaluate_scope -from tempfile import TemporaryDirectory - -import os -import pathlib -import pytest -import re -import shutil -import subprocess -import tempfile -import typing - -debug_mode = bool(os.environ.get("DEBUG_PRO2CMAKE_TEST_CONVERSION")) -test_script_dir = pathlib.Path(__file__).parent.resolve() -pro2cmake_dir = test_script_dir.parent.resolve() -pro2cmake_py = pro2cmake_dir.joinpath("pro2cmake.py") -test_data_dir = test_script_dir.joinpath("data", "conversion") - - -def convert(base_name: str): - pro_file_name = str(base_name) + ".pro" - pro_file_path = test_data_dir.joinpath(pro_file_name) - assert(pro_file_path.exists()) - with TemporaryDirectory(prefix="testqmake2cmake") as tmp_dir_str: - tmp_dir = pathlib.Path(tmp_dir_str) - output_file_path = tmp_dir.joinpath("CMakeLists.txt") - exit_code = subprocess.call([pro2cmake_py, "--is-example", "-o", output_file_path, pro_file_path]) - assert(exit_code == 0) - if debug_mode: - shutil.copyfile(output_file_path, tempfile.gettempdir() + "/pro2cmake/CMakeLists.txt") - f = open(output_file_path, "r") - assert(f) - content = f.read() - assert(content) - return content - - -def test_qt_modules(): - output = convert("required_qt_modules") - find_package_lines = [] - for line in output.split("\n"): - if "find_package(" in line: - find_package_lines.append(line.strip()) - assert(["find_package(QT NAMES Qt5 Qt6 REQUIRED COMPONENTS Core)", - "find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Network Widgets)"] == find_package_lines) - - output = convert("optional_qt_modules") - find_package_lines = [] - for line in output.split("\n"): - if "find_package(" in line: - find_package_lines.append(line.strip()) - assert(["find_package(QT NAMES Qt5 Qt6 REQUIRED COMPONENTS Core)", - "find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Network Widgets)", - "find_package(Qt${QT_VERSION_MAJOR} OPTIONAL_COMPONENTS OpenGL)"] == find_package_lines) - -def test_qt_version_check(): - output = convert("qt_version_check") - interesting_lines = [] - for line in output.split("\n"): - if line.startswith("if(") and "QT_VERSION" in line: - interesting_lines.append(line.strip()) - assert(["if(( ( (QT_VERSION_MAJOR GREATER 5) ) AND (QT_VERSION_MINOR LESS 1) ) AND (QT_VERSION_PATCH EQUAL 0))", "if(( ( (QT_VERSION VERSION_GREATER 6.6.5) ) AND (QT_VERSION VERSION_LESS 6.6.7) ) AND (QT_VERSION VERSION_EQUAL 6.6.6))"] == interesting_lines) diff --git a/util/cmake/tests/test_lc_fixup.py b/util/cmake/tests/test_lc_fixup.py deleted file mode 100755 index aa63e02fe1f..00000000000 --- a/util/cmake/tests/test_lc_fixup.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2018 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -from qmake_parser import fixup_linecontinuation - - -def test_no_change(): - input = "test \\\nline2\n line3" - output = "test line2\n line3" - result = fixup_linecontinuation(input) - assert output == result - - -def test_fix(): - input = "test \\\t\nline2\\\n line3\\ \nline4 \\ \t\nline5\\\n\n\n" - output = "test line2 line3 line4 line5 \n\n" - result = fixup_linecontinuation(input) - assert output == result diff --git a/util/cmake/tests/test_logic_mapping.py b/util/cmake/tests/test_logic_mapping.py deleted file mode 100755 index cc7d5a3636c..00000000000 --- a/util/cmake/tests/test_logic_mapping.py +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2018 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -from condition_simplifier import simplify_condition - - -def validate_simplify(input: str, expected: str) -> None: - output = simplify_condition(input) - assert output == expected - - -def validate_simplify_unchanged(input: str) -> None: - validate_simplify(input, input) - - -def test_simplify_on(): - validate_simplify_unchanged('ON') - - -def test_simplify_off(): - validate_simplify_unchanged('OFF') - - -def test_simplify_not_on(): - validate_simplify('NOT ON', 'OFF') - - -def test_simplify_not_off(): - validate_simplify('NOT OFF', 'ON') - - -def test_simplify_isEmpty(): - validate_simplify_unchanged('isEmpty(foo)') - - -def test_simplify_not_isEmpty(): - validate_simplify_unchanged('NOT isEmpty(foo)') - - -def test_simplify_simple_and(): - validate_simplify_unchanged('QT_FEATURE_bar AND QT_FEATURE_foo') - - -def test_simplify_simple_or(): - validate_simplify_unchanged('QT_FEATURE_bar OR QT_FEATURE_foo') - - -def test_simplify_simple_not(): - validate_simplify_unchanged('NOT QT_FEATURE_foo') - - -def test_simplify_simple_and_reorder(): - validate_simplify('QT_FEATURE_foo AND QT_FEATURE_bar', 'QT_FEATURE_bar AND QT_FEATURE_foo') - - -def test_simplify_simple_or_reorder(): - validate_simplify('QT_FEATURE_foo OR QT_FEATURE_bar', 'QT_FEATURE_bar OR QT_FEATURE_foo') - - -def test_simplify_unix_or_win32(): - validate_simplify('WIN32 OR UNIX', 'ON') - - -def test_simplify_unix_or_win32_or_foobar_or_barfoo(): - validate_simplify('WIN32 OR UNIX OR foobar OR barfoo', 'ON') - - -def test_simplify_not_not_bar(): - validate_simplify(' NOT NOT bar ', 'bar') - - -def test_simplify_not_unix(): - validate_simplify('NOT UNIX', 'WIN32') - - -def test_simplify_not_win32(): - validate_simplify('NOT WIN32', 'UNIX') - - -def test_simplify_unix_and_win32(): - validate_simplify('WIN32 AND UNIX', 'OFF') - - -def test_simplify_unix_or_win32(): - validate_simplify('WIN32 OR UNIX', 'ON') - - -def test_simplify_unix_and_win32_or_foobar_or_barfoo(): - validate_simplify('WIN32 AND foobar AND UNIX AND barfoo', 'OFF') - - -def test_simplify_watchos_and_win32(): - validate_simplify('WATCHOS AND WIN32', 'OFF') - - -def test_simplify_win32_and_watchos(): - validate_simplify('WIN32 AND WATCHOS', 'OFF') - - -def test_simplify_apple_and_appleosx(): - validate_simplify('APPLE AND MACOS', 'MACOS') - - -def test_simplify_apple_or_appleosx(): - validate_simplify('APPLE OR MACOS', 'APPLE') - - -def test_simplify_apple_or_appleosx_level1(): - validate_simplify('foobar AND (APPLE OR MACOS )', 'APPLE AND foobar') - - -def test_simplify_apple_or_appleosx_level1_double(): - validate_simplify('foobar AND (APPLE OR MACOS )', 'APPLE AND foobar') - - -def test_simplify_apple_or_appleosx_level1_double_with_extra_spaces(): - validate_simplify('foobar AND (APPLE OR MACOS ) ' - 'AND ( MACOS OR APPLE )', 'APPLE AND foobar') - - -def test_simplify_apple_or_appleosx_level2(): - validate_simplify('foobar AND ( ( APPLE OR WATCHOS ) ' - 'OR MACOS ) AND ( MACOS OR APPLE ) ' - 'AND ( (WIN32 OR WINRT) OR UNIX) ', 'APPLE AND foobar') - - -def test_simplify_not_apple_and_appleosx(): - validate_simplify('NOT APPLE AND MACOS', 'OFF') - - -def test_simplify_unix_and_bar_or_win32(): - validate_simplify('WIN32 AND bar AND UNIX', 'OFF') - - -def test_simplify_unix_or_bar_or_win32(): - validate_simplify('WIN32 OR bar OR UNIX', 'ON') - - -def test_simplify_complex_true(): - validate_simplify('WIN32 OR ( APPLE OR UNIX)', 'ON') - - -def test_simplify_apple_unix_freebsd(): - validate_simplify('( APPLE OR ( UNIX OR FREEBSD ))', 'UNIX') - - -def test_simplify_apple_unix_freebsd_foobar(): - validate_simplify('( APPLE OR ( UNIX OR FREEBSD ) OR foobar)', - 'UNIX OR foobar') - - -def test_simplify_complex_false(): - validate_simplify('WIN32 AND foobar AND ( ' - 'APPLE OR ( UNIX OR FREEBSD ))', - 'OFF') - - -def test_simplify_android_not_apple(): - validate_simplify('ANDROID AND NOT MACOS', 'ANDROID') diff --git a/util/cmake/tests/test_operations.py b/util/cmake/tests/test_operations.py deleted file mode 100755 index 95f894dae4b..00000000000 --- a/util/cmake/tests/test_operations.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2018 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -from pro2cmake import AddOperation, SetOperation, UniqueAddOperation, RemoveOperation - -def test_add_operation(): - op = AddOperation(['bar', 'buz']) - - result = op.process(['foo', 'bar'], ['foo', 'bar'], lambda x: x) - assert ['foo', 'bar', 'bar', 'buz'] == result - - -def test_uniqueadd_operation(): - op = UniqueAddOperation(['bar', 'buz']) - - result = op.process(['foo', 'bar'], ['foo', 'bar'], lambda x: x) - assert ['foo', 'bar', 'buz'] == result - - -def test_set_operation(): - op = SetOperation(['bar', 'buz']) - - result = op.process(['foo', 'bar'], ['foo', 'bar'], lambda x: x) - assert ['bar', 'buz'] == result - - -def test_remove_operation(): - op = RemoveOperation(['bar', 'buz']) - - result = op.process(['foo', 'bar'], ['foo', 'bar'], lambda x: x) - assert ['foo', '-buz'] == result diff --git a/util/cmake/tests/test_parsing.py b/util/cmake/tests/test_parsing.py deleted file mode 100755 index ceda348f534..00000000000 --- a/util/cmake/tests/test_parsing.py +++ /dev/null @@ -1,343 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2018 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -import os -from pro2cmake import map_condition -from qmake_parser import QmakeParser -from condition_simplifier import simplify_condition - - -_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']['value'] - assert value == to_validate.get('value', None) - - -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) - - print('\n\n#### Parser result:') - print(result) - print('\n#### End of parser result.\n') - - print('\n\n####Parser result dictionary:') - print(result.asDict()) - print('\n#### End of parser result dictionary.\n') - - result_dictionary = result.asDict() - - assert len(result_dictionary) == 1 - - return result_dictionary['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_multiline_assign(): - result = parse_file(_tests_path + '/data/multiline_assign.pro') - assert len(result) == 2 - validate_op('A', '=', ['42', '43', '44'], result[0]) - validate_op('B', '=', ['23'], result[1]) - - -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 'included' in include - assert include['included'].get('value', '') == '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_for(): - result = parse_file(_tests_path + '/data/for.pro') - assert len(result) == 2 - validate_op('SOURCES', '=', ['main.cpp'], result[0]) - assert result[1] == [] - - -def test_single_line_for(): - result = parse_file(_tests_path + '/data/single_line_for.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 - - -def test_realworld_standardpaths(): - result = parse_file(_tests_path + '/data/standardpaths.pro') - - (cond, if_branch, else_branch) = evaluate_condition(result[0]) - assert cond == 'win32' - assert len(if_branch) == 1 - assert len(else_branch) == 1 - - # win32: - (cond1, if_branch1, else_branch1) = evaluate_condition(if_branch[0]) - assert cond1 == '!winrt' - assert len(if_branch1) == 1 - validate_op('SOURCES', '+=', ['io/qstandardpaths_win.cpp'], if_branch1[0]) - assert len(else_branch1) == 1 - validate_op('SOURCES', '+=', ['io/qstandardpaths_winrt.cpp'], else_branch1[0]) - - # unix: - (cond2, if_branch2, else_branch2) = evaluate_condition(else_branch[0]) - assert cond2 == 'unix' - assert len(if_branch2) == 1 - assert len(else_branch2) == 0 - - # mac / else: - (cond3, if_branch3, else_branch3) = evaluate_condition(if_branch2[0]) - assert cond3 == 'mac' - assert len(if_branch3) == 1 - validate_op('OBJECTIVE_SOURCES', '+=', ['io/qstandardpaths_mac.mm'], if_branch3[0]) - assert len(else_branch3) == 1 - - # android / else: - (cond4, if_branch4, else_branch4) = evaluate_condition(else_branch3[0]) - assert cond4 == 'android' - assert len(if_branch4) == 1 - validate_op('SOURCES', '+=', ['io/qstandardpaths_android.cpp'], if_branch4[0]) - assert len(else_branch4) == 1 - - # haiku / else: - (cond5, if_branch5, else_branch5) = evaluate_condition(else_branch4[0]) - assert cond5 == 'haiku' - assert len(if_branch5) == 1 - validate_op('SOURCES', '+=', ['io/qstandardpaths_haiku.cpp'], if_branch5[0]) - assert len(else_branch5) == 1 - validate_op('SOURCES', '+=', ['io/qstandardpaths_unix.cpp'], else_branch5[0]) - - -def test_realworld_comment_scope(): - result = parse_file(_tests_path + '/data/comment_scope.pro') - assert len(result) == 2 - (cond, if_branch, else_branch) = evaluate_condition(result[0]) - assert cond == 'freebsd|openbsd' - assert len(if_branch) == 1 - validate_op('QMAKE_LFLAGS_NOUNDEF', '=', [], if_branch[0]) - - assert 'included' in result[1] - assert result[1]['included'].get('value', '') == 'animation/animation.pri' - - -def test_realworld_contains_scope(): - result = parse_file(_tests_path + '/data/contains_scope.pro') - assert len(result) == 2 - - -def test_realworld_complex_assign(): - result = parse_file(_tests_path + '/data/complex_assign.pro') - assert len(result) == 1 - validate_op('qmake-clean.commands', '+=', '( cd qmake && $(MAKE) clean ":-(==)-:" \'(Foo)\' )'.split(), - result[0]) - - -def test_realworld_complex_condition(): - result = parse_file(_tests_path + '/data/complex_condition.pro') - assert len(result) == 1 - (cond, if_branch, else_branch) = evaluate_condition(result[0]) - assert cond == '!system("dbus-send --session --type=signal / ' \ - 'local.AutotestCheck.Hello >$$QMAKE_SYSTEM_NULL_DEVICE ' \ - '2>&1")' - assert len(if_branch) == 1 - validate_op('SOURCES', '=', ['dbus.cpp'], if_branch[0]) - - assert len(else_branch) == 0 - - -def test_realworld_sql(): - result = parse_file(_tests_path + '/data/sql.pro') - assert len(result) == 2 - validate_op('TEMPLATE', '=', ['subdirs'], result[0]) - validate_op('SUBDIRS', '=', ['kernel'], result[1]) - - -def test_realworld_qtconfig(): - result = parse_file(_tests_path + '/data/escaped_value.pro') - assert len(result) == 1 - validate_op('MODULE_AUX_INCLUDES', '=', ['\\$\\$QT_MODULE_INCLUDE_BASE/QtANGLE'], result[0]) - - -def test_realworld_lc(): - result = parse_file(_tests_path + '/data/lc.pro') - assert len(result) == 3 - - -def test_realworld_lc_with_comment_in_between(): - result = parse_file(_tests_path + '/data/lc_with_comment.pro') - - my_var = result[1]['value'][0] - assert my_var == 'foo' - - my_var = result[2]['value'][0] - assert my_var == 'foo2' - - my_var = result[3]['value'][0] - assert my_var == 'foo3' - - my_var = result[4]['value'][0] - assert my_var == 'foo4' - - my_var = result[5]['value'][0] - assert my_var == 'foo5' - - sub_dirs = result[0]['value'] - assert sub_dirs[0] == 'tga' - assert sub_dirs[1] == 'wbmp' - assert len(result) == 6 - - -def test_condition_without_scope(): - result = parse_file(_tests_path + '/data/condition_without_scope.pro') - assert len(result) == 1 - - -def test_multi_condition_divided_by_lc(): - result = parse_file(_tests_path + '/data/multi_condition_divided_by_lc.pro') - assert len(result) == 1 - - -def test_nested_function_calls(): - result = parse_file(_tests_path + '/data/nested_function_calls.pro') - assert len(result) == 1 - -def test_value_function(): - result = parse_file(_tests_path + '/data/value_function.pro') - target = result[0]['value'][0] - assert target == 'Dummy' - value = result[1]['value'] - assert value[0] == '$$TARGET' - - -def test_condition_operator_precedence(): - result = parse_file(_tests_path + '/data/condition_operator_precedence.pro') - - def validate_simplify(input_str: str, expected: str) -> None: - output = simplify_condition(map_condition(input_str)) - assert output == expected - - validate_simplify(result[0]["condition"], "a1 OR a2") - validate_simplify(result[1]["condition"], "b3 AND (b1 OR b2)") - validate_simplify(result[2]["condition"], "c4 OR (c1 AND c3) OR (c2 AND c3)") diff --git a/util/cmake/tests/test_scope_handling.py b/util/cmake/tests/test_scope_handling.py deleted file mode 100755 index b36c5d5bcd2..00000000000 --- a/util/cmake/tests/test_scope_handling.py +++ /dev/null @@ -1,318 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2021 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -from pro2cmake import Scope, SetOperation, merge_scopes, recursive_evaluate_scope - -import pytest -import typing - -ScopeList = typing.List[Scope] - -def _map_to_operation(**kwargs): - result = {} # type: typing.Mapping[str, typing.List[SetOperation]] - for (key, value) in kwargs.items(): - result[key] = [SetOperation([value])] - return result - - -def _new_scope(*, parent_scope=None, condition='', **kwargs) -> Scope: - return Scope(parent_scope=parent_scope, - qmake_file='file1', condition=condition, operations=_map_to_operation(**kwargs)) - - -def _evaluate_scopes(scopes: ScopeList) -> ScopeList: - for s in scopes: - if not s.parent: - recursive_evaluate_scope(s) - return scopes - - -def _validate(input_scopes: ScopeList, output_scopes: ScopeList): - merged_scopes = merge_scopes(input_scopes) - assert merged_scopes == output_scopes - - -def test_evaluate_one_scope(): - scope = _new_scope(condition='QT_FEATURE_foo', test1='bar') - - input_scope = scope - recursive_evaluate_scope(scope) - assert scope == input_scope - - -def test_evaluate_child_scope(): - scope = _new_scope(condition='QT_FEATURE_foo', test1='bar') - _new_scope(parent_scope=scope, condition='QT_FEATURE_bar', test2='bar') - - input_scope = scope - recursive_evaluate_scope(scope) - - assert scope.total_condition == 'QT_FEATURE_foo' - assert len(scope.children) == 1 - assert scope.get_string('test1') == 'bar' - assert scope.get_string('test2', 'not found') == 'not found' - - child = scope.children[0] - assert child.total_condition == 'QT_FEATURE_bar AND QT_FEATURE_foo' - assert child.get_string('test1', 'not found') == 'not found' - assert child.get_string('test2') == 'bar' - - -def test_evaluate_two_child_scopes(): - scope = _new_scope(condition='QT_FEATURE_foo', test1='bar') - _new_scope(parent_scope=scope, condition='QT_FEATURE_bar', test2='bar') - _new_scope(parent_scope=scope, condition='QT_FEATURE_buz', test3='buz') - - input_scope = scope - recursive_evaluate_scope(scope) - - assert scope.total_condition == 'QT_FEATURE_foo' - assert len(scope.children) == 2 - assert scope.get_string('test1') == 'bar' - assert scope.get_string('test2', 'not found') == 'not found' - assert scope.get_string('test3', 'not found') == 'not found' - - child1 = scope.children[0] - assert child1.total_condition == 'QT_FEATURE_bar AND QT_FEATURE_foo' - assert child1.get_string('test1', 'not found') == 'not found' - assert child1.get_string('test2') == 'bar' - assert child1.get_string('test3', 'not found') == 'not found' - - child2 = scope.children[1] - assert child2.total_condition == 'QT_FEATURE_buz AND QT_FEATURE_foo' - assert child2.get_string('test1', 'not found') == 'not found' - assert child2.get_string('test2') == '' - assert child2.get_string('test3', 'not found') == 'buz' - - -def test_evaluate_else_child_scopes(): - scope = _new_scope(condition='QT_FEATURE_foo', test1='bar') - _new_scope(parent_scope=scope, condition='QT_FEATURE_bar', test2='bar') - _new_scope(parent_scope=scope, condition='else', test3='buz') - - input_scope = scope - recursive_evaluate_scope(scope) - - assert scope.total_condition == 'QT_FEATURE_foo' - assert len(scope.children) == 2 - assert scope.get_string('test1') == 'bar' - assert scope.get_string('test2', 'not found') == 'not found' - assert scope.get_string('test3', 'not found') == 'not found' - - child1 = scope.children[0] - assert child1.total_condition == 'QT_FEATURE_bar AND QT_FEATURE_foo' - assert child1.get_string('test1', 'not found') == 'not found' - assert child1.get_string('test2') == 'bar' - assert child1.get_string('test3', 'not found') == 'not found' - - child2 = scope.children[1] - assert child2.total_condition == 'QT_FEATURE_foo AND NOT QT_FEATURE_bar' - assert child2.get_string('test1', 'not found') == 'not found' - assert child2.get_string('test2') == '' - assert child2.get_string('test3', 'not found') == 'buz' - - -def test_evaluate_invalid_else_child_scopes(): - scope = _new_scope(condition='QT_FEATURE_foo', test1='bar') - _new_scope(parent_scope=scope, condition='else', test3='buz') - _new_scope(parent_scope=scope, condition='QT_FEATURE_bar', test2='bar') - - input_scope = scope - with pytest.raises(AssertionError): - recursive_evaluate_scope(scope) - - -def test_merge_empty_scope_list(): - _validate([], []) - - -def test_merge_one_scope(): - scopes = [_new_scope(test='foo')] - - recursive_evaluate_scope(scopes[0]) - - _validate(scopes, scopes) - - -def test_merge_one_on_scope(): - scopes = [_new_scope(condition='ON', test='foo')] - - recursive_evaluate_scope(scopes[0]) - - _validate(scopes, scopes) - - -def test_merge_one_off_scope(): - scopes = [_new_scope(condition='OFF', test='foo')] - - recursive_evaluate_scope(scopes[0]) - - _validate(scopes, []) - - -def test_merge_one_conditioned_scope(): - scopes = [_new_scope(condition='QT_FEATURE_foo', test='foo')] - - recursive_evaluate_scope(scopes[0]) - - _validate(scopes, scopes) - - -def test_merge_two_scopes_with_same_condition(): - scopes = [_new_scope(condition='QT_FEATURE_bar', test='foo'), - _new_scope(condition='QT_FEATURE_bar', test2='bar')] - - recursive_evaluate_scope(scopes[0]) - recursive_evaluate_scope(scopes[1]) - - result = merge_scopes(scopes) - - assert len(result) == 1 - r0 = result[0] - assert r0.total_condition == 'QT_FEATURE_bar' - assert r0.get_string('test') == 'foo' - assert r0.get_string('test2') == 'bar' - - -def test_merge_three_scopes_two_with_same_condition(): - scopes = [_new_scope(condition='QT_FEATURE_bar', test='foo'), - _new_scope(condition='QT_FEATURE_baz', test1='buz'), - _new_scope(condition='QT_FEATURE_bar', test2='bar')] - - recursive_evaluate_scope(scopes[0]) - recursive_evaluate_scope(scopes[1]) - recursive_evaluate_scope(scopes[2]) - - result = merge_scopes(scopes) - - assert len(result) == 2 - r0 = result[0] - assert r0.total_condition == 'QT_FEATURE_bar' - assert r0.get_string('test') == 'foo' - assert r0.get_string('test2') == 'bar' - - assert result[1] == scopes[1] - - -def test_merge_two_unrelated_on_off_scopes(): - scopes = [_new_scope(condition='ON', test='foo'), - _new_scope(condition='OFF', test2='bar')] - - recursive_evaluate_scope(scopes[0]) - recursive_evaluate_scope(scopes[1]) - - _validate(scopes, [scopes[0]]) - - -def test_merge_two_unrelated_on_off_scopes(): - scopes = [_new_scope(condition='OFF', test='foo'), - _new_scope(condition='ON', test2='bar')] - - recursive_evaluate_scope(scopes[0]) - recursive_evaluate_scope(scopes[1]) - - _validate(scopes, [scopes[1]]) - - -def test_merge_parent_child_scopes_with_different_conditions(): - scope = _new_scope(condition='FOO', test1='parent') - scopes = [scope, _new_scope(parent_scope=scope, condition='bar', test2='child')] - - recursive_evaluate_scope(scope) - - _validate(scopes, scopes) - - -def test_merge_parent_child_scopes_with_same_conditions(): - scope = _new_scope(condition='FOO AND bar', test1='parent') - scopes = [scope, _new_scope(parent_scope=scope, condition='FOO AND bar', test2='child')] - - recursive_evaluate_scope(scope) - - result = merge_scopes(scopes) - - assert len(result) == 1 - r0 = result[0] - assert r0.parent == None - assert r0.total_condition == 'FOO AND bar' - assert r0.get_string('test1') == 'parent' - assert r0.get_string('test2') == 'child' - - -def test_merge_parent_child_scopes_with_on_child_condition(): - scope = _new_scope(condition='FOO AND bar', test1='parent') - scopes = [scope, _new_scope(parent_scope=scope, condition='ON', test2='child')] - - recursive_evaluate_scope(scope) - - result = merge_scopes(scopes) - - assert len(result) == 1 - r0 = result[0] - assert r0.parent == None - assert r0.total_condition == 'FOO AND bar' - assert r0.get_string('test1') == 'parent' - assert r0.get_string('test2') == 'child' - - -# Real world examples: - -# qstandardpaths selection: - -def test_qstandardpaths_scopes(): - # top level: - scope1 = _new_scope(condition='ON', scope_id=1) - - # win32 { - scope2 = _new_scope(parent_scope=scope1, condition='WIN32') - # !winrt { - # SOURCES += io/qstandardpaths_win.cpp - scope3 = _new_scope(parent_scope=scope2, condition='NOT WINRT', - SOURCES='qsp_win.cpp') - # } else { - # SOURCES += io/qstandardpaths_winrt.cpp - scope4 = _new_scope(parent_scope=scope2, condition='else', - SOURCES='qsp_winrt.cpp') - # } - # else: unix { - scope5 = _new_scope(parent_scope=scope1, condition='else') - scope6 = _new_scope(parent_scope=scope5, condition='UNIX') - # mac { - # OBJECTIVE_SOURCES += io/qstandardpaths_mac.mm - scope7 = _new_scope(parent_scope=scope6, condition='MACOS', SOURCES='qsp_mac.mm') - # } else:android { - # SOURCES += io/qstandardpaths_android.cpp - scope8 = _new_scope(parent_scope=scope6, condition='else') - scope9 = _new_scope(parent_scope=scope8, condition='ANDROID AND NOT UNKNOWN_PLATFORM', SOURCES='qsp_android.cpp') - # } else:haiku { - # SOURCES += io/qstandardpaths_haiku.cpp - scope10 = _new_scope(parent_scope=scope8, condition='else') - scope11 = _new_scope(parent_scope=scope10, condition='HAIKU', SOURCES='qsp_haiku.cpp') - # } else { - # SOURCES +=io/qstandardpaths_unix.cpp - scope12 = _new_scope(parent_scope=scope10, condition='else', SOURCES='qsp_unix.cpp') - # } - # } - - recursive_evaluate_scope(scope1) - - assert scope1.total_condition == 'ON' - assert scope2.total_condition == 'WIN32' - assert scope3.total_condition == 'WIN32 AND NOT WINRT' - assert scope4.total_condition == 'WINRT' - assert scope5.total_condition == 'UNIX' - assert scope6.total_condition == 'UNIX' - assert scope7.total_condition == 'MACOS' - assert scope8.total_condition == 'UNIX AND NOT MACOS' - assert scope9.total_condition == 'ANDROID AND NOT UNKNOWN_PLATFORM' - assert scope10.total_condition == 'UNIX AND NOT MACOS AND (UNKNOWN_PLATFORM OR NOT ANDROID)' - assert scope11.total_condition == 'HAIKU AND (UNKNOWN_PLATFORM OR NOT ANDROID)' - assert scope12.total_condition == 'UNIX AND NOT HAIKU AND NOT MACOS AND (UNKNOWN_PLATFORM OR NOT ANDROID)' - -def test_recursive_expansion(): - scope = _new_scope(A='Foo',B='$$A/Bar') - assert scope.get_string('A') == 'Foo' - assert scope.get_string('B') == '$$A/Bar' - assert scope._expand_value('$$B/Source.cpp') == ['Foo/Bar/Source.cpp'] - assert scope._expand_value('$$B') == ['Foo/Bar']