aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild
diff options
context:
space:
mode:
authorJussi Pakkanen <jpakkane@gmail.com>2019-02-16 14:17:49 +0200
committerGitHub <noreply@github.com>2019-02-16 14:17:49 +0200
commit939b011114838b886d48521806988973205aecb4 (patch)
tree77eef03f5c136867100ded7dfcfcaa442b184f33 /mesonbuild
parent3ac7df6a6a016cf3ae92494bb510cbc4005571ed (diff)
parent0ce02b57d71bf4365e30063efa95a0a414a4a92a (diff)
downloadmeson-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.py4
-rw-r--r--mesonbuild/ast/introspection.py22
-rw-r--r--mesonbuild/rewriter.py367
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