diff options
author | Jussi Pakkanen <jpakkane@gmail.com> | 2019-02-16 14:17:49 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-02-16 14:17:49 +0200 |
commit | 939b011114838b886d48521806988973205aecb4 (patch) | |
tree | 77eef03f5c136867100ded7dfcfcaa442b184f33 /mesonbuild | |
parent | 3ac7df6a6a016cf3ae92494bb510cbc4005571ed (diff) | |
parent | 0ce02b57d71bf4365e30063efa95a0a414a4a92a (diff) | |
download | meson-939b011114838b886d48521806988973205aecb4.zip meson-939b011114838b886d48521806988973205aecb4.tar.gz meson-939b011114838b886d48521806988973205aecb4.tar.bz2 |
Merge pull request #4858 from mensinda/rwKWARGS
rewriter: Add support for kwargs modification
Diffstat (limited to 'mesonbuild')
-rw-r--r-- | mesonbuild/ast/interpreter.py | 4 | ||||
-rw-r--r-- | mesonbuild/ast/introspection.py | 22 | ||||
-rw-r--r-- | mesonbuild/rewriter.py | 367 |
3 files changed, 380 insertions, 13 deletions
diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index 68c017a..ce4b93c 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -113,7 +113,7 @@ class AstInterpreter(interpreterbase.InterpreterBase): prev_subdir = self.subdir subdir = os.path.join(prev_subdir, args[0]) absdir = os.path.join(self.source_root, subdir) - buildfilename = os.path.join(self.subdir, environment.build_filename) + buildfilename = os.path.join(subdir, environment.build_filename) absname = os.path.join(self.source_root, buildfilename) symlinkless_dir = os.path.realpath(absdir) if symlinkless_dir in self.visited_subdirs: @@ -128,7 +128,7 @@ class AstInterpreter(interpreterbase.InterpreterBase): code = f.read() assert(isinstance(code, str)) try: - codeblock = mparser.Parser(code, self.subdir).parse() + codeblock = mparser.Parser(code, subdir).parse() except mesonlib.MesonException as me: me.file = buildfilename raise me diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py index 4a03e98..b6a523b 100644 --- a/mesonbuild/ast/introspection.py +++ b/mesonbuild/ast/introspection.py @@ -51,9 +51,12 @@ class IntrospectionInterpreter(AstInterpreter): self.default_options = {'backend': self.backend} self.project_data = {} self.targets = [] + self.dependencies = [] + self.project_node = None self.funcs.update({ 'add_languages': self.func_add_languages, + 'dependency': self.func_dependency, 'executable': self.func_executable, 'jar': self.func_jar, 'library': self.func_library, @@ -65,6 +68,9 @@ class IntrospectionInterpreter(AstInterpreter): }) def func_project(self, node, args, kwargs): + if self.project_node: + raise InvalidArguments('Second call to project()') + self.project_node = node if len(args) < 1: raise InvalidArguments('Not enough arguments to project(). Needs at least the project name.') @@ -125,6 +131,16 @@ class IntrospectionInterpreter(AstInterpreter): if lang not in self.coredata.compilers: self.environment.detect_compilers(lang, need_cross_compiler) + def func_dependency(self, node, args, kwargs): + args = self.flatten_args(args) + if not args: + return + name = args[0] + self.dependencies += [{ + 'name': name, + 'node': node + }] + def build_target(self, node, args, kwargs, targetclass): if not args: return @@ -159,10 +175,8 @@ class IntrospectionInterpreter(AstInterpreter): if elemetary_nodes: source_nodes += [curr] - # Filter out kwargs from other target types. For example 'soversion' - # passed to library() when default_library == 'static'. - kwargs = {k: v for k, v in kwargs.items() if k in targetclass.known_kwargs} - + # Make sure nothing can crash when creating the build class + kwargs = {} is_cross = False objects = [] empty_sources = [] # Passing the unresolved sources list causes errors 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 |