summaryrefslogtreecommitdiffstats
path: root/util
diff options
context:
space:
mode:
authorTobias Hunger <tobias.hunger@qt.io>2019-01-23 16:40:23 +0100
committerTobias Hunger <tobias.hunger@qt.io>2019-01-29 09:29:38 +0000
commit4a2562e5db1605ba2d99445e6ea2bda69867a4d7 (patch)
tree7e305e95ab3fac6a0f0c45f0dde5620eb746a963 /util
parent2cdef0f5278d49fd868ed358c240c47fe43bcbe2 (diff)
CMake: pro2cmake.py: Simplify conditions
Use pysym to simplify conditions in extend_target. Do some manual changes to the condition based on domain knowledge. Change-Id: I7fbb9ebc93b620a483c6a3a796d84c9bc0e36ef7 Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
Diffstat (limited to 'util')
-rw-r--r--util/cmake/Pipfile1
-rwxr-xr-xutil/cmake/pro2cmake.py156
-rwxr-xr-xutil/cmake/tests/test_logic_mapping.py165
3 files changed, 314 insertions, 8 deletions
diff --git a/util/cmake/Pipfile b/util/cmake/Pipfile
index d7e1905378..7fbf716eb8 100644
--- a/util/cmake/Pipfile
+++ b/util/cmake/Pipfile
@@ -7,6 +7,7 @@ name = "pypi"
pytest = "*"
mypy = "*"
pyparsing = "*"
+sympy = "*"
[dev-packages]
diff --git a/util/cmake/pro2cmake.py b/util/cmake/pro2cmake.py
index 52e67e482d..fb55988600 100755
--- a/util/cmake/pro2cmake.py
+++ b/util/cmake/pro2cmake.py
@@ -33,6 +33,8 @@ import re
import io
import typing
+from sympy.logic import (simplify_logic, And, Or, Not,)
+from sympy.core import SympifyError
import pyparsing as pp
from helper import map_qt_library, map_qt_base_library, featureName, \
@@ -517,9 +519,13 @@ def parseProFile(file: str, *, debug=False):
def map_condition(condition: str) -> str:
- re.sub(r"\bif\s*\((.*?)\)", r"\1", condition)
- re.sub(r"\bisEmpty\s*\((.*?)\)", r"\1 STREQUAL \"\"", condition)
- re.sub(r"\bcontains\s*\((.*?), (.*)?\)", r"\1___contains___\2", condition)
+ print('##### Mapping condition: {}.'.format(condition))
+ re.sub(r'if\s*\((.*?)\)', r'\1', condition)
+ re.sub(r'(^|[^a-zA-Z0-9_])isEmpty\s*\((.*?)\)', r'\2_ISEMPTY', condition)
+ re.sub(r'(^|[^a-zA-Z0-9_])contains\s*\((.*?), (.*)?\)',
+ r'\2___contains___\3', condition)
+ re.sub(r'\s*==\s*', '___STREQUAL___', condition)
+ print(' # after regexp: {}.'.format(condition))
condition = condition.replace('*', '_x_')
condition = condition.replace('.$$', '__ss_')
@@ -528,9 +534,6 @@ def map_condition(condition: str) -> str:
condition = condition.replace('!', 'NOT ')
condition = condition.replace('&&', ' AND ')
condition = condition.replace('|', ' OR ')
- condition = condition.replace('==', ' STREQUAL ')
-
- condition = condition.replace('NOT NOT', '') # remove double negation
cmake_condition = ''
for part in condition.split():
@@ -550,7 +553,6 @@ def map_condition(condition: str) -> str:
part = part.replace('true', 'ON')
part = part.replace('false', 'OFF')
cmake_condition += ' ' + part
-
return cmake_condition.strip()
@@ -724,6 +726,144 @@ def write_ignored_keys(scope: Scope, ignored_keys, indent) -> 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):
+ args = expr.args
+ for arg in 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 _recursive_simplify(expr):
+ ''' Simplify the expression as much as possible based on
+ domain knowledge. '''
+ input_expr = expr
+
+ # Simplify even further, based on domain knowledge:
+ apples = ('APPLE_OSX', 'APPLE_UIKIT', 'APPLE_IOS',
+ 'APPLE_TVOS', 'APPLE_WATCHOS',)
+ bsds = ('APPLE', 'FREEBSD', 'OPENBSD', 'NETBSD',)
+ unixes = ('APPLE', *apples, 'BSD', *bsds, 'LINUX',
+ 'ANDROID', 'ANDROID_EMBEDDED',
+ '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)
+ for unix_flavor in unixes:
+ # unix_flavor [AND foo ] AND WIN32 -> FALSE [AND foo]
+ flavor_expr = simplify_logic(unix_flavor)
+ expr = _simplify_expressions(expr, And, (win_expr, flavor_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)
+
+ # 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')
+
+ try:
+ # Generate and simplify condition using sympy:
+ condition_expr = simplify_logic(condition)
+ condition = str(_recursive_simplify(condition_expr))
+
+ # 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')
+ except:
+ # sympy did not like our input, so leave this condition alone:
+ condition = input_condition
+
+ return condition
+
+
def recursive_evaluate_scope(scope: Scope, parent_condition: str = '',
previous_condition: str = '') -> str:
current_condition = scope.condition()
@@ -755,7 +895,7 @@ def recursive_evaluate_scope(scope: Scope, parent_condition: str = '',
total_condition = '({}) AND ({})'.format(parent_condition,
total_condition)
- scope.set_total_condition(total_condition)
+ scope.set_total_condition(simplify_condition(total_condition))
prev_condition = ''
for c in scope.children():
diff --git a/util/cmake/tests/test_logic_mapping.py b/util/cmake/tests/test_logic_mapping.py
new file mode 100755
index 0000000000..cf7913a6e4
--- /dev/null
+++ b/util/cmake/tests/test_logic_mapping.py
@@ -0,0 +1,165 @@
+#!/usr/bin/env python3
+#############################################################################
+##
+## Copyright (C) 2018 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$
+##
+#############################################################################
+
+from pro2cmake 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_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_and_win32_or_foobar_or_barfoo():
+ validate_simplify('WIN32 AND foobar AND UNIX AND barfoo', 'OFF')
+
+
+def test_simplify_watchos_and_win32():
+ validate_simplify('WIN32 AND APPLE_WATCHOS', 'OFF')
+
+
+def test_simplify_apple_and_appleosx():
+ validate_simplify('APPLE AND APPLE_OSX', 'APPLE_OSX')
+
+
+def test_simplify_apple_or_appleosx():
+ validate_simplify('APPLE OR APPLE_OSX', 'APPLE')
+
+
+def test_simplify_apple_or_appleosx_level1():
+ validate_simplify('foobar AND (APPLE OR APPLE_OSX )', 'APPLE AND foobar')
+
+
+def test_simplify_apple_or_appleosx_level1_double():
+ validate_simplify('foobar AND (APPLE OR APPLE_OSX )', 'APPLE AND foobar')
+
+
+def test_simplify_apple_or_appleosx_level1_double_with_extra_spaces():
+ validate_simplify('foobar AND (APPLE OR APPLE_OSX ) '
+ 'AND ( APPLE_OSX OR APPLE )', 'APPLE AND foobar')
+
+
+def test_simplify_apple_or_appleosx_level2():
+ validate_simplify('foobar AND ( ( APPLE OR APPLE_WATCHOS ) '
+ 'OR APPLE_OSX ) AND ( APPLE_OSX OR APPLE ) '
+ 'AND ( (WIN32 OR WINRT) OR UNIX) ', 'APPLE AND foobar')
+
+
+def test_simplify_not_apple_and_appleosx():
+ validate_simplify('NOT APPLE AND APPLE_OSX', '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')