Files
meson/mesonbuild/ast/interpreter.py
T
Dylan Baker e991c4d454 Use SPDX-License-Identifier consistently
This replaces all of the Apache blurbs at the start of each file with an
`# SPDX-License-Identifier: Apache-2.0` string. It also fixes existing
uses to be consistent in capitalization, and to be placed above any
copyright notices.

This removes nearly 3000 lines of boilerplate from the project (only
python files), which no developer cares to look at.

SPDX is in common use, particularly in the Linux kernel, and is the
recommended format for Meson's own `project(license: )` field
2023-12-13 15:19:21 -05:00

441 lines
18 KiB
Python

# 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 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: 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_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.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_nvar], TYPE_nkwargs]:
if isinstance(args, ArgumentNode):
kwargs: 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.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_nvar] = {}
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_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]
flattened_args: 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]
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_nvar], include_unknown_args: bool = False) -> T.Dict[str, TYPE_nvar]:
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)