xref: /qemu/scripts/qapi/introspect.py (revision c0e8d9f3)
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:
94*c0e8d9f3SJohn Snow            ret += indent(level) + f"/* {obj.comment} */\n"
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)
105*c0e8d9f3SJohn Snow
106*c0e8d9f3SJohn Snow    # Scalars:
107fb0bc835SMarkus Armbruster    if obj is None:
1087d0f982bSMarc-André Lureau        ret += 'QLIT_QNULL'
109fb0bc835SMarkus Armbruster    elif isinstance(obj, str):
110*c0e8d9f3SJohn Snow        ret += f"QLIT_QSTR({to_c_string(obj)})"
111*c0e8d9f3SJohn Snow    elif isinstance(obj, bool):
112*c0e8d9f3SJohn Snow        ret += f"QLIT_QBOOL({str(obj).lower()})"
113*c0e8d9f3SJohn Snow
114*c0e8d9f3SJohn Snow    # Non-scalars:
115fb0bc835SMarkus Armbruster    elif isinstance(obj, list):
1167d0f982bSMarc-André Lureau        ret += 'QLIT_QLIST(((QLitObject[]) {\n'
117*c0e8d9f3SJohn Snow        for value in obj:
118*c0e8d9f3SJohn Snow            ret += _tree_to_qlit(value, level + 1).strip('\n') + '\n'
119*c0e8d9f3SJohn Snow        ret += indent(level + 1) + '{}\n'
1207d0f982bSMarc-André Lureau        ret += indent(level) + '}))'
121fb0bc835SMarkus Armbruster    elif isinstance(obj, dict):
1227d0f982bSMarc-André Lureau        ret += 'QLIT_QDICT(((QLitDictEntry[]) {\n'
123*c0e8d9f3SJohn Snow        for key, value in sorted(obj.items()):
124*c0e8d9f3SJohn Snow            ret += indent(level + 1) + "{{ {:s}, {:s} }},\n".format(
125*c0e8d9f3SJohn Snow                to_c_string(key),
126*c0e8d9f3SJohn Snow                _tree_to_qlit(value, level + 1, dict_value=True)
127*c0e8d9f3SJohn Snow            )
128*c0e8d9f3SJohn Snow        ret += indent(level + 1) + '{}\n'
1297d0f982bSMarc-André Lureau        ret += indent(level) + '}))'
130fb0bc835SMarkus Armbruster    else:
1312a6c161bSJohn Snow        raise NotImplementedError(
1322a6c161bSJohn Snow            f"type '{type(obj).__name__}' not implemented"
1332a6c161bSJohn Snow        )
134*c0e8d9f3SJohn Snow
13540bb1376SMarc-André Lureau    if level > 0:
13640bb1376SMarc-André Lureau        ret += ','
137fb0bc835SMarkus Armbruster    return ret
138fb0bc835SMarkus Armbruster
139fb0bc835SMarkus Armbruster
140fb0bc835SMarkus Armbrusterdef to_c_string(string):
141fb0bc835SMarkus Armbruster    return '"' + string.replace('\\', r'\\').replace('"', r'\"') + '"'
142fb0bc835SMarkus Armbruster
143fb0bc835SMarkus Armbruster
14471b3f045SMarkus Armbrusterclass QAPISchemaGenIntrospectVisitor(QAPISchemaMonolithicCVisitor):
145fb0bc835SMarkus Armbruster
14671b3f045SMarkus Armbruster    def __init__(self, prefix, unmask):
1472cae67bcSMarkus Armbruster        super().__init__(
1482cae67bcSMarkus Armbruster            prefix, 'qapi-introspect',
14971b3f045SMarkus Armbruster            ' * QAPI/QMP schema introspection', __doc__)
15071b3f045SMarkus Armbruster        self._unmask = unmask
15171b3f045SMarkus Armbruster        self._schema = None
1522e8a843dSMarkus Armbruster        self._trees = []
153fb0bc835SMarkus Armbruster        self._used_types = []
154fb0bc835SMarkus Armbruster        self._name_map = {}
15571b3f045SMarkus Armbruster        self._genc.add(mcgen('''
15671b3f045SMarkus Armbruster#include "qemu/osdep.h"
157eb815e24SMarkus Armbruster#include "%(prefix)sqapi-introspect.h"
15871b3f045SMarkus Armbruster
15971b3f045SMarkus Armbruster''',
16071b3f045SMarkus Armbruster                             prefix=prefix))
16171b3f045SMarkus Armbruster
16271b3f045SMarkus Armbruster    def visit_begin(self, schema):
16371b3f045SMarkus Armbruster        self._schema = schema
164fb0bc835SMarkus Armbruster
165fb0bc835SMarkus Armbruster    def visit_end(self):
166fb0bc835SMarkus Armbruster        # visit the types that are actually used
167fb0bc835SMarkus Armbruster        for typ in self._used_types:
168fb0bc835SMarkus Armbruster            typ.visit(self)
169fb0bc835SMarkus Armbruster        # generate C
1707d0f982bSMarc-André Lureau        name = c_name(self._prefix, protect=False) + 'qmp_schema_qlit'
17171b3f045SMarkus Armbruster        self._genh.add(mcgen('''
1727d0f982bSMarc-André Lureau#include "qapi/qmp/qlit.h"
1737d0f982bSMarc-André Lureau
1747d0f982bSMarc-André Lureauextern const QLitObject %(c_name)s;
175fb0bc835SMarkus Armbruster''',
17671b3f045SMarkus Armbruster                             c_name=c_name(name)))
17771b3f045SMarkus Armbruster        self._genc.add(mcgen('''
1787d0f982bSMarc-André Lureauconst QLitObject %(c_name)s = %(c_string)s;
179fb0bc835SMarkus Armbruster''',
180fb0bc835SMarkus Armbruster                             c_name=c_name(name),
1812e8a843dSMarkus Armbruster                             c_string=_tree_to_qlit(self._trees)))
182fb0bc835SMarkus Armbruster        self._schema = None
1832e8a843dSMarkus Armbruster        self._trees = []
18471b3f045SMarkus Armbruster        self._used_types = []
18571b3f045SMarkus Armbruster        self._name_map = {}
186fb0bc835SMarkus Armbruster
187fb0bc835SMarkus Armbruster    def visit_needed(self, entity):
188fb0bc835SMarkus Armbruster        # Ignore types on first pass; visit_end() will pick up used types
189fb0bc835SMarkus Armbruster        return not isinstance(entity, QAPISchemaType)
190fb0bc835SMarkus Armbruster
191fb0bc835SMarkus Armbruster    def _name(self, name):
192fb0bc835SMarkus Armbruster        if self._unmask:
193fb0bc835SMarkus Armbruster            return name
194fb0bc835SMarkus Armbruster        if name not in self._name_map:
195fb0bc835SMarkus Armbruster            self._name_map[name] = '%d' % len(self._name_map)
196fb0bc835SMarkus Armbruster        return self._name_map[name]
197fb0bc835SMarkus Armbruster
198fb0bc835SMarkus Armbruster    def _use_type(self, typ):
1996b67bcacSJohn Snow        assert self._schema is not None
2006b67bcacSJohn Snow
201fb0bc835SMarkus Armbruster        # Map the various integer types to plain int
202fb0bc835SMarkus Armbruster        if typ.json_type() == 'int':
203fb0bc835SMarkus Armbruster            typ = self._schema.lookup_type('int')
204fb0bc835SMarkus Armbruster        elif (isinstance(typ, QAPISchemaArrayType) and
205fb0bc835SMarkus Armbruster              typ.element_type.json_type() == 'int'):
206fb0bc835SMarkus Armbruster            typ = self._schema.lookup_type('intList')
207fb0bc835SMarkus Armbruster        # Add type to work queue if new
208fb0bc835SMarkus Armbruster        if typ not in self._used_types:
209fb0bc835SMarkus Armbruster            self._used_types.append(typ)
210fb0bc835SMarkus Armbruster        # Clients should examine commands and events, not types.  Hide
2111aa806ccSEric Blake        # type names as integers to reduce the temptation.  Also, it
2121aa806ccSEric Blake        # saves a few characters on the wire.
213fb0bc835SMarkus Armbruster        if isinstance(typ, QAPISchemaBuiltinType):
214fb0bc835SMarkus Armbruster            return typ.name
215fb0bc835SMarkus Armbruster        if isinstance(typ, QAPISchemaArrayType):
216fb0bc835SMarkus Armbruster            return '[' + self._use_type(typ.element_type) + ']'
217fb0bc835SMarkus Armbruster        return self._name(typ.name)
218fb0bc835SMarkus Armbruster
21984bece7dSJohn Snow    @staticmethod
22084bece7dSJohn Snow    def _gen_features(features):
2214f7f97a7SJohn Snow        return [Annotated(f.name, f.ifcond) for f in features]
22284bece7dSJohn Snow
2232e8a843dSMarkus Armbruster    def _gen_tree(self, name, mtype, obj, ifcond, features):
2245f50cedeSJohn Snow        comment: Optional[str] = None
225fb0bc835SMarkus Armbruster        if mtype not in ('command', 'event', 'builtin', 'array'):
2268c643361SEric Blake            if not self._unmask:
2278c643361SEric Blake                # Output a comment to make it easy to map masked names
2288c643361SEric Blake                # back to the source when reading the generated output.
2295f50cedeSJohn Snow                comment = f'"{self._name(name)}" = {name}'
230fb0bc835SMarkus Armbruster            name = self._name(name)
231fb0bc835SMarkus Armbruster        obj['name'] = name
232fb0bc835SMarkus Armbruster        obj['meta-type'] = mtype
23384bece7dSJohn Snow        if features:
23484bece7dSJohn Snow            obj['features'] = self._gen_features(features)
2354f7f97a7SJohn Snow        self._trees.append(Annotated(obj, ifcond, comment))
236fb0bc835SMarkus Armbruster
237fb0bc835SMarkus Armbruster    def _gen_member(self, member):
23824cfd6adSMarkus Armbruster        obj = {'name': member.name, 'type': self._use_type(member.type)}
239fb0bc835SMarkus Armbruster        if member.optional:
24024cfd6adSMarkus Armbruster            obj['default'] = None
24184bece7dSJohn Snow        if member.features:
24284bece7dSJohn Snow            obj['features'] = self._gen_features(member.features)
2434f7f97a7SJohn Snow        return Annotated(obj, member.ifcond)
244fb0bc835SMarkus Armbruster
245fb0bc835SMarkus Armbruster    def _gen_variants(self, tag_name, variants):
246fb0bc835SMarkus Armbruster        return {'tag': tag_name,
247fb0bc835SMarkus Armbruster                'variants': [self._gen_variant(v) for v in variants]}
248fb0bc835SMarkus Armbruster
249fb0bc835SMarkus Armbruster    def _gen_variant(self, variant):
25024cfd6adSMarkus Armbruster        obj = {'case': variant.name, 'type': self._use_type(variant.type)}
2514f7f97a7SJohn Snow        return Annotated(obj, variant.ifcond)
252fb0bc835SMarkus Armbruster
253fb0bc835SMarkus Armbruster    def visit_builtin_type(self, name, info, json_type):
2542e8a843dSMarkus Armbruster        self._gen_tree(name, 'builtin', {'json-type': json_type}, [], None)
255fb0bc835SMarkus Armbruster
256013b4efcSMarkus Armbruster    def visit_enum_type(self, name, info, ifcond, features, members, prefix):
2574f7f97a7SJohn Snow        self._gen_tree(
2584f7f97a7SJohn Snow            name, 'enum',
2594f7f97a7SJohn Snow            {'values': [Annotated(m.name, m.ifcond) for m in members]},
2604f7f97a7SJohn Snow            ifcond, features
2614f7f97a7SJohn Snow        )
262fb0bc835SMarkus Armbruster
263fbf09a2fSMarc-André Lureau    def visit_array_type(self, name, info, ifcond, element_type):
264fb0bc835SMarkus Armbruster        element = self._use_type(element_type)
2652e8a843dSMarkus Armbruster        self._gen_tree('[' + element + ']', 'array', {'element-type': element},
266013b4efcSMarkus Armbruster                       ifcond, None)
267fb0bc835SMarkus Armbruster
2687b3bc9e2SMarkus Armbruster    def visit_object_type_flat(self, name, info, ifcond, features,
2697b3bc9e2SMarkus Armbruster                               members, variants):
270fb0bc835SMarkus Armbruster        obj = {'members': [self._gen_member(m) for m in members]}
271fb0bc835SMarkus Armbruster        if variants:
272fb0bc835SMarkus Armbruster            obj.update(self._gen_variants(variants.tag_member.name,
273fb0bc835SMarkus Armbruster                                          variants.variants))
2746a8c0b51SKevin Wolf
2752e8a843dSMarkus Armbruster        self._gen_tree(name, 'object', obj, ifcond, features)
276fb0bc835SMarkus Armbruster
277013b4efcSMarkus Armbruster    def visit_alternate_type(self, name, info, ifcond, features, variants):
2784f7f97a7SJohn Snow        self._gen_tree(
2794f7f97a7SJohn Snow            name, 'alternate',
2804f7f97a7SJohn Snow            {'members': [Annotated({'type': self._use_type(m.type)},
2814f7f97a7SJohn Snow                                   m.ifcond)
282013b4efcSMarkus Armbruster                         for m in variants.variants]},
2834f7f97a7SJohn Snow            ifcond, features
2844f7f97a7SJohn Snow        )
285fb0bc835SMarkus Armbruster
2867b3bc9e2SMarkus Armbruster    def visit_command(self, name, info, ifcond, features,
2877b3bc9e2SMarkus Armbruster                      arg_type, ret_type, gen, success_response, boxed,
28804f22362SKevin Wolf                      allow_oob, allow_preconfig, coroutine):
2896b67bcacSJohn Snow        assert self._schema is not None
2906b67bcacSJohn Snow
291fb0bc835SMarkus Armbruster        arg_type = arg_type or self._schema.the_empty_object_type
292fb0bc835SMarkus Armbruster        ret_type = ret_type or self._schema.the_empty_object_type
29325b1ef31SMarkus Armbruster        obj = {'arg-type': self._use_type(arg_type),
29425b1ef31SMarkus Armbruster               'ret-type': self._use_type(ret_type)}
29525b1ef31SMarkus Armbruster        if allow_oob:
29625b1ef31SMarkus Armbruster            obj['allow-oob'] = allow_oob
2972e8a843dSMarkus Armbruster        self._gen_tree(name, 'command', obj, ifcond, features)
29823394b4cSPeter Krempa
299013b4efcSMarkus Armbruster    def visit_event(self, name, info, ifcond, features, arg_type, boxed):
3006b67bcacSJohn Snow        assert self._schema is not None
301fb0bc835SMarkus Armbruster        arg_type = arg_type or self._schema.the_empty_object_type
3022e8a843dSMarkus Armbruster        self._gen_tree(name, 'event', {'arg-type': self._use_type(arg_type)},
303013b4efcSMarkus Armbruster                       ifcond, features)
304fb0bc835SMarkus Armbruster
305fb0bc835SMarkus Armbruster
306fb0bc835SMarkus Armbrusterdef gen_introspect(schema, output_dir, prefix, opt_unmask):
307fb0bc835SMarkus Armbruster    vis = QAPISchemaGenIntrospectVisitor(prefix, opt_unmask)
308fb0bc835SMarkus Armbruster    schema.visit(vis)
30971b3f045SMarkus Armbruster    vis.write(output_dir)
310