QAPI patches for 2019-06-12

-----BEGIN PGP SIGNATURE-----
 
 iQJGBAABCAAwFiEENUvIs9frKmtoZ05fOHC0AOuRhlMFAl0BLAISHGFybWJydUBy
 ZWRoYXQuY29tAAoJEDhwtADrkYZTlRMQAKLpFwk4kFlqYm0KHqBtdjn57Z756Boi
 9Uu/pDtet3+tpryMNcsM3lQ6uZCl35MY1ssYGCDc6JZVnLKgsN8EyO2MKnjAhogj
 hZWWhuJdR6vHGqgK+IyIOCgfIG26HS9+ejJIWJ4qm0sCAtmNEKjKRqJNPnSxbRHc
 wm1QnxFGikhKe74luDUk5zISlDFY1CILSk7W6K0nbdq6ByrMkq0dcjSLSpwWmALQ
 KSpWVkUz9CKxJ1dZn2iQ2af4VVKsL8xbl4R5nc5RInKFJ2W3fpVJqsXbEcZC6kiT
 WntZivbthbXXwc3I4IODs49Vu6PIt6OmH8vFmcWGt5hQB89ErhndVxtfCA11Qnml
 obhsQD+3AR4vncAJo+Y6yhbZQ0Fj2fy7xOrdPNXMdcXyLx9v7oOI66oXUpy4svT2
 TR0AK+8wipt8BihId6+YynC6I5hOMWDDjKJwCnWZ20rnnn/K+oLmG2BYpYpf0CmS
 3EE1SbdB/8eIhO0tc5Vyn3YegGkuAz0x0q3gGBTXvvk5ax0SffjXvgVLqb+aWcl4
 srfm5RwbWYvB+vynxCZvrPNikQWEqcOcIzBTvbpdkpsBpfX2pLBaYvHv91F960vR
 cjbo/CU+6Z4GMCHK43RXDAoZQmbjCBuqYer/+JL9Of2w7p5SnUIrBXgnKcg/SxEg
 VGxVLQmLYieP
 =YEV2
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/armbru/tags/pull-qapi-2019-06-12' into staging

QAPI patches for 2019-06-12

# gpg: Signature made Wed 12 Jun 2019 17:44:50 BST
# gpg:                using RSA key 354BC8B3D7EB2A6B68674E5F3870B400EB918653
# gpg:                issuer "armbru@redhat.com"
# gpg: Good signature from "Markus Armbruster <armbru@redhat.com>" [full]
# gpg:                 aka "Markus Armbruster <armbru@pond.sub.org>" [full]
# Primary key fingerprint: 354B C8B3 D7EB 2A6B 6867  4E5F 3870 B400 EB91 8653

* remotes/armbru/tags/pull-qapi-2019-06-12:
  qapi: Simplify how QAPIDoc implements its state machine
  file-posix: Add dynamic-auto-read-only QAPI feature
  qapi: Allow documentation for features
  qapi: Disentangle QAPIDoc code
  tests/qapi-schema: Error case tests for features in structs
  tests/qapi-schema: Test for good feature lists in structs
  qapi: Add feature flags to struct types
  block/gluster: update .help of BLOCK_OPT_PREALLOC option
  block/file-posix: update .help of BLOCK_OPT_PREALLOC option
  qapi/block-core: update documentation of preallocation parameter
  qdev: Delete unused LostTickPolicy "merge"

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2019-06-13 11:58:00 +01:00
commit 4747524f9f
42 changed files with 447 additions and 59 deletions

View File

@ -2752,7 +2752,11 @@ static QemuOptsList raw_create_opts = {
{
.name = BLOCK_OPT_PREALLOC,
.type = QEMU_OPT_STRING,
.help = "Preallocation mode (allowed values: off, falloc, full)"
.help = "Preallocation mode (allowed values: off"
#ifdef CONFIG_POSIX_FALLOCATE
", falloc"
#endif
", full)"
},
{ /* end of list */ }
}

View File

