xref: /qemu/scripts/qapi/introspect.py (revision 2a6c161b)
1fb0bc835SMarkus Armbruster"""
2fb0bc835SMarkus ArmbrusterQAPI introspection generator
3fb0bc835SMarkus Armbruster
4fb0bc835SMarkus ArmbrusterCopyright (C) 2015-2018 Red Hat, Inc.
5fb0bc835SMarkus Armbruster
6fb0bc835SMarkus ArmbrusterAuthors:
7fb0bc835SMarkus Armbruster Markus Armbruster <armbru@redhat.com>
8fb0bc835SMarkus Armbruster
9fb0bc835SMarkus ArmbrusterThis work is licensed under the terms of the GNU GPL, version 2.
10fb0bc835SMarkus ArmbrusterSee the COPYING file in the top-level directory.
11fb0bc835SMarkus Armbruster"""
12fb0bc835SMarkus Armbruster
139db27346SJohn Snowfrom typing import (
149db27346SJohn Snow    Any,
159db27346SJohn Snow    Dict,
164f7f97a7SJohn Snow    Generic,
174f7f97a7SJohn Snow    Iterable,
189db27346SJohn Snow    List,
199db27346SJohn Snow    Optional,
204f7f97a7SJohn Snow    Tuple,
214f7f97a7SJohn Snow    TypeVar,
229db27346SJohn Snow    Union,
239db27346SJohn Snow)
245f50cedeSJohn Snow
255af8263dSJohn Snowfrom .common import (
265af8263dSJohn Snow    c_name,
275af8263dSJohn Snow    gen_endif,
285af8263dSJohn Snow    gen_if,
295af8263dSJohn Snow    mcgen,
305af8263dSJohn Snow)
317137a960SJohn Snowfrom .gen import QAPISchemaMonolithicCVisitor
3267fea575SJohn Snowfrom .schema import (
3367fea575SJohn Snow    QAPISchemaArrayType,
3467fea575SJohn Snow    QAPISchemaBuiltinType,
3567fea575SJohn Snow    QAPISchemaType,
3667fea575SJohn Snow)
37fb0bc835SMarkus Armbruster
38fb0bc835SMarkus Armbruster
399db27346SJohn Snow# This module constructs a tree data structure that is used to
409db27346SJohn Snow# generate the introspection information for QEMU. It is shaped
419db27346SJohn Snow# like a JSON value.
429db27346SJohn Snow#
439db27346SJohn Snow# A complexity over JSON is that our values may or may not be annotated.
449db27346SJohn Snow#
459db27346SJohn Snow# Un-annotated values may be:
469db27346SJohn Snow#     Scalar: str, bool, None.
479db27346SJohn Snow#     Non-scalar: List, Dict
489db27346SJohn Snow# _value = Union[str, bool, None, Dict[str, JSONValue], List[JSONValue]]
499db27346SJohn Snow#
509db27346SJohn Snow# With optional annotations, the type of all values is:
519db27346SJohn Snow# JSONValue = Union[_Value, Annotated[_Value]]
529db27346SJohn Snow#
539db27346SJohn Snow# Sadly, mypy does not support recursive types; so the _Stub alias is used to
549db27346SJohn Snow# mark the imprecision in the type model where we'd otherwise use JSONValue.
559db27346SJohn Snow_Stub = Any
569db27346SJohn Snow_Scalar = Union[str, bool, None]
579db27346SJohn Snow_NonScalar = Union[Dict[str, _Stub], List[_Stub]]
589db27346SJohn Snow_Value = Union[_Scalar, _NonScalar]
594f7f97a7SJohn SnowJSONValue = Union[_Value, 'Annotated[_Value]']
609db27346SJohn Snow
619db27346SJohn Snow
624f7f97a7SJohn Snow_ValueT = TypeVar('_ValueT', bound=_Value)
634f7f97a7SJohn Snow
644f7f97a7SJohn Snow
654f7f97a7SJohn Snowclass Annotated(Generic[_ValueT]):
664f7f97a7SJohn Snow    """
674f7f97a7SJohn Snow    Annotated generally contains a SchemaInfo-like type (as a dict),
684f7f97a7SJohn Snow    But it also used to wrap comments/ifconds around scalar leaf values,
694f7f97a7SJohn Snow    for the benefit of features and enums.
704f7f97a7SJohn Snow    """
714f7f97a7SJohn Snow    # TODO: Remove after Python 3.7 adds @dataclass:
724f7f97a7SJohn Snow    # pylint: disable=too-few-public-methods
734f7f97a7SJohn Snow    def __init__(self, value: _ValueT, ifcond: Iterable[str],
744f7f97a7SJohn Snow                 comment: Optional[str] = None):
754f7f97a7SJohn Snow        self.value = value
764f7f97a7SJohn Snow        self.comment: Optional[str] = comment
774f7f97a7SJohn Snow        self.ifcond: Tuple[str, ...] = tuple(ifcond)
7824cfd6adSMarkus Armbruster
7924cfd6adSMarkus Armbruster
8005556960SJohn Snowdef _tree_to_qlit(obj, level=0, dict_value=False):
817d0f982bSMarc-André Lureau
827d0f982bSMarc-André Lureau    def indent(level):
837d0f982bSMarc-André Lureau        return level * 4 * ' '
847d0f982bSMarc-André Lureau
854f7f97a7SJohn Snow    if isinstance(obj, Annotated):
8605556960SJohn Snow        # NB: _tree_to_qlit is called recursively on the values of a
8705556960SJohn Snow        # key:value pair; those values can't be decorated with
8805556960SJohn Snow        # comments or conditionals.
8905556960SJohn Snow        msg = "dict values cannot have attached comments or if-conditionals."
9005556960SJohn Snow        assert not dict_value, msg
9105556960SJohn Snow
928c643361SEric Blake        ret = ''
934f7f97a7SJohn Snow        if obj.comment:
944f7f97a7SJohn Snow            ret += indent(level) + '/* %s */\n' % obj.comment
954f7f97a7SJohn Snow        if obj.ifcond:
964f7f97a7SJohn Snow            ret += gen_if(obj.ifcond)
974f7f97a7SJohn Snow        ret += _tree_to_qlit(obj.value, level)
984f7f97a7SJohn Snow        if obj.ifcond:
994f7f97a7SJohn Snow            ret += '\n' + gen_endif(obj.ifcond)
100d626b6c1SMarc-André Lureau        return ret
101d626b6c1SMarc-André Lureau
1027d0f982bSMarc-André Lureau    ret = ''
10305556960SJohn Snow    if not dict_value:
1047d0f982bSMarc-André Lureau        ret += indent(level)
105fb0bc835SMarkus Armbruster    if obj is None:
1067d0f982bSMarc-André Lureau        ret += 'QLIT_QNULL'
107fb0bc835SMarkus Armbruster    elif isinstance(obj, str):
1087d0f982bSMarc-André Lureau        ret += 'QLIT_QSTR(' + to_c_string(obj) + ')'
109fb0bc835SMarkus Armbruster    elif isinstance(obj, list):
1102e8a843dSMarkus Armbruster        elts = [_tree_to_qlit(elt, level + 1).strip('\n')
111fb0bc835SMarkus Armbruster                for elt in obj]
1127d0f982bSMarc-André Lureau        elts.append(indent(level + 1) + "{}")
1137d0f982bSMarc-André Lureau        ret += 'QLIT_QLIST(((QLitObject[]) {\n'
11440bb1376SMarc-André Lureau        ret += '\n'.join(elts) + '\n'
1157d0f982bSMarc-André Lureau        ret += indent(level) + '}))'
116fb0bc835SMarkus Armbruster    elif isinstance(obj, dict):
1177d0f982bSMarc-André Lureau        elts = []
1187d0f982bSMarc-André Lureau        for key, value in sorted(obj.items()):
1197d0f982bSMarc-André Lureau            elts.append(indent(level + 1) + '{ %s, %s }' %
1202e8a843dSMarkus Armbruster                        (to_c_string(key),
1212e8a843dSMarkus Armbruster                         _tree_to_qlit(value, level + 1, True)))
1227d0f982bSMarc-André Lureau        elts.append(indent(level + 1) + '{}')
1237d0f982bSMarc-André Lureau        ret += 'QLIT_QDICT(((QLitDictEntry[]) {\n'
1247d0f982bSMarc-André Lureau        ret += ',\n'.join(elts) + '\n'
1257d0f982bSMarc-André Lureau        ret += indent(level) + '}))'
126876c6751SPeter Xu    elif isinstance(obj, bool):
127876c6751SPeter Xu        ret += 'QLIT_QBOOL(%s)' % ('true' if obj else 'false')
128fb0bc835SMarkus Armbruster    else:
129*2a6c161bSJohn Snow        raise NotImplementedError(
130*2a6c161bSJohn Snow            f"type '{type(obj).__name__}' not implemented"
131*2a6c161bSJohn Snow        )
13240bb1376SMarc-André Lureau    if level > 0:
13340bb1376SMarc-André Lureau        ret += ','
134fb0bc835SMarkus Armbruster    return ret
135fb0bc835SMarkus Armbruster
136fb0bc835SMarkus Armbruster
137fb0bc835SMarkus Armbrusterdef to_c_string(string):
138fb0bc835SMarkus Armbruster    return '"' + string.replace('\\', r'\\').replace('"', r'\"') + '"'
139fb0bc835SMarkus Armbruster
140fb0bc835SMarkus Armbruster
14171b3f045SMarkus Armbrusterclass QAPISchemaGenIntrospectVisitor(QAPISchemaMonolithicCVisitor):
142fb0bc835SMarkus Armbruster
14371b3f045SMarkus Armbruster    def __init__(self, prefix, unmask):
1442cae67bcSMarkus Armbruster        super().__init__(
1452cae67bcSMarkus Armbruster            prefix, 'qapi-introspect',
14671b3f045SMarkus Armbruster            ' * QAPI/QMP schema introspection', __doc__)
14771b3f045SMarkus Armbruster        self._unmask = unmask
14871b3f045SMarkus Armbruster        self._schema = None
1492e8a843dSMarkus Armbruster        self._trees = []
150fb0bc835SMarkus Armbruster        self._used_types = []
151fb0bc835SMarkus Armbruster        self._name_map = {}
15271b3f045SMarkus Armbruster        self._genc.add(mcgen('''
15371b3f045SMarkus Armbruster#include "qemu/osdep.h"
154eb815e24SMarkus Armbruster#include "%(prefix)sqapi-introspect.h"
15571b3f045SMarkus Armbruster
15671b3f045SMarkus Armbruster''',
15771b3f045SMarkus Armbruster                             prefix=prefix))
15871b3f045SMarkus Armbruster
15971b3f045SMarkus Armbruster    def visit_begin(self, schema):
16071b3f045SMarkus Armbruster        self._schema = schema
161fb0bc835SMarkus Armbruster
162fb0bc835SMarkus Armbruster    def visit_end(self):
163fb0bc835SMarkus Armbruster        # visit the types that are actually used
164fb0bc835SMarkus Armbruster        for typ in self._used_types:
165fb0bc835SMarkus Armbruster            typ.visit(self)
166fb0bc835SMarkus Armbruster        # generate C
1677d0f982bSMarc-André Lureau        name = c_name(self._prefix, protect=False) + 'qmp_schema_qlit'
16871b3f045SMarkus Armbruster        self._genh.add(mcgen('''
1697d0f982bSMarc-André Lureau#include "qapi/qmp/qlit.h"
1707d0f982bSMarc-André Lureau
1717d0f982bSMarc-André Lureauextern const QLitObject %(c_name)s;
172fb0bc835SMarkus Armbruster''',
17371b3f045SMarkus Armbruster                             c_name=c_name(name)))
17471b3f045SMarkus Armbruster        self._genc.add(mcgen('''
1757d0f982bSMarc-André Lureauconst QLitObject %(c_name)s = %(c_string)s;
176fb0bc835SMarkus Armbruster''',
177fb0bc835SMarkus Armbruster                             c_name=c_name(name),
1782e8a843dSMarkus Armbruster                             c_string=_tree_to_qlit(self._trees)))
179fb0bc835SMarkus Armbruster        self._schema = None
1802e8a843dSMarkus Armbruster        self._trees = []
18171b3f045SMarkus Armbruster        self._used_types = []
18271b3f045SMarkus Armbruster        self._name_map = {}
183fb0bc835SMarkus Armbruster
184fb0bc835SMarkus Armbruster    def visit_needed(self, entity):
185fb0bc835SMarkus Armbruster        # Ignore types on first pass; visit_end() will pick up used types
186fb0bc835SMarkus Armbruster        return not isinstance(entity, QAPISchemaType)
187fb0bc835SMarkus Armbruster
188fb0bc835SMarkus Armbruster    def _name(self, name):
189fb0bc835SMarkus Armbruster        if self._unmask:
190fb0bc835SMarkus Armbruster            return name
191fb0bc835SMarkus Armbruster        if name not in self._name_map:
192fb0bc835SMarkus Armbruster            self._name_map[name] = '%d' % len(self._name_map)
193fb0bc835SMarkus Armbruster        return self._name_map[name]
194fb0bc835SMarkus Armbruster
195fb0bc835SMarkus Armbruster    def _use_type(self, typ):
1966b67bcacSJohn Snow        assert self._schema is not None
1976b67bcacSJohn Snow
198fb0bc835SMarkus Armbruster        # Map the various integer types to plain int
199fb0bc835SMarkus Armbruster        if typ.json_type() == 'int':
200fb0bc835SMarkus Armbruster            typ = self._schema.lookup_type('int')
201fb0bc835SMarkus Armbruster        elif (isinstance(typ, QAPISchemaArrayType) and
202fb0bc835SMarkus Armbruster              typ.element_type.json_type() == 'int'):
203fb0bc835SMarkus Armbruster            typ = self._schema.lookup_type('intList')
204fb0bc835SMarkus Armbruster        # Add type to work queue if new
205fb0bc835SMarkus Armbruster        if typ not in self._used_types:
206fb0bc835SMarkus Armbruster            self._used_types.append(typ)
207fb0bc835SMarkus Armbruster        # Clients should examine commands and events, not types.  Hide
2081aa806ccSEric Blake        # type names as integers to reduce the temptation.  Also, it
2091aa806ccSEric Blake        # saves a few characters on the wire.
210fb0bc835SMarkus Armbruster        if isinstance(typ, QAPISchemaBuiltinType):
211fb0bc835SMarkus Armbruster            return typ.name
212fb0bc835SMarkus Armbruster        if isinstance(typ, QAPISchemaArrayType):
213fb0bc835SMarkus Armbruster            return '[' + self._use_type(typ.element_type) + ']'
214fb0bc835SMarkus Armbruster        return self._name(typ.name)
215fb0bc835SMarkus Armbruster
21684bece7dSJohn Snow    @staticmethod
21784bece7dSJohn Snow    def _gen_features(features):
2184f7f97a7SJohn Snow        return [Annotated(f.name, f.ifcond) for f in features]
21984bece7dSJohn Snow
2202e8a843dSMarkus Armbruster    def _gen_tree(self, name, mtype, obj, ifcond, features):
2215f50cedeSJohn Snow        comment: Optional[str] = None
222fb0bc835SMarkus Armbruster        if mtype not in ('command', 'event', 'builtin', 'array'):
2238c643361SEric Blake            if not self._unmask:
2248c643361SEric Blake                # Output a comment to make it easy to map masked names
2258c643361SEric Blake                # back to the source when reading the generated output.
2265f50cedeSJohn Snow                comment = f'"{self._name(name)}" = {name}'
227fb0bc835SMarkus Armbruster            name = self._name(name)
228fb0bc835SMarkus Armbruster        obj['name'] = name
229fb0bc835SMarkus Armbruster        obj['meta-type'] = mtype
23084bece7dSJohn Snow        if features:
23184bece7dSJohn Snow            obj['features'] = self._gen_features(features)
2324f7f97a7SJohn Snow        self._trees.append(Annotated(obj, ifcond, comment))
233fb0bc835SMarkus Armbruster
234fb0bc835SMarkus Armbruster    def _gen_member(self, member):
23524cfd6adSMarkus Armbruster        obj = {'name': member.name, 'type': self._use_type(member.type)}
236fb0bc835SMarkus Armbruster        if member.optional:
23724cfd6adSMarkus Armbruster            obj['default'] = None
23884bece7dSJohn Snow        if member.features:
23984bece7dSJohn Snow            obj['features'] = self._gen_features(member.features)
2404f7f97a7SJohn Snow        return Annotated(obj, member.ifcond)
241fb0bc835SMarkus Armbruster
242fb0bc835SMarkus Armbruster    def _gen_variants(self, tag_name, variants):
243fb0bc835SMarkus Armbruster        return {'tag': tag_name,
244fb0bc835SMarkus Armbruster                'variants': [self._gen_variant(v) for v in variants]}
245fb0bc835SMarkus Armbruster
246fb0bc835SMarkus Armbruster    def _gen_variant(self, variant):
24724cfd6adSMarkus Armbruster        obj = {'case': variant.name, 'type': self._use_type(variant.type)}
2484f7f97a7SJohn Snow        return Annotated(obj, variant.ifcond)
249fb0bc835SMarkus Armbruster
250fb0bc835SMarkus Armbruster    def visit_builtin_type(self, name, info, json_type):
2512e8a843dSMarkus Armbruster        self._gen_tree(name, 'builtin', {'json-type': json_type}, [], None)
252fb0bc835SMarkus Armbruster
253013b4efcSMarkus Armbruster    def visit_enum_type(self, name, info, ifcond, features, members, prefix):
2544f7f97a7SJohn Snow        self._gen_tree(
2554f7f97a7SJohn Snow            name, 'enum',
2564f7f97a7SJohn Snow            {'values': [Annotated(m.name, m.ifcond) for m in members]},
2574f7f97a7SJohn Snow            ifcond, features
2584f7f97a7SJohn Snow        )
259fb0bc835SMarkus Armbruster
260fbf09a2fSMarc-André Lureau    def visit_array_type(self, name, info, ifcond, element_type):
261fb0bc835SMarkus Armbruster        element = self._use_type(element_type)
2622e8a843dSMarkus Armbruster        self._gen_tree('[' + element + ']', 'array', {'element-type': element},
263013b4efcSMarkus Armbruster                       ifcond, None)
264fb0bc835SMarkus Armbruster
2657b3bc9e2SMarkus Armbruster    def visit_object_type_flat(self, name, info, ifcond, features,
2667b3bc9e2SMarkus Armbruster                               members, variants):
267fb0bc835SMarkus Armbruster        obj = {'members': [self._gen_member(m) for m in members]}
268fb0bc835SMarkus Armbruster        if variants:
269fb0bc835SMarkus Armbruster            obj.update(self._gen_variants(variants.tag_member.name,
270fb0bc835SMarkus Armbruster                                          variants.variants))
2716a8c0b51SKevin Wolf
2722e8a843dSMarkus Armbruster        self._gen_tree(name, 'object', obj, ifcond, features)
273fb0bc835SMarkus Armbruster
274013b4efcSMarkus Armbruster    def visit_alternate_type(self, name, info, ifcond, features, variants):
2754f7f97a7SJohn Snow        self._gen_tree(
2764f7f97a7SJohn Snow            name, 'alternate',
2774f7f97a7SJohn Snow            {'members': [Annotated({'type': self._use_type(m.type)},
2784f7f97a7SJohn Snow                                   m.ifcond)
279013b4efcSMarkus Armbruster                         for m in variants.variants]},
2804f7f97a7SJohn Snow            ifcond, features
2814f7f97a7SJohn Snow        )
282fb0bc835SMarkus Armbruster
2837b3bc9e2SMarkus Armbruster    def visit_command(self, name, info, ifcond, features,
2847b3bc9e2SMarkus Armbruster                      arg_type, ret_type, gen, success_response, boxed,
28504f22362SKevin Wolf                      allow_oob, allow_preconfig, coroutine):
2866b67bcacSJohn Snow        assert self._schema is not None
2876b67bcacSJohn Snow
288fb0bc835SMarkus Armbruster        arg_type = arg_type or self._schema.the_empty_object_type
289fb0bc835SMarkus Armbruster        ret_type = ret_type or self._schema.the_empty_object_type
29025b1ef31SMarkus Armbruster        obj = {'arg-type': self._use_type(arg_type),
29125b1ef31SMarkus Armbruster               'ret-type': self._use_type(ret_type)}
29225b1ef31SMarkus Armbruster        if allow_oob:
29325b1ef31SMarkus Armbruster            obj['allow-oob'] = allow_oob
2942e8a843dSMarkus Armbruster        self._gen_tree(name, 'command', obj, ifcond, features)
29523394b4cSPeter Krempa
296013b4efcSMarkus Armbruster    def visit_event(self, name, info, ifcond, features, arg_type, boxed):
2976b67bcacSJohn Snow        assert self._schema is not None
298fb0bc835SMarkus Armbruster        arg_type = arg_type or self._schema.the_empty_object_type
2992e8a843dSMarkus Armbruster        self._gen_tree(name, 'event', {'arg-type': self._use_type(arg_type)},
300013b4efcSMarkus Armbruster                       ifcond, features)
301fb0bc835SMarkus Armbruster
302fb0bc835SMarkus Armbruster
303fb0bc835SMarkus Armbrusterdef gen_introspect(schema, output_dir, prefix, opt_unmask):
304fb0bc835SMarkus Armbruster    vis = QAPISchemaGenIntrospectVisitor(prefix, opt_unmask)
305fb0bc835SMarkus Armbruster    schema.visit(vis)
30671b3f045SMarkus Armbruster    vis.write(output_dir)
307