qemu/scripts/qapi-commands.py
Eric Blake 51e72bc1dd qapi: Swap visit_* arguments for consistent 'name' placement
JSON uses "name":value, but many of our visitor interfaces were
called with visit_type_FOO(v, &value, name, errp).  This can be
a bit confusing to have to mentally swap the parameter order to
match JSON order.  It's particularly bad for visit_start_struct(),
where the 'name' parameter is smack in the middle of the
otherwise-related group of 'obj, kind, size' parameters! It's
time to do a global swap of the parameter ordering, so that the
'name' parameter is always immediately after the Visitor argument.

Additional reason in favor of the swap: the existing include/qjson.h
prefers listing 'name' first in json_prop_*(), and I have plans to
unify that file with the qapi visitors; listing 'name' first in
qapi will minimize churn to the (admittedly few) qjson.h clients.

Later patches will then fix docs, object.h, visitor-impl.h, and
those clients to match.

Done by first patching scripts/qapi*.py by hand to make generated
files do what I want, then by running the following Coccinelle
script to affect the rest of the code base:
 $ spatch --sp-file script `git grep -l '\bvisit_' -- '**/*.[ch]'`
I then had to apply some touchups (Coccinelle insisted on TAB
indentation in visitor.h, and botched the signature of
visit_type_enum() by rewriting 'const char *const strings[]' to
the syntactically invalid 'const char*const[] strings').  The
movement of parameters is sufficient to provoke compiler errors
if any callers were missed.

    // Part 1: Swap declaration order
    @@
    type TV, TErr, TObj, T1, T2;
    identifier OBJ, ARG1, ARG2;
    @@
     void visit_start_struct
    -(TV v, TObj OBJ, T1 ARG1, const char *name, T2 ARG2, TErr errp)
    +(TV v, const char *name, TObj OBJ, T1 ARG1, T2 ARG2, TErr errp)
     { ... }

    @@
    type bool, TV, T1;
    identifier ARG1;
    @@
     bool visit_optional
    -(TV v, T1 ARG1, const char *name)
    +(TV v, const char *name, T1 ARG1)
     { ... }

    @@
    type TV, TErr, TObj, T1;
    identifier OBJ, ARG1;
    @@
     void visit_get_next_type
    -(TV v, TObj OBJ, T1 ARG1, const char *name, TErr errp)
    +(TV v, const char *name, TObj OBJ, T1 ARG1, TErr errp)
     { ... }

    @@
    type TV, TErr, TObj, T1, T2;
    identifier OBJ, ARG1, ARG2;
    @@
     void visit_type_enum
    -(TV v, TObj OBJ, T1 ARG1, T2 ARG2, const char *name, TErr errp)
    +(TV v, const char *name, TObj OBJ, T1 ARG1, T2 ARG2, TErr errp)
     { ... }

    @@
    type TV, TErr, TObj;
    identifier OBJ;
    identifier VISIT_TYPE =~ "^visit_type_";
    @@
     void VISIT_TYPE
    -(TV v, TObj OBJ, const char *name, TErr errp)
    +(TV v, const char *name, TObj OBJ, TErr errp)
     { ... }

    // Part 2: swap caller order
    @@
    expression V, NAME, OBJ, ARG1, ARG2, ERR;
    identifier VISIT_TYPE =~ "^visit_type_";
    @@
    (
    -visit_start_struct(V, OBJ, ARG1, NAME, ARG2, ERR)
    +visit_start_struct(V, NAME, OBJ, ARG1, ARG2, ERR)
    |
    -visit_optional(V, ARG1, NAME)
    +visit_optional(V, NAME, ARG1)
    |
    -visit_get_next_type(V, OBJ, ARG1, NAME, ERR)
    +visit_get_next_type(V, NAME, OBJ, ARG1, ERR)
    |
    -visit_type_enum(V, OBJ, ARG1, ARG2, NAME, ERR)
    +visit_type_enum(V, NAME, OBJ, ARG1, ARG2, ERR)
    |
    -VISIT_TYPE(V, OBJ, NAME, ERR)
    +VISIT_TYPE(V, NAME, OBJ, ERR)
    )

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <1454075341-13658-19-git-send-email-eblake@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2016-02-08 17:29:56 +01:00