@ -98,7 +98,14 @@ static QemuOptsList qemu_gluster_create_opts = {
{
.name = BLOCK_OPT_PREALLOC,
.type = QEMU_OPT_STRING,
.help = "Preallocation mode (allowed values: off, full)"
.help = "Preallocation mode (allowed values: off"
#ifdef CONFIG_GLUSTERFS_FALLOCATE
", falloc"
#endif
#ifdef CONFIG_GLUSTERFS_ZEROFILL
", full"
#endif
")"
},
{
.name = GLUSTER_OPT_DEBUG,

View File

@ -719,6 +719,34 @@ any non-empty complex type (struct, union, or alternate), and a
pointer to that QAPI type is passed as a single argument.
=== Features ===
Sometimes, the behaviour of QEMU changes compatibly, but without a
change in the QMP syntax (usually by allowing values or operations that
previously resulted in an error). QMP clients may still need to know
whether the extension is available.
For this purpose, a list of features can be specified for a struct type.
This is exposed to the client as a list of string, where each string
signals that this build of QEMU shows a certain behaviour.
In the schema, features can be specified as simple strings, for example:
{ 'struct': 'TestType',
'data': { 'number': 'int' },
'features': [ 'allow-negative-numbers' ] }
Another option is to specify features as dictionaries, where the key
'name' specifies the feature string to be exposed to clients:
{ 'struct': 'TestType',
'data': { 'number': 'int' },
'features': [ { 'name': 'allow-negative-numbers' } ] }
This expanded form is necessary if you want to make the feature
conditional (see below in "Configuring the schema").
=== Downstream extensions ===
QAPI schema names that are externally visible, say in the Client JSON
@ -771,6 +799,16 @@ Example: a conditional 'bar' enum member.
[ 'foo',
{ 'name' : 'bar', 'if': 'defined(IFCOND)' } ] }
Similarly, features can be specified as a dictionary with a 'name' and
an 'if' key.
Example: a conditional 'allow-negative-numbers' feature
{ 'struct': 'TestType',
'data': { 'number': 'int' },
'features': [ { 'name': 'allow-negative-numbers',
'if' 'defined(IFCOND)' } ] }
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.

View File

@ -2859,6 +2859,15 @@
# file is large, do not use in production.
# (default: off) (since: 3.0)
#
# Features:
# @dynamic-auto-read-only: If present, enabled auto-read-only means that the
# driver will open the image read-only at first,
# dynamically reopen the image file read-write when
# the first writer is attached to the node and reopen
# read-only when the last writer is detached. This
# allows giving QEMU write permissions only on demand
# when an operation actually needs write access.
#
# Since: 2.9
##
{ 'struct': 'BlockdevOptionsFile',
@ -2868,7 +2877,9 @@
'*aio': 'BlockdevAioOptions',
'*drop-cache': {'type': 'bool',
'if': 'defined(CONFIG_LINUX)'},
'*x-check-cache-dropped': 'bool' } }
'*x-check-cache-dropped': 'bool' },
'features': [ { 'name': 'dynamic-auto-read-only',
'if': 'defined(CONFIG_POSIX)' } ] }
##
# @BlockdevOptionsNull:
@ -4121,7 +4132,10 @@
#
# @filename Filename for the new image file
# @size Size of the virtual disk in bytes
# @preallocation Preallocation mode for the new image (default: off)
# @preallocation Preallocation mode for the new image (default: off;
# allowed values: off,
# falloc (if defined CONFIG_POSIX_FALLOCATE),
# full (if defined CONFIG_POSIX))
# @nocow Turn off copy-on-write (valid only on btrfs; default: off)
#
# Since: 2.12
@ -4139,7 +4153,10 @@
#
# @location Where to store the new image file
# @size Size of the virtual disk in bytes
# @preallocation Preallocation mode for the new image (default: off)
# @preallocation Preallocation mode for the new image (default: off;
# allowed values: off,
# falloc (if defined CONFIG_GLUSTERFS_FALLOCATE),
# full (if defined CONFIG_GLUSTERFS_ZEROFILL))
#
# Since: 2.12
##
@ -4243,7 +4260,8 @@
# @backing-fmt Name of the block driver to use for the backing file
# @encrypt Encryption options if the image should be encrypted
# @cluster-size qcow2 cluster size in bytes (default: 65536)
# @preallocation Preallocation mode for the new image (default: off)
# @preallocation Preallocation mode for the new image (default: off;
# allowed values: off, falloc, full, metadata)
# @lazy-refcounts True if refcounts may be updated lazily (default: off)
# @refcount-bits Width of reference counts in bits (default: 16)
#
@ -4426,7 +4444,8 @@
# @location Where to store the new image file
# @size Size of the virtual disk in bytes
# @backing-file File name of a base image
# @preallocation Preallocation mode (allowed values: off, full)
# @preallocation Preallocation mode for the new image (default: off;
# allowed values: off, full)
# @redundancy Redundancy of the image
# @object-size Object size of the image
#
@ -4461,8 +4480,8 @@
#
# @file Node to create the image format on
# @size Size of the virtual disk in bytes
# @preallocation Preallocation mode for the new image (allowed values: off,
# metadata; default: off)
# @preallocation Preallocation mode for the new image (default: off;
# allowed values: off, metadata)
#
# Since: 2.12
##

