]> Git Repo - qemu.git/blame - scripts/qapi/introspect.py
works with less than base ISA qemu-system-riscv32 -M virt -bios none -kernel output...
[qemu.git] / scripts / qapi / introspect.py
CommitLineData
5ddeec83
MA
1"""
2QAPI introspection generator
3
cf26906c 4Copyright (C) 2015-2021 Red Hat, Inc.
5ddeec83
MA
5
6Authors:
7 Markus Armbruster <[email protected]>
cf26906c 8 John Snow <[email protected]>
5ddeec83
MA
9
10This work is licensed under the terms of the GNU GPL, version 2.
11See the COPYING file in the top-level directory.
12"""
39a18158 13
9db27346
JS
14from typing import (
15 Any,
16 Dict,
4f7f97a7 17 Generic,
9db27346
JS
18 List,
19 Optional,
82b52f6b 20 Sequence,
4f7f97a7 21 TypeVar,
9db27346
JS
22 Union,
23)
5f50cede 24
1889e57a 25from .common import c_name, mcgen
7137a960 26from .gen import QAPISchemaMonolithicCVisitor
67fea575 27from .schema import (
82b52f6b 28 QAPISchema,
67fea575
JS
29 QAPISchemaArrayType,
30 QAPISchemaBuiltinType,
82b52f6b
JS
31 QAPISchemaEntity,
32 QAPISchemaEnumMember,
33 QAPISchemaFeature,
f17539c8 34 QAPISchemaIfCond,
82b52f6b
JS
35 QAPISchemaObjectType,
36 QAPISchemaObjectTypeMember,
67fea575 37 QAPISchemaType,
82b52f6b
JS
38 QAPISchemaVariant,
39 QAPISchemaVariants,
67fea575 40)
82b52f6b 41from .source import QAPISourceInfo
39a18158
MA
42
43
9db27346
JS
44# This module constructs a tree data structure that is used to
45# generate the introspection information for QEMU. It is shaped
46# like a JSON value.
47#
48# A complexity over JSON is that our values may or may not be annotated.
49#
50# Un-annotated values may be:
51# Scalar: str, bool, None.
52# Non-scalar: List, Dict
53# _value = Union[str, bool, None, Dict[str, JSONValue], List[JSONValue]]
54#
55# With optional annotations, the type of all values is:
56# JSONValue = Union[_Value, Annotated[_Value]]
57#
58# Sadly, mypy does not support recursive types; so the _Stub alias is used to
59# mark the imprecision in the type model where we'd otherwise use JSONValue.
60_Stub = Any
61_Scalar = Union[str, bool, None]
62_NonScalar = Union[Dict[str, _Stub], List[_Stub]]
63_Value = Union[_Scalar, _NonScalar]
4f7f97a7 64JSONValue = Union[_Value, 'Annotated[_Value]']
9db27346 65
82b52f6b
JS
66# These types are based on structures defined in QEMU's schema, so we
67# lack precise types for them here. Python 3.6 does not offer
68# TypedDict constructs, so they are broadly typed here as simple
69# Python Dicts.
70SchemaInfo = Dict[str, object]
75ecee72 71SchemaInfoEnumMember = Dict[str, object]
82b52f6b
JS
72SchemaInfoObject = Dict[str, object]
73SchemaInfoObjectVariant = Dict[str, object]
74SchemaInfoObjectMember = Dict[str, object]
75SchemaInfoCommand = Dict[str, object]
76
9db27346 77
4f7f97a7
JS
78_ValueT = TypeVar('_ValueT', bound=_Value)
79
80
81class Annotated(Generic[_ValueT]):
82 """
83 Annotated generally contains a SchemaInfo-like type (as a dict),
84 But it also used to wrap comments/ifconds around scalar leaf values,
85 for the benefit of features and enums.
86 """
87 # TODO: Remove after Python 3.7 adds @dataclass:
88 # pylint: disable=too-few-public-methods
f17539c8 89 def __init__(self, value: _ValueT, ifcond: QAPISchemaIfCond,
4f7f97a7
JS
90 comment: Optional[str] = None):
91 self.value = value
92 self.comment: Optional[str] = comment
f17539c8 93 self.ifcond = ifcond
24cfd6ad
MA
94
95
82b52f6b
JS
96def _tree_to_qlit(obj: JSONValue,
97 level: int = 0,
98 dict_value: bool = False) -> str:
5444dedf
JS
99 """
100 Convert the type tree into a QLIT C string, recursively.
101
102 :param obj: The value to convert.
103 This value may not be Annotated when dict_value is True.
104 :param level: The indentation level for this particular value.
105 :param dict_value: True when the value being processed belongs to a
106 dict key; which suppresses the output indent.
107 """
7d0f982b 108
82b52f6b 109 def indent(level: int) -> str:
7d0f982b
MAL
110 return level * 4 * ' '
111
4f7f97a7 112 if isinstance(obj, Annotated):
05556960
JS
113 # NB: _tree_to_qlit is called recursively on the values of a
114 # key:value pair; those values can't be decorated with
115 # comments or conditionals.
116 msg = "dict values cannot have attached comments or if-conditionals."
117 assert not dict_value, msg
118
8c643361 119 ret = ''
4f7f97a7 120 if obj.comment:
c0e8d9f3 121 ret += indent(level) + f"/* {obj.comment} */\n"
33aa3267 122 if obj.ifcond.is_present():
1889e57a 123 ret += obj.ifcond.gen_if()
4f7f97a7 124 ret += _tree_to_qlit(obj.value, level)
33aa3267 125 if obj.ifcond.is_present():
1889e57a 126 ret += '\n' + obj.ifcond.gen_endif()
d626b6c1
MAL
127 return ret
128
7d0f982b 129 ret = ''
05556960 130 if not dict_value:
7d0f982b 131 ret += indent(level)
c0e8d9f3
JS
132
133 # Scalars:
39a18158 134 if obj is None:
7d0f982b 135 ret += 'QLIT_QNULL'
39a18158 136 elif isinstance(obj, str):
c0e8d9f3
JS
137 ret += f"QLIT_QSTR({to_c_string(obj)})"
138 elif isinstance(obj, bool):
139 ret += f"QLIT_QBOOL({str(obj).lower()})"
140
141 # Non-scalars:
39a18158 142 elif isinstance(obj, list):
7d0f982b 143 ret += 'QLIT_QLIST(((QLitObject[]) {\n'
c0e8d9f3
JS
144 for value in obj:
145 ret += _tree_to_qlit(value, level + 1).strip('\n') + '\n'
146 ret += indent(level + 1) + '{}\n'
7d0f982b 147 ret += indent(level) + '}))'
39a18158 148 elif isinstance(obj, dict):
7d0f982b 149 ret += 'QLIT_QDICT(((QLitDictEntry[]) {\n'
c0e8d9f3
JS
150 for key, value in sorted(obj.items()):
151 ret += indent(level + 1) + "{{ {:s}, {:s} }},\n".format(
152 to_c_string(key),
153 _tree_to_qlit(value, level + 1, dict_value=True)
154 )
155 ret += indent(level + 1) + '{}\n'
7d0f982b 156 ret += indent(level) + '}))'
39a18158 157 else:
2a6c161b
JS
158 raise NotImplementedError(
159 f"type '{type(obj).__name__}' not implemented"
160 )
c0e8d9f3 161
40bb1376
MAL
162 if level > 0:
163 ret += ','
39a18158
MA
164 return ret
165
166
82b52f6b 167def to_c_string(string: str) -> str:
39a18158
MA
168 return '"' + string.replace('\\', r'\\').replace('"', r'\"') + '"'
169
170
71b3f045
MA
171class QAPISchemaGenIntrospectVisitor(QAPISchemaMonolithicCVisitor):
172
82b52f6b 173 def __init__(self, prefix: str, unmask: bool):
2cae67bc
MA
174 super().__init__(
175 prefix, 'qapi-introspect',
71b3f045 176 ' * QAPI/QMP schema introspection', __doc__)
1a9a507b 177 self._unmask = unmask
82b52f6b
JS
178 self._schema: Optional[QAPISchema] = None
179 self._trees: List[Annotated[SchemaInfo]] = []
180 self._used_types: List[QAPISchemaType] = []
181 self._name_map: Dict[str, str] = {}
71b3f045
MA
182 self._genc.add(mcgen('''
183#include "qemu/osdep.h"
eb815e24 184#include "%(prefix)sqapi-introspect.h"
71b3f045
MA
185
186''',
187 prefix=prefix))
188
82b52f6b 189 def visit_begin(self, schema: QAPISchema) -> None:
71b3f045 190 self._schema = schema
39a18158 191
82b52f6b 192 def visit_end(self) -> None:
39a18158
MA
193 # visit the types that are actually used
194 for typ in self._used_types:
195 typ.visit(self)
39a18158 196 # generate C
7d0f982b 197 name = c_name(self._prefix, protect=False) + 'qmp_schema_qlit'
71b3f045 198 self._genh.add(mcgen('''
7d0f982b
MAL
199#include "qapi/qmp/qlit.h"
200
201extern const QLitObject %(c_name)s;
39a18158 202''',
71b3f045 203 c_name=c_name(name)))
71b3f045 204 self._genc.add(mcgen('''
7d0f982b 205const QLitObject %(c_name)s = %(c_string)s;
39a18158 206''',
71b3f045 207 c_name=c_name(name),
2e8a843d 208 c_string=_tree_to_qlit(self._trees)))
39a18158 209 self._schema = None
2e8a843d 210 self._trees = []
71b3f045
MA
211 self._used_types = []
212 self._name_map = {}
1a9a507b 213
82b52f6b 214 def visit_needed(self, entity: QAPISchemaEntity) -> bool:
25a0d9c9
EB
215 # Ignore types on first pass; visit_end() will pick up used types
216 return not isinstance(entity, QAPISchemaType)
217
82b52f6b 218 def _name(self, name: str) -> str:
1a9a507b
MA
219 if self._unmask:
220 return name
221 if name not in self._name_map:
222 self._name_map[name] = '%d' % len(self._name_map)
223 return self._name_map[name]
39a18158 224
82b52f6b 225 def _use_type(self, typ: QAPISchemaType) -> str:
6b67bcac
JS
226 assert self._schema is not None
227
39a18158
MA
228 # Map the various integer types to plain int
229 if typ.json_type() == 'int':
230 typ = self._schema.lookup_type('int')
231 elif (isinstance(typ, QAPISchemaArrayType) and
232 typ.element_type.json_type() == 'int'):
233 typ = self._schema.lookup_type('intList')
234 # Add type to work queue if new
235 if typ not in self._used_types:
236 self._used_types.append(typ)
1a9a507b 237 # Clients should examine commands and events, not types. Hide
1aa806cc
EB
238 # type names as integers to reduce the temptation. Also, it
239 # saves a few characters on the wire.
1a9a507b
MA
240 if isinstance(typ, QAPISchemaBuiltinType):
241 return typ.name
ce5fcb47
EB
242 if isinstance(typ, QAPISchemaArrayType):
243 return '[' + self._use_type(typ.element_type) + ']'
1a9a507b 244 return self._name(typ.name)
39a18158 245
84bece7d 246 @staticmethod
cea53c31 247 def _gen_features(features: Sequence[QAPISchemaFeature]
82b52f6b 248 ) -> List[Annotated[str]]:
4f7f97a7 249 return [Annotated(f.name, f.ifcond) for f in features]
84bece7d 250
82b52f6b 251 def _gen_tree(self, name: str, mtype: str, obj: Dict[str, object],
f17539c8 252 ifcond: QAPISchemaIfCond = QAPISchemaIfCond(),
cea53c31 253 features: Sequence[QAPISchemaFeature] = ()) -> None:
5444dedf
JS
254 """
255 Build and append a SchemaInfo object to self._trees.
256
257 :param name: The SchemaInfo's name.
258 :param mtype: The SchemaInfo's meta-type.
259 :param obj: Additional SchemaInfo members, as appropriate for
260 the meta-type.
261 :param ifcond: Conditionals to apply to the SchemaInfo.
262 :param features: The SchemaInfo's features.
263 Will be omitted from the output if empty.
264 """
5f50cede 265 comment: Optional[str] = None
ce5fcb47 266 if mtype not in ('command', 'event', 'builtin', 'array'):
8c643361
EB
267 if not self._unmask:
268 # Output a comment to make it easy to map masked names
269 # back to the source when reading the generated output.
5f50cede 270 comment = f'"{self._name(name)}" = {name}'
1a9a507b 271 name = self._name(name)
39a18158
MA
272 obj['name'] = name
273 obj['meta-type'] = mtype
84bece7d
JS
274 if features:
275 obj['features'] = self._gen_features(features)
4f7f97a7 276 self._trees.append(Annotated(obj, ifcond, comment))
39a18158 277
b6c18755 278 def _gen_enum_member(self, member: QAPISchemaEnumMember
75ecee72
MA
279 ) -> Annotated[SchemaInfoEnumMember]:
280 obj: SchemaInfoEnumMember = {
281 'name': member.name,
282 }
b6c18755
MA
283 if member.features:
284 obj['features'] = self._gen_features(member.features)
75ecee72
MA
285 return Annotated(obj, member.ifcond)
286
287 def _gen_object_member(self, member: QAPISchemaObjectTypeMember
288 ) -> Annotated[SchemaInfoObjectMember]:
82b52f6b
JS
289 obj: SchemaInfoObjectMember = {
290 'name': member.name,
291 'type': self._use_type(member.type)
292 }
39a18158 293 if member.optional:
24cfd6ad 294 obj['default'] = None
84bece7d
JS
295 if member.features:
296 obj['features'] = self._gen_features(member.features)
4f7f97a7 297 return Annotated(obj, member.ifcond)
39a18158 298
82b52f6b
JS
299 def _gen_variant(self, variant: QAPISchemaVariant
300 ) -> Annotated[SchemaInfoObjectVariant]:
301 obj: SchemaInfoObjectVariant = {
302 'case': variant.name,
303 'type': self._use_type(variant.type)
304 }
4f7f97a7 305 return Annotated(obj, variant.ifcond)
39a18158 306
82b52f6b
JS
307 def visit_builtin_type(self, name: str, info: Optional[QAPISourceInfo],
308 json_type: str) -> None:
9b77d946 309 self._gen_tree(name, 'builtin', {'json-type': json_type})
39a18158 310
82b52f6b 311 def visit_enum_type(self, name: str, info: Optional[QAPISourceInfo],
f17539c8 312 ifcond: QAPISchemaIfCond,
82b52f6b
JS
313 features: List[QAPISchemaFeature],
314 members: List[QAPISchemaEnumMember],
315 prefix: Optional[str]) -> None:
4f7f97a7
JS
316 self._gen_tree(
317 name, 'enum',
75ecee72
MA
318 {'members': [self._gen_enum_member(m) for m in members],
319 'values': [Annotated(m.name, m.ifcond) for m in members]},
4f7f97a7
JS
320 ifcond, features
321 )
39a18158 322
82b52f6b 323 def visit_array_type(self, name: str, info: Optional[QAPISourceInfo],
f17539c8 324 ifcond: QAPISchemaIfCond,
82b52f6b 325 element_type: QAPISchemaType) -> None:
ce5fcb47 326 element = self._use_type(element_type)
2e8a843d 327 self._gen_tree('[' + element + ']', 'array', {'element-type': element},
cea53c31 328 ifcond)
39a18158 329
82b52f6b 330 def visit_object_type_flat(self, name: str, info: Optional[QAPISourceInfo],
f17539c8 331 ifcond: QAPISchemaIfCond,
82b52f6b
JS
332 features: List[QAPISchemaFeature],
333 members: List[QAPISchemaObjectTypeMember],
334 variants: Optional[QAPISchemaVariants]) -> None:
335 obj: SchemaInfoObject = {
75ecee72 336 'members': [self._gen_object_member(m) for m in members]
82b52f6b 337 }
39a18158 338 if variants:
cf5db214
JS
339 obj['tag'] = variants.tag_member.name
340 obj['variants'] = [self._gen_variant(v) for v in variants.variants]
2e8a843d 341 self._gen_tree(name, 'object', obj, ifcond, features)
39a18158 342
82b52f6b 343 def visit_alternate_type(self, name: str, info: Optional[QAPISourceInfo],
f17539c8 344 ifcond: QAPISchemaIfCond,
82b52f6b
JS
345 features: List[QAPISchemaFeature],
346 variants: QAPISchemaVariants) -> None:
4f7f97a7
JS
347 self._gen_tree(
348 name, 'alternate',
349 {'members': [Annotated({'type': self._use_type(m.type)},
350 m.ifcond)
351 for m in variants.variants]},
352 ifcond, features
353 )
39a18158 354
82b52f6b 355 def visit_command(self, name: str, info: Optional[QAPISourceInfo],
f17539c8 356 ifcond: QAPISchemaIfCond,
82b52f6b
JS
357 features: List[QAPISchemaFeature],
358 arg_type: Optional[QAPISchemaObjectType],
359 ret_type: Optional[QAPISchemaType], gen: bool,
360 success_response: bool, boxed: bool, allow_oob: bool,
361 allow_preconfig: bool, coroutine: bool) -> None:
6b67bcac
JS
362 assert self._schema is not None
363
39a18158
MA
364 arg_type = arg_type or self._schema.the_empty_object_type
365 ret_type = ret_type or self._schema.the_empty_object_type
82b52f6b
JS
366 obj: SchemaInfoCommand = {
367 'arg-type': self._use_type(arg_type),
368 'ret-type': self._use_type(ret_type)
369 }
25b1ef31
MA
370 if allow_oob:
371 obj['allow-oob'] = allow_oob
2e8a843d 372 self._gen_tree(name, 'command', obj, ifcond, features)
23394b4c 373
82b52f6b 374 def visit_event(self, name: str, info: Optional[QAPISourceInfo],
f17539c8
MAL
375 ifcond: QAPISchemaIfCond,
376 features: List[QAPISchemaFeature],
82b52f6b
JS
377 arg_type: Optional[QAPISchemaObjectType],
378 boxed: bool) -> None:
6b67bcac 379 assert self._schema is not None
82b52f6b 380
39a18158 381 arg_type = arg_type or self._schema.the_empty_object_type
2e8a843d 382 self._gen_tree(name, 'event', {'arg-type': self._use_type(arg_type)},
013b4efc 383 ifcond, features)
39a18158 384
1a9a507b 385
82b52f6b
JS
386def gen_introspect(schema: QAPISchema, output_dir: str, prefix: str,
387 opt_unmask: bool) -> None:
26df4e7f
MA
388 vis = QAPISchemaGenIntrospectVisitor(prefix, opt_unmask)
389 schema.visit(vis)
71b3f045 390 vis.write(output_dir)
This page took 0.435315 seconds and 4 git commands to generate.