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:
commit
4747524f9f
@ -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 */ }
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
##
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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']
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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'.
|
||||
|
1
tests/qapi-schema/features-bad-type.err
Normal file
1
tests/qapi-schema/features-bad-type.err
Normal file
@ -0,0 +1 @@
|
||||
tests/qapi-schema/features-bad-type.json:1: Feature of struct FeatureStruct0 requires a string name
|
1
tests/qapi-schema/features-bad-type.exit
Normal file
1
tests/qapi-schema/features-bad-type.exit
Normal file
@ -0,0 +1 @@
|
||||
1
|
3
tests/qapi-schema/features-bad-type.json
Normal file
3
tests/qapi-schema/features-bad-type.json
Normal file
@ -0,0 +1,3 @@
|
||||
{ 'struct': 'FeatureStruct0',
|
||||
'data': { 'foo': 'int' },
|
||||
'features': [ [ 'a feature cannot be an array' ] ] }
|
0
tests/qapi-schema/features-bad-type.out
Normal file
0
tests/qapi-schema/features-bad-type.out
Normal file
1
tests/qapi-schema/features-duplicate-name.err
Normal file
1
tests/qapi-schema/features-duplicate-name.err
Normal file
@ -0,0 +1 @@
|
||||
tests/qapi-schema/features-duplicate-name.json:1: 'foo' (feature of FeatureStruct0) collides with 'foo' (feature of FeatureStruct0)
|
1
tests/qapi-schema/features-duplicate-name.exit
Normal file
1
tests/qapi-schema/features-duplicate-name.exit
Normal file
@ -0,0 +1 @@
|
||||
1
|
3
tests/qapi-schema/features-duplicate-name.json
Normal file
3
tests/qapi-schema/features-duplicate-name.json
Normal file
@ -0,0 +1,3 @@
|
||||
{ 'struct': 'FeatureStruct0',
|
||||
'data': { 'foo': 'int' },
|
||||
'features': [ 'foo', 'bar', 'foo' ] }
|
0
tests/qapi-schema/features-duplicate-name.out
Normal file
0
tests/qapi-schema/features-duplicate-name.out
Normal file
1
tests/qapi-schema/features-missing-name.err
Normal file
1
tests/qapi-schema/features-missing-name.err
Normal file
@ -0,0 +1 @@
|
||||
tests/qapi-schema/features-missing-name.json:1: Key 'name' is missing from feature of struct FeatureStruct0
|
1
tests/qapi-schema/features-missing-name.exit
Normal file
1
tests/qapi-schema/features-missing-name.exit
Normal file
@ -0,0 +1 @@
|
||||
1
|
3
tests/qapi-schema/features-missing-name.json
Normal file
3
tests/qapi-schema/features-missing-name.json
Normal file
@ -0,0 +1,3 @@
|
||||
{ 'struct': 'FeatureStruct0',
|
||||
'data': { 'foo': 'int' },
|
||||
'features': [ { 'if': 'defined(NAMELESS_FEATURES)' } ] }
|
0
tests/qapi-schema/features-missing-name.out
Normal file
0
tests/qapi-schema/features-missing-name.out
Normal file
1
tests/qapi-schema/features-name-bad-type.err
Normal file
1
tests/qapi-schema/features-name-bad-type.err
Normal file
@ -0,0 +1 @@
|
||||
tests/qapi-schema/features-name-bad-type.json:1: Feature of struct FeatureStruct0 requires a string name
|
1
tests/qapi-schema/features-name-bad-type.exit
Normal file
1
tests/qapi-schema/features-name-bad-type.exit
Normal file
@ -0,0 +1 @@
|
||||
1
|
3
tests/qapi-schema/features-name-bad-type.json
Normal file
3
tests/qapi-schema/features-name-bad-type.json
Normal file
@ -0,0 +1,3 @@
|
||||
{ 'struct': 'FeatureStruct0',
|
||||
'data': { 'foo': 'int' },
|
||||
'features': [ { 'name': { 'feature-type': 'object' } } ] }
|
0
tests/qapi-schema/features-name-bad-type.out
Normal file
0
tests/qapi-schema/features-name-bad-type.out
Normal file
1
tests/qapi-schema/features-no-list.err
Normal file
1
tests/qapi-schema/features-no-list.err
Normal file
@ -0,0 +1 @@
|
||||
tests/qapi-schema/features-no-list.json:1: Struct 'FeatureStruct0' requires an array for 'features'
|
1
tests/qapi-schema/features-no-list.exit
Normal file
1
tests/qapi-schema/features-no-list.exit
Normal file
@ -0,0 +1 @@
|
||||
1
|
3
tests/qapi-schema/features-no-list.json
Normal file
3
tests/qapi-schema/features-no-list.json
Normal file
@ -0,0 +1,3 @@
|
||||
{ 'struct': 'FeatureStruct0',
|
||||
'data': { 'foo': 'int' },
|
||||
'features': 'bar' }
|
0
tests/qapi-schema/features-no-list.out
Normal file
0
tests/qapi-schema/features-no-list.out
Normal file
2
tests/qapi-schema/features-unknown-key.err
Normal file
2
tests/qapi-schema/features-unknown-key.err
Normal 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'.
|
1
tests/qapi-schema/features-unknown-key.exit
Normal file
1
tests/qapi-schema/features-unknown-key.exit
Normal file
@ -0,0 +1 @@
|
||||
1
|
3
tests/qapi-schema/features-unknown-key.json
Normal file
3
tests/qapi-schema/features-unknown-key.json
Normal file
@ -0,0 +1,3 @@
|
||||
{ 'struct': 'FeatureStruct0',
|
||||
'data': { 'foo': 'int' },
|
||||
'features': [ { 'name': 'bar', 'colour': 'red' } ] }
|
0
tests/qapi-schema/features-unknown-key.out
Normal file
0
tests/qapi-schema/features-unknown-key.out
Normal 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' } }
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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'.
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user