View File

@ -174,6 +174,9 @@
# and may even differ from the order of the values of the
# enum type of the @tag.
#
# @features: names of features associated with the type, in no particular
# order. (since: 4.1)
#
# Values of this type are JSON object on the wire.
#
# Since: 2.5
@ -181,7 +184,8 @@
{ 'struct': 'SchemaInfoObject',
'data': { 'members': [ 'SchemaInfoObjectMember' ],
'*tag': 'str',
'*variants': [ 'SchemaInfoObjectVariant' ] } }
'*variants': [ 'SchemaInfoObjectVariant' ],
'*features': [ 'str' ] } }
##
# @SchemaInfoObjectMember:

View File

@ -172,17 +172,13 @@
# @delay: continue to deliver ticks at the normal rate. Guest time will be
# delayed due to the late tick
#
# @merge: merge the missed tick(s) into one tick and inject. Guest time
# may be delayed, depending on how the OS reacts to the merging
# of ticks
#
# @slew: deliver ticks at a higher rate to catch up with the missed tick. The
# guest time should not be delayed once catchup is complete.
#
# Since: 2.0
##
{ 'enum': 'LostTickPolicy',
'data': ['discard', 'delay', 'merge', 'slew' ] }
'data': ['discard', 'delay', 'slew' ] }
##
# @add_client:

View File

