summaryrefslogtreecommitdiffstats
path: root/util/cmake/condition_simplifier.py
diff options
context:
space:
mode:
Diffstat (limited to 'util/cmake/condition_simplifier.py')
-rw-r--r--util/cmake/condition_simplifier.py237
1 files changed, 237 insertions, 0 deletions
diff --git a/util/cmake/condition_simplifier.py b/util/cmake/condition_simplifier.py
new file mode 100644
index 0000000000..d02e70e489
--- /dev/null
+++ b/util/cmake/condition_simplifier.py
@@ -0,0 +1,237 @@
+#!/usr/bin/env python3
+#############################################################################
+##
+## Copyright (C) 2019 The Qt Company Ltd.
+## Contact: https://www.qt.io/licensing/
+##
+## This file is part of the plugins of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:GPL-EXCEPT$
+## Commercial License Usage
+## Licensees holding valid commercial Qt licenses may use this file in
+## accordance with the commercial license agreement provided with the
+## Software or, alternatively, in accordance with the terms contained in
+## a written agreement between you and The Qt Company. For licensing terms
+## and conditions see https://www.qt.io/terms-conditions. For further
+## information use the contact form at https://www.qt.io/contact-us.
+##
+## GNU General Public License Usage
+## Alternatively, this file may be used under the terms of the GNU
+## General Public License version 3 as published by the Free Software
+## Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+## included in the packaging of this file. Please review the following
+## information to ensure the GNU General Public License requirements will
+## be met: https://www.gnu.org/licenses/gpl-3.0.html.
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+
+import re
+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 = ("APPLE_OSX", "APPLE_UIKIT", "APPLE_IOS", "APPLE_TVOS", "APPLE_WATCHOS")
+ bsds = ("FREEBSD", "OPENBSD", "NETBSD")
+ androids = ("ANDROID", "ANDROID_EMBEDDED")
+ 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)
+ expr = _simplify_flavors_in_condition("ANDROID", ("ANDROID_EMBEDDED",), 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"