qapi-introspect.py 6.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11
"""
QAPI introspection generator

Copyright (C) 2015-2018 Red Hat, Inc.

Authors:
 Markus Armbruster <armbru@redhat.com>

This work is licensed under the terms of the GNU GPL, version 2.
See the COPYING file in the top-level directory.
"""
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

from qapi import *


# Caveman's json.dumps() replacement (we're stuck at Python 2.4)
# TODO try to use json.dumps() once we get unstuck
def to_json(obj, level=0):
    if obj is None:
        ret = 'null'
    elif isinstance(obj, str):
        ret = '"' + obj.replace('"', r'\"') + '"'
    elif isinstance(obj, list):
        elts = [to_json(elt, level + 1)
                for elt in obj]
        ret = '[' + ', '.join(elts) + ']'
    elif isinstance(obj, dict):
        elts = ['"%s": %s' % (key.replace('"', r'\"'),
                              to_json(obj[key], level + 1))
                for key in sorted(obj.keys())]
        ret = '{' + ', '.join(elts) + '}'
    else:
        assert False                # not implemented
    if level == 1:
        ret = '\n' + ret
    return ret


def to_c_string(string):
    return '"' + string.replace('\\', r'\\').replace('"', r'\"') + '"'


class QAPISchemaGenIntrospectVisitor(QAPISchemaVisitor):
44 45
    def __init__(self, prefix, unmask):
        self._prefix = prefix
46
        self._unmask = unmask
47 48 49 50 51
        self.defn = None
        self.decl = None
        self._schema = None
        self._jsons = None
        self._used_types = None
52
        self._name_map = None
53 54 55 56 57

    def visit_begin(self, schema):
        self._schema = schema
        self._jsons = []
        self._used_types = []
58
        self._name_map = {}
59 60 61

    def visit_end(self):
        # visit the types that are actually used
62 63
        jsons = self._jsons
        self._jsons = []
64 65 66 67
        for typ in self._used_types:
            typ.visit(self)
        # generate C
        # TODO can generate awfully long lines
68
        jsons.extend(self._jsons)
69
        name = c_name(self._prefix, protect=False) + 'qmp_schema_json'
70 71 72 73
        self.decl = mcgen('''
extern const char %(c_name)s[];
''',
                          c_name=c_name(name))
74
        lines = to_json(jsons).split('\n')
75 76 77 78 79 80 81 82 83
        c_string = '\n    '.join([to_c_string(line) for line in lines])
        self.defn = mcgen('''
const char %(c_name)s[] = %(c_string)s;
''',
                          c_name=c_name(name),
                          c_string=c_string)
        self._schema = None
        self._jsons = None
        self._used_types = None
84 85
        self._name_map = None

86 87 88 89
    def visit_needed(self, entity):
        # Ignore types on first pass; visit_end() will pick up used types
        return not isinstance(entity, QAPISchemaType)

90 91 92 93 94 95
    def _name(self, name):
        if self._unmask:
            return name
        if name not in self._name_map:
            self._name_map[name] = '%d' % len(self._name_map)
        return self._name_map[name]
96 97 98 99 100 101 102 103 104 105 106

    def _use_type(self, typ):
        # Map the various integer types to plain int
        if typ.json_type() == 'int':
            typ = self._schema.lookup_type('int')
        elif (isinstance(typ, QAPISchemaArrayType) and
              typ.element_type.json_type() == 'int'):
            typ = self._schema.lookup_type('intList')
        # Add type to work queue if new
        if typ not in self._used_types:
            self._used_types.append(typ)
107 108 109 110 111
        # Clients should examine commands and events, not types.  Hide
        # type names to reduce the temptation.  Also saves a few
        # characters.
        if isinstance(typ, QAPISchemaBuiltinType):
            return typ.name
112 113
        if isinstance(typ, QAPISchemaArrayType):
            return '[' + self._use_type(typ.element_type) + ']'
114
        return self._name(typ.name)
115 116

    def _gen_json(self, name, mtype, obj):
117
        if mtype not in ('command', 'event', 'builtin', 'array'):
118
            name = self._name(name)
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
        obj['name'] = name
        obj['meta-type'] = mtype
        self._jsons.append(obj)

    def _gen_member(self, member):
        ret = {'name': member.name, 'type': self._use_type(member.type)}
        if member.optional:
            ret['default'] = None
        return ret

    def _gen_variants(self, tag_name, variants):
        return {'tag': tag_name,
                'variants': [self._gen_variant(v) for v in variants]}

    def _gen_variant(self, variant):
        return {'case': variant.name, 'type': self._use_type(variant.type)}

    def visit_builtin_type(self, name, info, json_type):
        self._gen_json(name, 'builtin', {'json-type': json_type})

    def visit_enum_type(self, name, info, values, prefix):
        self._gen_json(name, 'enum', {'values': values})

    def visit_array_type(self, name, info, element_type):
143 144
        element = self._use_type(element_type)
        self._gen_json('[' + element + ']', 'array', {'element-type': element})
145 146 147 148 149 150 151 152 153 154 155 156 157 158

    def visit_object_type_flat(self, name, info, members, variants):
        obj = {'members': [self._gen_member(m) for m in members]}
        if variants:
            obj.update(self._gen_variants(variants.tag_member.name,
                                          variants.variants))
        self._gen_json(name, 'object', obj)

    def visit_alternate_type(self, name, info, variants):
        self._gen_json(name, 'alternate',
                       {'members': [{'type': self._use_type(m.type)}
                                    for m in variants.variants]})

    def visit_command(self, name, info, arg_type, ret_type,
159
                      gen, success_response, boxed):
160 161 162 163 164 165
        arg_type = arg_type or self._schema.the_empty_object_type
        ret_type = ret_type or self._schema.the_empty_object_type
        self._gen_json(name, 'command',
                       {'arg-type': self._use_type(arg_type),
                        'ret-type': self._use_type(ret_type)})

166
    def visit_event(self, name, info, arg_type, boxed):
167 168 169
        arg_type = arg_type or self._schema.the_empty_object_type
        self._gen_json(name, 'event', {'arg-type': self._use_type(arg_type)})

170 171 172 173 174
# Debugging aid: unmask QAPI schema's type names
# We normally mask them, because they're not QMP wire ABI
opt_unmask = False

(input_file, output_dir, do_c, do_h, prefix, opts) = \
175
    parse_command_line('u', ['unmask-non-abi-names'])
176 177

for o, a in opts:
178
    if o in ('-u', '--unmask-non-abi-names'):
179
        opt_unmask = True
180

181
blurb = ' * QAPI/QMP schema introspection'
182

183 184
genc = QAPIGenC(blurb, __doc__)
genh = QAPIGenH(blurb, __doc__)
185

186
genc.add(mcgen('''
187
#include "qemu/osdep.h"
188 189 190
#include "%(prefix)sqmp-introspect.h"

''',
191
               prefix=prefix))
192 193

schema = QAPISchema(input_file)
194
vis = QAPISchemaGenIntrospectVisitor(prefix, opt_unmask)
195
schema.visit(vis)
196 197
genc.add(vis.defn)
genh.add(vis.decl)
198

199 200 201 202
if do_c:
    genc.write(output_dir, prefix + 'qmp-introspect.c')
if do_h:
    genh.write(output_dir, prefix + 'qmp-introspect.h')