From d7a739bde116116ec6cacdb99c3a235705017529 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Tue, 11 Feb 2025 13:30:02 +0100 Subject: [PATCH] CMake: Remove pro2cmake and configurejson2cmake Most qt repos and modules are now ported from qmake to CMake. The CMake API for internal Qt modules has evolved, and these tools have not been kept up-to-date. It's time to remove them. Developers can still use qmake2cmake for their own user projects, which is hosted in a different repo. If we do end up needing the scripts again, they can be used from one of the older branches like 6.9. Fixes: QTBUG-133678 Change-Id: I8d9a765f2575a6c0fcfe9a0346a06a7eec302914 Reviewed-by: Alexey Edelev Reviewed-by: Joerg Bornemann --- util/cmake/.gitignore | 1 - util/cmake/Makefile | 20 - util/cmake/Pipfile | 18 - util/cmake/README.md | 57 - util/cmake/cmakeconversionrate.py | 116 - util/cmake/condition_simplifier.py | 211 - util/cmake/condition_simplifier_cache.py | 154 - util/cmake/configurejson2cmake.py | 1559 ----- util/cmake/generate_module_map.sh | 13 - util/cmake/helper.py | 868 --- util/cmake/json_parser.py | 76 - util/cmake/pro2cmake.py | 5094 ----------------- util/cmake/pro_conversion_rate.py | 210 - util/cmake/qmake_parser.py | 422 -- util/cmake/requirements.txt | 8 - util/cmake/run_pro2cmake.py | 221 - util/cmake/special_case_helper.py | 397 -- util/cmake/tests/__init__.py | 0 util/cmake/tests/data/comment_scope.pro | 6 - util/cmake/tests/data/complex_assign.pro | 2 - util/cmake/tests/data/complex_condition.pro | 4 - util/cmake/tests/data/complex_values.pro | 22 - .../data/condition_operator_precedence.pro | 11 - .../tests/data/condition_without_scope.pro | 2 - util/cmake/tests/data/contains_scope.pro | 4 - .../data/conversion/optional_qt_modules.pro | 4 - .../data/conversion/qt_version_check.pro | 8 - .../data/conversion/required_qt_modules.pro | 3 - util/cmake/tests/data/definetest.pro | 6 - util/cmake/tests/data/else.pro | 6 - util/cmake/tests/data/else2.pro | 4 - util/cmake/tests/data/else3.pro | 7 - util/cmake/tests/data/else4.pro | 6 - util/cmake/tests/data/else5.pro | 10 - util/cmake/tests/data/else6.pro | 11 - util/cmake/tests/data/else7.pro | 2 - util/cmake/tests/data/else8.pro | 5 - util/cmake/tests/data/escaped_value.pro | 2 - util/cmake/tests/data/for.pro | 11 - util/cmake/tests/data/function_if.pro | 4 - util/cmake/tests/data/include.pro | 3 - util/cmake/tests/data/lc.pro | 10 - util/cmake/tests/data/lc_with_comment.pro | 22 - util/cmake/tests/data/load.pro | 3 - .../data/multi_condition_divided_by_lc.pro | 3 - util/cmake/tests/data/multiline_assign.pro | 4 - .../tests/data/nested_function_calls.pro | 2 - util/cmake/tests/data/quoted.pro | 5 - util/cmake/tests/data/single_line_for.pro | 4 - util/cmake/tests/data/sql.pro | 3 - util/cmake/tests/data/standardpaths.pro | 17 - util/cmake/tests/data/unset.pro | 2 - util/cmake/tests/data/value_function.pro | 2 - util/cmake/tests/test_conversion.py | 66 - util/cmake/tests/test_lc_fixup.py | 19 - util/cmake/tests/test_logic_mapping.py | 160 - util/cmake/tests/test_operations.py | 32 - util/cmake/tests/test_parsing.py | 343 -- util/cmake/tests/test_scope_handling.py | 318 - 59 files changed, 10603 deletions(-) delete mode 100644 util/cmake/.gitignore delete mode 100644 util/cmake/Makefile delete mode 100644 util/cmake/Pipfile delete mode 100644 util/cmake/README.md delete mode 100755 util/cmake/cmakeconversionrate.py delete mode 100755 util/cmake/condition_simplifier.py delete mode 100755 util/cmake/condition_simplifier_cache.py delete mode 100755 util/cmake/configurejson2cmake.py delete mode 100755 util/cmake/generate_module_map.sh delete mode 100644 util/cmake/helper.py delete mode 100644 util/cmake/json_parser.py delete mode 100755 util/cmake/pro2cmake.py delete mode 100755 util/cmake/pro_conversion_rate.py delete mode 100755 util/cmake/qmake_parser.py delete mode 100644 util/cmake/requirements.txt delete mode 100755 util/cmake/run_pro2cmake.py delete mode 100644 util/cmake/special_case_helper.py delete mode 100644 util/cmake/tests/__init__.py delete mode 100644 util/cmake/tests/data/comment_scope.pro delete mode 100644 util/cmake/tests/data/complex_assign.pro delete mode 100644 util/cmake/tests/data/complex_condition.pro delete mode 100644 util/cmake/tests/data/complex_values.pro delete mode 100644 util/cmake/tests/data/condition_operator_precedence.pro delete mode 100644 util/cmake/tests/data/condition_without_scope.pro delete mode 100644 util/cmake/tests/data/contains_scope.pro delete mode 100644 util/cmake/tests/data/conversion/optional_qt_modules.pro delete mode 100644 util/cmake/tests/data/conversion/qt_version_check.pro delete mode 100644 util/cmake/tests/data/conversion/required_qt_modules.pro delete mode 100644 util/cmake/tests/data/definetest.pro delete mode 100644 util/cmake/tests/data/else.pro delete mode 100644 util/cmake/tests/data/else2.pro delete mode 100644 util/cmake/tests/data/else3.pro delete mode 100644 util/cmake/tests/data/else4.pro delete mode 100644 util/cmake/tests/data/else5.pro delete mode 100644 util/cmake/tests/data/else6.pro delete mode 100644 util/cmake/tests/data/else7.pro delete mode 100644 util/cmake/tests/data/else8.pro delete mode 100644 util/cmake/tests/data/escaped_value.pro delete mode 100644 util/cmake/tests/data/for.pro delete mode 100644 util/cmake/tests/data/function_if.pro delete mode 100644 util/cmake/tests/data/include.pro delete mode 100644 util/cmake/tests/data/lc.pro delete mode 100644 util/cmake/tests/data/lc_with_comment.pro delete mode 100644 util/cmake/tests/data/load.pro delete mode 100644 util/cmake/tests/data/multi_condition_divided_by_lc.pro delete mode 100644 util/cmake/tests/data/multiline_assign.pro delete mode 100644 util/cmake/tests/data/nested_function_calls.pro delete mode 100644 util/cmake/tests/data/quoted.pro delete mode 100644 util/cmake/tests/data/single_line_for.pro delete mode 100644 util/cmake/tests/data/sql.pro delete mode 100644 util/cmake/tests/data/standardpaths.pro delete mode 100644 util/cmake/tests/data/unset.pro delete mode 100644 util/cmake/tests/data/value_function.pro delete mode 100755 util/cmake/tests/test_conversion.py delete mode 100755 util/cmake/tests/test_lc_fixup.py delete mode 100755 util/cmake/tests/test_logic_mapping.py delete mode 100755 util/cmake/tests/test_operations.py delete mode 100755 util/cmake/tests/test_parsing.py delete mode 100755 util/cmake/tests/test_scope_handling.py 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']