pro2cmake: Be more faithful in the representation of operations

Do a better approximation of =, +=, *= and -=.

Change-Id: I94765532f278deaac330b27cd5a3f41f319c6477
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
This commit is contained in:
Tobias Hunger 2018-12-20 16:15:10 +01:00
parent f0aa8fa48e
commit 19874e2381

View File

@ -119,9 +119,74 @@ def map_source_to_fs(base_dir: str, file: str, source: str) -> typing.Optional[s
return os.path.join(base_dir, source)
class Operation:
def __init__(self, value):
if isinstance(value, list):
self._value = value
else:
self._value = [str(value),]
def process(self, input):
assert(False)
def __str__(self):
assert(False)
class AddOperation(Operation):
def process(self, input):
return input + self._value
def __str__(self):
return '+({})'.format(self._value)
class UniqueAddOperation(Operation):
def process(self, input):
result = input
for v in self._value:
if not v in result:
result += [v,]
return result
def __str__(self):
return '*({})'.format(self._value)
class SetOperation(Operation):
def process(self, input):
return self._value
def __str__(self):
return '=({})'.format(self._value)
class RemoveOperation(Operation):
def __init__(self, value):
super().__init__(value)
def process(self, input):
input_set = set(input)
result = []
for v in self._value:
if v in input_set:
continue
else:
result += ['-{}'.format(v),]
return result
def __str__(self):
return '-({})'.format(self._value)
class Scope:
def __init__(self, file: typing.Optional[str]=None, condition: str='', base_dir: str='') -> None:
self._parent = None # type: Scope
def __init__(self, parent_scope: typing.Optional['Scope'],
file: typing.Optional[str]=None, condition: str='', base_dir: str='') -> None:
if parent_scope:
parent_scope._add_child(self)
else:
self._parent = None
self._basedir = base_dir
if file:
self._currentdir = os.path.dirname(file)
@ -133,27 +198,14 @@ class Scope:
self._file = file
self._condition = map_condition(condition)
self._children = [] # type: List[Scope]
self._values = {} # type: Dict[str, List[str]]
self._operations = {} # type: Dict[str]
def merge(self, other: 'Scope') -> None:
for c in other._children:
self.add_child(c)
other.set_basedir(self._basedir)
self._add_child(c)
for k in self._values.keys():
if k == 'TEMPLATE':
assert other.get(k, []) == self.get(k, [])
else:
self.append_value(k, other.get(k, []))
for k in other._values.keys():
if k not in self._values:
self.set_value(k, other.get(k))
def set_basedir(self, dir: str) -> None:
self._basedir = dir
for c in self._children:
c.set_basedir(dir)
for k in other._operations.keys():
self._operations[key] = other._operations[k]
def basedir(self) -> str:
return self._basedir
@ -161,9 +213,27 @@ class Scope:
def currentdir(self) -> str:
return self._currentdir
def diff(self, key: str, default: typing.Optional[List[str]]=[]) -> List[str]:
mine = self.get(key, default)
if self._parent:
parent = self._parent.get(key, default)
if (parent == mine):
return []
parent_set = set(parent)
mine_set = set(mine)
added = [x for x in mine if not x in parent_set]
removed = [x for x in parent if not x in mine_set]
return added + list('# {}'.format(x) for x in removed)
return mine
@staticmethod
def FromDict(file: str, statements, cond: str = '', base_dir: str = ''):
scope = Scope(file, cond, base_dir)
def FromDict(parent_scope: typing.Optional['Scope'],
file: str, statements, cond: str = '', base_dir: str = ''):
scope = Scope(parent_scope, file, cond, base_dir)
for statement in statements:
if isinstance(statement, list): # Handle skipped parts...
assert not statement
@ -174,16 +244,19 @@ class Scope:
key = statement.get('key', '')
value = statement.get('value', [])
assert key != ''
print('#### {}: {} = {}.'.format(operation, key, value))
if key in ('HEADERS', 'SOURCES', 'INCLUDEPATH') or key.endswith('_HEADERS') or key.endswith('_SOURCES'):
value = [map_to_file(v, scope.basedir(), scope.currentdir()) for v in value]
if operation == '=':
scope.set_value(key, value)
scope._append_operation(key, SetOperation(value))
elif operation == '-=':
scope.substract_value(key, value)
elif operation == '+=' or operation == '*=':
scope.append_value(key, value)
scope._append_operation(key, RemoveOperation(value))
elif operation == '+=':
scope._append_operation(key, AddOperation(value))
elif operation == '*=':
scope._append_operation(key, UniqueAddOperation(value))
else:
print('Unexpected operation "{}" in scope with condition {}.'.format(operation, cond))
assert(False)
@ -192,33 +265,36 @@ class Scope:
condition = statement.get('condition', None)
if condition:
child = Scope.FromDict(file, statement.get('statements'), condition, scope.basedir())
scope.add_child(child)
child = Scope.FromDict(scope, file, statement.get('statements'), condition, scope.basedir())
else_statements = statement.get('else_statements')
if else_statements:
child = Scope.FromDict(file, else_statements, 'NOT ' + condition, scope.basedir())
scope.add_child(child)
child = Scope.FromDict(scope, file, else_statements, 'NOT ' + condition, scope.basedir())
continue
loaded = statement.get('loaded', None)
loaded = statement.get('loaded')
if loaded:
scope.append_value('_LOADED', loaded)
scope._append_operation('_LOADED', UniqueAddOperation(loaded))
continue
option = statement.get('option', None)
if option:
scope.append_value('_OPTION', option)
scope._append_operation('_OPTION', UniqueAddOperation(option))
continue
included = statement.get('included', None)
if included:
scope.append_value('_INCLUDED',
map_to_file(included, scope.basedir(), scope.currentdir()))
scope.append_operation('_INCLUDED', UniqueAddOperation(map_to_file(included, scope.basedir(), scope.currentdir())))
continue
return scope
def _append_operation(self, key: str, op: Operation) -> None:
if key in self._operations:
self._operations[key].append(op)
else:
self._operations[key] = [op,]
def file(self) -> str:
return self._file or ''
@ -228,38 +304,10 @@ class Scope:
def condition(self) -> str:
return self._condition
def _push_down_TEMPLATE(self, template: str) -> None:
self.set_value('TEMPLATE', [template, ])
for c in self._children:
c._push_down_TEMPLATE(template)
def add_child(self, scope: 'Scope') -> None:
def _add_child(self, scope: 'Scope') -> None:
scope._parent = self
if not scope._rawTemplate():
scope._push_down_TEMPLATE(self.getTemplate())
self._children.append(scope)
def set_value(self, key: str, value: List[str]) -> None:
self._values[key] = value
def append_value(self, key: str, value: Union[str, List[str]]) -> None:
array = self._values.get(key, [])
if isinstance(value, str):
array.append(value)
elif isinstance(value, list):
array += value
else:
assert False
self._values[key] = array
def substract_value(self, key: str, value: Union[str, List[str]]) -> None:
if isinstance(value, str):
to_remove = [value, ]
if isinstance(value, list):
to_remove = value
self.append_value(key, ['-{}'.format(v) for v in to_remove])
def children(self) -> List['Scope']:
return self._children
@ -270,28 +318,34 @@ class Scope:
else:
print('{}Scope {} in {} with condition: {}.'.format(ind, self._file, self._basedir, self._condition))
print('{}Keys:'.format(ind))
for k in sorted(self._values.keys()):
print('{} {} = "{}"'.format(ind, k, self._values[k]))
for k in sorted(self._operations.keys()):
print('{} {} = "{}"'.format(ind, k, self._operations.get(k, [])))
print('{}Children:'.format(ind))
for c in self._children:
c.dump(indent=indent + 1)
def get(self, key: str, default=None) -> List[str]:
default = default or []
return self._values.get(key, default)
result = []
if self._parent:
result = self._parent.get(key, default)
else:
if default:
if isinstance(default, list):
result = default
else:
result = [str(default),]
for op in self._operations.get(key, []):
result = op.process(result)
return result
def getString(self, key: str, default: str = '') -> str:
v = self.get(key)
if isinstance(v, list):
if len(v) == 0:
return default
assert len(v) == 1
return v[0]
elif isinstance(v, str):
return v
else:
assert False
return default
v = self.get(key, default)
if len(v) == 0:
return default
assert len(v) == 1
return v[0]
def getTemplate(self) -> str:
return self.getString('TEMPLATE', 'app')
@ -425,7 +479,7 @@ def handle_subdir(scope: Scope, cm_fh: IO[str], *, indent: int = 0) -> None:
cm_fh.write('{}add_subdirectory({})\n'.format(ind, sd))
elif os.path.isfile(full_sd):
subdir_result = parseProFile(full_sd, debug=False)
subdir_scope = Scope.FromDict(full_sd, subdir_result.asDict().get('statements'),
subdir_scope = Scope.FromDict(scope, full_sd, subdir_result.asDict().get('statements'),
'', scope.basedir())
cmakeify_scope(subdir_scope, cm_fh, indent=indent + 1)
@ -490,8 +544,8 @@ def write_sources_section(cm_fh: IO[str], scope: Scope, *, indent: int=0,
if plugin_type:
cm_fh.write('{} TYPE {}\n'.format(ind, plugin_type[0]))
sources = scope.get('SOURCES') + scope.get('HEADERS') + scope.get('OBJECTIVE_SOURCES') + scope.get('NO_PCH_SOURCES') + scope.get('FORMS')
resources = scope.get('RESOURCES')
sources = scope.diff('SOURCES') + scope.diff('HEADERS') + scope.diff('OBJECTIVE_SOURCES') + scope.diff('NO_PCH_SOURCES') + scope.diff('FORMS')
resources = scope.diff('RESOURCES')
if resources:
qrc_only = True
for r in resources:
@ -512,19 +566,21 @@ def write_sources_section(cm_fh: IO[str], scope: Scope, *, indent: int=0,
for l in sort_sources(sources):
cm_fh.write('{} {}\n'.format(ind, l))
if scope.get('DEFINES'):
defines = scope.diff('DEFINES')
if defines:
cm_fh.write('{} DEFINES\n'.format(ind))
for d in scope.get('DEFINES'):
for d in defines:
d = d.replace('=\\\\\\"$$PWD/\\\\\\"', '="${CMAKE_CURRENT_SOURCE_DIR}/"')
cm_fh.write('{} {}\n'.format(ind, d))
if scope.get('INCLUDEPATH'):
includes = scope.diff('INCLUDEPATH')
if includes:
cm_fh.write('{} INCLUDE_DIRECTORIES\n'.format(ind))
for i in scope.get('INCLUDEPATH'):
for i in includes:
cm_fh.write('{} {}\n'.format(ind, i))
dependencies = [map_qt_library(q) for q in scope.get('QT') if map_qt_library(q) not in known_libraries]
dependencies += [map_qt_library(q) for q in scope.get('QT_FOR_PRIVATE') if map_qt_library(q) not in known_libraries]
dependencies += scope.get('QMAKE_USE_PRIVATE') + scope.get('LIBS_PRIVATE') + scope.get('LIBS')
dependencies = [map_qt_library(q) for q in scope.diff('QT') if map_qt_library(q) not in known_libraries]
dependencies += [map_qt_library(q) for q in scope.diff('QT_FOR_PRIVATE') if map_qt_library(q) not in known_libraries]
dependencies += scope.diff('QMAKE_USE_PRIVATE') + scope.diff('LIBS_PRIVATE') + scope.diff('LIBS')
if dependencies:
cm_fh.write('{} LIBRARIES\n'.format(ind))
is_framework = False
@ -714,10 +770,8 @@ def do_include(scope: Scope, *, debug: bool=False) -> None:
continue
include_result = parseProFile(include_file, debug=debug)
include_scope = Scope.FromDict(include_file, include_result.asDict().get('statements'),
include_scope = Scope.FromDict(scope, include_file, include_result.asDict().get('statements'),
'', dir)
if not include_scope._rawTemplate():
include_scope._push_down_TEMPLATE(scope.getTemplate())
do_include(include_scope)
@ -744,7 +798,7 @@ def main() -> None:
print(parseresult.asDict())
print('\n#### End of parser result dictionary.\n')
file_scope = Scope.FromDict(file, parseresult.asDict().get('statements'))
file_scope = Scope.FromDict(None, file, parseresult.asDict().get('statements'))
if args.debug_pro_structure or args.debug:
print('\n\n#### .pro/.pri file structure:')