mirror of
https://github.com/mesonbuild/meson.git
synced 2026-11-06 05:16:42 +00:00
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
441 lines
18 KiB
Python
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)
|