From 26ee12ad1f5fc1239b7366b696e813a27319b752 Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Mon, 5 Mar 2018 18:29:48 +0100 Subject: qapi2texi: minor python code simplification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marc-André Lureau Reviewed-by: Markus Armbruster Message-Id: <20180305172951.2150-2-marcandre.lureau@redhat.com> Signed-off-by: Eric Blake --- scripts/qapi/doc.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'scripts') diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py index 0ea68bf813..79d11bbe9b 100644 --- a/scripts/qapi/doc.py +++ b/scripts/qapi/doc.py @@ -134,10 +134,9 @@ def texi_enum_value(value): def texi_member(member, suffix=''): """Format a table of members item for an object type member""" typ = member.type.doc_type() - return '@item @code{%s%s%s}%s%s\n' % ( - member.name, - ': ' if typ else '', - typ if typ else '', + membertype = ': ' + typ if typ else '' + return '@item @code{%s%s}%s%s\n' % ( + member.name, membertype, ' (optional)' if member.optional else '', suffix) -- cgit 1.4.1 From 7d0f982bfbb92fbfc1bf6c9bcaffe745996a763c Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Mon, 5 Mar 2018 18:29:51 +0100 Subject: qapi: generate a literal qobject for introspection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the generated json string with a literal qobject. The later is easier to deal with, at run time as well as compile time: adding #if conditionals will be easier than in a json string. The output of query-qmp-schema is not changed. Signed-off-by: Marc-André Lureau Reviewed-by: Markus Armbruster Message-Id: <20180305172951.2150-5-marcandre.lureau@redhat.com> [eblake: fix python 3 failure] Signed-off-by: Eric Blake --- docs/devel/qapi-code-gen.txt | 29 ++++++++++----- monitor.c | 2 +- scripts/qapi/introspect.py | 76 +++++++++++++++++++++----------------- tests/test-qobject-input-visitor.c | 11 ++++-- 4 files changed, 70 insertions(+), 48 deletions(-) (limited to 'scripts') diff --git a/docs/devel/qapi-code-gen.txt b/docs/devel/qapi-code-gen.txt index 25b7180a18..99d81230e2 100644 --- a/docs/devel/qapi-code-gen.txt +++ b/docs/devel/qapi-code-gen.txt @@ -1319,18 +1319,27 @@ Example: #ifndef EXAMPLE_QMP_INTROSPECT_H #define EXAMPLE_QMP_INTROSPECT_H - extern const char example_qmp_schema_json[]; + extern const QLitObject qmp_schema_qlit; #endif $ cat qapi-generated/example-qapi-introspect.c [Uninteresting stuff omitted...] - const char example_qmp_schema_json[] = "[" - "{\"arg-type\": \"0\", \"meta-type\": \"event\", \"name\": \"MY_EVENT\"}, " - "{\"arg-type\": \"1\", \"meta-type\": \"command\", \"name\": \"my-command\", \"ret-type\": \"2\"}, " - "{\"members\": [], \"meta-type\": \"object\", \"name\": \"0\"}, " - "{\"members\": [{\"name\": \"arg1\", \"type\": \"[2]\"}], \"meta-type\": \"object\", \"name\": \"1\"}, " - "{\"members\": [{\"name\": \"integer\", \"type\": \"int\"}, {\"default\": null, \"name\": \"string\", \"type\": \"str\"}], \"meta-type\": \"object\", \"name\": \"2\"}, " - "{\"element-type\": \"2\", \"meta-type\": \"array\", \"name\": \"[2]\"}, " - "{\"json-type\": \"int\", \"meta-type\": \"builtin\", \"name\": \"int\"}, " - "{\"json-type\": \"string\", \"meta-type\": \"builtin\", \"name\": \"str\"}]"; + const QLitObject example_qmp_schema_qlit = QLIT_QLIST(((QLitObject[]) { + QLIT_QDICT(((QLitDictEntry[]) { + { "arg-type", QLIT_QSTR("0") }, + { "meta-type", QLIT_QSTR("event") }, + { "name", QLIT_QSTR("Event") }, + { } + })), + QLIT_QDICT(((QLitDictEntry[]) { + { "members", QLIT_QLIST(((QLitObject[]) { + { } + })) }, + { "meta-type", QLIT_QSTR("object") }, + { "name", QLIT_QSTR("0") }, + { } + })), + ... + { } + })); diff --git a/monitor.c b/monitor.c index 3117a3e689..d92f02d98c 100644 --- a/monitor.c +++ b/monitor.c @@ -956,7 +956,7 @@ EventInfoList *qmp_query_events(Error **errp) static void qmp_query_qmp_schema(QDict *qdict, QObject **ret_data, Error **errp) { - *ret_data = qobject_from_json(qmp_schema_json, &error_abort); + *ret_data = qobject_from_qlit(&qmp_schema_qlit); } /* diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index f66c397fb0..1d46a6d6b6 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -13,26 +13,36 @@ See the COPYING file in the top-level directory. from qapi.common 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): +def to_qlit(obj, level=0, suppress_first_indent=False): + + def indent(level): + return level * 4 * ' ' + + ret = '' + if not suppress_first_indent: + ret += indent(level) if obj is None: - ret = 'null' + ret += 'QLIT_QNULL' elif isinstance(obj, str): - ret = '"' + obj.replace('"', r'\"') + '"' + ret += 'QLIT_QSTR(' + to_c_string(obj) + ')' elif isinstance(obj, list): - elts = [to_json(elt, level + 1) + elts = [to_qlit(elt, level + 1) for elt in obj] - ret = '[' + ', '.join(elts) + ']' + elts.append(indent(level + 1) + "{}") + ret += 'QLIT_QLIST(((QLitObject[]) {\n' + ret += ',\n'.join(elts) + '\n' + ret += indent(level) + '}))' 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) + '}' + elts = [] + for key, value in sorted(obj.items()): + elts.append(indent(level + 1) + '{ %s, %s }' % + (to_c_string(key), to_qlit(value, level + 1, True))) + elts.append(indent(level + 1) + '{}') + ret += 'QLIT_QDICT(((QLitDictEntry[]) {\n' + ret += ',\n'.join(elts) + '\n' + ret += indent(level) + '}))' else: assert False # not implemented - if level == 1: - ret = '\n' + ret return ret @@ -48,7 +58,7 @@ class QAPISchemaGenIntrospectVisitor(QAPISchemaMonolithicCVisitor): ' * QAPI/QMP schema introspection', __doc__) self._unmask = unmask self._schema = None - self._jsons = [] + self._qlits = [] self._used_types = [] self._name_map = {} self._genc.add(mcgen(''' @@ -63,27 +73,27 @@ class QAPISchemaGenIntrospectVisitor(QAPISchemaMonolithicCVisitor): def visit_end(self): # visit the types that are actually used - jsons = self._jsons - self._jsons = [] + qlits = self._qlits + self._qlits = [] for typ in self._used_types: typ.visit(self) # generate C # TODO can generate awfully long lines - jsons.extend(self._jsons) - name = c_name(self._prefix, protect=False) + 'qmp_schema_json' + qlits.extend(self._qlits) + name = c_name(self._prefix, protect=False) + 'qmp_schema_qlit' self._genh.add(mcgen(''' -extern const char %(c_name)s[]; +#include "qapi/qmp/qlit.h" + +extern const QLitObject %(c_name)s; ''', c_name=c_name(name))) - lines = to_json(jsons).split('\n') - c_string = '\n '.join([to_c_string(line) for line in lines]) self._genc.add(mcgen(''' -const char %(c_name)s[] = %(c_string)s; +const QLitObject %(c_name)s = %(c_string)s; ''', c_name=c_name(name), - c_string=c_string)) + c_string=to_qlit(qlits))) self._schema = None - self._jsons = [] + self._qlits = [] self._used_types = [] self._name_map = {} @@ -117,12 +127,12 @@ const char %(c_name)s[] = %(c_string)s; return '[' + self._use_type(typ.element_type) + ']' return self._name(typ.name) - def _gen_json(self, name, mtype, obj): + def _gen_qlit(self, name, mtype, obj): if mtype not in ('command', 'event', 'builtin', 'array'): name = self._name(name) obj['name'] = name obj['meta-type'] = mtype - self._jsons.append(obj) + self._qlits.append(obj) def _gen_member(self, member): ret = {'name': member.name, 'type': self._use_type(member.type)} @@ -138,24 +148,24 @@ const char %(c_name)s[] = %(c_string)s; 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}) + self._gen_qlit(name, 'builtin', {'json-type': json_type}) def visit_enum_type(self, name, info, values, prefix): - self._gen_json(name, 'enum', {'values': values}) + self._gen_qlit(name, 'enum', {'values': values}) def visit_array_type(self, name, info, element_type): element = self._use_type(element_type) - self._gen_json('[' + element + ']', 'array', {'element-type': element}) + self._gen_qlit('[' + element + ']', 'array', {'element-type': element}) 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) + self._gen_qlit(name, 'object', obj) def visit_alternate_type(self, name, info, variants): - self._gen_json(name, 'alternate', + self._gen_qlit(name, 'alternate', {'members': [{'type': self._use_type(m.type)} for m in variants.variants]}) @@ -163,13 +173,13 @@ const char %(c_name)s[] = %(c_string)s; gen, success_response, boxed): 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', + self._gen_qlit(name, 'command', {'arg-type': self._use_type(arg_type), 'ret-type': self._use_type(ret_type)}) def visit_event(self, name, info, arg_type, boxed): arg_type = arg_type or self._schema.the_empty_object_type - self._gen_json(name, 'event', {'arg-type': self._use_type(arg_type)}) + self._gen_qlit(name, 'event', {'arg-type': self._use_type(arg_type)}) def gen_introspect(schema, output_dir, prefix, opt_unmask): diff --git a/tests/test-qobject-input-visitor.c b/tests/test-qobject-input-visitor.c index 79b1a8cb17..55db6bdef1 100644 --- a/tests/test-qobject-input-visitor.c +++ b/tests/test-qobject-input-visitor.c @@ -1250,24 +1250,27 @@ static void test_visitor_in_fail_alternate(TestInputVisitorData *data, } static void do_test_visitor_in_qmp_introspect(TestInputVisitorData *data, - const char *schema_json) + const QLitObject *qlit) { SchemaInfoList *schema = NULL; + QObject *obj = qobject_from_qlit(qlit); Visitor *v; - v = visitor_input_test_init_raw(data, schema_json); + v = qobject_input_visitor_new(obj); visit_type_SchemaInfoList(v, NULL, &schema, &error_abort); g_assert(schema); qapi_free_SchemaInfoList(schema); + qobject_decref(obj); + visit_free(v); } static void test_visitor_in_qmp_introspect(TestInputVisitorData *data, const void *unused) { - do_test_visitor_in_qmp_introspect(data, test_qmp_schema_json); - do_test_visitor_in_qmp_introspect(data, qmp_schema_json); + do_test_visitor_in_qmp_introspect(data, &test_qmp_schema_qlit); + do_test_visitor_in_qmp_introspect(data, &qmp_schema_qlit); } int main(int argc, char **argv) -- cgit 1.4.1 From 876c67512e2af8c05686faa9f9ff49b38d7a392c Mon Sep 17 00:00:00 2001 From: Peter Xu Date: Fri, 9 Mar 2018 17:00:00 +0800 Subject: qapi: introduce new cmd option "allow-oob" Here "oob" stands for "Out-Of-Band". When "allow-oob" is set, it means the command allows out-of-band execution. The "oob" idea is proposed by Markus Armbruster in following thread: https://lists.gnu.org/archive/html/qemu-devel/2017-09/msg02057.html This new "allow-oob" boolean will be exposed by "query-qmp-schema" as well for command entries, so that QMP clients can know which commands can be used in out-of-band calls. For example the command "migrate" originally looks like: {"name": "migrate", "ret-type": "17", "meta-type": "command", "arg-type": "86"} And it'll be changed into: {"name": "migrate", "ret-type": "17", "allow-oob": false, "meta-type": "command", "arg-type": "86"} This patch only provides the QMP interface level changes. It does not contain the real out-of-band execution implementation yet. Suggested-by: Markus Armbruster Reviewed-by: Stefan Hajnoczi Reviewed-by: Fam Zheng Signed-off-by: Peter Xu Message-Id: <20180309090006.10018-18-peterx@redhat.com> Reviewed-by: Eric Blake [eblake: rebase on introspection done by qlit] Signed-off-by: Eric Blake --- include/qapi/qmp/dispatch.h | 5 +++-- qapi/introspect.json | 6 +++++- scripts/qapi/commands.py | 18 +++++++++++++----- scripts/qapi/common.py | 15 ++++++++++----- scripts/qapi/doc.py | 2 +- scripts/qapi/introspect.py | 7 +++++-- tests/qapi-schema/test-qapi.py | 2 +- 7 files changed, 38 insertions(+), 17 deletions(-) (limited to 'scripts') diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h index 1e694b5ecf..26eb13ff41 100644 --- a/include/qapi/qmp/dispatch.h +++ b/include/qapi/qmp/dispatch.h @@ -20,8 +20,9 @@ typedef void (QmpCommandFunc)(QDict *, QObject **, Error **); typedef enum QmpCommandOptions { - QCO_NO_OPTIONS = 0x0, - QCO_NO_SUCCESS_RESP = 0x1, + QCO_NO_OPTIONS = 0x0, + QCO_NO_SUCCESS_RESP = (1U << 0), + QCO_ALLOW_OOB = (1U << 1), } QmpCommandOptions; typedef struct QmpCommand diff --git a/qapi/introspect.json b/qapi/introspect.json index 5b3e6e9d78..c7f67b7d78 100644 --- a/qapi/introspect.json +++ b/qapi/introspect.json @@ -259,12 +259,16 @@ # # @ret-type: the name of the command's result type. # +# @allow-oob: whether the command allows out-of-band execution. +# (Since: 2.12) +# # TODO: @success-response (currently irrelevant, because it's QGA, not QMP) # # Since: 2.5 ## { 'struct': 'SchemaInfoCommand', - 'data': { 'arg-type': 'str', 'ret-type': 'str' } } + 'data': { 'arg-type': 'str', 'ret-type': 'str', + 'allow-oob': 'bool' } } ## # @SchemaInfoEvent: diff --git a/scripts/qapi/commands.py b/scripts/qapi/commands.py index 21a7e0dbe6..0c5da3a54d 100644 --- a/scripts/qapi/commands.py +++ b/scripts/qapi/commands.py @@ -193,10 +193,18 @@ out: return ret -def gen_register_command(name, success_response): - options = 'QCO_NO_OPTIONS' +def gen_register_command(name, success_response, allow_oob): + options = [] + if not success_response: - options = 'QCO_NO_SUCCESS_RESP' + options += ['QCO_NO_SUCCESS_RESP'] + if allow_oob: + options += ['QCO_ALLOW_OOB'] + + if not options: + options = ['QCO_NO_OPTIONS'] + + options = " | ".join(options) ret = mcgen(''' qmp_register_command(cmds, "%(name)s", @@ -268,7 +276,7 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds); genc.add(gen_registry(self._regy, self._prefix)) def visit_command(self, name, info, arg_type, ret_type, - gen, success_response, boxed): + gen, success_response, boxed, allow_oob): if not gen: return self._genh.add(gen_command_decl(name, arg_type, boxed, ret_type)) @@ -277,7 +285,7 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds); self._genc.add(gen_marshal_output(ret_type)) self._genh.add(gen_marshal_decl(name)) self._genc.add(gen_marshal(name, arg_type, boxed, ret_type)) - self._regy += gen_register_command(name, success_response) + self._regy += gen_register_command(name, success_response, allow_oob) def gen_commands(schema, output_dir, prefix): diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index 97e9060b67..2c05e3c284 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -921,7 +921,8 @@ def check_exprs(exprs): elif 'command' in expr: meta = 'command' check_keys(expr_elem, 'command', [], - ['data', 'returns', 'gen', 'success-response', 'boxed']) + ['data', 'returns', 'gen', 'success-response', + 'boxed', 'allow-oob']) elif 'event' in expr: meta = 'event' check_keys(expr_elem, 'event', [], ['data', 'boxed']) @@ -1044,7 +1045,7 @@ class QAPISchemaVisitor(object): pass def visit_command(self, name, info, arg_type, ret_type, - gen, success_response, boxed): + gen, success_response, boxed, allow_oob): pass def visit_event(self, name, info, arg_type, boxed): @@ -1421,7 +1422,7 @@ class QAPISchemaAlternateType(QAPISchemaType): class QAPISchemaCommand(QAPISchemaEntity): def __init__(self, name, info, doc, arg_type, ret_type, - gen, success_response, boxed): + gen, success_response, boxed, allow_oob): QAPISchemaEntity.__init__(self, name, info, doc) assert not arg_type or isinstance(arg_type, str) assert not ret_type or isinstance(ret_type, str) @@ -1432,6 +1433,7 @@ class QAPISchemaCommand(QAPISchemaEntity): self.gen = gen self.success_response = success_response self.boxed = boxed + self.allow_oob = allow_oob def check(self, schema): if self._arg_type_name: @@ -1455,7 +1457,8 @@ class QAPISchemaCommand(QAPISchemaEntity): def visit(self, visitor): visitor.visit_command(self.name, self.info, self.arg_type, self.ret_type, - self.gen, self.success_response, self.boxed) + self.gen, self.success_response, + self.boxed, self.allow_oob) class QAPISchemaEvent(QAPISchemaEntity): @@ -1674,6 +1677,7 @@ class QAPISchema(object): gen = expr.get('gen', True) success_response = expr.get('success-response', True) boxed = expr.get('boxed', False) + allow_oob = expr.get('allow-oob', False) if isinstance(data, OrderedDict): data = self._make_implicit_object_type( name, info, doc, 'arg', self._make_members(data, info)) @@ -1681,7 +1685,8 @@ class QAPISchema(object): assert len(rets) == 1 rets = self._make_array_type(rets[0], info) self._def_entity(QAPISchemaCommand(name, info, doc, data, rets, - gen, success_response, boxed)) + gen, success_response, + boxed, allow_oob)) def _def_event(self, expr, info, doc): name = expr['event'] diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py index 79d11bbe9b..9b312b2c51 100644 --- a/scripts/qapi/doc.py +++ b/scripts/qapi/doc.py @@ -227,7 +227,7 @@ class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor): body=texi_entity(doc, 'Members'))) def visit_command(self, name, info, arg_type, ret_type, - gen, success_response, boxed): + gen, success_response, boxed, allow_oob): doc = self.cur_doc if boxed: body = texi_body(doc) diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index 1d46a6d6b6..f9e67e8227 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -41,6 +41,8 @@ def to_qlit(obj, level=0, suppress_first_indent=False): ret += 'QLIT_QDICT(((QLitDictEntry[]) {\n' ret += ',\n'.join(elts) + '\n' ret += indent(level) + '}))' + elif isinstance(obj, bool): + ret += 'QLIT_QBOOL(%s)' % ('true' if obj else 'false') else: assert False # not implemented return ret @@ -170,12 +172,13 @@ const QLitObject %(c_name)s = %(c_string)s; for m in variants.variants]}) def visit_command(self, name, info, arg_type, ret_type, - gen, success_response, boxed): + gen, success_response, boxed, allow_oob): arg_type = arg_type or self._schema.the_empty_object_type ret_type = ret_type or self._schema.the_empty_object_type self._gen_qlit(name, 'command', {'arg-type': self._use_type(arg_type), - 'ret-type': self._use_type(ret_type)}) + 'ret-type': self._use_type(ret_type), + 'allow-oob': allow_oob}) def visit_event(self, name, info, arg_type, boxed): arg_type = arg_type or self._schema.the_empty_object_type diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py index 67e417e298..10e68b01d9 100644 --- a/tests/qapi-schema/test-qapi.py +++ b/tests/qapi-schema/test-qapi.py @@ -42,7 +42,7 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor): self._print_variants(variants) def visit_command(self, name, info, arg_type, ret_type, - gen, success_response, boxed): + gen, success_response, boxed, allow_oob): print('command %s %s -> %s' % \ (name, arg_type and arg_type.name, ret_type and ret_type.name)) print(' gen=%s success_response=%s boxed=%s' % \ -- cgit 1.4.1