aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/ast
diff options
context:
space:
mode:
Diffstat (limited to 'mesonbuild/ast')
-rw-r--r--mesonbuild/ast/interpreter.py718
-rw-r--r--mesonbuild/ast/introspection.py217
-rw-r--r--mesonbuild/ast/printer.py50
3 files changed, 681 insertions, 304 deletions
diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py
index cd8156a..62c4839 100644
--- a/mesonbuild/ast/interpreter.py
+++ b/mesonbuild/ast/interpreter.py
@@ -8,8 +8,12 @@ from __future__ import annotations
import os
import sys
import typing as T
+from collections import defaultdict
+from dataclasses import dataclass
+import itertools
+from pathlib import Path
-from .. import mparser, mesonlib
+from .. import mparser, mesonlib, mlog
from .. import environment
from ..interpreterbase import (
@@ -20,8 +24,14 @@ from ..interpreterbase import (
ContinueRequest,
Disabler,
default_resolve_key,
+ is_disabled,
+ UnknownValue,
+ UndefinedVariable,
+ InterpreterObject,
)
+from ..interpreterbase.helpers import flatten
+
from ..interpreter import (
StringHolder,
BooleanHolder,
@@ -36,19 +46,21 @@ from ..mparser import (
ArrayNode,
AssignmentNode,
BaseNode,
- ElementaryNode,
EmptyNode,
IdNode,
MethodNode,
NotNode,
PlusAssignmentNode,
TernaryNode,
+ SymbolNode,
+ Token,
+ FunctionNode,
)
if T.TYPE_CHECKING:
from .visitor import AstVisitor
from ..interpreter import Interpreter
- from ..interpreterbase import SubProject, TYPE_nkwargs, TYPE_var
+ from ..interpreterbase import SubProject, TYPE_var, TYPE_nvar
from ..mparser import (
AndNode,
ComparisonNode,
@@ -60,38 +72,122 @@ if T.TYPE_CHECKING:
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')
+def _symbol(val: str) -> SymbolNode:
+ return SymbolNode(Token('', '', 0, 0, 0, (0, 0), val))
+
+# `IntrospectionFile` is to the `IntrospectionInterpreter` what `File` is to the normal `Interpreter`.
+@dataclass
+class IntrospectionFile:
+ subdir: str
+ rel: str
+
+ def to_abs_path(self, root_dir: Path) -> Path:
+ return (root_dir / self.subdir / self.rel).resolve()
+
+ def __hash__(self) -> int:
+ return hash((self.__class__.__name__, self.subdir, self.rel))
+
+# `IntrospectionDependency` is to the `IntrospectionInterpreter` what `Dependency` is to the normal `Interpreter`.
+@dataclass
+class IntrospectionDependency(MesonInterpreterObject):
+ name: T.Union[str, UnknownValue]
+ required: T.Union[bool, UnknownValue]
+ version: T.Union[T.List[str], UnknownValue]
+ has_fallback: bool
+ conditional: bool
+ node: FunctionNode
+
+# `IntrospectionBuildTarget` is to the `IntrospectionInterpreter` what `BuildTarget` is to the normal `Interpreter`.
+@dataclass
+class IntrospectionBuildTarget(MesonInterpreterObject):
+ name: str
+ machine: str
+ id: str
+ typename: str
+ defined_in: str
+ subdir: str
+ build_by_default: bool
+ installed: bool
+ outputs: T.List[str]
+ source_nodes: T.List[BaseNode]
+ extra_files: BaseNode
+ kwargs: T.Dict[str, TYPE_var]
+ node: FunctionNode
+
+def is_ignored_edge(src: T.Union[BaseNode, UnknownValue]) -> bool:
+ return (isinstance(src, FunctionNode) and src.func_name.value not in {'files', 'get_variable'}) or isinstance(src, MethodNode)
+
+class DataflowDAG:
+ src_to_tgts: T.DefaultDict[T.Union[BaseNode, UnknownValue], T.Set[T.Union[BaseNode, UnknownValue]]]
+ tgt_to_srcs: T.DefaultDict[T.Union[BaseNode, UnknownValue], T.Set[T.Union[BaseNode, UnknownValue]]]
+
+ def __init__(self) -> None:
+ self.src_to_tgts = defaultdict(set)
+ self.tgt_to_srcs = defaultdict(set)
+
+ def add_edge(self, source: T.Union[BaseNode, UnknownValue], target: T.Union[BaseNode, UnknownValue]) -> None:
+ self.src_to_tgts[source].add(target)
+ self.tgt_to_srcs[target].add(source)
+
+ # Returns all nodes in the DAG that are reachable from a node in `srcs`.
+ # In other words, A node `a` is part of the returned set exactly if data
+ # from `srcs` flows into `a`, directly or indirectly.
+ # Certain edges are ignored.
+ def reachable(self, srcs: T.Set[T.Union[BaseNode, UnknownValue]], reverse: bool) -> T.Set[T.Union[BaseNode, UnknownValue]]:
+ reachable = srcs.copy()
+ active = srcs.copy()
+ while active:
+ new: T.Set[T.Union[BaseNode, UnknownValue]] = set()
+ if reverse:
+ for tgt in active:
+ new.update(src for src in self.tgt_to_srcs[tgt] if not is_ignored_edge(src))
+ else:
+ for src in active:
+ if is_ignored_edge(src):
+ continue
+ new.update(tgt for tgt in self.src_to_tgts[src])
+ reachable.update(new)
+ active = new
+ return reachable
+
+ # Returns all paths from src to target.
+ # Certain edges are ignored.
+ def find_all_paths(self, src: T.Union[BaseNode, UnknownValue], target: T.Union[BaseNode, UnknownValue]) -> T.List[T.List[T.Union[BaseNode, UnknownValue]]]:
+ queue = [(src, [src])]
+ paths = []
+ while queue:
+ cur, path = queue.pop()
+ if cur == target:
+ paths.append(path)
+ if is_ignored_edge(cur):
+ continue
+ queue.extend((tgt, path + [tgt]) for tgt in self.src_to_tgts[cur])
+ return paths
class AstInterpreter(InterpreterBase):
def __init__(self, source_root: str, subdir: str, subproject: SubProject, subproject_dir: str, env: environment.Environment, visitors: T.Optional[T.List[AstVisitor]] = None):
super().__init__(source_root, subdir, subproject, subproject_dir, env)
self.visitors = visitors if visitors is not None else []
- self.assignments: T.Dict[str, BaseNode] = {}
- self.assign_vals: T.Dict[str, T.Any] = {}
- self.reverse_assignment: T.Dict[str, BaseNode] = {}
+ self.nesting: T.List[int] = []
+ self.cur_assignments: T.DefaultDict[str, T.List[T.Tuple[T.List[int], T.Union[BaseNode, UnknownValue]]]] = defaultdict(list)
+ self.all_assignment_nodes: T.DefaultDict[str, T.List[AssignmentNode]] = defaultdict(list)
+ # dataflow_dag is an acyclic directed graph that contains an edge
+ # from one instance of `BaseNode` to another instance of `BaseNode` if
+ # data flows directly from one to the other. Example: If meson.build
+ # contains this:
+ # var = 'foo' + '123'
+ # executable(var, 'src.c')
+ # var = 'bar'
+ # dataflow_dag will contain an edge from the IdNode corresponding to
+ # 'var' in line 2 to the ArithmeticNode corresponding to 'foo' + '123'.
+ # This graph is crucial for e.g. node_to_runtime_value because we have
+ # to know that 'var' in line2 is 'foo123' and not 'bar'.
+ self.dataflow_dag = DataflowDAG()
+ self.funcvals: T.Dict[BaseNode, T.Any] = {}
+ self.tainted = False
self.funcs.update({'project': self.func_do_nothing,
'test': self.func_do_nothing,
'benchmark': self.func_do_nothing,
@@ -124,7 +220,7 @@ class AstInterpreter(InterpreterBase):
'vcs_tag': self.func_do_nothing,
'add_languages': self.func_do_nothing,
'declare_dependency': self.func_do_nothing,
- 'files': self.func_do_nothing,
+ 'files': self.func_files,
'executable': self.func_do_nothing,
'static_library': self.func_do_nothing,
'shared_library': self.func_do_nothing,
@@ -133,9 +229,9 @@ class AstInterpreter(InterpreterBase):
'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,
+ 'set_variable': self.func_set_variable,
+ 'get_variable': self.func_get_variable,
+ 'unset_variable': self.func_unset_variable,
'is_disabler': self.func_do_nothing,
'is_variable': self.func_do_nothing,
'disabler': self.func_do_nothing,
@@ -153,14 +249,14 @@ class AstInterpreter(InterpreterBase):
'debug': self.func_do_nothing,
})
- def _unholder_args(self, args: _T, kwargs: _V) -> T.Tuple[_T, _V]:
+ def _unholder_args(self, args: T.Any, kwargs: T.Any) -> T.Tuple[T.Any, T.Any]:
return args, kwargs
- def _holderify(self, res: _T) -> _T:
+ def _holderify(self, res: T.Any) -> T.Any:
return res
- def func_do_nothing(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> bool:
- return True
+ def func_do_nothing(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> UnknownValue:
+ return UnknownValue()
def load_root_meson_file(self) -> None:
super().load_root_meson_file()
@@ -182,24 +278,50 @@ class AstInterpreter(InterpreterBase):
buildfilename = os.path.join(subdir, environment.build_filename)
sys.stderr.write(f'Unable to find build file {buildfilename} --> Skipping\n')
- def method_call(self, node: BaseNode) -> bool:
- return True
+ def inner_method_call(self, obj: BaseNode, method_name: str, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Any:
+ for arg in itertools.chain(args, kwargs.values()):
+ if isinstance(arg, UnknownValue):
+ return UnknownValue()
+
+ if isinstance(obj, str):
+ result = StringHolder(obj, T.cast('Interpreter', self)).method_call(method_name, args, kwargs)
+ elif isinstance(obj, bool):
+ result = BooleanHolder(obj, T.cast('Interpreter', self)).method_call(method_name, args, kwargs)
+ elif isinstance(obj, int):
+ result = IntegerHolder(obj, T.cast('Interpreter', self)).method_call(method_name, args, kwargs)
+ elif isinstance(obj, list):
+ result = ArrayHolder(obj, T.cast('Interpreter', self)).method_call(method_name, args, kwargs)
+ elif isinstance(obj, dict):
+ result = DictHolder(obj, T.cast('Interpreter', self)).method_call(method_name, args, kwargs)
+ else:
+ return UnknownValue()
+ return result
- def evaluate_fstring(self, node: mparser.StringNode) -> str:
- assert isinstance(node, mparser.StringNode)
- return node.value
+ def method_call(self, node: mparser.MethodNode) -> None:
+ invocable = node.source_object
+ self.evaluate_statement(invocable)
+ obj = self.node_to_runtime_value(invocable)
+ method_name = node.name.value
+ (args, kwargs) = self.reduce_arguments(node.args)
+ if is_disabled(args, kwargs):
+ res = Disabler()
+ else:
+ res = self.inner_method_call(obj, method_name, args, kwargs)
+ self.funcvals[node] = res
+
+ def evaluate_fstring(self, node: mparser.StringNode) -> None:
+ pass
- def evaluate_arraystatement(self, cur: mparser.ArrayNode) -> TYPE_var:
- return self.reduce_arguments(cur.args)[0]
+ def evaluate_arraystatement(self, cur: mparser.ArrayNode) -> None:
+ for arg in cur.args.arguments:
+ self.evaluate_statement(arg)
- def evaluate_arithmeticstatement(self, cur: ArithmeticNode) -> int:
+ def evaluate_arithmeticstatement(self, cur: ArithmeticNode) -> None:
self.evaluate_statement(cur.left)
self.evaluate_statement(cur.right)
- return 0
- def evaluate_uminusstatement(self, cur: UMinusNode) -> int:
+ def evaluate_uminusstatement(self, cur: UMinusNode) -> None:
self.evaluate_statement(cur.value)
- return 0
def evaluate_ternary(self, node: TernaryNode) -> None:
assert isinstance(node, TernaryNode)
@@ -207,42 +329,27 @@ class AstInterpreter(InterpreterBase):
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.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_dictstatement(self, node: mparser.DictNode) -> None:
+ for k, v in node.args.kwargs.items():
+ self.evaluate_statement(k)
+ self.evaluate_statement(v)
- def evaluate_indexing(self, node: IndexNode) -> int:
- return 0
-
- def unknown_function_called(self, func_name: str) -> None:
- pass
+ def evaluate_indexing(self, node: IndexNode) -> None:
+ self.evaluate_statement(node.iobject)
+ self.evaluate_statement(node.index)
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]:
+ ) -> T.Tuple[T.List[T.Any], T.Any]:
+ for arg in args.arguments:
+ self.evaluate_statement(arg)
+ for value in args.kwargs.values():
+ self.evaluate_statement(value)
if isinstance(args, ArgumentNode):
- kwargs: T.Dict[str, TYPE_var] = {}
+ kwargs = {}
for key, val in args.kwargs.items():
kwargs[key_resolver(key)] = val
if args.incorrect_order():
@@ -251,139 +358,370 @@ class AstInterpreter(InterpreterBase):
else:
return self.flatten_args(args), {}
- def evaluate_comparison(self, node: ComparisonNode) -> bool:
+ def evaluate_comparison(self, node: ComparisonNode) -> None:
self.evaluate_statement(node.left)
self.evaluate_statement(node.right)
- return False
- def evaluate_andstatement(self, cur: AndNode) -> bool:
+ def evaluate_andstatement(self, cur: AndNode) -> None:
self.evaluate_statement(cur.left)
self.evaluate_statement(cur.right)
- return False
- def evaluate_orstatement(self, cur: OrNode) -> bool:
+ def evaluate_orstatement(self, cur: OrNode) -> None:
self.evaluate_statement(cur.left)
self.evaluate_statement(cur.right)
- return False
- def evaluate_notstatement(self, cur: NotNode) -> bool:
+ def evaluate_notstatement(self, cur: NotNode) -> None:
self.evaluate_statement(cur.value)
- return False
+
+ def find_potential_writes(self, node: BaseNode) -> T.Set[str]:
+ if isinstance(node, mparser.ForeachClauseNode):
+ return {el.value for el in node.varnames} | self.find_potential_writes(node.block)
+ elif isinstance(node, mparser.CodeBlockNode):
+ ret = set()
+ for line in node.lines:
+ ret.update(self.find_potential_writes(line))
+ return ret
+ elif isinstance(node, (AssignmentNode, PlusAssignmentNode)):
+ return set([node.var_name.value]) | self.find_potential_writes(node.value)
+ elif isinstance(node, IdNode):
+ return set()
+ elif isinstance(node, ArrayNode):
+ ret = set()
+ for arg in node.args.arguments:
+ ret.update(self.find_potential_writes(arg))
+ return ret
+ elif isinstance(node, mparser.DictNode):
+ ret = set()
+ for k, v in node.args.kwargs.items():
+ ret.update(self.find_potential_writes(k))
+ ret.update(self.find_potential_writes(v))
+ return ret
+ elif isinstance(node, FunctionNode):
+ ret = set()
+ for arg in node.args.arguments:
+ ret.update(self.find_potential_writes(arg))
+ for arg in node.args.kwargs.values():
+ ret.update(self.find_potential_writes(arg))
+ return ret
+ elif isinstance(node, MethodNode):
+ ret = self.find_potential_writes(node.source_object)
+ for arg in node.args.arguments:
+ ret.update(self.find_potential_writes(arg))
+ for arg in node.args.kwargs.values():
+ ret.update(self.find_potential_writes(arg))
+ return ret
+ elif isinstance(node, ArithmeticNode):
+ return self.find_potential_writes(node.left) | self.find_potential_writes(node.right)
+ elif isinstance(node, (mparser.NumberNode, mparser.StringNode, mparser.BreakNode, mparser.BooleanNode, mparser.ContinueNode)):
+ return set()
+ elif isinstance(node, mparser.IfClauseNode):
+ if isinstance(node.elseblock, EmptyNode):
+ ret = set()
+ else:
+ ret = self.find_potential_writes(node.elseblock.block)
+ for i in node.ifs:
+ ret.update(self.find_potential_writes(i))
+ return ret
+ elif isinstance(node, mparser.IndexNode):
+ return self.find_potential_writes(node.iobject) | self.find_potential_writes(node.index)
+ elif isinstance(node, mparser.IfNode):
+ return self.find_potential_writes(node.condition) | self.find_potential_writes(node.block)
+ elif isinstance(node, (mparser.ComparisonNode, mparser.OrNode, mparser.AndNode)):
+ return self.find_potential_writes(node.left) | self.find_potential_writes(node.right)
+ elif isinstance(node, mparser.NotNode):
+ return self.find_potential_writes(node.value)
+ elif isinstance(node, mparser.TernaryNode):
+ return self.find_potential_writes(node.condition) | self.find_potential_writes(node.trueblock) | self.find_potential_writes(node.falseblock)
+ elif isinstance(node, mparser.UMinusNode):
+ return self.find_potential_writes(node.value)
+ elif isinstance(node, mparser.ParenthesizedNode):
+ return self.find_potential_writes(node.inner)
+ raise mesonlib.MesonBugException('Unhandled node type')
def evaluate_foreach(self, node: ForeachClauseNode) -> None:
+ asses = self.find_potential_writes(node)
+ for ass in asses:
+ self.cur_assignments[ass].append((self.nesting.copy(), UnknownValue()))
try:
self.evaluate_codeblock(node.block)
except ContinueRequest:
pass
except BreakRequest:
pass
+ for ass in asses:
+ self.cur_assignments[ass].append((self.nesting.copy(), UnknownValue())) # In case the foreach loops 0 times.
def evaluate_if(self, node: IfClauseNode) -> None:
+ self.nesting.append(0)
for i in node.ifs:
self.evaluate_codeblock(i.block)
+ self.nesting[-1] += 1
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
+ self.nesting.pop()
+ for var_name in self.cur_assignments:
+ potential_values = []
+ oldval = self.get_cur_value_if_defined(var_name)
+ if not isinstance(oldval, UndefinedVariable):
+ potential_values.append(oldval)
+ for nesting, value in self.cur_assignments[var_name]:
+ if len(nesting) > len(self.nesting):
+ potential_values.append(value)
+ self.cur_assignments[var_name] = [(nesting, v) for (nesting, v) in self.cur_assignments[var_name] if len(nesting) <= len(self.nesting)]
+ if len(potential_values) > 1 or (len(potential_values) > 0 and isinstance(oldval, UndefinedVariable)):
+ uv = UnknownValue()
+ for pv in potential_values:
+ self.dataflow_dag.add_edge(pv, uv)
+ self.cur_assignments[var_name].append((self.nesting.copy(), uv))
+
+ def func_files(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Any:
+ ret: T.List[T.Union[IntrospectionFile, UnknownValue]] = []
+ for arg in args:
+ if isinstance(arg, str):
+ ret.append(IntrospectionFile(self.subdir, arg))
+ elif isinstance(arg, UnknownValue):
+ ret.append(UnknownValue())
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
-
+ raise TypeError
+ return ret
+
+ def get_cur_value_if_defined(self, var_name: str) -> T.Union[BaseNode, UnknownValue, UndefinedVariable]:
+ if var_name in {'meson', 'host_machine', 'build_machine', 'target_machine'}:
+ return UnknownValue()
+ ret: T.Union[BaseNode, UnknownValue, UndefinedVariable] = UndefinedVariable()
+ for nesting, value in reversed(self.cur_assignments[var_name]):
+ if len(self.nesting) >= len(nesting) and self.nesting[:len(nesting)] == nesting:
+ ret = value
+ break
+ if isinstance(ret, UndefinedVariable) and self.tainted:
+ return UnknownValue()
+ return ret
+
+ def get_cur_value(self, var_name: str) -> T.Union[BaseNode, UnknownValue]:
+ ret = self.get_cur_value_if_defined(var_name)
+ if isinstance(ret, UndefinedVariable):
+ path = mlog.get_relative_path(Path(self.current_node.filename), Path(os.getcwd()))
+ mlog.warning(f"{path}:{self.current_node.lineno}:{self.current_node.colno} will always crash if executed, since a variable named `{var_name}` is not defined")
+ # We could add more advanced analysis of code referencing undefined
+ # variables, but it is probably not worth the effort and the
+ # complexity. So we do the simplest thing, returning an
+ # UnknownValue.
+ return UnknownValue()
+ return ret
+
+ # The function `node_to_runtime_value` takes a node of the ast as an
+ # argument and tries to return the same thing that would be passed to e.g.
+ # `func_message` if you put `message(node)` in your `meson.build` file and
+ # run `meson setup`. If this is not possible, `UnknownValue()` is returned.
+ # There are 3 Reasons why this is sometimes impossible:
+ # 1. Because the meson rewriter is imperfect and has not implemented everything yet
+ # 2. Because the value is different on different machines, example:
+ # ```meson
+ # node = somedep.found()
+ # message(node)
+ # ```
+ # will print `true` on some machines and `false` on others, so
+ # `node_to_runtime_value` does not know whether to return `true` or
+ # `false` and will return `UnknownValue()`.
+ # 3. Here:
+ # ```meson
+ # foreach x : [1, 2]
+ # node = x
+ # message(node)
+ # endforeach
+ # ```
+ # `node_to_runtime_value` does not know whether to return `1` or `2` and
+ # will return `UnknownValue()`.
+ #
+ # If you have something like
+ # ```
+ # node = [123, somedep.found()]
+ # ```
+ # `node_to_runtime_value` will return `[123, UnknownValue()]`.
+ def node_to_runtime_value(self, node: T.Union[UnknownValue, BaseNode, TYPE_var]) -> T.Any:
+ if isinstance(node, (mparser.StringNode, mparser.BooleanNode, mparser.NumberNode)):
+ return node.value
+ elif isinstance(node, mparser.StringNode):
+ if node.is_fstring:
+ return UnknownValue()
+ else:
+ return node.value
+ elif isinstance(node, list):
+ return [self.node_to_runtime_value(x) for x in node]
elif isinstance(node, ArrayNode):
- result = node.args.arguments.copy()
+ return [self.node_to_runtime_value(x) for x in node.args.arguments]
+ elif isinstance(node, mparser.DictNode):
+ return {self.node_to_runtime_value(k): self.node_to_runtime_value(v) for k, v in node.args.kwargs.items()}
+ elif isinstance(node, IdNode):
+ assert len(self.dataflow_dag.tgt_to_srcs[node]) == 1
+ val = next(iter(self.dataflow_dag.tgt_to_srcs[node]))
+ return self.node_to_runtime_value(val)
+ elif isinstance(node, (MethodNode, FunctionNode)):
+ funcval = self.funcvals[node]
+ if isinstance(funcval, (dict, str)):
+ return funcval
+ else:
+ return self.node_to_runtime_value(funcval)
+ elif isinstance(node, ArithmeticNode):
+ left = self.node_to_runtime_value(node.left)
+ right = self.node_to_runtime_value(node.right)
+ if isinstance(left, list) and isinstance(right, UnknownValue):
+ return left + [right]
+ if isinstance(right, list) and isinstance(left, UnknownValue):
+ return [left] + right
+ if isinstance(left, UnknownValue) or isinstance(right, UnknownValue):
+ return UnknownValue()
+ if node.operation == 'add':
+ if isinstance(left, dict) and isinstance(right, dict):
+ ret = left.copy()
+ for k, v in right.items():
+ ret[k] = v
+ return ret
+ if isinstance(left, list):
+ if not isinstance(right, list):
+ right = [right]
+ return left + right
+ return left + right
+ elif node.operation == 'sub':
+ return left - right
+ elif node.operation == 'mul':
+ return left * right
+ elif node.operation == 'div':
+ if isinstance(left, int) and isinstance(right, int):
+ return left // right
+ elif isinstance(left, str) and isinstance(right, str):
+ return os.path.join(left, right).replace('\\', '/')
+ elif node.operation == 'mod':
+ if isinstance(left, int) and isinstance(right, int):
+ return left % right
+ elif isinstance(node, (UnknownValue, IntrospectionBuildTarget, IntrospectionFile, IntrospectionDependency, str, bool, int)):
+ return node
+ elif isinstance(node, mparser.IndexNode):
+ iobject = self.node_to_runtime_value(node.iobject)
+ index = self.node_to_runtime_value(node.index)
+ if isinstance(iobject, UnknownValue) or isinstance(index, UnknownValue):
+ return UnknownValue()
+ return iobject[index]
+ elif isinstance(node, mparser.ComparisonNode):
+ left = self.node_to_runtime_value(node.left)
+ right = self.node_to_runtime_value(node.right)
+ if isinstance(left, UnknownValue) or isinstance(right, UnknownValue):
+ return UnknownValue()
+ if node.ctype == '==':
+ return left == right
+ elif node.ctype == '!=':
+ return left != right
+ elif node.ctype == 'in':
+ return left in right
+ elif node.ctype == 'notin':
+ return left not in right
+ elif isinstance(node, mparser.TernaryNode):
+ cond = self.node_to_runtime_value(node.condition)
+ if isinstance(cond, UnknownValue):
+ return UnknownValue()
+ if cond is True:
+ return self.node_to_runtime_value(node.trueblock)
+ if cond is False:
+ return self.node_to_runtime_value(node.falseblock)
+ elif isinstance(node, mparser.OrNode):
+ left = self.node_to_runtime_value(node.left)
+ right = self.node_to_runtime_value(node.right)
+ if isinstance(left, UnknownValue) or isinstance(right, UnknownValue):
+ return UnknownValue()
+ return left or right
+ elif isinstance(node, mparser.AndNode):
+ left = self.node_to_runtime_value(node.left)
+ right = self.node_to_runtime_value(node.right)
+ if isinstance(left, UnknownValue) or isinstance(right, UnknownValue):
+ return UnknownValue()
+ return left and right
+ elif isinstance(node, mparser.UMinusNode):
+ val = self.node_to_runtime_value(node.value)
+ if isinstance(val, UnknownValue):
+ return val
+ if isinstance(val, (int, float)):
+ return -val
+ elif isinstance(node, mparser.NotNode):
+ val = self.node_to_runtime_value(node.value)
+ if isinstance(val, UnknownValue):
+ return val
+ if isinstance(val, bool):
+ return not val
+ elif isinstance(node, mparser.ParenthesizedNode):
+ return self.node_to_runtime_value(node.inner)
+ raise mesonlib.MesonBugException('Unhandled node type')
- elif isinstance(node, ArgumentNode):
- result = node.arguments.copy()
+ def assignment(self, node: AssignmentNode) -> None:
+ assert isinstance(node, AssignmentNode)
+ self.evaluate_statement(node.value)
+ self.cur_assignments[node.var_name.value].append((self.nesting.copy(), node.value))
+ self.all_assignment_nodes[node.var_name.value].append(node)
- 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
+ def evaluate_plusassign(self, node: PlusAssignmentNode) -> None:
+ assert isinstance(node, PlusAssignmentNode)
+ self.evaluate_statement(node.value)
+ lhs = self.get_cur_value(node.var_name.value)
+ newval: T.Union[UnknownValue, ArithmeticNode]
+ if isinstance(lhs, UnknownValue):
+ newval = UnknownValue()
+ else:
+ newval = mparser.ArithmeticNode(operation='add', left=lhs, operator=_symbol('+'), right=node.value)
+ self.cur_assignments[node.var_name.value].append((self.nesting.copy(), newval))
+ self.all_assignment_nodes[node.var_name.value].append(node)
+
+ self.dataflow_dag.add_edge(lhs, newval)
+ self.dataflow_dag.add_edge(node.value, newval)
+
+ def func_set_variable(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> None:
+ assert isinstance(node, FunctionNode)
+ if bool(node.args.kwargs):
+ raise InvalidArguments('set_variable accepts no keyword arguments')
+ if len(node.args.arguments) != 2:
+ raise InvalidArguments('set_variable requires exactly two positional arguments')
+ var_name = args[0]
+ value = node.args.arguments[1]
+ if isinstance(var_name, UnknownValue):
+ self.evaluate_statement(value)
+ self.tainted = True
+ return
+ assert isinstance(var_name, str)
+ equiv = AssignmentNode(var_name=IdNode(Token('', '', 0, 0, 0, (0, 0), var_name)), value=value, operator=_symbol('='))
+ equiv.ast_id = str(id(equiv))
+ self.evaluate_statement(equiv)
+
+ def func_get_variable(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Any:
+ assert isinstance(node, FunctionNode)
+ var_name = args[0]
+ assert isinstance(var_name, str)
+ val = self.get_cur_value(var_name)
+ self.dataflow_dag.add_edge(val, node)
+ return val
+
+ def func_unset_variable(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> None:
+ assert isinstance(node, FunctionNode)
+ if bool(node.args.kwargs):
+ raise InvalidArguments('unset_variable accepts no keyword arguments')
+ if len(node.args.arguments) != 1:
+ raise InvalidArguments('unset_variable requires exactly one positional arguments')
+ var_name = args[0]
+ assert isinstance(var_name, str)
+ self.cur_assignments[var_name].append((self.nesting.copy(), node))
+
+ def nodes_to_pretty_filelist(self, root_path: Path, subdir: str, nodes: T.List[BaseNode]) -> T.List[T.Union[str, UnknownValue]]:
+ def src_to_abs(src: T.Union[str, IntrospectionFile, UnknownValue]) -> T.Union[str, UnknownValue]:
+ if isinstance(src, str):
+ return os.path.normpath(os.path.join(root_path, subdir, src))
+ elif isinstance(src, IntrospectionFile):
+ return str(src.to_abs_path(root_path))
+ elif isinstance(src, UnknownValue):
+ return src
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
+ raise TypeError
- return result
+ rtvals: T.List[T.Any] = flatten([self.node_to_runtime_value(sn) for sn in nodes])
+ return [src_to_abs(x) for x in rtvals]
- 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]:
+ def flatten_args(self, args_raw: T.Union[TYPE_nvar, T.Sequence[TYPE_nvar]], include_unknown_args: bool = False) -> T.List[TYPE_var]:
# Make sure we are always dealing with lists
if isinstance(args_raw, list):
args = args_raw
@@ -395,14 +733,38 @@ class AstInterpreter(InterpreterBase):
# Resolve the contents of args
for i in args:
if isinstance(i, BaseNode):
- resolved = self.resolve_node(i, include_unknown_args, id_loop_detect)
+ resolved = self.node_to_runtime_value(i)
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:
+ elif isinstance(i, (str, bool, int, float, UnknownValue, IntrospectionFile)) or include_unknown_args:
flattened_args += [i]
+ else:
+ raise NotImplementedError
return flattened_args
def evaluate_testcase(self, node: TestCaseClauseNode) -> Disabler | None:
return Disabler(subproject=self.subproject)
+
+ def evaluate_statement(self, cur: mparser.BaseNode) -> T.Optional[InterpreterObject]:
+ if hasattr(cur, 'args'):
+ for arg in cur.args.arguments:
+ self.dataflow_dag.add_edge(arg, cur)
+ for k, v in cur.args.kwargs.items():
+ self.dataflow_dag.add_edge(v, cur)
+ for attr in ['source_object', 'left', 'right', 'items', 'iobject', 'index', 'condition']:
+ if hasattr(cur, attr):
+ assert isinstance(getattr(cur, attr), mparser.BaseNode)
+ self.dataflow_dag.add_edge(getattr(cur, attr), cur)
+ if isinstance(cur, mparser.IdNode):
+ self.dataflow_dag.add_edge(self.get_cur_value(cur.value), cur)
+ return None
+ else:
+ return super().evaluate_statement(cur)
+
+ def function_call(self, node: mparser.FunctionNode) -> T.Any:
+ ret = super().function_call(node)
+ if ret is not None:
+ self.funcvals[node] = ret
+ return ret
diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py
index 4eb3fec..decce4b 100644
--- a/mesonbuild/ast/introspection.py
+++ b/mesonbuild/ast/introspection.py
@@ -6,19 +6,17 @@
# or an interpreter-based tool
from __future__ import annotations
-import copy
import os
import typing as T
from .. import compilers, environment, mesonlib, options
-from .. import coredata as cdata
from ..build import Executable, Jar, SharedLibrary, SharedModule, StaticLibrary
from ..compilers import detect_compiler_for
-from ..interpreterbase import InvalidArguments, SubProject
+from ..interpreterbase import InvalidArguments, SubProject, UnknownValue
from ..mesonlib import MachineChoice
from ..options import OptionKey
-from ..mparser import BaseNode, ArithmeticNode, ArrayNode, ElementaryNode, IdNode, FunctionNode, StringNode
-from .interpreter import AstInterpreter
+from ..mparser import BaseNode, ArrayNode, ElementaryNode, IdNode, FunctionNode, StringNode
+from .interpreter import AstInterpreter, IntrospectionBuildTarget, IntrospectionDependency
if T.TYPE_CHECKING:
from ..build import BuildTarget
@@ -44,8 +42,11 @@ class IntrospectionHelper:
return NotImplemented
class IntrospectionInterpreter(AstInterpreter):
- # Interpreter to detect the options without a build directory
- # Most of the code is stolen from interpreter.Interpreter
+ # If you run `meson setup ...` the `Interpreter`-class walks over the AST.
+ # If you run `meson rewrite ...` and `meson introspect meson.build ...`,
+ # the `AstInterpreter`-class walks over the AST.
+ # Works without a build directory.
+ # Most of the code is stolen from interpreter.Interpreter .
def __init__(self,
source_root: str,
subdir: str,
@@ -61,11 +62,10 @@ class IntrospectionInterpreter(AstInterpreter):
self.cross_file = cross_file
self.backend = backend
- self.default_options = {OptionKey('backend'): self.backend}
self.project_data: T.Dict[str, T.Any] = {}
- self.targets: T.List[T.Dict[str, T.Any]] = []
- self.dependencies: T.List[T.Dict[str, T.Any]] = []
- self.project_node: BaseNode = None
+ self.targets: T.List[IntrospectionBuildTarget] = []
+ self.dependencies: T.List[IntrospectionDependency] = []
+ self.project_node: FunctionNode = None
self.funcs.update({
'add_languages': self.func_add_languages,
@@ -83,6 +83,7 @@ class IntrospectionInterpreter(AstInterpreter):
def func_project(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> None:
if self.project_node:
raise InvalidArguments('Second call to project()')
+ assert isinstance(node, FunctionNode)
self.project_node = node
if len(args) < 1:
raise InvalidArguments('Not enough arguments to project(). Needs at least the project name.')
@@ -99,6 +100,16 @@ class IntrospectionInterpreter(AstInterpreter):
return [node.value]
return None
+ def create_options_dict(options: T.List[str], subproject: str = '') -> T.Mapping[OptionKey, str]:
+ result: T.MutableMapping[OptionKey, str] = {}
+ for o in options:
+ try:
+ (key, value) = o.split('=', 1)
+ except ValueError:
+ raise mesonlib.MesonException(f'Option {o!r} must have a value separated by equals sign.')
+ result[OptionKey(key)] = value
+ return result
+
proj_name = args[0]
proj_vers = kwargs.get('version', 'undefined')
if isinstance(proj_vers, ElementaryNode):
@@ -114,25 +125,6 @@ class IntrospectionInterpreter(AstInterpreter):
self._load_option_file()
- def_opts = self.flatten_args(kwargs.get('default_options', []))
- _project_default_options = mesonlib.stringlistify(def_opts)
- string_dict = cdata.create_options_dict(_project_default_options, self.subproject)
- self.project_default_options = {OptionKey(s): v for s, v in string_dict.items()}
- self.default_options.update(self.project_default_options)
- if self.environment.first_invocation or (self.subproject != '' and self.subproject not in self.coredata.initialized_subprojects):
- if self.subproject == '':
- self.coredata.optstore.initialize_from_top_level_project_call(
- T.cast('T.Dict[T.Union[OptionKey, str], str]', string_dict),
- {}, # TODO: not handled by this Interpreter.
- self.environment.options)
- else:
- self.coredata.optstore.initialize_from_subproject_call(
- self.subproject,
- {}, # TODO: this isn't handled by the introspection interpreter...
- T.cast('T.Dict[T.Union[OptionKey, str], str]', string_dict),
- {}) # TODO: this isn't handled by the introspection interpreter...
- self.coredata.initialized_subprojects.add(self.subproject)
-
if not self.is_subproject() and 'subproject_dir' in kwargs:
spdirname = kwargs['subproject_dir']
if isinstance(spdirname, StringNode):
@@ -164,10 +156,10 @@ class IntrospectionInterpreter(AstInterpreter):
except (mesonlib.MesonException, RuntimeError):
pass
- def func_add_languages(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> None:
+ def func_add_languages(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> UnknownValue:
kwargs = self.flatten_kwargs(kwargs)
required = kwargs.get('required', True)
- assert isinstance(required, (bool, options.UserFeatureOption)), 'for mypy'
+ assert isinstance(required, (bool, options.UserFeatureOption, UnknownValue)), 'for mypy'
if isinstance(required, options.UserFeatureOption):
required = required.is_enabled()
if 'native' in kwargs:
@@ -176,8 +168,9 @@ class IntrospectionInterpreter(AstInterpreter):
else:
for for_machine in [MachineChoice.BUILD, MachineChoice.HOST]:
self._add_languages(args, required, for_machine)
+ return UnknownValue()
- def _add_languages(self, raw_langs: T.List[TYPE_var], required: bool, for_machine: MachineChoice) -> None:
+ def _add_languages(self, raw_langs: T.List[TYPE_var], required: T.Union[bool, UnknownValue], for_machine: MachineChoice) -> None:
langs: T.List[str] = []
for l in self.flatten_args(raw_langs):
if isinstance(l, str):
@@ -192,48 +185,47 @@ class IntrospectionInterpreter(AstInterpreter):
comp = detect_compiler_for(self.environment, lang, for_machine, True, self.subproject)
except mesonlib.MesonException:
# do we even care about introspecting this language?
- if required:
+ if isinstance(required, UnknownValue) or required:
raise
else:
continue
- if self.subproject:
- options = {}
- for k in comp.get_options():
- v = copy.copy(self.coredata.optstore.get_value_object(k))
- k = k.evolve(subproject=self.subproject)
- options[k] = v
- self.coredata.add_compiler_options(options, lang, for_machine, self.environment, self.subproject)
-
- def func_dependency(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> None:
+ if comp:
+ self.coredata.process_compiler_options(lang, comp, self.subproject)
+
+ def func_dependency(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Optional[IntrospectionDependency]:
+ assert isinstance(node, FunctionNode)
args = self.flatten_args(args)
kwargs = self.flatten_kwargs(kwargs)
if not args:
- return
+ return None
name = args[0]
+ assert isinstance(name, (str, UnknownValue))
has_fallback = 'fallback' in kwargs
required = kwargs.get('required', True)
version = kwargs.get('version', [])
- if not isinstance(version, list):
- version = [version]
- if isinstance(required, ElementaryNode):
- required = required.value
- if not isinstance(required, bool):
- required = False
- self.dependencies += [{
- 'name': name,
- 'required': required,
- 'version': version,
- 'has_fallback': has_fallback,
- 'conditional': node.condition_level > 0,
- 'node': node
- }]
-
- def build_target(self, node: BaseNode, args: T.List[TYPE_var], kwargs_raw: T.Dict[str, TYPE_var], targetclass: T.Type[BuildTarget]) -> T.Optional[T.Dict[str, T.Any]]:
+ if not isinstance(version, UnknownValue):
+ if not isinstance(version, list):
+ version = [version]
+ assert all(isinstance(el, str) for el in version)
+ version = T.cast(T.List[str], version)
+ assert isinstance(required, (bool, UnknownValue))
+ newdep = IntrospectionDependency(
+ name=name,
+ required=required,
+ version=version,
+ has_fallback=has_fallback,
+ conditional=node.condition_level > 0,
+ node=node)
+ self.dependencies += [newdep]
+ return newdep
+
+ def build_target(self, node: BaseNode, args: T.List[TYPE_var], kwargs_raw: T.Dict[str, TYPE_var], targetclass: T.Type[BuildTarget]) -> T.Union[IntrospectionBuildTarget, UnknownValue]:
+ assert isinstance(node, FunctionNode)
args = self.flatten_args(args)
if not args or not isinstance(args[0], str):
- return None
+ return UnknownValue()
name = args[0]
- srcqueue = [node]
+ srcqueue: T.List[BaseNode] = [node]
extra_queue = []
# Process the sources BEFORE flattening the kwargs, to preserve the original nodes
@@ -245,43 +237,23 @@ class IntrospectionInterpreter(AstInterpreter):
kwargs = self.flatten_kwargs(kwargs_raw, True)
- def traverse_nodes(inqueue: T.List[BaseNode]) -> T.List[BaseNode]:
- res: T.List[BaseNode] = []
- while inqueue:
- curr = inqueue.pop(0)
- arg_node = None
- assert isinstance(curr, BaseNode)
- if isinstance(curr, FunctionNode):
- arg_node = curr.args
- elif isinstance(curr, ArrayNode):
- arg_node = curr.args
- elif isinstance(curr, IdNode):
- # Try to resolve the ID and append the node to the queue
- assert isinstance(curr.value, str)
- var_name = curr.value
- if var_name in self.assignments:
- tmp_node = self.assignments[var_name]
- if isinstance(tmp_node, (ArrayNode, IdNode, FunctionNode)):
- inqueue += [tmp_node]
- elif isinstance(curr, ArithmeticNode):
- inqueue += [curr.left, curr.right]
- if arg_node is None:
- continue
- arg_nodes = arg_node.arguments.copy()
- # Pop the first element if the function is a build target function
- if isinstance(curr, FunctionNode) and curr.func_name.value in BUILD_TARGET_FUNCTIONS:
- arg_nodes.pop(0)
- elementary_nodes = [x for x in arg_nodes if isinstance(x, (str, StringNode))]
- inqueue += [x for x in arg_nodes if isinstance(x, (FunctionNode, ArrayNode, IdNode, ArithmeticNode))]
- if elementary_nodes:
- res += [curr]
- return res
-
- source_nodes = traverse_nodes(srcqueue)
- extraf_nodes = traverse_nodes(extra_queue)
+ oldlen = len(node.args.arguments)
+ source_nodes = node.args.arguments[1:]
+ for k, v in node.args.kwargs.items():
+ assert isinstance(k, IdNode)
+ if k.value == 'sources':
+ source_nodes.append(v)
+ assert oldlen == len(node.args.arguments)
+
+ extraf_nodes = None
+ for k, v in node.args.kwargs.items():
+ assert isinstance(k, IdNode)
+ if k.value == 'extra_files':
+ assert extraf_nodes is None
+ extraf_nodes = v
# Make sure nothing can crash when creating the build class
- kwargs_reduced = {k: v for k, v in kwargs.items() if k in targetclass.known_kwargs and k in {'install', 'build_by_default', 'build_always'}}
+ kwargs_reduced = {k: v for k, v in kwargs.items() if k in targetclass.known_kwargs and k in {'install', 'build_by_default', 'build_always', 'name_prefix'}}
kwargs_reduced = {k: v.value if isinstance(v, ElementaryNode) else v for k, v in kwargs_reduced.items()}
kwargs_reduced = {k: v for k, v in kwargs_reduced.items() if not isinstance(v, BaseNode)}
for_machine = MachineChoice.BUILD if kwargs.get('native', False) else MachineChoice.HOST
@@ -293,27 +265,26 @@ class IntrospectionInterpreter(AstInterpreter):
self.environment, self.coredata.compilers[for_machine], kwargs_reduced)
target.process_compilers_late()
- new_target = {
- 'name': target.get_basename(),
- 'machine': target.for_machine.get_lower_case_name(),
- 'id': target.get_id(),
- 'type': target.get_typename(),
- 'defined_in': os.path.normpath(os.path.join(self.source_root, self.subdir, environment.build_filename)),
- 'subdir': self.subdir,
- 'build_by_default': target.build_by_default,
- 'installed': target.should_install(),
- 'outputs': target.get_outputs(),
- 'sources': source_nodes,
- 'extra_files': extraf_nodes,
- 'kwargs': kwargs,
- 'node': node,
- }
+ new_target = IntrospectionBuildTarget(
+ name=target.get_basename(),
+ machine=target.for_machine.get_lower_case_name(),
+ id=target.get_id(),
+ typename=target.get_typename(),
+ defined_in=os.path.normpath(os.path.join(self.source_root, self.subdir, environment.build_filename)),
+ subdir=self.subdir,
+ build_by_default=target.build_by_default,
+ installed=target.should_install(),
+ outputs=target.get_outputs(),
+ source_nodes=source_nodes,
+ extra_files=extraf_nodes,
+ kwargs=kwargs,
+ node=node)
self.targets += [new_target]
return new_target
- def build_library(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Optional[T.Dict[str, T.Any]]:
- default_library = self.coredata.optstore.get_value_for(OptionKey('default_library'))
+ def build_library(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Union[IntrospectionBuildTarget, UnknownValue]:
+ default_library = self.coredata.optstore.get_value_for(OptionKey('default_library', subproject=self.subproject))
if default_library == 'shared':
return self.build_target(node, args, kwargs, SharedLibrary)
elif default_library == 'static':
@@ -322,28 +293,28 @@ class IntrospectionInterpreter(AstInterpreter):
return self.build_target(node, args, kwargs, SharedLibrary)
return None
- def func_executable(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Optional[T.Dict[str, T.Any]]:
+ def func_executable(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Union[IntrospectionBuildTarget, UnknownValue]:
return self.build_target(node, args, kwargs, Executable)
- def func_static_lib(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Optional[T.Dict[str, T.Any]]:
+ def func_static_lib(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Union[IntrospectionBuildTarget, UnknownValue]:
return self.build_target(node, args, kwargs, StaticLibrary)
- def func_shared_lib(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Optional[T.Dict[str, T.Any]]:
+ def func_shared_lib(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Union[IntrospectionBuildTarget, UnknownValue]:
return self.build_target(node, args, kwargs, SharedLibrary)
- def func_both_lib(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Optional[T.Dict[str, T.Any]]:
+ def func_both_lib(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Union[IntrospectionBuildTarget, UnknownValue]:
return self.build_target(node, args, kwargs, SharedLibrary)
- def func_shared_module(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Optional[T.Dict[str, T.Any]]:
+ def func_shared_module(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Union[IntrospectionBuildTarget, UnknownValue]:
return self.build_target(node, args, kwargs, SharedModule)
- def func_library(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Optional[T.Dict[str, T.Any]]:
+ def func_library(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Union[IntrospectionBuildTarget, UnknownValue]:
return self.build_library(node, args, kwargs)
- def func_jar(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Optional[T.Dict[str, T.Any]]:
+ def func_jar(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Union[IntrospectionBuildTarget, UnknownValue]:
return self.build_target(node, args, kwargs, Jar)
- def func_build_target(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Optional[T.Dict[str, T.Any]]:
+ def func_build_target(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Union[IntrospectionBuildTarget, UnknownValue]:
if 'target_type' not in kwargs:
return None
target_type = kwargs.pop('target_type')
@@ -395,7 +366,7 @@ class IntrospectionInterpreter(AstInterpreter):
flattened_kwargs = {}
for key, val in kwargs.items():
if isinstance(val, BaseNode):
- resolved = self.resolve_node(val, include_unknown_args)
+ resolved = self.node_to_runtime_value(val)
if resolved is not None:
flattened_kwargs[key] = resolved
elif isinstance(val, (str, bool, int, float)) or include_unknown_args:
diff --git a/mesonbuild/ast/printer.py b/mesonbuild/ast/printer.py
index 4ce3b3f..0d0c821 100644
--- a/mesonbuild/ast/printer.py
+++ b/mesonbuild/ast/printer.py
@@ -7,12 +7,46 @@ from __future__ import annotations
from .. import mparser
from .visitor import AstVisitor, FullAstVisitor
+from ..mesonlib import MesonBugException
import re
import typing as T
+# Also known as "order of operations" or "binding power".
+# This is the counterpart to Parser.e1, Parser.e2, Parser.e3, Parser.e4, Parser.e5, Parser.e6, Parser.e7, Parser.e8, Parser.e9, Parser.e10
+def precedence_level(node: mparser.BaseNode) -> int:
+ if isinstance(node, (mparser.PlusAssignmentNode, mparser.AssignmentNode, mparser.TernaryNode)):
+ return 1
+ elif isinstance(node, mparser.OrNode):
+ return 2
+ elif isinstance(node, mparser.AndNode):
+ return 3
+ elif isinstance(node, mparser.ComparisonNode):
+ return 4
+ elif isinstance(node, mparser.ArithmeticNode):
+ if node.operation in {'add', 'sub'}:
+ return 5
+ elif node.operation in {'mod', 'mul', 'div'}:
+ return 6
+ elif isinstance(node, (mparser.NotNode, mparser.UMinusNode)):
+ return 7
+ elif isinstance(node, mparser.FunctionNode):
+ return 8
+ elif isinstance(node, (mparser.ArrayNode, mparser.DictNode)):
+ return 9
+ elif isinstance(node, (mparser.BooleanNode, mparser.IdNode, mparser.NumberNode, mparser.StringNode, mparser.EmptyNode)):
+ return 10
+ elif isinstance(node, mparser.ParenthesizedNode):
+ # Parenthesize have the highest binding power, but since the AstPrinter
+ # ignores ParanthesizedNode, the binding power of the inner node is
+ # relevant.
+ return precedence_level(node.inner)
+ raise MesonBugException('Unhandled node type')
+
class AstPrinter(AstVisitor):
+ escape_trans: T.Dict[int, str] = str.maketrans({'\\': '\\\\', "'": "\'"})
+
def __init__(self, indent: int = 2, arg_newline_cutoff: int = 5, update_ast_line_nos: bool = False):
self.result = ''
self.indent = indent
@@ -57,7 +91,7 @@ class AstPrinter(AstVisitor):
node.lineno = self.curr_line or node.lineno
def escape(self, val: str) -> str:
- return val.replace('\\', '\\\\').replace("'", "\'")
+ return val.translate(self.escape_trans)
def visit_StringNode(self, node: mparser.StringNode) -> None:
assert isinstance(node.value, str)
@@ -108,11 +142,21 @@ class AstPrinter(AstVisitor):
node.lineno = self.curr_line or node.lineno
node.right.accept(self)
+ def maybe_parentheses(self, outer: mparser.BaseNode, inner: mparser.BaseNode, parens: bool) -> None:
+ if parens:
+ self.append('(', inner)
+ inner.accept(self)
+ if parens:
+ self.append(')', inner)
+
def visit_ArithmeticNode(self, node: mparser.ArithmeticNode) -> None:
- node.left.accept(self)
+ prec = precedence_level(node)
+ prec_left = precedence_level(node.left)
+ prec_right = precedence_level(node.right)
+ self.maybe_parentheses(node, node.left, prec > prec_left)
self.append_padded(node.operator.value, node)
node.lineno = self.curr_line or node.lineno
- node.right.accept(self)
+ self.maybe_parentheses(node, node.right, prec > prec_right or (prec == prec_right and node.operation in {'sub', 'div', 'mod'}))
def visit_NotNode(self, node: mparser.NotNode) -> None:
node.lineno = self.curr_line or node.lineno