330 lines
7.6 KiB
Python

#
# QAPI command marshaller generator
#
# Copyright IBM, Corp. 2011
# Copyright (C) 2014-2015 Red Hat, Inc.
#
# Authors:
# Anthony Liguori <aliguori@us.ibm.com>
# Michael Roth <mdroth@linux.vnet.ibm.com>
# 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.
from qapi import *
import re
def gen_command_decl(name, arg_type, ret_type):
return mcgen('''
%(c_type)s qmp_%(c_name)s(%(params)s);
''',
c_type=(ret_type and ret_type.c_type()) or 'void',
c_name=c_name(name),
params=gen_params(arg_type, 'Error **errp'))
def gen_call(name, arg_type, ret_type):
ret = ''
argstr = ''
if arg_type:
for memb in arg_type.members:
if memb.optional:
argstr += 'has_%s, ' % c_name(memb.name)
argstr += '%s, ' % c_name(memb.name)
lhs = ''
if ret_type:
lhs = 'retval = '
ret = mcgen('''
%(lhs)sqmp_%(c_name)s(%(args)s&err);
''',
c_name=c_name(name), args=argstr, lhs=lhs)
if ret_type:
ret += gen_err_check()
ret += mcgen('''
qmp_marshal_output_%(c_name)s(retval, ret, &err);
''',
c_name=ret_type.c_name())
return ret
def gen_marshal_vars(arg_type, ret_type):
ret = mcgen('''
Error *err = NULL;
''')
if ret_type:
ret += mcgen('''
%(c_type)s retval;
''',
c_type=ret_type.c_type())
if arg_type:
ret += mcgen('''
QmpInputVisitor *qiv = qmp_input_visitor_new_strict(QOBJECT(args));
QapiDeallocVisitor *qdv;
Visitor *v;
''')
for memb in arg_type.members:
if memb.optional:
ret += mcgen('''
bool has_%(c_name)s = false;
''',
c_name=c_name(memb.name))
ret += mcgen('''
%(c_type)s %(c_name)s = %(c_null)s;
''',
c_name=c_name(memb.name),
c_type=memb.type.c_type(),
c_null=memb.type.c_null())
ret += '\n'
else:
ret += mcgen('''
(void)args;
''')
return ret
def gen_marshal_input_visit(arg_type, dealloc=False):
ret = ''
if not arg_type:
return ret
if dealloc:
ret += mcgen('''
qmp_input_visitor_cleanup(qiv);
qdv = qapi_dealloc_visitor_new();
v = qapi_dealloc_get_visitor(qdv);
''')
else:
ret += mcgen('''
v = qmp_input_get_visitor(qiv);
''')
ret += gen_visit_fields(arg_type.members, skiperr=dealloc)
if dealloc:
ret += mcgen('''
qapi_dealloc_visitor_cleanup(qdv);
''')
return ret
def gen_marshal_output(ret_type):
return mcgen('''
static void qmp_marshal_output_%(c_name)s(%(c_type)s ret_in, QObject **ret_out, Error **errp)
{
Error *err = NULL;
QmpOutputVisitor *qov = qmp_output_visitor_new();
QapiDeallocVisitor *qdv;
Visitor *v;
v = qmp_output_get_visitor(qov);
visit_type_%(c_name)s(v, "unused", &ret_in, &err);
if (err) {
goto out;
}
*ret_out = qmp_output_get_qobject(qov);
out:
error_propagate(errp, err);
qmp_output_visitor_cleanup(qov);
qdv = qapi_dealloc_visitor_new();
v = qapi_dealloc_get_visitor(qdv);
visit_type_%(c_name)s(v, "unused", &ret_in, NULL);
qapi_dealloc_visitor_cleanup(qdv);
}
''',
c_type=ret_type.c_type(), c_name=ret_type.c_name())
def gen_marshal_proto(name):
ret = 'void qmp_marshal_%s(QDict *args, QObject **ret, Error **errp)' % c_name(name)
if not middle_mode:
ret = 'static ' + ret
return ret
def gen_marshal_decl(name):
return mcgen('''
%(proto)s;
''',
proto=gen_marshal_proto(name))
def gen_marshal(name, arg_type, ret_type):
ret = mcgen('''
%(proto)s
{
''',
proto=gen_marshal_proto(name))
ret += gen_marshal_vars(arg_type, ret_type)
ret += gen_marshal_input_visit(arg_type)
ret += gen_call(name, arg_type, ret_type)
# 'goto out' produced by gen_marshal_input_visit->gen_visit_fields()
# for each arg_type member, and by gen_call() for ret_type
if (arg_type and arg_type.members) or ret_type:
ret += mcgen('''
out:
''')
ret += mcgen('''
error_propagate(errp, err);
''')
ret += gen_marshal_input_visit(arg_type, dealloc=True)
ret += mcgen('''
}
''')
return ret
def gen_register_command(name, success_response):
options = 'QCO_NO_OPTIONS'
if not success_response:
options = 'QCO_NO_SUCCESS_RESP'
ret = mcgen('''
qmp_register_command("%(name)s", qmp_marshal_%(c_name)s, %(opts)s);
''',
name=name, c_name=c_name(name),
opts=options)
return ret
def gen_registry(registry):
ret = mcgen('''
static void qmp_init_marshal(void)
{
''')
ret += registry
ret += mcgen('''
}
qapi_init(qmp_init_marshal);
''')
return ret
class QAPISchemaGenCommandVisitor(QAPISchemaVisitor):
def __init__(self):
self.decl = None
self.defn = None
self._regy = None
self._visited_ret_types = None
def visit_begin(self, schema):
self.decl = ''
self.defn = ''
self._regy = ''
self._visited_ret_types = set()
def visit_end(self):
if not middle_mode:
self.defn += gen_registry(self._regy)
self._regy = None
self._visited_ret_types = None
def visit_command(self, name, info, arg_type, ret_type,
gen, success_response):
if not gen:
return
self.decl += gen_command_decl(name, arg_type, ret_type)
if ret_type and ret_type not in self._visited_ret_types:
self._visited_ret_types.add(ret_type)
self.defn += gen_marshal_output(ret_type)
if middle_mode:
self.decl += gen_marshal_decl(name)
self.defn += gen_marshal(name, arg_type, ret_type)
if not middle_mode:
self._regy += gen_register_command(name, success_response)
middle_mode = False
(input_file, output_dir, do_c, do_h, prefix, opts) = \
parse_command_line("m", ["middle"])
for o, a in opts:
if o in ("-m", "--middle"):
middle_mode = True
c_comment = '''
/*
* schema-defined QMP->QAPI command dispatch
*
* Copyright IBM, Corp. 2011
*
* Authors:
* Anthony Liguori <aliguori@us.ibm.com>
*
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
* See the COPYING.LIB file in the top-level directory.
*
*/
'''
h_comment = '''
/*
* schema-defined QAPI function prototypes
*
* Copyright IBM, Corp. 2011
*
* Authors:
* Anthony Liguori <aliguori@us.ibm.com>
*
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
* See the COPYING.LIB file in the top-level directory.
*
*/
'''
(fdef, fdecl) = open_output(output_dir, do_c, do_h, prefix,
'qmp-marshal.c', 'qmp-commands.h',
c_comment, h_comment)
fdef.write(mcgen('''
#include "qemu-common.h"
#include "qemu/module.h"
#include "qapi/qmp/types.h"
#include "qapi/qmp/dispatch.h"
#include "qapi/visitor.h"
#include "qapi/qmp-output-visitor.h"
#include "qapi/qmp-input-visitor.h"
#include "qapi/dealloc-visitor.h"
#include "%(prefix)sqapi-types.h"
#include "%(prefix)sqapi-visit.h"
#include "%(prefix)sqmp-commands.h"
''',
prefix=prefix))
fdecl.write(mcgen('''
#include "%(prefix)sqapi-types.h"
#include "qapi/qmp/qdict.h"
#include "qapi/error.h"
''',
prefix=prefix))
schema = QAPISchema(input_file)
gen = QAPISchemaGenCommandVisitor()
schema.visit(gen)
fdef.write(gen.defn)
fdecl.write(gen.decl)
close_output(fdef, fdecl)