diff options
Diffstat (limited to 'mesonbuild/rewriter.py')
-rw-r--r-- | mesonbuild/rewriter.py | 367 |
1 files changed, 360 insertions, 7 deletions
diff --git a/mesonbuild/rewriter.py b/mesonbuild/rewriter.py index 277835c..60c762e 100644 --- a/mesonbuild/rewriter.py +++ b/mesonbuild/rewriter.py @@ -71,15 +71,224 @@ class RequiredKeys: return wrapped +class MTypeBase: + def __init__(self, node: mparser.BaseNode): + if node is None: + self.node = self._new_node() + else: + self.node = node + self.node_type = None + for i in self.supported_nodes(): + if isinstance(self.node, i): + self.node_type = i + + def _new_node(self): + # Overwrite in derived class + return mparser.BaseNode() + + def can_modify(self): + return self.node_type is not None + + def get_node(self): + return self.node + + def supported_nodes(self): + # Overwrite in derived class + return [] + + def set_value(self, value): + # Overwrite in derived class + mlog.warning('Cannot set the value of type', mlog.bold(type(self).__name__), '--> skipping') + + def add_value(self, value): + # Overwrite in derived class + mlog.warning('Cannot add a value of type', mlog.bold(type(self).__name__), '--> skipping') + + def remove_value(self, value): + # Overwrite in derived class + mlog.warning('Cannot remove a value of type', mlog.bold(type(self).__name__), '--> skipping') + +class MTypeStr(MTypeBase): + def __init__(self, node: mparser.BaseNode): + super().__init__(node) + + def _new_node(self): + return mparser.StringNode(mparser.Token('', '', 0, 0, 0, None, '')) + + def supported_nodes(self): + return [mparser.StringNode] + + def set_value(self, value): + self.node.value = str(value) + +class MTypeBool(MTypeBase): + def __init__(self, node: mparser.BaseNode): + super().__init__(node) + + def _new_node(self): + return mparser.StringNode(mparser.Token('', '', 0, 0, 0, None, False)) + + def supported_nodes(self): + return [mparser.BooleanNode] + + def set_value(self, value): + self.node.value = bool(value) + +class MTypeID(MTypeBase): + def __init__(self, node: mparser.BaseNode): + super().__init__(node) + + def _new_node(self): + return mparser.StringNode(mparser.Token('', '', 0, 0, 0, None, '')) + + def supported_nodes(self): + return [mparser.IdNode] + + def set_value(self, value): + self.node.value = str(value) + +class MTypeList(MTypeBase): + def __init__(self, node: mparser.BaseNode): + super().__init__(node) + + def _new_node(self): + return mparser.ArrayNode(mparser.ArgumentNode(mparser.Token('', '', 0, 0, 0, None, '')), 0, 0) + + def _new_element_node(self, value): + # Overwrite in derived class + return mparser.BaseNode() + + def _ensure_array_node(self): + if not isinstance(self.node, mparser.ArrayNode): + tmp = self.node + self.node = self._new_node() + self.node.args.arguments += [tmp] + + def _check_is_equal(self, node, value): + # Overwrite in derived class + return False + + def get_node(self): + if isinstance(self.node, mparser.ArrayNode): + if len(self.node.args.arguments) == 1: + return self.node.args.arguments[0] + return self.node + + def supported_element_nodes(self): + # Overwrite in derived class + return [] + + def supported_nodes(self): + return [mparser.ArrayNode] + self.supported_element_nodes() + + def set_value(self, value): + if not isinstance(value, list): + value = [value] + self._ensure_array_node() + self.node.args.arguments = [] # Remove all current nodes + for i in value: + self.node.args.arguments += [self._new_element_node(i)] + + def add_value(self, value): + if not isinstance(value, list): + value = [value] + self._ensure_array_node() + for i in value: + self.node.args.arguments += [self._new_element_node(i)] + + def remove_value(self, value): + def check_remove_node(node): + for j in value: + if self._check_is_equal(i, j): + return True + return False + + if not isinstance(value, list): + value = [value] + self._ensure_array_node() + removed_list = [] + for i in self.node.args.arguments: + if not check_remove_node(i): + removed_list += [i] + self.node.args.arguments = removed_list + +class MtypeStrList(MTypeList): + def __init__(self, node: mparser.BaseNode): + super().__init__(node) + + def _new_element_node(self, value): + return mparser.StringNode(mparser.Token('', '', 0, 0, 0, None, str(value))) + + def _check_is_equal(self, node, value): + if isinstance(node, mparser.StringNode): + return node.value == value + return False + + def supported_element_nodes(self): + return [mparser.StringNode] + +class MTypeIDList(MTypeList): + def __init__(self, node: mparser.BaseNode): + super().__init__(node) + + def _new_element_node(self, value): + return mparser.IdNode(mparser.Token('', '', 0, 0, 0, None, str(value))) + + def _check_is_equal(self, node, value): + if isinstance(node, mparser.IdNode): + return node.value == value + return False + + def supported_element_nodes(self): + return [mparser.IdNode] + rewriter_keys = { + 'kwargs': { + 'function': (str, None, None), + 'id': (str, None, None), + 'operation': (str, None, ['set', 'delete', 'add', 'remove', 'info']), + 'kwargs': (dict, {}, None) + }, 'target': { 'target': (str, None, None), - 'operation': (str, None, ['src_add', 'src_rm', 'test']), + 'operation': (str, None, ['src_add', 'src_rm', 'info']), 'sources': (list, [], None), 'debug': (bool, False, None) } } +rewriter_func_kwargs = { + 'dependency': { + 'language': MTypeStr, + 'method': MTypeStr, + 'native': MTypeBool, + 'not_found_message': MTypeStr, + 'required': MTypeBool, + 'static': MTypeBool, + 'version': MtypeStrList, + 'modules': MtypeStrList + }, + 'target': { + 'build_by_default': MTypeBool, + 'build_rpath': MTypeStr, + 'dependencies': MTypeIDList, + 'gui_app': MTypeBool, + 'link_with': MTypeIDList, + 'export_dynamic': MTypeBool, + 'implib': MTypeBool, + 'install': MTypeBool, + 'install_dir': MTypeStr, + 'install_rpath': MTypeStr, + 'pie': MTypeBool + }, + 'project': { + 'meson_version': MTypeStr, + 'license': MtypeStrList, + 'subproject_dir': MTypeStr, + 'version': MTypeStr + } +} + class Rewriter: def __init__(self, sourcedir: str, generator: str = 'ninja'): self.sourcedir = sourcedir @@ -87,8 +296,10 @@ class Rewriter: self.id_generator = AstIDGenerator() self.modefied_nodes = [] self.functions = { + 'kwargs': self.process_kwargs, 'target': self.process_target, } + self.info_dump = None def analyze_meson(self): mlog.log('Analyzing meson file:', mlog.bold(os.path.join(self.sourcedir, environment.build_filename))) @@ -98,11 +309,152 @@ class Rewriter: self.interpreter.ast.accept(AstIndentationGenerator()) self.interpreter.ast.accept(self.id_generator) + def add_info(self, cmd_type: str, cmd_id: str, data: dict): + if self.info_dump is None: + self.info_dump = {} + if cmd_type not in self.info_dump: + self.info_dump[cmd_type] = {} + self.info_dump[cmd_type][cmd_id] = data + + def print_info(self): + if self.info_dump is None: + return + # Wrap the dump in magic strings + print('!!==JSON DUMP: BEGIN==!!') + print(json.dumps(self.info_dump, indent=2)) + print('!!==JSON DUMP: END==!!') + def find_target(self, target: str): - for i in self.interpreter.targets: - if target == i['name'] or target == i['id']: - return i - return None + def check_list(name: str): + for i in self.interpreter.targets: + if name == i['name'] or name == i['id']: + return i + return None + + tgt = check_list(target) + if tgt is not None: + return tgt + + # Check the assignments + if target in self.interpreter.assignments: + node = self.interpreter.assignments[target][0] + if isinstance(node, mparser.FunctionNode): + if node.func_name in ['executable', 'jar', 'library', 'shared_library', 'shared_module', 'static_library', 'both_libraries']: + name = self.interpreter.flatten_args(node.args)[0] + tgt = check_list(name) + + return tgt + + def find_dependency(self, dependency: str): + def check_list(name: str): + for i in self.interpreter.dependencies: + if name == i['name']: + return i + return None + + dep = check_list(dependency) + if dep is not None: + return dep + + # Check the assignments + if dependency in self.interpreter.assignments: + node = self.interpreter.assignments[dependency][0] + if isinstance(node, mparser.FunctionNode): + if node.func_name in ['dependency']: + name = self.interpreter.flatten_args(node.args)[0] + dep = check_list(name) + + return dep + + @RequiredKeys(rewriter_keys['kwargs']) + def process_kwargs(self, cmd): + mlog.log('Processing function type', mlog.bold(cmd['function']), 'with id', mlog.cyan("'" + cmd['id'] + "'")) + if cmd['function'] not in rewriter_func_kwargs: + mlog.error('Unknown function type {} --> skipping'.format(cmd['function'])) + return + kwargs_def = rewriter_func_kwargs[cmd['function']] + + # Find the function node to modify + node = None + arg_node = None + if cmd['function'] == 'project': + node = self.interpreter.project_node + arg_node = node.args + elif cmd['function'] == 'target': + tmp = self.find_target(cmd['id']) + if tmp: + node = tmp['node'] + arg_node = node.args + elif cmd['function'] == 'dependency': + tmp = self.find_dependency(cmd['id']) + if tmp: + node = tmp['node'] + arg_node = node.args + if not node: + mlog.error('Unable to find the function node') + assert(isinstance(node, mparser.FunctionNode)) + assert(isinstance(arg_node, mparser.ArgumentNode)) + + # Print kwargs info + if cmd['operation'] == 'info': + info_data = {} + for key, val in arg_node.kwargs.items(): + info_data[key] = None + if isinstance(val, mparser.ElementaryNode): + info_data[key] = val.value + elif isinstance(val, mparser.ArrayNode): + data_list = [] + for i in val.args.arguments: + element = None + if isinstance(i, mparser.ElementaryNode): + element = i.value + data_list += [element] + info_data[key] = data_list + + self.add_info('kwargs', '{}#{}'.format(cmd['function'], cmd['id']), info_data) + return # Nothing else to do + + # Modify the kwargs + num_changed = 0 + for key, val in cmd['kwargs'].items(): + if key not in kwargs_def: + mlog.error('Cannot modify unknown kwarg --> skipping', mlog.bold(key)) + continue + + # Remove the key from the kwargs + if cmd['operation'] == 'delete': + if key in arg_node.kwargs: + mlog.log(' -- Deleting', mlog.bold(key), 'from the kwargs') + del arg_node.kwargs[key] + num_changed += 1 + else: + mlog.log(' -- Key', mlog.bold(key), 'is already deleted') + continue + + if key not in arg_node.kwargs: + arg_node.kwargs[key] = None + modifyer = kwargs_def[key](arg_node.kwargs[key]) + if not modifyer.can_modify(): + mlog.log(' -- Skipping', mlog.bold(key), 'because it is to complex to modify') + + # Apply the operation + val_str = str(val) + if cmd['operation'] == 'set': + mlog.log(' -- Setting', mlog.bold(key), 'to', mlog.yellow(val_str)) + modifyer.set_value(val) + elif cmd['operation'] == 'add': + mlog.log(' -- Adding', mlog.yellow(val_str), 'to', mlog.bold(key)) + modifyer.add_value(val) + elif cmd['operation'] == 'remove': + mlog.log(' -- Removing', mlog.yellow(val_str), 'from', mlog.bold(key)) + modifyer.remove_value(val) + + # Write back the result + arg_node.kwargs[key] = modifyer.get_node() + num_changed += 1 + + if num_changed > 0 and node not in self.modefied_nodes: + self.modefied_nodes += [node] @RequiredKeys(rewriter_keys['target']) def process_target(self, cmd): @@ -191,7 +543,7 @@ class Rewriter: if root not in self.modefied_nodes: self.modefied_nodes += [root] - elif cmd['operation'] == 'test': + elif cmd['operation'] == 'info': # List all sources in the target src_list = [] for i in target['sources']: @@ -202,7 +554,7 @@ class Rewriter: 'name': target['name'], 'sources': src_list } - mlog.log(' !! target {}={}'.format(target['id'], json.dumps(test_data))) + self.add_info('target', target['id'], test_data) def process(self, cmd): if 'type' not in cmd: @@ -314,4 +666,5 @@ def run(options): rewriter.process(i) rewriter.apply_changes() + rewriter.print_info() return 0 |