qapi: add 'if' to top-level expressions

Accept 'if' key in top-level elements, accepted as string or list of
string type. The following patches will modify the test visitor to
check the value is correctly saved, and generate #if/#endif code (as a
single #if/endif line or a series for a list).

Example of 'if' key:
{ 'struct': 'TestIfStruct', 'data': { 'foo': 'int' },
  'if': 'defined(TEST_IF_STRUCT)' }

The generated code is for now *unconditional*. Later patches generate
the conditionals.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180703155648.11933-2-marcandre.lureau@redhat.com>
[Commit message and Documentation improved]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
This commit is contained in:
Marc-André Lureau 2018-07-03 17:56:35 +02:00 committed by Markus Armbruster
parent b07cd3e748
commit 967c885108
22 changed files with 146 additions and 6 deletions

View File

@ -744,6 +744,35 @@ Example: Red Hat, Inc. controls redhat.com, and may therefore add a
downstream command __com.redhat_drive-mirror. downstream command __com.redhat_drive-mirror.
=== Configuring the schema ===
The 'struct', 'enum', 'union', 'alternate', 'command' and 'event'
top-level expressions can take an 'if' key. Its value must be a string
or a list of strings. A string is shorthand for a list containing just
that string. The code generated for the top-level expression will then
be guarded by #if COND for each COND in the list.
Example: a conditional struct
{ 'struct': 'IfStruct', 'data': { 'foo': 'int' },
'if': ['defined(CONFIG_FOO)', 'defined(HAVE_BAR)'] }
gets its generated code guarded like this:
#if defined(CONFIG_FOO)
#if defined(HAVE_BAR)
... generated code ...
#endif /* defined(HAVE_BAR) */
#endif /* defined(CONFIG_FOO) */
Please note that you are responsible to ensure that the C code will
compile with an arbitrary combination of conditions, since the
generators are unable to check it at this point.
The presence of 'if' keys in the schema is reflected through to the
introspection output depending on the build configuration.
== Client JSON Protocol introspection == == Client JSON Protocol introspection ==
Clients of a Client JSON Protocol commonly need to figure out what Clients of a Client JSON Protocol commonly need to figure out what

View File

@ -638,6 +638,27 @@ def add_name(name, info, meta, implicit=False):
all_names[name] = meta all_names[name] = meta
def check_if(expr, info):
def check_if_str(ifcond, info):
if not isinstance(ifcond, str):
raise QAPISemError(
info, "'if' condition must be a string or a list of strings")
if ifcond == '':
raise QAPISemError(info, "'if' condition '' makes no sense")
ifcond = expr.get('if')
if ifcond is None:
return
if isinstance(ifcond, list):
if ifcond == []:
raise QAPISemError(info, "'if' condition [] is useless")
for elt in ifcond:
check_if_str(elt, info)
else:
check_if_str(ifcond, info)
def check_type(info, source, value, allow_array=False, def check_type(info, source, value, allow_array=False,
allow_dict=False, allow_optional=False, allow_dict=False, allow_optional=False,
allow_metas=[]): allow_metas=[]):
@ -871,6 +892,8 @@ def check_keys(expr_elem, meta, required, optional=[]):
raise QAPISemError(info, raise QAPISemError(info,
"'%s' of %s '%s' should only use true value" "'%s' of %s '%s' should only use true value"
% (key, meta, name)) % (key, meta, name))
if key == 'if':
check_if(expr, info)
for key in required: for key in required:
if key not in expr: if key not in expr:
raise QAPISemError(info, "Key '%s' is missing from %s '%s'" raise QAPISemError(info, "Key '%s' is missing from %s '%s'"
@ -899,28 +922,28 @@ def check_exprs(exprs):
if 'enum' in expr: if 'enum' in expr:
meta = 'enum' meta = 'enum'
check_keys(expr_elem, 'enum', ['data'], ['prefix']) check_keys(expr_elem, 'enum', ['data'], ['if', 'prefix'])
enum_types[expr[meta]] = expr enum_types[expr[meta]] = expr
elif 'union' in expr: elif 'union' in expr:
meta = 'union' meta = 'union'
check_keys(expr_elem, 'union', ['data'], check_keys(expr_elem, 'union', ['data'],
['base', 'discriminator']) ['base', 'discriminator', 'if'])
union_types[expr[meta]] = expr union_types[expr[meta]] = expr
elif 'alternate' in expr: elif 'alternate' in expr:
meta = 'alternate' meta = 'alternate'
check_keys(expr_elem, 'alternate', ['data']) check_keys(expr_elem, 'alternate', ['data'], ['if'])
elif 'struct' in expr: elif 'struct' in expr:
meta = 'struct' meta = 'struct'
check_keys(expr_elem, 'struct', ['data'], ['base']) check_keys(expr_elem, 'struct', ['data'], ['base', 'if'])
struct_types[expr[meta]] = expr struct_types[expr[meta]] = expr
elif 'command' in expr: elif 'command' in expr:
meta = 'command' meta = 'command'
check_keys(expr_elem, 'command', [], check_keys(expr_elem, 'command', [],
['data', 'returns', 'gen', 'success-response', ['data', 'returns', 'gen', 'success-response',
'boxed', 'allow-oob', 'allow-preconfig']) 'boxed', 'allow-oob', 'allow-preconfig', 'if'])
elif 'event' in expr: elif 'event' in expr:
meta = 'event' meta = 'event'
check_keys(expr_elem, 'event', [], ['data', 'boxed']) check_keys(expr_elem, 'event', [], ['data', 'boxed', 'if'])
else: else:
raise QAPISemError(expr_elem['info'], raise QAPISemError(expr_elem['info'],
"Expression is missing metatype") "Expression is missing metatype")

View File

@ -442,6 +442,10 @@ qapi-schema += args-unknown.json
qapi-schema += bad-base.json qapi-schema += bad-base.json
qapi-schema += bad-data.json qapi-schema += bad-data.json
qapi-schema += bad-ident.json qapi-schema += bad-ident.json
qapi-schema += bad-if.json
qapi-schema += bad-if-empty.json
qapi-schema += bad-if-empty-list.json
qapi-schema += bad-if-list.json
qapi-schema += bad-type-bool.json qapi-schema += bad-type-bool.json
qapi-schema += bad-type-dict.json qapi-schema += bad-type-dict.json
qapi-schema += bad-type-int.json qapi-schema += bad-type-int.json

View File

@ -0,0 +1 @@
tests/qapi-schema/bad-if-empty-list.json:2: 'if' condition [] is useless

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,3 @@
# check empty 'if' list
{ 'struct': 'TestIfStruct', 'data': { 'foo': 'int' },
'if': [] }

View File

View File

@ -0,0 +1 @@
tests/qapi-schema/bad-if-empty.json:2: 'if' condition '' makes no sense

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,3 @@
# check empty 'if'
{ 'struct': 'TestIfStruct', 'data': { 'foo': 'int' },
'if': '' }

View File

View File

@ -0,0 +1 @@
tests/qapi-schema/bad-if-list.json:2: 'if' condition '' makes no sense

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,3 @@
# check invalid 'if' content
{ 'struct': 'TestIfStruct', 'data': { 'foo': 'int' },
'if': ['foo', ''] }

View File

View File

@ -0,0 +1 @@
tests/qapi-schema/bad-if.json:2: 'if' condition must be a string or a list of strings

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,3 @@
# check invalid 'if' type
{ 'struct': 'TestIfStruct', 'data': { 'foo': 'int' },
'if': { 'value': 'defined(TEST_IF_STRUCT)' } }

View File

View File

@ -56,6 +56,9 @@
'data': { 'string0': 'str', 'data': { 'string0': 'str',
'dict1': 'UserDefTwoDict' } } 'dict1': 'UserDefTwoDict' } }
{ 'struct': 'UserDefThree',
'data': { 'string0': 'str' } }
# dummy struct to force generation of array types not otherwise mentioned # dummy struct to force generation of array types not otherwise mentioned
{ 'struct': 'ForceArrays', { 'struct': 'ForceArrays',
'data': { 'unused1':['UserDefOne'], 'unused2':['UserDefTwo'], 'data': { 'unused1':['UserDefOne'], 'unused2':['UserDefTwo'],
@ -193,3 +196,26 @@
'data': { 'a': ['__org.qemu_x-Enum'], 'b': ['__org.qemu_x-Struct'], 'data': { 'a': ['__org.qemu_x-Enum'], 'b': ['__org.qemu_x-Struct'],
'c': '__org.qemu_x-Union2', 'd': '__org.qemu_x-Alt' }, 'c': '__org.qemu_x-Union2', 'd': '__org.qemu_x-Alt' },
'returns': '__org.qemu_x-Union1' } 'returns': '__org.qemu_x-Union1' }
# test 'if' condition handling
{ 'struct': 'TestIfStruct', 'data': { 'foo': 'int' },
'if': 'defined(TEST_IF_STRUCT)' }
{ 'enum': 'TestIfEnum', 'data': [ 'foo', 'bar' ],
'if': 'defined(TEST_IF_ENUM)' }
{ 'union': 'TestIfUnion', 'data': { 'foo': 'TestStruct' },
'if': 'defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)' }
{ 'alternate': 'TestIfAlternate', 'data': { 'foo': 'int', 'bar': 'TestStruct' },
'if': 'defined(TEST_IF_ALT) && defined(TEST_IF_STRUCT)' }
{ 'command': 'TestIfCmd', 'data': { 'foo': 'TestIfStruct' },
'returns': 'UserDefThree',
'if': ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'] }
{ 'command': 'TestCmdReturnDefThree', 'returns': 'UserDefThree' }
{ 'event': 'TestIfEvent', 'data': { 'foo': 'TestIfStruct' },
'if': 'defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)' }

View File

@ -36,6 +36,8 @@ object UserDefTwoDict
object UserDefTwo object UserDefTwo
member string0: str optional=False member string0: str optional=False
member dict1: UserDefTwoDict optional=False member dict1: UserDefTwoDict optional=False
object UserDefThree
member string0: str optional=False
object ForceArrays object ForceArrays
member unused1: UserDefOneList optional=False member unused1: UserDefOneList optional=False
member unused2: UserDefTwoList optional=False member unused2: UserDefTwoList optional=False
@ -233,3 +235,27 @@ object q_obj___org.qemu_x-command-arg
member d: __org.qemu_x-Alt optional=False member d: __org.qemu_x-Alt optional=False
command __org.qemu_x-command q_obj___org.qemu_x-command-arg -> __org.qemu_x-Union1 command __org.qemu_x-command q_obj___org.qemu_x-command-arg -> __org.qemu_x-Union1
gen=True success_response=True boxed=False oob=False preconfig=False gen=True success_response=True boxed=False oob=False preconfig=False
object TestIfStruct
member foo: int optional=False
enum TestIfEnum ['foo', 'bar']
object q_obj_TestStruct-wrapper
member data: TestStruct optional=False
enum TestIfUnionKind ['foo']
object TestIfUnion
member type: TestIfUnionKind optional=False
tag type
case foo: q_obj_TestStruct-wrapper
alternate TestIfAlternate
tag type
case foo: int
case bar: TestStruct
object q_obj_TestIfCmd-arg
member foo: TestIfStruct optional=False
command TestIfCmd q_obj_TestIfCmd-arg -> UserDefThree
gen=True success_response=True boxed=False oob=False preconfig=False
command TestCmdReturnDefThree None -> UserDefThree
gen=True success_response=True boxed=False oob=False preconfig=False
object q_obj_TestIfEvent-arg
member foo: TestIfStruct optional=False
event TestIfEvent q_obj_TestIfEvent-arg
boxed=False

View File

@ -12,6 +12,18 @@
static QmpCommandList qmp_commands; static QmpCommandList qmp_commands;
/* #if defined(TEST_IF_STRUCT) && defined(TEST_IF_CMD) */
UserDefThree *qmp_TestIfCmd(TestIfStruct *foo, Error **errp)
{
return NULL;
}
/* #endif */
UserDefThree *qmp_TestCmdReturnDefThree(Error **errp)
{
return NULL;
}
void qmp_user_def_cmd(Error **errp) void qmp_user_def_cmd(Error **errp)
{ {
} }