qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-devel] [PATCH v5 02/20] qapi.py: add a simple #ifdef condition


From: Markus Armbruster
Subject: Re: [Qemu-devel] [PATCH v5 02/20] qapi.py: add a simple #ifdef conditional
Date: Wed, 07 Sep 2016 15:40:46 +0200
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/24.5 (gnu/linux)

Markus Armbruster <address@hidden> writes:

> Marc-André Lureau <address@hidden> writes:
>
>> Hi
>>
>> On Tue, Sep 6, 2016 at 7:58 PM Markus Armbruster <address@hidden> wrote:
>>
>>> QAPI language design issues, copying Eric.
>>>
>>> Marc-André Lureau <address@hidden> writes:
>>>
>>> > Hi
>>> >
>>> > On Tue, Sep 6, 2016 at 5:00 PM Markus Armbruster <address@hidden>
>>> wrote:
[...]
>>> >> Yet another option is to add 'ifdef' keys to the definitions
>>> >> themselves, i.e.
>>> >>
>>> >>     { 'command': 'query-gic-capabilities', 'returns': ['GICCapability'],
>>> >>       'ifdef': 'TARGET_ARM' }
>>> >>
>>> >
>>> > That's the worst of all options imho, as it makes it hard to filter out
>>> > unions/enums, ex:
>>> >
>>> >  @ -3446,8 +3466,10 @@
>>> >                                         'testdev': 'ChardevCommon',
>>> >                                         'stdio'  : 'ChardevStdio',
>>> >                                         'console': 'ChardevCommon',
>>> > +#ifdef CONFIG_SPICE
>>> >                                         'spicevmc' :
>>> 'ChardevSpiceChannel',
>>> >                                         'spiceport' : 'ChardevSpicePort',
>>> > +#endif
>>> >                                         'vc'     : 'ChardevVC',
>>> >                                         'ringbuf': 'ChardevRingbuf',
>>>
>>> Point taken.
>>>
>>> Fixable the same way as always when a definition needs to grow
>>> additional properties, but the syntax only provides a single value: make
>>> that single value an object, and the old non-object value syntactic
>>> sugar for the equivalent object value.  We've previously discussed this
>>> technique in the context of giving command arguments default values.
>>> I'm not saying this is what we should do here, only pointing out it's
>>> possible.
>>>
>>
>> Ok, but let's find something, if possible simple and convenient, no?
>
> I agree it needs to be simple, both the interface (QAPI language) and
> the implementation.  However, I don't like "first past the post".  I
> prefer to explore the design space a bit.
>
> So let me explore the "add 'ifdef' keys to definitions" corner of the
> QAPI language design space.
>
> Easily done for top-level definitions, because they're all JSON objects.
> Could even add it to the include directive if we wanted to.
>
> Less easily done for enumeration, struct, union and alternate members.
> Note that command and event arguments specified inline are a special
> case of struct members.
>
> The "can't specify additional stuff for struct members" problem isn't
> new.  We hacked around it to specify "optional": encode it into the
> member name.  Doesn't scale.  We need to solve the problem to be able to
> specify default values, and we already decided how: have an JSON object
> instead of a mere JSON string, make the string syntax sugar for {
> 'type': STRING }.  See commit 6b5abc7 and the discussion in qemu-devel
> leading up to it.  For consistency, we'll do it for union and alternate
> members, too.
>
> That leaves just enumeration members.  The same technique applies.
>
> If I remember correctly, we only need conditional commands right now, to
> avoid regressing query-commands.  The more complicated member work could
> be done later.

To gauge whether this idea is practical, I implemented key 'if' for
commands.  It's just a sketch, and has a number of issues, which I
marked FIXME.

I ported qmp-commands.hx's #if to qapi-schema.json.  The TARGET_FOO are
poisoned, so I commented them out.  There's a CONFIG_SPICE left, which
will do for testing.

I also turned key 'gen': false into 'if': false.  Possibly a bad idea.

Anyway, diffstat isn't bad:

 docs/qapi-code-gen.txt                     | 14 ++++++-----
 qapi-schema.json                           | 15 ++++++++---
 qapi/introspect.json                       |  2 +-
 scripts/qapi-commands.py                   | 12 +++++++--
 scripts/qapi-introspect.py                 | 22 ++++++++++------
 scripts/qapi.py                            | 40 ++++++++++++++++++++++--------
 tests/qapi-schema/type-bypass-bad-gen.err  |  2 +-
 tests/qapi-schema/type-bypass-bad-gen.json |  4 +--
 8 files changed, 77 insertions(+), 34 deletions(-)

diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index de298dc..93e99d8 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -423,7 +423,7 @@ part of a Client JSON Protocol command.  The 'data' member 
is optional
 and defaults to {} (an empty dictionary).  If present, it must be the
 string name of a complex type, or a dictionary that declares an
 anonymous type with the same semantics as a 'struct' expression, with
-one exception noted below when 'gen' is used.
+one exception noted below when 'if': false is used.
 
 The 'returns' member describes what will appear in the "return" member
 of a Client JSON Protocol reply on successful completion of a command.
@@ -431,8 +431,8 @@ The member is optional from the command declaration; if 
absent, the
 "return" member will be an empty dictionary.  If 'returns' is present,
 it must be the string name of a complex or built-in type, a
 one-element array containing the name of a complex or built-in type,
-with one exception noted below when 'gen' is used.  Although it is
-permitted to have the 'returns' member name a built-in type or an
+with one exception noted below when 'if':false is used.  Although it
+is permitted to have the 'returns' member name a built-in type or an
 array of built-in types, any command that does this cannot be extended
 to return additional information in the future; thus, new commands
 should strongly consider returning a dictionary-based type or an array
@@ -475,16 +475,18 @@ arguments for the user's function out of an input QDict, 
calls the
 user's function, and if it succeeded, builds an output QObject from
 its return value.
 
+FIXME document 'if'
+
 In rare cases, QAPI cannot express a type-safe representation of a
 corresponding Client JSON Protocol command.  You then have to suppress
-generation of a marshalling function by including a key 'gen' with
+generation of a marshalling function by including a key 'if' with
 boolean value false, and instead write your own function.  Please try
 to avoid adding new commands that rely on this, and instead use
 type-safe unions.  For an example of this usage:
 
  { 'command': 'netdev_add',
-   'data': {'type': 'str', 'id': 'str'},
-   'gen': false }
+   'if': false,
+   'data': {'type': 'str', 'id': 'str'} }
 
 Normally, the QAPI schema is used to describe synchronous exchanges,
 where a response is expected.  But in some cases, the action of a
diff --git a/qapi-schema.json b/qapi-schema.json
index c4f3674..ad0559e 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1269,7 +1269,9 @@
 #
 # Since: 0.14.0
 ##
-{ 'command': 'query-spice', 'returns': 'SpiceInfo' }
+{ 'command': 'query-spice',
+  'if': 'defined(CONFIG_SPICE)',
+  'returns': 'SpiceInfo' }
 
 ##
 # @BalloonInfo:
@@ -2355,6 +2357,7 @@
 # Since: 2.5
 ##
 { 'command': 'dump-skeys',
+#  'if': 'defined(TARGET_S390X)',
   'data': { 'filename': 'str' } }
 
 ##
@@ -2380,8 +2383,8 @@
 #          If @type is not a valid network backend, DeviceNotFound
 ##
 { 'command': 'netdev_add',
-  'data': {'type': 'str', 'id': 'str'},
-  'gen': false }                # so we can get the additional arguments
+  'if': false,                  # so we can get the additional arguments
+  'data': {'type': 'str', 'id': 'str'} }
 
 ##
 # @netdev_del:
@@ -4455,6 +4458,7 @@
 # Since: 2.1
 ##
 { 'command': 'rtc-reset-reinjection' }
+#  'if': 'defined(TARGET_I386)'
 
 # Rocker ethernet network switch
 { 'include': 'qapi/rocker.json' }
@@ -4525,7 +4529,10 @@
 #
 # Since: 2.6
 ##
-{ 'command': 'query-gic-capabilities', 'returns': ['GICCapability'] }
+{ 'command': 'query-gic-capabilities',
+#  'if': 'defined(TARGET_ARM)',
+  'returns': ['GICCapability']
+}
 
 ##
 # CpuInstanceProperties
diff --git a/qapi/introspect.json b/qapi/introspect.json
index 3fd81fb..b8f421a 100644
--- a/qapi/introspect.json
+++ b/qapi/introspect.json
@@ -46,7 +46,7 @@
 ##
 { 'command': 'query-qmp-schema',
   'returns': [ 'SchemaInfo' ],
-  'gen': false }                # just to simplify qmp_query_json()
+  'if': false }                 # just to simplify qmp_query_json()
 
 ##
 # @SchemaMetaType
diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py
index a06a2c4..f34e4cc 100644
--- a/scripts/qapi-commands.py
+++ b/scripts/qapi-commands.py
@@ -215,9 +215,13 @@ class QAPISchemaGenCommandVisitor(QAPISchemaVisitor):
         self._visited_ret_types = None
 
     def visit_command(self, name, info, arg_type, ret_type,
-                      gen, success_response, boxed):
-        if not gen:
+                      genif, success_response, boxed):
+        if genif is False:
             return
+        pp_if = gen_pp_if(genif)
+        pp_endif = gen_pp_endif(genif)
+        self.decl += pp_if
+        self.defn += pp_if      # FIXME blank lines are off
         self.decl += gen_command_decl(name, arg_type, boxed, ret_type)
         if ret_type and ret_type not in self._visited_ret_types:
             self._visited_ret_types.add(ret_type)
@@ -226,7 +230,11 @@ class QAPISchemaGenCommandVisitor(QAPISchemaVisitor):
             self.decl += gen_marshal_decl(name)
         self.defn += gen_marshal(name, arg_type, boxed, ret_type)
         if not middle_mode:
+            self._regy += pp_if
             self._regy += gen_register_command(name, success_response)
+            self._regy += pp_endif
+        self.decl += pp_endif
+        self.defn += pp_endif
 
 
 middle_mode = False
diff --git a/scripts/qapi-introspect.py b/scripts/qapi-introspect.py
index 541644e..0d8cec7 100644
--- a/scripts/qapi-introspect.py
+++ b/scripts/qapi-introspect.py
@@ -30,8 +30,6 @@ def to_json(obj, level=0):
         ret = '{' + ', '.join(elts) + '}'
     else:
         assert False                # not implemented
-    if level == 1:
-        ret = '\n' + ret
     return ret
 
 
@@ -69,8 +67,15 @@ class QAPISchemaGenIntrospectVisitor(QAPISchemaVisitor):
 extern const char %(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])
+        c_string = '"["'
+        for i in jsons:
+            js, genif = i
+            # FIXME blank lines are off
+            c_string += gen_pp_if(genif or True)
+            c_string += '\n    ' + to_c_string(to_json(js) + ', ')
+            c_string += gen_pp_endif(genif or True)
+        # FIXME trailing comma (JSON sucks)
+        c_string += '\n    "]"'
         self.defn = mcgen('''
 const char %(c_name)s[] = %(c_string)s;
 ''',
@@ -111,12 +116,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_json(self, name, mtype, obj, genif=True):
         if mtype not in ('command', 'event', 'builtin', 'array'):
             name = self._name(name)
         obj['name'] = name
         obj['meta-type'] = mtype
-        self._jsons.append(obj)
+        self._jsons.append((obj, genif))
 
     def _gen_member(self, member):
         ret = {'name': member.name, 'type': self._use_type(member.type)}
@@ -154,12 +159,13 @@ const char %(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):
+                      genif, 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',
                        {'arg-type': self._use_type(arg_type),
-                        'ret-type': self._use_type(ret_type)})
+                        'ret-type': self._use_type(ret_type)},
+                       genif)
 
     def visit_event(self, name, info, arg_type, boxed):
         arg_type = arg_type or self._schema.the_empty_object_type
diff --git a/scripts/qapi.py b/scripts/qapi.py
index 21bc32f..6c0cf9f 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -698,7 +698,13 @@ def check_keys(expr_elem, meta, required, optional=[]):
             raise QAPIExprError(info,
                                 "Unknown key '%s' in %s '%s'"
                                 % (key, meta, name))
-        if (key == 'gen' or key == 'success-response') and value is not False:
+        if (key == 'if'
+            and value is not False and not isinstance(value, str)):
+            # FIXME update error message
+            raise QAPIExprError(info,
+                                "'%s' of %s '%s' should only use false value"
+                                % (key, meta, name))
+        if (key == 'success-response') and value is not False:
             raise QAPIExprError(info,
                                 "'%s' of %s '%s' should only use false value"
                                 % (key, meta, name))
