summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexandru Croitor <[email protected]>2019-09-30 18:11:15 +0200
committerTobias Hunger <[email protected]>2019-10-01 14:13:12 +0000
commit590213e531f3a14f9b5365a10122f815d5fd6e8f (patch)
treed0f5259356799b50abe3e2761669d09b47fef405
parent447c868a5df9aac84ae39989d09bc2b704940698 (diff)
Move sympy condition simplification code into separate file
Change-Id: I3f062bf939b452bb41b7a27508a83cbf93abff8c Reviewed-by: Simon Hausmann <[email protected]> Reviewed-by: Qt CMake Build Bot
-rw-r--r--util/cmake/condition_simplifier.py236
-rwxr-xr-xutil/cmake/pro2cmake.py207
-rwxr-xr-xutil/cmake/tests/test_logic_mapping.py2
3 files changed, 239 insertions, 206 deletions
diff --git a/util/cmake/condition_simplifier.py b/util/cmake/condition_simplifier.py
new file mode 100644
index 00000000000..c67b78ffadb
--- /dev/null
+++ b/util/cmake/condition_simplifier.py
@@ -0,0 +1,236 @@
+#!/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
+
+
+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
+
+
+def simplify_condition(condition: str) -> str:
+ input_condition = condition.strip()
+
+ # Map to sympy syntax:
+ condition = " " + input_condition + " "
+ condition = condition.replace("(", " ( ")
+ condition = condition.replace(")", " ) ")
+
+ tmp = ""
+ while tmp != condition:
+ tmp = condition
+
+ condition = condition.replace(" NOT ", " ~ ")
+ condition = condition.replace(" AND ", " & ")
+ condition = condition.replace(" OR ", " | ")
+ condition = condition.replace(" ON ", " true ")
+ condition = condition.replace(" OFF ", " false ")
+ # Replace dashes with a token
+ condition = condition.replace("-", "_dash_")
+
+ # SymPy chokes on expressions that contain two tokens one next to
+ # the other delimited by a space, which are not an operation.
+ # So a CMake condition like "TARGET Foo::Bar" fails the whole
+ # expression simplifying process.
+ # Turn these conditions into a single token so that SymPy can parse
+ # the expression, and thus simplify it.
+ # Do this by replacing and keeping a map of conditions to single
+ # token symbols.
+ # Support both target names without double colons, and with double
+ # colons.
+ pattern = re.compile(r"(TARGET [a-zA-Z]+(?:::[a-zA-Z]+)?)")
+ target_symbol_mapping = {}
+ all_target_conditions = re.findall(pattern, condition)
+ for target_condition in all_target_conditions:
+ # Replace spaces and colons with underscores.
+ target_condition_symbol_name = re.sub("[ :]", "_", target_condition)
+ target_symbol_mapping[target_condition_symbol_name] = target_condition
+ condition = re.sub(target_condition, target_condition_symbol_name, condition)
+
+ # Do similar token mapping for comparison operators.
+ pattern = re.compile(r"([a-zA-Z_0-9]+ (?:STRLESS|STREQUAL|STRGREATER) [a-zA-Z_0-9]+)")
+ comparison_symbol_mapping = {}
+ all_comparisons = re.findall(pattern, condition)
+ for comparison in all_comparisons:
+ # Replace spaces and colons with underscores.
+ comparison_symbol_name = re.sub("[ ]", "_", comparison)
+ comparison_symbol_mapping[comparison_symbol_name] = comparison
+ condition = re.sub(comparison, comparison_symbol_name, condition)
+
+ try:
+ # Generate and simplify condition using sympy:
+ condition_expr = simplify_logic(condition)
+ condition = str(_recursive_simplify(condition_expr))
+
+ # Restore the target conditions.
+ for symbol_name in target_symbol_mapping:
+ condition = re.sub(symbol_name, target_symbol_mapping[symbol_name], condition)
+
+ # Restore comparisons.
+ for comparison in comparison_symbol_mapping:
+ condition = re.sub(comparison, comparison_symbol_mapping[comparison], condition)
+
+ # Map back to CMake syntax:
+ condition = condition.replace("~", "NOT ")
+ condition = condition.replace("&", "AND")
+ condition = condition.replace("|", "OR")
+ condition = condition.replace("True", "ON")
+ condition = condition.replace("False", "OFF")
+ condition = condition.replace("_dash_", "-")
+ except (SympifyError, TypeError, AttributeError):
+ # sympy did not like our input, so leave this condition alone:
+ condition = input_condition
+
+ return condition or "ON"
diff --git a/util/cmake/pro2cmake.py b/util/cmake/pro2cmake.py
index a2ea6ce0bf8..96d9712bb25 100755
--- a/util/cmake/pro2cmake.py
+++ b/util/cmake/pro2cmake.py
@@ -39,6 +39,8 @@ import io
import glob
import collections
+from condition_simplifier import simplify_condition
+
try:
collectionsAbc = collections.abc
except AttributeError:
@@ -53,8 +55,6 @@ from textwrap import indent as textwrap_indent
from itertools import chain
from functools import lru_cache
from shutil import copyfile
-from sympy.logic import simplify_logic, And, Or, Not
-from sympy.core.sympify import SympifyError
from typing import (
List,
Optional,
@@ -2054,209 +2054,6 @@ def write_ignored_keys(scope: Scope, indent: str) -> str:
return result
-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 knownledge 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
-
-
-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"
-
-
def recursive_evaluate_scope(
scope: Scope, parent_condition: str = "", previous_condition: str = ""
) -> str:
diff --git a/util/cmake/tests/test_logic_mapping.py b/util/cmake/tests/test_logic_mapping.py
index c477aa83510..c18c3ddc65e 100755
--- a/util/cmake/tests/test_logic_mapping.py
+++ b/util/cmake/tests/test_logic_mapping.py
@@ -27,7 +27,7 @@
##
#############################################################################
-from pro2cmake import simplify_condition
+from condition_simplifier import simplify_condition
def validate_simplify(input: str, expected: str) -> None: