-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathidentification.py
More file actions
133 lines (104 loc) · 4.54 KB
/
Copy pathidentification.py
File metadata and controls
133 lines (104 loc) · 4.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# Copyright 2024 Christian Rauch
# Licensed under the Apache License, Version 2.0
import os
import typing
# colcon
from colcon_core.logging import colcon_logger
from colcon_core.package_descriptor import PackageDescriptor
from colcon_core.package_identification import PackageIdentificationExtensionPoint
# meson
from mesonbuild import mesonlib
from mesonbuild.ast import IntrospectionInterpreter
from mesonbuild.interpreter import primitives
from mesonbuild.interpreterbase.baseobjects import InterpreterObject, mparser
logger = colcon_logger.getChild(__name__)
class CustomInterpreter(IntrospectionInterpreter):
"""A custom interpreter to parse metadata for Meson projects."""
def __init__(self, source_root: str, subdir: str, backend: str,):
"""Initialise the interpreter and a data structure for metadata."""
super().__init__(source_root, subdir, backend)
self.holder_map.update({
list: primitives.ArrayHolder,
dict: primitives.DictHolder,
int: primitives.IntegerHolder,
bool: primitives.BooleanHolder,
str: primitives.StringHolder,
})
self.data = {}
self.data["dependencies"] = set()
def evaluate_statement(self, cur: mparser.BaseNode) -> typing.Optional[InterpreterObject]:
"""Evaluate the statements in the Meson project file.
Args:
cur (mparser.BaseNode): a node in the project file
Returns:
typing.Optional[InterpreterObject]:
"""
if isinstance(cur, mparser.FunctionNode):
return self._function_call(cur)
elif isinstance(cur, mparser.AssignmentNode):
self._assignment(cur)
elif isinstance(cur, mparser.StringNode):
return self._holderify(cur.value)
elif isinstance(cur, mparser.ArrayNode):
return self._evaluate_arraystatement(cur)
return None
def _function_call(self, node: mparser.FunctionNode) -> typing.Optional[InterpreterObject]:
node_func_name = f"{type(node.func_name).__module__}.{type(node.func_name).__qualname__}"
if node_func_name == "builtins.str":
# meson <= 1.2
func_name = node.func_name
elif node_func_name == "mesonbuild.mparser.IdNode":
# meson >= 1.3
func_name = node.func_name.value
else:
raise AttributeError("Cannot determine meson project name.")
assert type(func_name) is str
reduced_pos = [self.evaluate_statement(arg) for arg in node.args.arguments]
reduced_pos = list(filter(None, reduced_pos))
args = self._unholder_args(reduced_pos, {})[0]
if func_name == "project":
self.data["name"] = args[0]
elif func_name == "dependency":
self.data["dependencies"].update(args)
elif func_name == "subdir":
subpath = os.path.join(self.source_root, args[0])
parser = CustomInterpreter(subpath, "", "")
subdata = parser.parse()
for k in subdata.keys():
self.data[k].update(subdata[k])
return None
def _assignment(self, node: mparser.AssignmentNode) -> None:
self.evaluate_statement(node.value)
return None
def _evaluate_arraystatement(self, cur: mparser.ArrayNode) -> InterpreterObject:
arguments = [self.evaluate_statement(arg) for arg in cur.args.arguments]
arguments = list(filter(None, arguments))
return self._holderify(self._unholder_args(arguments, {})[0])
def parse(self) -> dict:
"""Run the interpreter on a Meson project file.
Returns:
dict: extracted metadata
"""
try:
self.load_root_meson_file()
except mesonlib.MesonException:
return {}
self.evaluate_codeblock(self.ast)
return self.data
class MesonPackageIdentification(PackageIdentificationExtensionPoint):
"""Meson package identification."""
def identify(self, desc: PackageDescriptor):
"""Identify a Meson project for colcon.
Args:
desc (PackageDescriptor): package description that will be updated
"""
parser = CustomInterpreter(desc.path, "", "")
data = parser.parse()
if not data:
return
desc.type = 'meson'
if desc.name is None:
desc.name = data["name"]
logger.info("'%s' dependencies: %s", desc.name, data['dependencies'])
desc.dependencies['build'].update(data['dependencies'])
desc.dependencies['run'].update(data['dependencies'])