aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--block/file-posix.c6
-rw-r--r--block/gluster.c9
-rw-r--r--docs/devel/qapi-code-gen.txt38
-rw-r--r--qapi/block-core.json33
-rw-r--r--qapi/introspect.json6
-rw-r--r--qapi/misc.json6
-rw-r--r--scripts/qapi/common.py243
-rwxr-xr-xscripts/qapi/doc.py15
-rw-r--r--scripts/qapi/introspect.py6
-rw-r--r--scripts/qapi/types.py3
-rw-r--r--scripts/qapi/visit.py3
-rw-r--r--tests/Makefile.include6
-rw-r--r--tests/qapi-schema/double-type.err2
-rw-r--r--tests/qapi-schema/features-bad-type.err1
-rw-r--r--tests/qapi-schema/features-bad-type.exit1
-rw-r--r--tests/qapi-schema/features-bad-type.json3
-rw-r--r--tests/qapi-schema/features-bad-type.out0
-rw-r--r--tests/qapi-schema/features-duplicate-name.err1
-rw-r--r--tests/qapi-schema/features-duplicate-name.exit1
-rw-r--r--tests/qapi-schema/features-duplicate-name.json3
-rw-r--r--tests/qapi-schema/features-duplicate-name.out0
-rw-r--r--tests/qapi-schema/features-missing-name.err1
-rw-r--r--tests/qapi-schema/features-missing-name.exit1
-rw-r--r--tests/qapi-schema/features-missing-name.json3
-rw-r--r--tests/qapi-schema/features-missing-name.out0
-rw-r--r--tests/qapi-schema/features-name-bad-type.err1
-rw-r--r--tests/qapi-schema/features-name-bad-type.exit1
-rw-r--r--tests/qapi-schema/features-name-bad-type.json3
-rw-r--r--tests/qapi-schema/features-name-bad-type.out0
-rw-r--r--tests/qapi-schema/features-no-list.err1
-rw-r--r--tests/qapi-schema/features-no-list.exit1
-rw-r--r--tests/qapi-schema/features-no-list.json3
-rw-r--r--tests/qapi-schema/features-no-list.out0
-rw-r--r--tests/qapi-schema/features-unknown-key.err2
-rw-r--r--tests/qapi-schema/features-unknown-key.exit1
-rw-r--r--tests/qapi-schema/features-unknown-key.json3
-rw-r--r--tests/qapi-schema/features-unknown-key.out0
-rw-r--r--tests/qapi-schema/qapi-schema-test.json39
-rw-r--r--tests/qapi-schema/qapi-schema-test.out43
-rw-r--r--tests/qapi-schema/test-qapi.py7
-rw-r--r--tests/qapi-schema/unknown-expr-key.err2
-rw-r--r--tests/test-qmp-cmds.c8
42 files changed, 447 insertions, 59 deletions
diff --git a/block/file-posix.c b/block/file-posix.c
index 6e6bf3f4a5..83ab1b78ef 100644
--- a/block/file-posix.c
+++ b/block/file-posix.c
@@ -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 */ }
}
diff --git a/block/gluster.c b/block/gluster.c
index dc16f0c329..62f8ff2147 100644
--- a/block/gluster.c
+++ b/block/gluster.c
@@ -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,
diff --git a/docs/devel/qapi-code-gen.txt b/docs/devel/qapi-code-gen.txt
index b517b0cfbf..e8ec8ac1de 100644
--- a/docs/devel/qapi-code-gen.txt
+++ b/docs/devel/qapi-code-gen.txt
@@ -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.
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 1defcde048..fcd054fcb1 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -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
##
diff --git a/qapi/introspect.json b/qapi/introspect.json
index 3d22166b2b..1843c1cb17 100644
--- a/qapi/introspect.json
+++ b/qapi/introspect.json
@@ -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:
diff --git a/qapi/misc.json b/qapi/misc.json
index 8b3ca4fdd3..dc4cf9da20 100644
--- a/qapi/misc.json
+++ b/qapi/misc.json
@@ -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:
diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index f07869ec73..d61bfdc526 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -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.
+
+ 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.
- def _append_symbol_line(self, line):
+ """
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']
diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py
index 5c8c136899..5fc0fc7e06 100755
--- a/scripts/qapi/doc.py
+++ b/scripts/qapi/doc.py
@@ -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
diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py
index f7f2ca07e4..f62cf0a2e1 100644
--- a/scripts/qapi/introspect.py
+++ b/scripts/qapi/introspect.py
@@ -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):
diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py
index 2bd6fcd44f..3edd9374aa 100644
--- a/scripts/qapi/types.py
+++ b/scripts/qapi/types.py
@@ -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
diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py
index fd356151d2..484ebb66ad 100644
--- a/scripts/qapi/visit.py
+++ b/scripts/qapi/visit.py
@@ -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
diff --git a/tests/Makefile.include b/tests/Makefile.include
index 0cd5f465b7..db750dd6d0 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -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
diff --git a/tests/qapi-schema/double-type.err b/tests/qapi-schema/double-type.err
index 799193dba1..69457173a7 100644
--- a/tests/qapi-schema/double-type.err
+++ b/tests/qapi-schema/double-type.err
@@ -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'.
diff --git a/tests/qapi-schema/features-bad-type.err b/tests/qapi-schema/features-bad-type.err
new file mode 100644
index 0000000000..5fb95c2f90
--- /dev/null
+++ b/tests/qapi-schema/features-bad-type.err
@@ -0,0 +1 @@
+tests/qapi-schema/features-bad-type.json:1: Feature of struct FeatureStruct0 requires a string name
diff --git a/tests/qapi-schema/features-bad-type.exit b/tests/qapi-schema/features-bad-type.exit
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/tests/qapi-schema/features-bad-type.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/features-bad-type.json b/tests/qapi-schema/features-bad-type.json
new file mode 100644
index 0000000000..57db5540e7
--- /dev/null
+++ b/tests/qapi-schema/features-bad-type.json
@@ -0,0 +1,3 @@
+{ 'struct': 'FeatureStruct0',
+ 'data': { 'foo': 'int' },
+ 'features': [ [ 'a feature cannot be an array' ] ] }
diff --git a/tests/qapi-schema/features-bad-type.out b/tests/qapi-schema/features-bad-type.out
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/qapi-schema/features-bad-type.out
diff --git a/tests/qapi-schema/features-duplicate-name.err b/tests/qapi-schema/features-duplicate-name.err
new file mode 100644
index 0000000000..c0a4cccae6
--- /dev/null
+++ b/tests/qapi-schema/features-duplicate-name.err
@@ -0,0 +1 @@
+tests/qapi-schema/features-duplicate-name.json:1: 'foo' (feature of FeatureStruct0) collides with 'foo' (feature of FeatureStruct0)
diff --git a/tests/qapi-schema/features-duplicate-name.exit b/tests/qapi-schema/features-duplicate-name.exit
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/tests/qapi-schema/features-duplicate-name.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/features-duplicate-name.json b/tests/qapi-schema/features-duplicate-name.json
new file mode 100644
index 0000000000..29358e6220
--- /dev/null
+++ b/tests/qapi-schema/features-duplicate-name.json
@@ -0,0 +1,3 @@
+{ 'struct': 'FeatureStruct0',
+ 'data': { 'foo': 'int' },
+ 'features': [ 'foo', 'bar', 'foo' ] }
diff --git a/tests/qapi-schema/features-duplicate-name.out b/tests/qapi-schema/features-duplicate-name.out
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/qapi-schema/features-duplicate-name.out
diff --git a/tests/qapi-schema/features-missing-name.err b/tests/qapi-schema/features-missing-name.err
new file mode 100644
index 0000000000..4f1d2715aa
--- /dev/null
+++ b/tests/qapi-schema/features-missing-name.err
@@ -0,0 +1 @@
+tests/qapi-schema/features-missing-name.json:1: Key 'name' is missing from feature of struct FeatureStruct0
diff --git a/tests/qapi-schema/features-missing-name.exit b/tests/qapi-schema/features-missing-name.exit
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/tests/qapi-schema/features-missing-name.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/features-missing-name.json b/tests/qapi-schema/features-missing-name.json
new file mode 100644
index 0000000000..2314f97c00
--- /dev/null
+++ b/tests/qapi-schema/features-missing-name.json
@@ -0,0 +1,3 @@
+{ 'struct': 'FeatureStruct0',
+ 'data': { 'foo': 'int' },
+ 'features': [ { 'if': 'defined(NAMELESS_FEATURES)' } ] }
diff --git a/tests/qapi-schema/features-missing-name.out b/tests/qapi-schema/features-missing-name.out
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/qapi-schema/features-missing-name.out
diff --git a/tests/qapi-schema/features-name-bad-type.err b/tests/qapi-schema/features-name-bad-type.err
new file mode 100644
index 0000000000..8a3eecb972
--- /dev/null
+++ b/tests/qapi-schema/features-name-bad-type.err
@@ -0,0 +1 @@
+tests/qapi-schema/features-name-bad-type.json:1: Feature of struct FeatureStruct0 requires a string name
diff --git a/tests/qapi-schema/features-name-bad-type.exit b/tests/qapi-schema/features-name-bad-type.exit
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/tests/qapi-schema/features-name-bad-type.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/features-name-bad-type.json b/tests/qapi-schema/features-name-bad-type.json
new file mode 100644
index 0000000000..b07139978a
--- /dev/null
+++ b/tests/qapi-schema/features-name-bad-type.json
@@ -0,0 +1,3 @@
+{ 'struct': 'FeatureStruct0',
+ 'data': { 'foo': 'int' },
+ 'features': [ { 'name': { 'feature-type': 'object' } } ] }
diff --git a/tests/qapi-schema/features-name-bad-type.out b/tests/qapi-schema/features-name-bad-type.out
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/qapi-schema/features-name-bad-type.out
diff --git a/tests/qapi-schema/features-no-list.err b/tests/qapi-schema/features-no-list.err
new file mode 100644
index 0000000000..61ed68612b
--- /dev/null
+++ b/tests/qapi-schema/features-no-list.err
@@ -0,0 +1 @@
+tests/qapi-schema/features-no-list.json:1: Struct 'FeatureStruct0' requires an array for 'features'
diff --git a/tests/qapi-schema/features-no-list.exit b/tests/qapi-schema/features-no-list.exit
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/tests/qapi-schema/features-no-list.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/features-no-list.json b/tests/qapi-schema/features-no-list.json
new file mode 100644
index 0000000000..9484fd94fc
--- /dev/null
+++ b/tests/qapi-schema/features-no-list.json
@@ -0,0 +1,3 @@
+{ 'struct': 'FeatureStruct0',
+ 'data': { 'foo': 'int' },
+ 'features': 'bar' }
diff --git a/tests/qapi-schema/features-no-list.out b/tests/qapi-schema/features-no-list.out
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/qapi-schema/features-no-list.out
diff --git a/tests/qapi-schema/features-unknown-key.err b/tests/qapi-schema/features-unknown-key.err
new file mode 100644
index 0000000000..a1d693030d
--- /dev/null
+++ b/tests/qapi-schema/features-unknown-key.err
@@ -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'.
diff --git a/tests/qapi-schema/features-unknown-key.exit b/tests/qapi-schema/features-unknown-key.exit
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/tests/qapi-schema/features-unknown-key.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/features-unknown-key.json b/tests/qapi-schema/features-unknown-key.json
new file mode 100644
index 0000000000..134df3b503
--- /dev/null
+++ b/tests/qapi-schema/features-unknown-key.json
@@ -0,0 +1,3 @@
+{ 'struct': 'FeatureStruct0',
+ 'data': { 'foo': 'int' },
+ 'features': [ { 'name': 'bar', 'colour': 'red' } ] }
diff --git a/tests/qapi-schema/features-unknown-key.out b/tests/qapi-schema/features-unknown-key.out
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/qapi-schema/features-unknown-key.out
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index 0952c68734..c6d59acc3e 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -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' } }
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index 77fb1e1aa9..85d510bc00 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -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
diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
index d21fca01fc..b0f770b9bd 100644
--- a/tests/qapi-schema/test-qapi.py
+++ b/tests/qapi-schema/test-qapi.py
@@ -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)
diff --git a/tests/qapi-schema/unknown-expr-key.err b/tests/qapi-schema/unknown-expr-key.err
index 6ff8bb99c5..4340eaf894 100644
--- a/tests/qapi-schema/unknown-expr-key.err
+++ b/tests/qapi-schema/unknown-expr-key.err
@@ -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'.
diff --git a/tests/test-qmp-cmds.c b/tests/test-qmp-cmds.c
index f0b95dcbc3..ab389f42da 100644
--- a/tests/test-qmp-cmds.c
+++ b/tests/test-qmp-cmds.c
@@ -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)