@ -102,6 +102,24 @@ class QAPISemError(QAPIError):
class QAPIDoc(object):
"""
A documentation comment block, either expression or free-form
Expression documentation blocks consist of
* a body section: one line naming the expression, followed by an
overview (any number of lines)
* argument sections: a description of each argument (for commands
and events) or member (for structs, unions and alternates)
* features sections: a description of each feature flag
* additional (non-argument) sections, possibly tagged
Free-form documentation blocks consist only of a body section.
"""
class Section(object):
def __init__(self, name=None):
# optional section name (argument/member or section name)
@ -131,10 +149,12 @@ class QAPIDoc(object):
self.body = QAPIDoc.Section()
# dict mapping parameter name to ArgSection
self.args = OrderedDict()
self.features = OrderedDict()
# a list of Section
self.sections = []
# the current section
self._section = self.body
self._append_line = self._append_body_line
def has_section(self, name):
"""Return True if we have a section with this name."""
@ -144,7 +164,16 @@ class QAPIDoc(object):
return False
def append(self, line):
"""Parse a comment line and add it to the documentation."""
"""
Parse a comment line and add it to the documentation.
The way that the line is dealt with depends on which part of
the documentation we're parsing right now:
* The body section: ._append_line is ._append_body_line
* An argument section: ._append_line is ._append_args_line
* A features section: ._append_line is ._append_features_line
* An additional section: ._append_line is ._append_various_line
"""
line = line[1:]
if not line:
self._append_freeform(line)
@ -153,54 +182,155 @@ class QAPIDoc(object):
if line[0] != ' ':
raise QAPIParseError(self._parser, "Missing space after #")
line = line[1:]
self._append_line(line)
def end_comment(self):
self._end_section()
@staticmethod
def _is_section_tag(name):
return name in ('Returns:', 'Since:',
# those are often singular or plural
'Note:', 'Notes:',
'Example:', 'Examples:',
'TODO:')
def _append_body_line(self, line):
"""
Process a line of documentation text in the body section.
If this a symbol line and it is the section's first line, this
is an expression documentation block for that symbol.
If it's an expression documentation block, another symbol line
begins the argument section for the argument named by it, and
a section tag begins an additional section. Start that
section and append the line to it.
Else, append the line to the current section.
"""
name = line.split(' ', 1)[0]
# FIXME not nice: things like '# @foo:' and '# @foo: ' aren't
# recognized, and get silently treated as ordinary text
if self.symbol:
self._append_symbol_line(line)
elif not self.body.text and line.startswith('@'):
if not self.symbol and not self.body.text and line.startswith('@'):
if not line.endswith(':'):
raise QAPIParseError(self._parser, "Line should end with :")
self.symbol = line[1:-1]
# FIXME invalid names other than the empty string aren't flagged
if not self.symbol:
raise QAPIParseError(self._parser, "Invalid name")
elif self.symbol:
# This is an expression documentation block
if name.startswith('@') and name.endswith(':'):
self._append_line = self._append_args_line
self._append_args_line(line)
elif line == 'Features:':
self._append_line = self._append_features_line
elif self._is_section_tag(name):
self._append_line = self._append_various_line
self._append_various_line(line)
else:
self._append_freeform(line.strip())
else:
self._append_freeform(line)
# This is a free-form documentation block
self._append_freeform(line.strip())
def end_comment(self):
self._end_section()
def _append_args_line(self, line):
"""
Process a line of documentation text in an argument section.
def _append_symbol_line(self, line):
A symbol line begins the next argument section, a section tag
section or a non-indented line after a blank line begins an
additional section. Start that section and append the line to
it.
Else, append the line to the current section.
"""
name = line.split(' ', 1)[0]
if name.startswith('@') and name.endswith(':'):
line = line[len(name)+1:]
self._start_args_section(name[1:-1])
elif name in ('Returns:', 'Since:',
# those are often singular or plural
'Note:', 'Notes:',
'Example:', 'Examples:',
'TODO:'):
elif self._is_section_tag(name):
self._append_line = self._append_various_line
self._append_various_line(line)
return
elif (self._section.text.endswith('\n\n')
and line and not line[0].isspace()):
if line == 'Features:':
self._append_line = self._append_features_line
else:
self._start_section()
self._append_line = self._append_various_line
self._append_various_line(line)
return
self._append_freeform(line.strip())
def _append_features_line(self, line):
name = line.split(' ', 1)[0]
if name.startswith('@') and name.endswith(':'):
line = line[len(name)+1:]
self._start_features_section(name[1:-1])
elif self._is_section_tag(name):
self._append_line = self._append_various_line
self._append_various_line(line)
return
elif (self._section.text.endswith('\n\n')
and line and not line[0].isspace()):
self._start_section()
self._append_line = self._append_various_line
self._append_various_line(line)
return
self._append_freeform(line.strip())
def _append_various_line(self, line):
"""
Process a line of documentation text in an additional section.
A symbol line is an error.
A section tag begins an additional section. Start that
section and append the line to it.
Else, append the line to the current section.
"""
name = line.split(' ', 1)[0]
if name.startswith('@') and name.endswith(':'):
raise QAPIParseError(self._parser,
"'%s' can't follow '%s' section"
% (name, self.sections[0].name))
elif self._is_section_tag(name):
line = line[len(name)+1:]
self._start_section(name[:-1])
if (not self._section.name or
not self._section.name.startswith('Example')):
line = line.strip()
self._append_freeform(line)
def _start_args_section(self, name):
def _start_symbol_section(self, symbols_dict, name):
# FIXME invalid names other than the empty string aren't flagged
if not name:
raise QAPIParseError(self._parser, "Invalid parameter name")
if name in self.args:
if name in symbols_dict:
raise QAPIParseError(self._parser,
"'%s' parameter name duplicated" % name)
if self.sections:
raise QAPIParseError(self._parser,
"'@%s:' can't follow '%s' section"
% (name, self.sections[0].name))
assert not self.sections
self._end_section()
self._section = QAPIDoc.ArgSection(name)
self.args[name] = self._section
symbols_dict[name] = self._section
def _start_args_section(self, name):
self._start_symbol_section(self.args, name)
def _start_features_section(self, name):
self._start_symbol_section(self.features, name)
def _start_section(self, name=None):
if name in ('Returns', 'Since') and self.has_section(name):
@ -219,13 +349,6 @@ class QAPIDoc(object):
self._section = None
def _append_freeform(self, line):
in_arg = isinstance(self._section, QAPIDoc.ArgSection)
if (in_arg and self._section.text.endswith('\n\n')
and line and not line[0].isspace()):
self._start_section()
if (in_arg or not self._section.name
or not self._section.name.startswith('Example')):
line = line.strip()
match = re.match(r'(@\S+:)', line)
if match:
raise QAPIParseError(self._parser,
@ -886,12 +1009,26 @@ def check_enum(expr, info):
def check_struct(expr, info):
name = expr['struct']
members = expr['data']
features = expr.get('features')
check_type(info, "'data' for struct '%s'" % name, members,
allow_dict=True, allow_optional=True)
check_type(info, "'base' for struct '%s'" % name, expr.get('base'),
allow_metas=['struct'])
if features:
if not isinstance(features, list):
raise QAPISemError(info,
"Struct '%s' requires an array for 'features'" %
name)
for f in features:
assert isinstance(f, dict)
check_known_keys(info, "feature of struct %s" % name, f,
['name'], ['if'])
check_if(f, info)
check_name(info, "Feature of struct %s" % name, f['name'])
def check_known_keys(info, source, keys, required, optional):
@ -948,6 +1085,12 @@ def normalize_members(members):
members[key] = {'type': arg}
def normalize_features(features):
if isinstance(features, list):
features[:] = [f if isinstance(f, dict) else {'name': f}
for f in features]
def check_exprs(exprs):
global all_names
@ -986,8 +1129,10 @@ def check_exprs(exprs):
normalize_members(expr['data'])
elif 'struct' in expr:
meta = 'struct'
check_keys(expr_elem, 'struct', ['data'], ['base', 'if'])
check_keys(expr_elem, 'struct', ['data'],
['base', 'if', 'features'])
normalize_members(expr['data'])
normalize_features(expr.get('features'))
struct_types[expr[meta]] = expr
elif 'command' in expr:
meta = 'command'
@ -1126,10 +1271,12 @@ class QAPISchemaVisitor(object):
def visit_array_type(self, name, info, ifcond, element_type):
pass
def visit_object_type(self, name, info, ifcond, base, members, variants):
def visit_object_type(self, name, info, ifcond, base, members, variants,
features):
pass
def visit_object_type_flat(self, name, info, ifcond, members, variants):
def visit_object_type_flat(self, name, info, ifcond, members, variants,
features):
pass
def visit_alternate_type(self, name, info, ifcond, variants):
@ -1290,7 +1437,7 @@ class QAPISchemaArrayType(QAPISchemaType):
class QAPISchemaObjectType(QAPISchemaType):
def __init__(self, name, info, doc, ifcond,
base, local_members, variants):
base, local_members, variants, features):
# struct has local_members, optional base, and no variants
# flat union has base, variants, and no local_members
# simple union has local_members, variants, and no base
@ -1302,11 +1449,15 @@ class QAPISchemaObjectType(QAPISchemaType):
if variants is not None:
assert isinstance(variants, QAPISchemaObjectTypeVariants)
variants.set_owner(name)
for f in features:
assert isinstance(f, QAPISchemaFeature)
f.set_owner(name)
self._base_name = base
self.base = None
self.local_members = local_members
self.variants = variants
self.members = None
self.features = features
def check(self, schema):
QAPISchemaType.check(self, schema)
@ -1332,6 +1483,12 @@ class QAPISchemaObjectType(QAPISchemaType):
self.variants.check(schema, seen)
assert self.variants.tag_member in self.members
self.variants.check_clash(self.info, seen)
# Features are in a name space separate from members
seen = {}
for f in self.features:
f.check_clash(self.info, seen)
if self.doc:
self.doc.check()
@ -1368,12 +1525,15 @@ class QAPISchemaObjectType(QAPISchemaType):
def visit(self, visitor):
visitor.visit_object_type(self.name, self.info, self.ifcond,
self.base, self.local_members, self.variants)
self.base, self.local_members, self.variants,
self.features)
visitor.visit_object_type_flat(self.name, self.info, self.ifcond,
self.members, self.variants)
self.members, self.variants,
self.features)
class QAPISchemaMember(object):
""" Represents object members, enum members and features """
role = 'member'
def __init__(self, name, ifcond=None):
@ -1419,6 +1579,10 @@ class QAPISchemaMember(object):
return "'%s' %s" % (self.name, self._pretty_owner())
class QAPISchemaFeature(QAPISchemaMember):
role = 'feature'
class QAPISchemaObjectTypeMember(QAPISchemaMember):
def __init__(self, name, typ, optional, ifcond=None):
QAPISchemaMember.__init__(self, name, ifcond)
@ -1675,7 +1839,7 @@ class QAPISchema(object):
('null', 'null', 'QNull' + pointer_suffix)]:
self._def_builtin_type(*t)
self.the_empty_object_type = QAPISchemaObjectType(
'q_empty', None, None, None, None, [], None)
'q_empty', None, None, None, None, [], None, [])
self._def_entity(self.the_empty_object_type)
qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
@ -1685,6 +1849,9 @@ class QAPISchema(object):
self._def_entity(QAPISchemaEnumType('QType', None, None, None,
qtype_values, 'QTYPE'))
def _make_features(self, features):
return [QAPISchemaFeature(f['name'], f.get('if')) for f in features]
def _make_enum_members(self, values):
return [QAPISchemaMember(v['name'], v.get('if')) for v in values]
@ -1721,7 +1888,7 @@ class QAPISchema(object):
assert ifcond == typ._ifcond # pylint: disable=protected-access
else:
self._def_entity(QAPISchemaObjectType(name, info, doc, ifcond,
None, members, None))
None, members, None, []))
return name
def _def_enum_type(self, expr, info, doc):
@ -1752,9 +1919,11 @@ class QAPISchema(object):
base = expr.get('base')
data = expr['data']
ifcond = expr.get('if')
features = expr.get('features', [])
self._def_entity(QAPISchemaObjectType(name, info, doc, ifcond, base,
self._make_members(data, info),
None))
None,
self._make_features(features)))
def _make_variant(self, case, typ, ifcond):
return QAPISchemaObjectTypeVariant(case, typ, ifcond)
@ -1795,7 +1964,7 @@ class QAPISchema(object):
QAPISchemaObjectType(name, info, doc, ifcond, base, members,
QAPISchemaObjectTypeVariants(tag_name,
tag_member,
variants)))
variants), []))
def _def_alternate_type(self, expr, info, doc):
name = expr['alternate']

