aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/ast/introspection.py
diff options
context:
space:
mode:
Diffstat (limited to 'mesonbuild/ast/introspection.py')
-rw-r--r--mesonbuild/ast/introspection.py215
1 files changed, 93 insertions, 122 deletions
diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py
index 4eb3fec..147436d 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,26 +265,25 @@ 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]]:
+ 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'))
if default_library == 'shared':
return self.build_target(node, args, kwargs, SharedLibrary)
@@ -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: