[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: |
Marc-André Lureau |
Subject: |
Re: [Qemu-devel] [PATCH v5 02/20] qapi.py: add a simple #ifdef conditional |
Date: |
Wed, 07 Sep 2016 14:23:42 +0000 |
Hi
On Wed, Sep 7, 2016 at 5:40 PM Markus Armbruster <address@hidden> wrote:
> 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.
>
Tbh, I think it is scratching the surface doing it only for commands.
Furthermore, it's pushing the burdden of conditional generation at a
different level, at the C level, which is probably not so nice if some day
it targets a different language.
If the main issue is using #\w* for preprocessing, we could use something
else like #! (oh no someone else did it, there are many ex where comments
are not just comments), or .\w* (.ifdef / .endif etc).
As you said, the main goal of this patch was to disable commands from the
qapi-dispatch that are currently disabled with the middle mode.
Do you think it would be reasonable to accept this simple approach for now,
until we find a better more suitable solution? After all, this is internal,
we can iterate. Or should we drop this patch for this series? I don't like
seeing it delayed by weeks...
>
> 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 }
>
--
Marc-André Lureau