View File

@ -182,6 +182,17 @@ def texi_members(doc, what, base, variants, member_func):
return '\n@b{%s:}\n@table @asis\n%s@end table\n' % (what, items)
def texi_features(doc):
"""Format the table of features"""
items = ''
for section in doc.features.values():
desc = texi_format(section.text)
items += '@item @code{%s}\n%s' % (section.name, desc)
if not items:
return ''
return '\n@b{Features:}\n@table @asis\n%s@end table\n' % (items)
def texi_sections(doc, ifcond):
"""Format additional sections following arguments"""
body = ''
@ -201,6 +212,7 @@ def texi_entity(doc, what, ifcond, base=None, variants=None,
member_func=texi_member):
return (texi_body(doc)
+ texi_members(doc, what, base, variants, member_func)
+ texi_features(doc)
+ texi_sections(doc, ifcond))
@ -220,7 +232,8 @@ class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor):
body=texi_entity(doc, 'Values', ifcond,
member_func=texi_enum_value)))
def visit_object_type(self, name, info, ifcond, base, members, variants):
def visit_object_type(self, name, info, ifcond, base, members, variants,
features):
doc = self.cur_doc
if base and base.is_implicit():
base = None

View File

@ -188,11 +188,15 @@ const QLitObject %(c_name)s = %(c_string)s;
self._gen_qlit('[' + element + ']', 'array', {'element-type': element},
ifcond)
def visit_object_type_flat(self, name, info, ifcond, members, variants):
def visit_object_type_flat(self, name, info, ifcond, members, variants,
features):
obj = {'members': [self._gen_member(m) for m in members]}
if variants:
obj.update(self._gen_variants(variants.tag_member.name,
variants.variants))
if features:
obj['features'] = [(f.name, {'if': f.ifcond}) for f in features]
self._gen_qlit(name, 'object', obj, ifcond)
def visit_alternate_type(self, name, info, ifcond, variants):

View File

@ -227,7 +227,8 @@ class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor):
self._genh.add(gen_array(name, element_type))
self._gen_type_cleanup(name)
def visit_object_type(self, name, info, ifcond, base, members, variants):
def visit_object_type(self, name, info, ifcond, base, members, variants,
features):
# Nothing to do for the special empty builtin
if name == 'q_empty':
return

View File

@ -324,7 +324,8 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor):
self._genh.add(gen_visit_decl(name))
self._genc.add(gen_visit_list(name, element_type))
def visit_object_type(self, name, info, ifcond, base, members, variants):
def visit_object_type(self, name, info, ifcond, base, members, variants,
features):
# Nothing to do for the special empty builtin
if name == 'q_empty':
return

View File

@ -378,6 +378,12 @@ qapi-schema += event-boxed-empty.json
qapi-schema += event-case.json
qapi-schema += event-member-invalid-dict.json
qapi-schema += event-nest-struct.json
qapi-schema += features-bad-type.json
qapi-schema += features-duplicate-name.json
qapi-schema += features-missing-name.json
qapi-schema += features-name-bad-type.json
qapi-schema += features-no-list.json
qapi-schema += features-unknown-key.json
qapi-schema += flat-union-array-branch.json
qapi-schema += flat-union-bad-base.json
qapi-schema += flat-union-bad-discriminator.json

View File

@ -1,2 +1,2 @@
tests/qapi-schema/double-type.json:2: Unknown key 'command' in struct 'bar'
Valid keys are 'base', 'data', 'if', 'struct'.
Valid keys are 'base', 'data', 'features', 'if', 'struct'.

View File

@ -0,0 +1 @@
tests/qapi-schema/features-bad-type.json:1: Feature of struct FeatureStruct0 requires a string name

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,3 @@
{ 'struct': 'FeatureStruct0',
'data': { 'foo': 'int' },
'features': [ [ 'a feature cannot be an array' ] ] }

View File

View File

@ -0,0 +1 @@
tests/qapi-schema/features-duplicate-name.json:1: 'foo' (feature of FeatureStruct0) collides with 'foo' (feature of FeatureStruct0)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,3 @@
{ 'struct': 'FeatureStruct0',
'data': { 'foo': 'int' },
'features': [ 'foo', 'bar', 'foo' ] }

View File

@ -0,0 +1 @@
tests/qapi-schema/features-missing-name.json:1: Key 'name' is missing from feature of struct FeatureStruct0

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,3 @@
{ 'struct': 'FeatureStruct0',
'data': { 'foo': 'int' },
'features': [ { 'if': 'defined(NAMELESS_FEATURES)' } ] }

View File

@ -0,0 +1 @@
tests/qapi-schema/features-name-bad-type.json:1: Feature of struct FeatureStruct0 requires a string name

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,3 @@
{ 'struct': 'FeatureStruct0',
'data': { 'foo': 'int' },
'features': [ { 'name': { 'feature-type': 'object' } } ] }

View File

@ -0,0 +1 @@
tests/qapi-schema/features-no-list.json:1: Struct 'FeatureStruct0' requires an array for 'features'

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,3 @@
{ 'struct': 'FeatureStruct0',
'data': { 'foo': 'int' },
'features': 'bar' }

View File

View File

@ -0,0 +1,2 @@
tests/qapi-schema/features-unknown-key.json:1: Unknown key 'colour' in feature of struct FeatureStruct0
Valid keys are 'if', 'name'.

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,3 @@
{ 'struct': 'FeatureStruct0',
'data': { 'foo': 'int' },
'features': [ { 'name': 'bar', 'colour': 'red' } ] }

View File

@ -242,3 +242,42 @@
{ 'foo': 'TestIfStruct',
'bar': { 'type': ['TestIfEnum'], 'if': 'defined(TEST_IF_EVT_BAR)' } },
'if': 'defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)' }
# test 'features' for structs
{ 'struct': 'FeatureStruct0',
'data': { 'foo': 'int' },
'features': [] }
{ 'struct': 'FeatureStruct1',
'data': { 'foo': 'int' },
'features': [ 'feature1' ] }
{ 'struct': 'FeatureStruct2',
'data': { 'foo': 'int' },
'features': [ { 'name': 'feature1' } ] }
{ 'struct': 'FeatureStruct3',
'data': { 'foo': 'int' },
'features': [ 'feature1', 'feature2' ] }
{ 'struct': 'FeatureStruct4',
'data': { 'namespace-test': 'int' },
'features': [ 'namespace-test', 'int', 'name', 'if' ] }
{ 'struct': 'CondFeatureStruct1',
'data': { 'foo': 'int' },
'features': [ { 'name': 'feature1', 'if': 'defined(TEST_IF_FEATURE_1)'} ] }
{ 'struct': 'CondFeatureStruct2',
'data': { 'foo': 'int' },
'features': [ { 'name': 'feature1', 'if': 'defined(TEST_IF_FEATURE_1)'},
{ 'name': 'feature2', 'if': 'defined(TEST_IF_FEATURE_2)'} ] }
{ 'struct': 'CondFeatureStruct3',
'data': { 'foo': 'int' },
'features': [ { 'name': 'feature1', 'if': [ 'defined(TEST_IF_COND_1)',
'defined(TEST_IF_COND_2)'] } ] }
{ 'command': 'test-features',
'data': { 'fs0': 'FeatureStruct0',
'fs1': 'FeatureStruct1',
'fs2': 'FeatureStruct2',
'fs3': 'FeatureStruct3',
'fs4': 'FeatureStruct4',
'cfs1': 'CondFeatureStruct1',
'cfs2': 'CondFeatureStruct2',
'cfs3': 'CondFeatureStruct3' } }

View File

@ -354,3 +354,46 @@ object q_obj_TestIfEvent-arg
event TestIfEvent q_obj_TestIfEvent-arg
boxed=False
if ['defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)']
object FeatureStruct0
member foo: int optional=False
object FeatureStruct1
member foo: int optional=False
feature feature1
object FeatureStruct2
member foo: int optional=False
feature feature1
object FeatureStruct3
member foo: int optional=False
feature feature1
feature feature2
object FeatureStruct4
member namespace-test: int optional=False
feature namespace-test
feature int
feature name
feature if
object CondFeatureStruct1
member foo: int optional=False
feature feature1
if ['defined(TEST_IF_FEATURE_1)']
object CondFeatureStruct2
member foo: int optional=False
feature feature1
if ['defined(TEST_IF_FEATURE_1)']
feature feature2
if ['defined(TEST_IF_FEATURE_2)']
object CondFeatureStruct3
member foo: int optional=False
feature feature1
if ['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)']
object q_obj_test-features-arg
member fs0: FeatureStruct0 optional=False
member fs1: FeatureStruct1 optional=False
member fs2: FeatureStruct2 optional=False
member fs3: FeatureStruct3 optional=False
member fs4: FeatureStruct4 optional=False
member cfs1: CondFeatureStruct1 optional=False
member cfs2: CondFeatureStruct2 optional=False
member cfs3: CondFeatureStruct3 optional=False
command test-features q_obj_test-features-arg -> None
gen=True success_response=True boxed=False oob=False preconfig=False

View File

@ -38,7 +38,8 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor):
print('array %s %s' % (name, element_type.name))
self._print_if(ifcond)
def visit_object_type(self, name, info, ifcond, base, members, variants):
def visit_object_type(self, name, info, ifcond, base, members, variants,
features):
print('object %s' % name)
if base:
print(' base %s' % base.name)
@ -48,6 +49,10 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor):
self._print_if(m.ifcond, 8)
self._print_variants(variants)
self._print_if(ifcond)
if features:
for f in features:
print(' feature %s' % f.name)
self._print_if(f.ifcond, 8)
def visit_alternate_type(self, name, info, ifcond, variants):
print('alternate %s' % name)

View File

@ -1,2 +1,2 @@
tests/qapi-schema/unknown-expr-key.json:2: Unknown keys 'bogus', 'phony' in struct 'bar'
Valid keys are 'base', 'data', 'if', 'struct'.
Valid keys are 'base', 'data', 'features', 'if', 'struct'.

View File

@ -43,6 +43,14 @@ void qmp_user_def_cmd1(UserDefOne * ud1, Error **errp)
{
}
void qmp_test_features(FeatureStruct0 *fs0, FeatureStruct1 *fs1,
FeatureStruct2 *fs2, FeatureStruct3 *fs3,
FeatureStruct4 *fs4, CondFeatureStruct1 *cfs1,
CondFeatureStruct2 *cfs2, CondFeatureStruct3 *cfs3,
Error **errp)
{
}
UserDefTwo *qmp_user_def_cmd2(UserDefOne *ud1a,
bool has_udb1, UserDefOne *ud1b,
Error **errp)