summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVolker Weißmann <volker.weissmann@gmx.de>2025-03-23 15:48:19 +0100
committerDylan Baker <dylan@pnwbakers.com>2025-05-29 09:20:27 -0700
commitaef7c97bc142c96dd1acb7944d8a19c40d8890f4 (patch)
tree01e16eb2a85b10e1624ceed74af860f81c90033c
parent1a191d9b9569e080c55b09ab2939274d576c70f1 (diff)
downloadmeson-aef7c97bc142c96dd1acb7944d8a19c40d8890f4.tar.gz
Add AstInterpreter.funcvals
`AstInterpreter.node_to_runtime_value` can now resolve function calls.
-rw-r--r--mesonbuild/ast/interpreter.py119
-rw-r--r--mesonbuild/ast/introspection.py8
2 files changed, 112 insertions, 15 deletions
diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py
index 0d53873ca..2886ad860 100644
--- a/mesonbuild/ast/interpreter.py
+++ b/mesonbuild/ast/interpreter.py
@@ -10,6 +10,7 @@ import sys
import typing as T
from collections import defaultdict
from dataclasses import dataclass
+import itertools
from .. import mparser, mesonlib
from .. import environment
@@ -22,6 +23,7 @@ from ..interpreterbase import (
ContinueRequest,
Disabler,
default_resolve_key,
+ is_disabled,
UnknownValue,
InterpreterObject,
)
@@ -72,6 +74,18 @@ _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):
@@ -168,6 +182,8 @@ class AstInterpreter(InterpreterBase):
# 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.assign_vals: T.Dict[str, T.Any] = {}
self.funcs.update({'project': self.func_do_nothing,
'test': self.func_do_nothing,
@@ -201,7 +217,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,
@@ -210,9 +226,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,
@@ -236,8 +252,8 @@ class AstInterpreter(InterpreterBase):
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 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()
@@ -259,8 +275,36 @@ 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 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
@@ -427,6 +471,17 @@ class AstInterpreter(InterpreterBase):
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:
+ raise TypeError
+ return ret
+
def get_cur_value(self, var_name: str, allow_none: bool = False) -> T.Union[BaseNode, UnknownValue, None]:
if var_name in {'meson', 'host_machine', 'build_machine', 'target_machine'}:
return UnknownValue()
@@ -437,6 +492,8 @@ class AstInterpreter(InterpreterBase):
break
if ret is None and allow_none:
return ret
+ if ret is None and self.tainted:
+ return UnknownValue()
assert ret is not None
return ret
@@ -491,7 +548,11 @@ class AstInterpreter(InterpreterBase):
val = next(iter(self.dataflow_dag.tgt_to_srcs[node]))
return self.node_to_runtime_value(val)
elif isinstance(node, (MethodNode, FunctionNode)):
- return UnknownValue()
+ 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)
@@ -524,7 +585,7 @@ class AstInterpreter(InterpreterBase):
elif node.operation == 'mod':
if isinstance(left, int) and isinstance(right, int):
return left % right
- elif isinstance(node, (UnknownValue, IntrospectionBuildTarget, IntrospectionDependency, str, bool, int)):
+ 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)
@@ -604,12 +665,40 @@ class AstInterpreter(InterpreterBase):
self.assign_vals[node.var_name.value] = self.evaluate_statement(node.value)
- def func_get_variable(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> None:
+ 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 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
@@ -628,7 +717,7 @@ class AstInterpreter(InterpreterBase):
if not isinstance(resolved, list):
resolved = [resolved]
flattened_args += resolved
- elif isinstance(i, (str, bool, int, float, UnknownValue)) or include_unknown_args:
+ elif isinstance(i, (str, bool, int, float, UnknownValue, IntrospectionFile)) or include_unknown_args:
flattened_args += [i]
else:
raise NotImplementedError
@@ -652,3 +741,9 @@ class AstInterpreter(InterpreterBase):
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 7dc4281cf..26caa727e 100644
--- a/mesonbuild/ast/introspection.py
+++ b/mesonbuild/ast/introspection.py
@@ -156,7 +156,7 @@ 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, UnknownValue)), 'for mypy'
@@ -168,6 +168,7 @@ 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: T.Union[bool, UnknownValue], for_machine: MachineChoice) -> None:
langs: T.List[str] = []
@@ -191,12 +192,12 @@ class IntrospectionInterpreter(AstInterpreter):
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]) -> None:
+ 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
@@ -216,6 +217,7 @@ class IntrospectionInterpreter(AstInterpreter):
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)