diff options
author | Tobias Hunger <tobias.hunger@qt.io> | 2019-01-23 16:40:23 +0100 |
---|---|---|
committer | Tobias Hunger <tobias.hunger@qt.io> | 2019-01-29 09:29:38 +0000 |
commit | 4a2562e5db1605ba2d99445e6ea2bda69867a4d7 (patch) | |
tree | 7e305e95ab3fac6a0f0c45f0dde5620eb746a963 /util | |
parent | 2cdef0f5278d49fd868ed358c240c47fe43bcbe2 (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/Pipfile | 1 | ||||
-rwxr-xr-x | util/cmake/pro2cmake.py | 156 | ||||
-rwxr-xr-x | util/cmake/tests/test_logic_mapping.py | 165 |
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') |