mirror of
https://github.com/crystalidea/qt6windows7.git
synced 2025-07-05 16:55:25 +08:00
qt 6.5.1 original
This commit is contained in:
20
util/cmake/Makefile
Normal file
20
util/cmake/Makefile
Normal file
@ -0,0 +1,20 @@
|
||||
|
||||
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
|
18
util/cmake/Pipfile
Normal file
18
util/cmake/Pipfile
Normal file
@ -0,0 +1,18 @@
|
||||
[[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"
|
57
util/cmake/README.md
Normal file
57
util/cmake/README.md
Normal file
@ -0,0 +1,57 @@
|
||||
# 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
|
||||
```
|
116
util/cmake/cmakeconversionrate.py
Normal file
116
util/cmake/cmakeconversionrate.py
Normal file
@ -0,0 +1,116 @@
|
||||
#!/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="<Source Directory>",
|
||||
type=str,
|
||||
help="The Qt module source directory",
|
||||
)
|
||||
parser.add_argument(
|
||||
"binary_directory",
|
||||
metavar="<CMake build directory>",
|
||||
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()
|
211
util/cmake/condition_simplifier.py
Normal file
211
util/cmake/condition_simplifier.py
Normal file
@ -0,0 +1,211 @@
|
||||
#!/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"
|
154
util/cmake/condition_simplifier_cache.py
Normal file
154
util/cmake/condition_simplifier_cache.py
Normal file
@ -0,0 +1,154 @@
|
||||
#!/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
|
1559
util/cmake/configurejson2cmake.py
Normal file
1559
util/cmake/configurejson2cmake.py
Normal file
File diff suppressed because it is too large
Load Diff
13
util/cmake/generate_module_map.sh
Normal file
13
util/cmake/generate_module_map.sh
Normal file
@ -0,0 +1,13 @@
|
||||
#!/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
|
869
util/cmake/helper.py
Normal file
869
util/cmake/helper.py
Normal file
@ -0,0 +1,869 @@
|
||||
# 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",
|
||||
"nacl": "NACL",
|
||||
"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
|
||||
)
|
76
util/cmake/json_parser.py
Normal file
76
util/cmake/json_parser.py
Normal file
@ -0,0 +1,76 @@
|
||||
#!/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
|
5102
util/cmake/pro2cmake.py
Normal file
5102
util/cmake/pro2cmake.py
Normal file
File diff suppressed because it is too large
Load Diff
210
util/cmake/pro_conversion_rate.py
Normal file
210
util/cmake/pro_conversion_rate.py
Normal file
@ -0,0 +1,210 @@
|
||||
#!/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 <src dir>
|
||||
where <src dir> 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="<src dir>", 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()
|
422
util/cmake/qmake_parser.py
Normal file
422
util/cmake/qmake_parser.py
Normal file
@ -0,0 +1,422 @@
|
||||
#!/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)
|
8
util/cmake/requirements.txt
Normal file
8
util/cmake/requirements.txt
Normal file
@ -0,0 +1,8 @@
|
||||
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'
|
||||
|
221
util/cmake/run_pro2cmake.py
Normal file
221
util/cmake/run_pro2cmake.py
Normal file
@ -0,0 +1,221 @@
|
||||
#!/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="<path>", 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()
|
397
util/cmake/special_case_helper.py
Normal file
397
util/cmake/special_case_helper.py
Normal file
@ -0,0 +1,397 @@
|
||||
#!/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
|
0
util/cmake/tests/__init__.py
Normal file
0
util/cmake/tests/__init__.py
Normal file
6
util/cmake/tests/data/comment_scope.pro
Normal file
6
util/cmake/tests/data/comment_scope.pro
Normal file
@ -0,0 +1,6 @@
|
||||
# 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)
|
2
util/cmake/tests/data/complex_assign.pro
Normal file
2
util/cmake/tests/data/complex_assign.pro
Normal file
@ -0,0 +1,2 @@
|
||||
qmake-clean.commands += (cd qmake && $(MAKE) clean ":-(==)-:" '(Foo)' )
|
||||
|
4
util/cmake/tests/data/complex_condition.pro
Normal file
4
util/cmake/tests/data/complex_condition.pro
Normal file
@ -0,0 +1,4 @@
|
||||
!system("dbus-send --session --type=signal / local.AutotestCheck.Hello >$$QMAKE_SYSTEM_NULL_DEVICE 2>&1") {
|
||||
SOURCES = dbus.cpp
|
||||
}
|
||||
|
22
util/cmake/tests/data/complex_values.pro
Normal file
22
util/cmake/tests/data/complex_values.pro
Normal file
@ -0,0 +1,22 @@
|
||||
linux:!static {
|
||||
precompile_header {
|
||||
# we'll get an error if we just use SOURCES +=
|
||||
no_pch_assembler.commands = $$QMAKE_CC -c $(CFLAGS) $(INCPATH) ${QMAKE_FILE_IN} -o ${QMAKE_FILE_OUT}
|
||||
no_pch_assembler.dependency_type = TYPE_C
|
||||
no_pch_assembler.output = ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_OBJ)}
|
||||
no_pch_assembler.input = NO_PCH_ASM
|
||||
no_pch_assembler.name = compiling[no_pch] ${QMAKE_FILE_IN}
|
||||
silent: no_pch_assembler.commands = @echo compiling[no_pch] ${QMAKE_FILE_IN} && $$no_pch_assembler.commands
|
||||
CMAKE_ANGLE_GLES2_IMPLIB_RELEASE = libGLESv2.$${QMAKE_EXTENSION_STATICLIB}
|
||||
HOST_BINS = $$[QT_HOST_BINS]
|
||||
CMAKE_HOST_DATA_DIR = $$[QT_HOST_DATA/src]/
|
||||
TR_EXCLUDE += ../3rdparty/*
|
||||
|
||||
QMAKE_EXTRA_COMPILERS += no_pch_assembler
|
||||
NO_PCH_ASM += global/minimum-linux.S
|
||||
} else {
|
||||
SOURCES += global/minimum-linux.S
|
||||
}
|
||||
HEADERS += global/minimum-linux_p.h
|
||||
}
|
||||
|
11
util/cmake/tests/data/condition_operator_precedence.pro
Normal file
11
util/cmake/tests/data/condition_operator_precedence.pro
Normal file
@ -0,0 +1,11 @@
|
||||
a1|a2 {
|
||||
DEFINES += d
|
||||
}
|
||||
|
||||
b1|b2:b3 {
|
||||
DEFINES += d
|
||||
}
|
||||
|
||||
c1|c2:c3|c4 {
|
||||
DEFINES += d
|
||||
}
|
2
util/cmake/tests/data/condition_without_scope.pro
Normal file
2
util/cmake/tests/data/condition_without_scope.pro
Normal file
@ -0,0 +1,2 @@
|
||||
write_file("a", contents)|error()
|
||||
|
4
util/cmake/tests/data/contains_scope.pro
Normal file
4
util/cmake/tests/data/contains_scope.pro
Normal file
@ -0,0 +1,4 @@
|
||||
contains(DEFINES,QT_EVAL):include(eval.pri)
|
||||
|
||||
HOST_BINS = $$[QT_HOST_BINS]
|
||||
|
4
util/cmake/tests/data/conversion/optional_qt_modules.pro
Normal file
4
util/cmake/tests/data/conversion/optional_qt_modules.pro
Normal file
@ -0,0 +1,4 @@
|
||||
TARGET = myapp
|
||||
QT = core network widgets
|
||||
win32: QT += opengl
|
||||
SOURCES = main.cpp
|
8
util/cmake/tests/data/conversion/qt_version_check.pro
Normal file
8
util/cmake/tests/data/conversion/qt_version_check.pro
Normal file
@ -0,0 +1,8 @@
|
||||
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
|
||||
}
|
3
util/cmake/tests/data/conversion/required_qt_modules.pro
Normal file
3
util/cmake/tests/data/conversion/required_qt_modules.pro
Normal file
@ -0,0 +1,3 @@
|
||||
TARGET = myapp
|
||||
QT = core network widgets
|
||||
SOURCES = main.cpp
|
6
util/cmake/tests/data/definetest.pro
Normal file
6
util/cmake/tests/data/definetest.pro
Normal file
@ -0,0 +1,6 @@
|
||||
defineTest(pathIsAbsolute) {
|
||||
p = $$clean_path($$1)
|
||||
!isEmpty(p):isEqual(p, $$absolute_path($$p)): return(true)
|
||||
return(false)
|
||||
}
|
||||
|
6
util/cmake/tests/data/else.pro
Normal file
6
util/cmake/tests/data/else.pro
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
linux {
|
||||
SOURCES += a.cpp
|
||||
} else {
|
||||
SOURCES += b.cpp
|
||||
}
|
4
util/cmake/tests/data/else2.pro
Normal file
4
util/cmake/tests/data/else2.pro
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
osx: A = 1
|
||||
else: win32: B = 2
|
||||
else: C = 3
|
7
util/cmake/tests/data/else3.pro
Normal file
7
util/cmake/tests/data/else3.pro
Normal file
@ -0,0 +1,7 @@
|
||||
qtConfig(timezone) {
|
||||
A = 1
|
||||
} else:win32 {
|
||||
B = 2
|
||||
} else {
|
||||
C = 3
|
||||
}
|
6
util/cmake/tests/data/else4.pro
Normal file
6
util/cmake/tests/data/else4.pro
Normal file
@ -0,0 +1,6 @@
|
||||
qtConfig(timezone) {
|
||||
A = 1
|
||||
} else:win32: B = 2
|
||||
else {
|
||||
C = 3
|
||||
}
|
10
util/cmake/tests/data/else5.pro
Normal file
10
util/cmake/tests/data/else5.pro
Normal file
@ -0,0 +1,10 @@
|
||||
# comments
|
||||
qtConfig(timezone) { # bar
|
||||
A = 1
|
||||
} else:win32 {
|
||||
B = 2 # foo
|
||||
} else { C = 3
|
||||
# baz
|
||||
# foobar
|
||||
}
|
||||
# endcomment
|
11
util/cmake/tests/data/else6.pro
Normal file
11
util/cmake/tests/data/else6.pro
Normal file
@ -0,0 +1,11 @@
|
||||
qtConfig(timezone) \
|
||||
{
|
||||
A = \
|
||||
1
|
||||
} \
|
||||
else:win32: \
|
||||
B = 2
|
||||
else: \
|
||||
C \
|
||||
= 3
|
||||
|
2
util/cmake/tests/data/else7.pro
Normal file
2
util/cmake/tests/data/else7.pro
Normal file
@ -0,0 +1,2 @@
|
||||
msvc:equals(QT_ARCH, i386): QMAKE_LFLAGS += /BASE:0x65000000
|
||||
|
5
util/cmake/tests/data/else8.pro
Normal file
5
util/cmake/tests/data/else8.pro
Normal file
@ -0,0 +1,5 @@
|
||||
qtConfig(timezone) { A = 1 } else:win32: {\
|
||||
B = 2 \
|
||||
} else: \
|
||||
C \
|
||||
= 3 \
|
2
util/cmake/tests/data/escaped_value.pro
Normal file
2
util/cmake/tests/data/escaped_value.pro
Normal file
@ -0,0 +1,2 @@
|
||||
MODULE_AUX_INCLUDES = \
|
||||
\$\$QT_MODULE_INCLUDE_BASE/QtANGLE
|
11
util/cmake/tests/data/for.pro
Normal file
11
util/cmake/tests/data/for.pro
Normal file
@ -0,0 +1,11 @@
|
||||
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)
|
||||
}
|
||||
}
|
4
util/cmake/tests/data/function_if.pro
Normal file
4
util/cmake/tests/data/function_if.pro
Normal file
@ -0,0 +1,4 @@
|
||||
pathIsAbsolute($$CMAKE_HOST_DATA_DIR) {
|
||||
CMAKE_HOST_DATA_DIR = $$[QT_HOST_DATA/src]/
|
||||
}
|
||||
|
3
util/cmake/tests/data/include.pro
Normal file
3
util/cmake/tests/data/include.pro
Normal file
@ -0,0 +1,3 @@
|
||||
A = 42
|
||||
include(foo) # load foo
|
||||
B=23
|
10
util/cmake/tests/data/lc.pro
Normal file
10
util/cmake/tests/data/lc.pro
Normal file
@ -0,0 +1,10 @@
|
||||
TEMPLATE=subdirs
|
||||
SUBDIRS=\
|
||||
qmacstyle \
|
||||
qstyle \
|
||||
qstyleoption \
|
||||
qstylesheetstyle \
|
||||
|
||||
!qtConfig(private_tests): SUBDIRS -= \
|
||||
qstylesheetstyle \
|
||||
|
22
util/cmake/tests/data/lc_with_comment.pro
Normal file
22
util/cmake/tests/data/lc_with_comment.pro
Normal file
@ -0,0 +1,22 @@
|
||||
SUBDIRS = \
|
||||
# dds \
|
||||
tga \
|
||||
wbmp
|
||||
|
||||
MYVAR = foo # comment
|
||||
MYVAR = foo2# comment
|
||||
MYVAR = foo3# comment #
|
||||
|
||||
MYVAR = foo4# comment #
|
||||
|
||||
##
|
||||
#
|
||||
#
|
||||
##
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
# #
|
||||
|
||||
MYVAR = foo5# comment # #
|
3
util/cmake/tests/data/load.pro
Normal file
3
util/cmake/tests/data/load.pro
Normal file
@ -0,0 +1,3 @@
|
||||
A = 42
|
||||
load(foo)# load foo
|
||||
B=23
|
3
util/cmake/tests/data/multi_condition_divided_by_lc.pro
Normal file
3
util/cmake/tests/data/multi_condition_divided_by_lc.pro
Normal file
@ -0,0 +1,3 @@
|
||||
equals(a): \
|
||||
greaterThan(a):flags += 1
|
||||
|
4
util/cmake/tests/data/multiline_assign.pro
Normal file
4
util/cmake/tests/data/multiline_assign.pro
Normal file
@ -0,0 +1,4 @@
|
||||
A = 42 \
|
||||
43 \
|
||||
44
|
||||
B=23
|
2
util/cmake/tests/data/nested_function_calls.pro
Normal file
2
util/cmake/tests/data/nested_function_calls.pro
Normal file
@ -0,0 +1,2 @@
|
||||
requires(qtConfig(dlopen))
|
||||
|
5
util/cmake/tests/data/quoted.pro
Normal file
5
util/cmake/tests/data/quoted.pro
Normal file
@ -0,0 +1,5 @@
|
||||
if(linux*|hurd*):!cross_compile:!static:!*-armcc* {
|
||||
prog=$$quote(if (/program interpreter: (.*)]/) { print $1; })
|
||||
DEFINES += ELF_INTERPRETER=\\\"$$system(LC_ALL=C readelf -l /bin/ls | perl -n -e \'$$prog\')\\\"
|
||||
}
|
||||
|
4
util/cmake/tests/data/single_line_for.pro
Normal file
4
util/cmake/tests/data/single_line_for.pro
Normal file
@ -0,0 +1,4 @@
|
||||
for(d, sd): \
|
||||
exists($$d/$${d}.pro): \
|
||||
SUBDIRS += $$d
|
||||
|
3
util/cmake/tests/data/sql.pro
Normal file
3
util/cmake/tests/data/sql.pro
Normal file
@ -0,0 +1,3 @@
|
||||
TEMPLATE = subdirs
|
||||
SUBDIRS = \
|
||||
kernel \
|
17
util/cmake/tests/data/standardpaths.pro
Normal file
17
util/cmake/tests/data/standardpaths.pro
Normal file
@ -0,0 +1,17 @@
|
||||
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
|
||||
}
|
||||
}
|
2
util/cmake/tests/data/unset.pro
Normal file
2
util/cmake/tests/data/unset.pro
Normal file
@ -0,0 +1,2 @@
|
||||
unset(f16c_cxx)
|
||||
|
2
util/cmake/tests/data/value_function.pro
Normal file
2
util/cmake/tests/data/value_function.pro
Normal file
@ -0,0 +1,2 @@
|
||||
TARGET = Dummy
|
||||
TARGET = $$qtLibraryTarget($$TARGET)
|
66
util/cmake/tests/test_conversion.py
Normal file
66
util/cmake/tests/test_conversion.py
Normal file
@ -0,0 +1,66 @@
|
||||
#!/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)
|
19
util/cmake/tests/test_lc_fixup.py
Normal file
19
util/cmake/tests/test_lc_fixup.py
Normal file
@ -0,0 +1,19 @@
|
||||
#!/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
|
160
util/cmake/tests/test_logic_mapping.py
Normal file
160
util/cmake/tests/test_logic_mapping.py
Normal file
@ -0,0 +1,160 @@
|
||||
#!/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')
|
32
util/cmake/tests/test_operations.py
Normal file
32
util/cmake/tests/test_operations.py
Normal file
@ -0,0 +1,32 @@
|
||||
#!/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
|
343
util/cmake/tests/test_parsing.py
Normal file
343
util/cmake/tests/test_parsing.py
Normal file
@ -0,0 +1,343 @@
|
||||
#!/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)")
|
318
util/cmake/tests/test_scope_handling.py
Normal file
318
util/cmake/tests/test_scope_handling.py
Normal file
@ -0,0 +1,318 @@
|
||||
#!/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']
|
Reference in New Issue
Block a user