# SPDX-License-Identifier: Apache-2.0 # Copyright 2016 The Meson development team # 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, Disabler, 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, TestCaseClauseNode, ) if T.TYPE_CHECKING: from .visitor import AstVisitor from ..interpreter import Interpreter from ..interpreterbase import SubProject, TYPE_nkwargs, TYPE_var 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: SubProject, 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: T.Set[str] = set() self.assignments: T.Dict[str, BaseNode] = {} self.assign_vals: T.Dict[str, T.Any] = {} self.reverse_assignment: 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, '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, '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_var], kwargs: T.Dict[str, TYPE_var]) -> 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_var], kwargs: T.Dict[str, TYPE_var]) -> 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_var: 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.BaseStringNode): 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.value] = 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.value] = 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_var], TYPE_nkwargs]: if isinstance(args, ArgumentNode): kwargs: T.Dict[str, TYPE_var] = {} 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.block) def get_variable(self, varname: str) -> int: return 0 def assignment(self, node: AssignmentNode) -> None: assert isinstance(node, AssignmentNode) self.assignments[node.var_name.value] = 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.value] = 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 evaluate 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 = self.resolve_node(node.left, include_unknown_args, id_loop_detect) r = self.resolve_node(node.right, include_unknown_args, id_loop_detect) 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: T.Dict[str, TYPE_var] = {} method_name = node.name.value try: if isinstance(src, str): result = StringHolder(src, T.cast('Interpreter', self)).method_call(method_name, margs, mkwargs) elif isinstance(src, bool): result = BooleanHolder(src, T.cast('Interpreter', self)).method_call(method_name, margs, mkwargs) elif isinstance(src, int): result = IntegerHolder(src, T.cast('Interpreter', self)).method_call(method_name, margs, mkwargs) elif isinstance(src, list): result = ArrayHolder(src, T.cast('Interpreter', self)).method_call(method_name, margs, mkwargs) elif isinstance(src, dict): result = DictHolder(src, T.cast('Interpreter', self)).method_call(method_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: T.List[TYPE_var] = [] 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_var, T.Sequence[TYPE_var]], include_unknown_args: bool = False, id_loop_detect: T.Optional[T.List[str]] = None) -> T.List[TYPE_var]: # Make sure we are always dealing with lists if isinstance(args_raw, list): args = args_raw else: args = [args_raw] flattened_args: T.List[TYPE_var] = [] # 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] flattened_args += resolved elif isinstance(i, (str, bool, int, float)) or include_unknown_args: flattened_args += [i] return flattened_args def flatten_kwargs(self, kwargs: T.Dict[str, TYPE_var], include_unknown_args: bool = False) -> T.Dict[str, TYPE_var]: flattened_kwargs = {} for key, val in kwargs.items(): if isinstance(val, BaseNode): resolved = self.resolve_node(val, include_unknown_args) if resolved is not None: flattened_kwargs[key] = resolved elif isinstance(val, (str, bool, int, float)) or include_unknown_args: flattened_kwargs[key] = val return flattened_kwargs def evaluate_testcase(self, node: TestCaseClauseNode) -> Disabler | None: return Disabler(subproject=self.subproject)