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>
This commit is contained in:
parent
2cdef0f527
commit
4a2562e5db
@ -7,6 +7,7 @@ name = "pypi"
|
|||||||
pytest = "*"
|
pytest = "*"
|
||||||
mypy = "*"
|
mypy = "*"
|
||||||
pyparsing = "*"
|
pyparsing = "*"
|
||||||
|
sympy = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
|
|
||||||
|
@ -33,6 +33,8 @@ import re
|
|||||||
import io
|
import io
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
|
from sympy.logic import (simplify_logic, And, Or, Not,)
|
||||||
|
from sympy.core import SympifyError
|
||||||
import pyparsing as pp
|
import pyparsing as pp
|
||||||
|
|
||||||
from helper import map_qt_library, map_qt_base_library, featureName, \
|
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:
|
def map_condition(condition: str) -> str:
|
||||||
re.sub(r"\bif\s*\((.*?)\)", r"\1", condition)
|
print('##### Mapping condition: {}.'.format(condition))
|
||||||
re.sub(r"\bisEmpty\s*\((.*?)\)", r"\1 STREQUAL \"\"", condition)
|
re.sub(r'if\s*\((.*?)\)', r'\1', condition)
|
||||||
re.sub(r"\bcontains\s*\((.*?), (.*)?\)", r"\1___contains___\2", 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('*', '_x_')
|
||||||
condition = condition.replace('.$$', '__ss_')
|
condition = condition.replace('.$$', '__ss_')
|
||||||
@ -528,9 +534,6 @@ def map_condition(condition: str) -> str:
|
|||||||
condition = condition.replace('!', 'NOT ')
|
condition = condition.replace('!', 'NOT ')
|
||||||
condition = condition.replace('&&', ' AND ')
|
condition = condition.replace('&&', ' AND ')
|
||||||
condition = condition.replace('|', ' OR ')
|
condition = condition.replace('|', ' OR ')
|
||||||
condition = condition.replace('==', ' STREQUAL ')
|
|
||||||
|
|
||||||
condition = condition.replace('NOT NOT', '') # remove double negation
|
|
||||||
|
|
||||||
cmake_condition = ''
|
cmake_condition = ''
|
||||||
for part in condition.split():
|
for part in condition.split():
|
||||||
@ -550,7 +553,6 @@ def map_condition(condition: str) -> str:
|
|||||||
part = part.replace('true', 'ON')
|
part = part.replace('true', 'ON')
|
||||||
part = part.replace('false', 'OFF')
|
part = part.replace('false', 'OFF')
|
||||||
cmake_condition += ' ' + part
|
cmake_condition += ' ' + part
|
||||||
|
|
||||||
return cmake_condition.strip()
|
return cmake_condition.strip()
|
||||||
|
|
||||||
|
|
||||||
@ -724,6 +726,144 @@ def write_ignored_keys(scope: Scope, ignored_keys, indent) -> str:
|
|||||||
return result
|
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 = '',
|
def recursive_evaluate_scope(scope: Scope, parent_condition: str = '',
|
||||||
previous_condition: str = '') -> str:
|
previous_condition: str = '') -> str:
|
||||||
current_condition = scope.condition()
|
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 = '({}) AND ({})'.format(parent_condition,
|
||||||
total_condition)
|
total_condition)
|
||||||
|
|
||||||
scope.set_total_condition(total_condition)
|
scope.set_total_condition(simplify_condition(total_condition))
|
||||||
|
|
||||||
prev_condition = ''
|
prev_condition = ''
|
||||||
for c in scope.children():
|
for c in scope.children():
|
||||||
|
165
util/cmake/tests/test_logic_mapping.py
Executable file
165
util/cmake/tests/test_logic_mapping.py
Executable file
@ -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')
|
Loading…
x
Reference in New Issue
Block a user