qemu/scripts/qapi-introspect.py
<<
>>
Prefs
   1#
   2# QAPI introspection generator
   3#
   4# Copyright (C) 2015-2016 Red Hat, Inc.
   5#
   6# Authors:
   7#  Markus Armbruster <armbru@redhat.com>
   8#
   9# This work is licensed under the terms of the GNU GPL, version 2.
  10# See the COPYING file in the top-level directory.
  11
  12from qapi import *
  13
  14
  15# Caveman's json.dumps() replacement (we're stuck at Python 2.4)
  16# TODO try to use json.dumps() once we get unstuck
  17def to_json(obj, level=0):
  18    if obj is None:
  19        ret = 'null'
  20    elif isinstance(obj, str):
  21        ret = '"' + obj.replace('"', r'\"') + '"'
  22    elif isinstance(obj, list):
  23        elts = [to_json(elt, level + 1)
  24                for elt in obj]
  25        ret = '[' + ', '.join(elts) + ']'
  26    elif isinstance(obj, dict):
  27        elts = ['"%s": %s' % (key.replace('"', r'\"'),
  28                              to_json(obj[key], level + 1))
  29                for key in sorted(obj.keys())]
  30        ret = '{' + ', '.join(elts) + '}'
  31    else:
  32        assert False                # not implemented
  33    if level == 1:
  34        ret = '\n' + ret
  35    return ret
  36
  37
  38def to_c_string(string):
  39    return '"' + string.replace('\\', r'\\').replace('"', r'\"') + '"'
  40
  41
  42class QAPISchemaGenIntrospectVisitor(QAPISchemaVisitor):
  43    def __init__(self, unmask):
  44        self._unmask = unmask
  45        self.defn = None
  46        self.decl = None
  47        self._schema = None
  48        self._jsons = None
  49        self._used_types = None
  50        self._name_map = None
  51
  52    def visit_begin(self, schema):
  53        self._schema = schema
  54        self._jsons = []
  55        self._used_types = []
  56        self._name_map = {}
  57
  58    def visit_end(self):
  59        # visit the types that are actually used
  60        jsons = self._jsons
  61        self._jsons = []
  62        for typ in self._used_types:
  63            typ.visit(self)
  64        # generate C
  65        # TODO can generate awfully long lines
  66        jsons.extend(self._jsons)
  67        name = prefix + 'qmp_schema_json'
  68        self.decl = mcgen('''
  69extern const char %(c_name)s[];
  70''',
  71                          c_name=c_name(name))
  72        lines = to_json(jsons).split('\n')
  73        c_string = '\n    '.join([to_c_string(line) for line in lines])
  74        self.defn = mcgen('''
  75const char %(c_name)s[] = %(c_string)s;
  76''',
  77                          c_name=c_name(name),
  78                          c_string=c_string)
  79        self._schema = None
  80        self._jsons = None
  81        self._used_types = None
  82        self._name_map = None
  83
  84    def visit_needed(self, entity):
  85        # Ignore types on first pass; visit_end() will pick up used types
  86        return not isinstance(entity, QAPISchemaType)
  87
  88    def _name(self, name):
  89        if self._unmask:
  90            return name
  91        if name not in self._name_map:
  92            self._name_map[name] = '%d' % len(self._name_map)
  93        return self._name_map[name]
  94
  95    def _use_type(self, typ):
  96        # Map the various integer types to plain int
  97        if typ.json_type() == 'int':
  98            typ = self._schema.lookup_type('int')
  99        elif (isinstance(typ, QAPISchemaArrayType) and
 100              typ.element_type.json_type() == 'int'):
 101            typ = self._schema.lookup_type('intList')
 102        # Add type to work queue if new
 103        if typ not in self._used_types:
 104            self._used_types.append(typ)
 105        # Clients should examine commands and events, not types.  Hide
 106        # type names to reduce the temptation.  Also saves a few
 107        # characters.
 108        if isinstance(typ, QAPISchemaBuiltinType):
 109            return typ.name
 110        if isinstance(typ, QAPISchemaArrayType):
 111            return '[' + self._use_type(typ.element_type) + ']'
 112        return self._name(typ.name)
 113
 114    def _gen_json(self, name, mtype, obj):
 115        if mtype not in ('command', 'event', 'builtin', 'array'):
 116            name = self._name(name)
 117        obj['name'] = name
 118        obj['meta-type'] = mtype
 119        self._jsons.append(obj)
 120
 121    def _gen_member(self, member):
 122        ret = {'name': member.name, 'type': self._use_type(member.type)}
 123        if member.optional:
 124            ret['default'] = None
 125        return ret
 126
 127    def _gen_variants(self, tag_name, variants):
 128        return {'tag': tag_name,
 129                'variants': [self._gen_variant(v) for v in variants]}
 130
 131    def _gen_variant(self, variant):
 132        return {'case': variant.name, 'type': self._use_type(variant.type)}
 133
 134    def visit_builtin_type(self, name, info, json_type):
 135        self._gen_json(name, 'builtin', {'json-type': json_type})
 136
 137    def visit_enum_type(self, name, info, values, prefix):
 138        self._gen_json(name, 'enum', {'values': values})
 139
 140    def visit_array_type(self, name, info, element_type):
 141        element = self._use_type(element_type)
 142        self._gen_json('[' + element + ']', 'array', {'element-type': element})
 143
 144    def visit_object_type_flat(self, name, info, members, variants):
 145        obj = {'members': [self._gen_member(m) for m in members]}
 146        if variants:
 147            obj.update(self._gen_variants(variants.tag_member.name,
 148                                          variants.variants))
 149        self._gen_json(name, 'object', obj)
 150
 151    def visit_alternate_type(self, name, info, variants):
 152        self._gen_json(name, 'alternate',
 153                       {'members': [{'type': self._use_type(m.type)}
 154                                    for m in variants.variants]})
 155
 156    def visit_command(self, name, info, arg_type, ret_type,
 157                      gen, success_response):
 158        arg_type = arg_type or self._schema.the_empty_object_type
 159        ret_type = ret_type or self._schema.the_empty_object_type
 160        self._gen_json(name, 'command',
 161                       {'arg-type': self._use_type(arg_type),
 162                        'ret-type': self._use_type(ret_type)})
 163
 164    def visit_event(self, name, info, arg_type):
 165        arg_type = arg_type or self._schema.the_empty_object_type
 166        self._gen_json(name, 'event', {'arg-type': self._use_type(arg_type)})
 167
 168# Debugging aid: unmask QAPI schema's type names
 169# We normally mask them, because they're not QMP wire ABI
 170opt_unmask = False
 171
 172(input_file, output_dir, do_c, do_h, prefix, opts) = \
 173    parse_command_line("u", ["unmask-non-abi-names"])
 174
 175for o, a in opts:
 176    if o in ("-u", "--unmask-non-abi-names"):
 177        opt_unmask = True
 178
 179c_comment = '''
 180/*
 181 * QAPI/QMP schema introspection
 182 *
 183 * Copyright (C) 2015 Red Hat, Inc.
 184 *
 185 * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
 186 * See the COPYING.LIB file in the top-level directory.
 187 *
 188 */
 189'''
 190h_comment = '''
 191/*
 192 * QAPI/QMP schema introspection
 193 *
 194 * Copyright (C) 2015 Red Hat, Inc.
 195 *
 196 * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
 197 * See the COPYING.LIB file in the top-level directory.
 198 *
 199 */
 200'''
 201
 202(fdef, fdecl) = open_output(output_dir, do_c, do_h, prefix,
 203                            'qmp-introspect.c', 'qmp-introspect.h',
 204                            c_comment, h_comment)
 205
 206fdef.write(mcgen('''
 207#include "qemu/osdep.h"
 208#include "%(prefix)sqmp-introspect.h"
 209
 210''',
 211                 prefix=prefix))
 212
 213schema = QAPISchema(input_file)
 214gen = QAPISchemaGenIntrospectVisitor(opt_unmask)
 215schema.visit(gen)
 216fdef.write(gen.defn)
 217fdecl.write(gen.decl)
 218
 219close_output(fdef, fdecl)
 220