diff options
Diffstat (limited to 'mesonbuild/ast/interpreter.py')
-rw-r--r-- | mesonbuild/ast/interpreter.py | 447 |
1 files changed, 447 insertions, 0 deletions
diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py new file mode 100644 index 0000000..7484e04 --- /dev/null +++ b/mesonbuild/ast/interpreter.py @@ -0,0 +1,447 @@ +# Copyright 2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This class contains the basic functionality needed to run any interpreter +# or an interpreter-based tool. +from __future__ import annotations + +import os +import sys +import typing as T + +from .. import mparser, mesonlib +from .. import environment + +from ..interpreterbase import ( + MesonInterpreterObject, + InterpreterBase, + InvalidArguments, + BreakRequest, + ContinueRequest, + default_resolve_key, +) + +from ..interpreter import ( + StringHolder, + BooleanHolder, + IntegerHolder, + ArrayHolder, + DictHolder, +) + +from ..mparser import ( + ArgumentNode, + ArithmeticNode, + ArrayNode, + AssignmentNode, + BaseNode, + ElementaryNode, + EmptyNode, + IdNode, + MethodNode, + NotNode, + PlusAssignmentNode, + TernaryNode, +) + +if T.TYPE_CHECKING: + from .visitor import AstVisitor + from ..interpreter import Interpreter + from ..interpreterbase import TYPE_nkwargs, TYPE_nvar + from ..mparser import ( + AndNode, + ComparisonNode, + ForeachClauseNode, + IfClauseNode, + IndexNode, + OrNode, + UMinusNode, + ) + +class DontCareObject(MesonInterpreterObject): + pass + +class MockExecutable(MesonInterpreterObject): + pass + +class MockStaticLibrary(MesonInterpreterObject): + pass + +class MockSharedLibrary(MesonInterpreterObject): + pass + +class MockCustomTarget(MesonInterpreterObject): + pass + +class MockRunTarget(MesonInterpreterObject): + pass + +ADD_SOURCE = 0 +REMOVE_SOURCE = 1 + +_T = T.TypeVar('_T') +_V = T.TypeVar('_V') + +class AstInterpreter(InterpreterBase): + def __init__(self, source_root: str, subdir: str, subproject: str, visitors: T.Optional[T.List[AstVisitor]] = None): + super().__init__(source_root, subdir, subproject) + self.visitors = visitors if visitors is not None else [] + self.processed_buildfiles = set() # type: T.Set[str] + self.assignments = {} # type: T.Dict[str, BaseNode] + self.assign_vals = {} # type: T.Dict[str, T.Any] + self.reverse_assignment = {} # type: T.Dict[str, BaseNode] + self.funcs.update({'project': self.func_do_nothing, + 'test': self.func_do_nothing, + 'benchmark': self.func_do_nothing, + 'install_headers': self.func_do_nothing, + 'install_man': self.func_do_nothing, + 'install_data': self.func_do_nothing, + 'install_subdir': self.func_do_nothing, + 'install_symlink': self.func_do_nothing, + 'install_emptydir': self.func_do_nothing, + 'configuration_data': self.func_do_nothing, + 'configure_file': self.func_do_nothing, + 'find_program': self.func_do_nothing, + 'include_directories': self.func_do_nothing, + 'add_global_arguments': self.func_do_nothing, + 'add_global_link_arguments': self.func_do_nothing, + 'add_project_arguments': self.func_do_nothing, + 'add_project_dependencies': self.func_do_nothing, + 'add_project_link_arguments': self.func_do_nothing, + 'message': self.func_do_nothing, + 'generator': self.func_do_nothing, + 'error': self.func_do_nothing, + 'run_command': self.func_do_nothing, + 'assert': self.func_do_nothing, + 'subproject': self.func_do_nothing, + 'dependency': self.func_do_nothing, + 'get_option': self.func_do_nothing, + 'join_paths': self.func_do_nothing, + 'environment': self.func_do_nothing, + 'import': self.func_do_nothing, + 'vcs_tag': self.func_do_nothing, + 'add_languages': self.func_do_nothing, + 'declare_dependency': self.func_do_nothing, + 'files': self.func_do_nothing, + 'executable': self.func_do_nothing, + 'static_library': self.func_do_nothing, + 'shared_library': self.func_do_nothing, + 'library': self.func_do_nothing, + 'build_target': self.func_do_nothing, + 'custom_target': self.func_do_nothing, + 'run_target': self.func_do_nothing, + 'subdir': self.func_subdir, + 'set_variable': self.func_do_nothing, + 'get_variable': self.func_do_nothing, + 'unset_variable': self.func_do_nothing, + 'is_disabler': self.func_do_nothing, + 'is_variable': self.func_do_nothing, + 'disabler': self.func_do_nothing, + 'gettext': self.func_do_nothing, + 'jar': self.func_do_nothing, + 'warning': self.func_do_nothing, + 'shared_module': self.func_do_nothing, + 'option': self.func_do_nothing, + 'both_libraries': self.func_do_nothing, + 'add_test_setup': self.func_do_nothing, + 'find_library': self.func_do_nothing, + 'subdir_done': self.func_do_nothing, + 'alias_target': self.func_do_nothing, + 'summary': self.func_do_nothing, + 'range': self.func_do_nothing, + 'structured_sources': self.func_do_nothing, + 'debug': self.func_do_nothing, + }) + + def _unholder_args(self, args: _T, kwargs: _V) -> T.Tuple[_T, _V]: + return args, kwargs + + def _holderify(self, res: _T) -> _T: + return res + + def func_do_nothing(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> bool: + return True + + def load_root_meson_file(self) -> None: + super().load_root_meson_file() + for i in self.visitors: + self.ast.accept(i) + + def func_subdir(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> None: + args = self.flatten_args(args) + if len(args) != 1 or not isinstance(args[0], str): + sys.stderr.write(f'Unable to evaluate subdir({args}) in AstInterpreter --> Skipping\n') + return + + prev_subdir = self.subdir + subdir = os.path.join(prev_subdir, args[0]) + absdir = os.path.join(self.source_root, subdir) + buildfilename = os.path.join(subdir, environment.build_filename) + absname = os.path.join(self.source_root, buildfilename) + symlinkless_dir = os.path.realpath(absdir) + build_file = os.path.join(symlinkless_dir, 'meson.build') + if build_file in self.processed_buildfiles: + sys.stderr.write('Trying to enter {} which has already been visited --> Skipping\n'.format(args[0])) + return + self.processed_buildfiles.add(build_file) + + if not os.path.isfile(absname): + sys.stderr.write(f'Unable to find build file {buildfilename} --> Skipping\n') + return + with open(absname, encoding='utf-8') as f: + code = f.read() + assert isinstance(code, str) + try: + codeblock = mparser.Parser(code, absname).parse() + except mesonlib.MesonException as me: + me.file = absname + raise me + + self.subdir = subdir + for i in self.visitors: + codeblock.accept(i) + self.evaluate_codeblock(codeblock) + self.subdir = prev_subdir + + def method_call(self, node: BaseNode) -> bool: + return True + + def evaluate_fstring(self, node: mparser.FormatStringNode) -> str: + assert isinstance(node, mparser.FormatStringNode) + return node.value + + def evaluate_arraystatement(self, cur: mparser.ArrayNode) -> TYPE_nvar: + return self.reduce_arguments(cur.args)[0] + + def evaluate_arithmeticstatement(self, cur: ArithmeticNode) -> int: + self.evaluate_statement(cur.left) + self.evaluate_statement(cur.right) + return 0 + + def evaluate_uminusstatement(self, cur: UMinusNode) -> int: + self.evaluate_statement(cur.value) + return 0 + + def evaluate_ternary(self, node: TernaryNode) -> None: + assert isinstance(node, TernaryNode) + self.evaluate_statement(node.condition) + self.evaluate_statement(node.trueblock) + self.evaluate_statement(node.falseblock) + + def evaluate_dictstatement(self, node: mparser.DictNode) -> TYPE_nkwargs: + def resolve_key(node: mparser.BaseNode) -> str: + if isinstance(node, mparser.StringNode): + return node.value + return '__AST_UNKNOWN__' + arguments, kwargs = self.reduce_arguments(node.args, key_resolver=resolve_key) + assert not arguments + self.argument_depth += 1 + for key, value in kwargs.items(): + if isinstance(key, BaseNode): + self.evaluate_statement(key) + self.argument_depth -= 1 + return {} + + def evaluate_plusassign(self, node: PlusAssignmentNode) -> None: + assert isinstance(node, PlusAssignmentNode) + # Cheat by doing a reassignment + self.assignments[node.var_name] = node.value # Save a reference to the value node + if node.value.ast_id: + self.reverse_assignment[node.value.ast_id] = node + self.assign_vals[node.var_name] = self.evaluate_statement(node.value) + + def evaluate_indexing(self, node: IndexNode) -> int: + return 0 + + def unknown_function_called(self, func_name: str) -> None: + pass + + def reduce_arguments( + self, + args: mparser.ArgumentNode, + key_resolver: T.Callable[[mparser.BaseNode], str] = default_resolve_key, + duplicate_key_error: T.Optional[str] = None, + ) -> T.Tuple[T.List[TYPE_nvar], TYPE_nkwargs]: + if isinstance(args, ArgumentNode): + kwargs = {} # type: T.Dict[str, TYPE_nvar] + for key, val in args.kwargs.items(): + kwargs[key_resolver(key)] = val + if args.incorrect_order(): + raise InvalidArguments('All keyword arguments must be after positional arguments.') + return self.flatten_args(args.arguments), kwargs + else: + return self.flatten_args(args), {} + + def evaluate_comparison(self, node: ComparisonNode) -> bool: + self.evaluate_statement(node.left) + self.evaluate_statement(node.right) + return False + + def evaluate_andstatement(self, cur: AndNode) -> bool: + self.evaluate_statement(cur.left) + self.evaluate_statement(cur.right) + return False + + def evaluate_orstatement(self, cur: OrNode) -> bool: + self.evaluate_statement(cur.left) + self.evaluate_statement(cur.right) + return False + + def evaluate_notstatement(self, cur: NotNode) -> bool: + self.evaluate_statement(cur.value) + return False + + def evaluate_foreach(self, node: ForeachClauseNode) -> None: + try: + self.evaluate_codeblock(node.block) + except ContinueRequest: + pass + except BreakRequest: + pass + + def evaluate_if(self, node: IfClauseNode) -> None: + for i in node.ifs: + self.evaluate_codeblock(i.block) + if not isinstance(node.elseblock, EmptyNode): + self.evaluate_codeblock(node.elseblock) + + def get_variable(self, varname: str) -> int: + return 0 + + def assignment(self, node: AssignmentNode) -> None: + assert isinstance(node, AssignmentNode) + self.assignments[node.var_name] = node.value # Save a reference to the value node + if node.value.ast_id: + self.reverse_assignment[node.value.ast_id] = node + self.assign_vals[node.var_name] = self.evaluate_statement(node.value) # Evaluate the value just in case + + def resolve_node(self, node: BaseNode, include_unknown_args: bool = False, id_loop_detect: T.Optional[T.List[str]] = None) -> T.Optional[T.Any]: + def quick_resolve(n: BaseNode, loop_detect: T.Optional[T.List[str]] = None) -> T.Any: + if loop_detect is None: + loop_detect = [] + if isinstance(n, IdNode): + assert isinstance(n.value, str) + if n.value in loop_detect or n.value not in self.assignments: + return [] + return quick_resolve(self.assignments[n.value], loop_detect = loop_detect + [n.value]) + elif isinstance(n, ElementaryNode): + return n.value + else: + return n + + if id_loop_detect is None: + id_loop_detect = [] + result = None + + if not isinstance(node, BaseNode): + return None + + assert node.ast_id + if node.ast_id in id_loop_detect: + return None # Loop detected + id_loop_detect += [node.ast_id] + + # Try to evealuate the value of the node + if isinstance(node, IdNode): + result = quick_resolve(node) + + elif isinstance(node, ElementaryNode): + result = node.value + + elif isinstance(node, NotNode): + result = self.resolve_node(node.value, include_unknown_args, id_loop_detect) + if isinstance(result, bool): + result = not result + + elif isinstance(node, ArrayNode): + result = node.args.arguments.copy() + + elif isinstance(node, ArgumentNode): + result = node.arguments.copy() + + elif isinstance(node, ArithmeticNode): + if node.operation != 'add': + return None # Only handle string and array concats + l = quick_resolve(node.left) + r = quick_resolve(node.right) + if isinstance(l, str) and isinstance(r, str): + result = l + r # String concatenation detected + else: + result = self.flatten_args(l, include_unknown_args, id_loop_detect) + self.flatten_args(r, include_unknown_args, id_loop_detect) + + elif isinstance(node, MethodNode): + src = quick_resolve(node.source_object) + margs = self.flatten_args(node.args.arguments, include_unknown_args, id_loop_detect) + mkwargs = {} # type: T.Dict[str, TYPE_nvar] + try: + if isinstance(src, str): + result = StringHolder(src, T.cast('Interpreter', self)).method_call(node.name, margs, mkwargs) + elif isinstance(src, bool): + result = BooleanHolder(src, T.cast('Interpreter', self)).method_call(node.name, margs, mkwargs) + elif isinstance(src, int): + result = IntegerHolder(src, T.cast('Interpreter', self)).method_call(node.name, margs, mkwargs) + elif isinstance(src, list): + result = ArrayHolder(src, T.cast('Interpreter', self)).method_call(node.name, margs, mkwargs) + elif isinstance(src, dict): + result = DictHolder(src, T.cast('Interpreter', self)).method_call(node.name, margs, mkwargs) + except mesonlib.MesonException: + return None + + # Ensure that the result is fully resolved (no more nodes) + if isinstance(result, BaseNode): + result = self.resolve_node(result, include_unknown_args, id_loop_detect) + elif isinstance(result, list): + new_res = [] # type: T.List[TYPE_nvar] + for i in result: + if isinstance(i, BaseNode): + resolved = self.resolve_node(i, include_unknown_args, id_loop_detect) + if resolved is not None: + new_res += self.flatten_args(resolved, include_unknown_args, id_loop_detect) + else: + new_res += [i] + result = new_res + + return result + + def flatten_args(self, args_raw: T.Union[TYPE_nvar, T.Sequence[TYPE_nvar]], include_unknown_args: bool = False, id_loop_detect: T.Optional[T.List[str]] = None) -> T.List[TYPE_nvar]: + # Make sure we are always dealing with lists + if isinstance(args_raw, list): + args = args_raw + else: + args = [args_raw] + + flattend_args = [] # type: T.List[TYPE_nvar] + + # Resolve the contents of args + for i in args: + if isinstance(i, BaseNode): + resolved = self.resolve_node(i, include_unknown_args, id_loop_detect) + if resolved is not None: + if not isinstance(resolved, list): + resolved = [resolved] + flattend_args += resolved + elif isinstance(i, (str, bool, int, float)) or include_unknown_args: + flattend_args += [i] + return flattend_args + + def flatten_kwargs(self, kwargs: T.Dict[str, TYPE_nvar], include_unknown_args: bool = False) -> T.Dict[str, TYPE_nvar]: + flattend_kwargs = {} + for key, val in kwargs.items(): + if isinstance(val, BaseNode): + resolved = self.resolve_node(val, include_unknown_args) + if resolved is not None: + flattend_kwargs[key] = resolved + elif isinstance(val, (str, bool, int, float)) or include_unknown_args: + flattend_kwargs[key] = val + return flattend_kwargs |