@@ -737,7 +743,7 @@ def check_exprs(exprs):
             add_struct(expr, info)
         elif 'command' in expr:
             check_keys(expr_elem, 'command', [],
-                       ['data', 'returns', 'gen', 'success-response', 'boxed'])
+                       ['data', 'returns', 'if', 'success-response', 'boxed'])
             add_name(expr['command'], info, 'command')
         elif 'event' in expr:
             check_keys(expr_elem, 'event', [], ['data', 'boxed'])
@@ -838,7 +844,7 @@ class QAPISchemaVisitor(object):
         pass
 
     def visit_command(self, name, info, arg_type, ret_type,
-                      gen, success_response, boxed):
+                      genif, success_response, boxed):
         pass
 
     def visit_event(self, name, info, arg_type, boxed):
@@ -1180,8 +1186,8 @@ class QAPISchemaAlternateType(QAPISchemaType):
 
 
 class QAPISchemaCommand(QAPISchemaEntity):
-    def __init__(self, name, info, arg_type, ret_type, gen, success_response,
-                 boxed):
+    def __init__(self, name, info, arg_type, ret_type,
+                 genif, success_response, boxed):
         QAPISchemaEntity.__init__(self, name, info)
         assert not arg_type or isinstance(arg_type, str)
         assert not ret_type or isinstance(ret_type, str)
@@ -1189,7 +1195,7 @@ class QAPISchemaCommand(QAPISchemaEntity):
         self.arg_type = None
         self._ret_type_name = ret_type
         self.ret_type = None
-        self.gen = gen
+        self.genif = genif
         self.success_response = success_response
         self.boxed = boxed
 
@@ -1216,7 +1222,7 @@ 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.genif, self.success_response, self.boxed)
 
 
 class QAPISchemaEvent(QAPISchemaEntity):
@@ -1419,17 +1425,20 @@ class QAPISchema(object):
         name = expr['command']
         data = expr.get('data')
         rets = expr.get('returns')
-        gen = expr.get('gen', True)
+        genif = expr.get('if', True)
         success_response = expr.get('success-response', True)
         boxed = expr.get('boxed', False)
         if isinstance(data, OrderedDict):
+            # TODO apply genif to the implicit object type
             data = self._make_implicit_object_type(
                 name, info, 'arg', self._make_members(data, info))
         if isinstance(rets, list):
+            # TODO apply genif to the implicit array type
+            # TODO disjunction of all the genif
             assert len(rets) == 1
             rets = self._make_array_type(rets[0], info)
-        self._def_entity(QAPISchemaCommand(name, info, data, rets, gen,
-                                           success_response, boxed))
+        self._def_entity(QAPISchemaCommand(name, info, data, rets,
+                                           genif, success_response, boxed))
 
     def _def_event(self, expr, info):
         name = expr['event']
@@ -1704,6 +1713,17 @@ def gen_params(arg_type, boxed, extra):
     return ret
 
 
+def gen_pp_if(cond):
+    if cond is True:
+        return ''
+    return '\n#if ' + cond + '\n'
+
+
+def gen_pp_endif(cond):
+    if cond is True:
+        return ''
+    return '\n#endif  /* ' + cond + ' */\n'
+
 #
 # Common command line parsing
 #
diff --git a/tests/qapi-schema/type-bypass-bad-gen.err 
b/tests/qapi-schema/type-bypass-bad-gen.err
index a83c3c6..cca25f1 100644
--- a/tests/qapi-schema/type-bypass-bad-gen.err
+++ b/tests/qapi-schema/type-bypass-bad-gen.err
@@ -1 +1 @@
-tests/qapi-schema/type-bypass-bad-gen.json:2: 'gen' of command 'foo' should 
only use false value
+tests/qapi-schema/type-bypass-bad-gen.json:2: 'if' of command 'foo' should 
only use false value
diff --git a/tests/qapi-schema/type-bypass-bad-gen.json 
b/tests/qapi-schema/type-bypass-bad-gen.json
index e8dec34..637b11f 100644
--- a/tests/qapi-schema/type-bypass-bad-gen.json
+++ b/tests/qapi-schema/type-bypass-bad-gen.json
@@ -1,2 +1,2 @@
-# 'gen' should only appear with value false
-{ 'command': 'foo', 'gen': 'whatever' }
+# 'if' should only appear with value false FIXME or str
+{ 'command': 'foo', 'if': null }



reply via email to

[Prev in Thread] Current Thread [Next in Thread]