diff options
author | Peter Maydell <peter.maydell@linaro.org> | 2019-06-13 11:58:00 +0100 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2019-06-13 11:58:00 +0100 |
commit | 4747524f9f243ca5ff1f146d37e423c00e923ee1 (patch) | |
tree | 487c935041a08eba8a83c3399ca6a9aacfc6c1fe /scripts | |
parent | 8e23e34d989d5ce542fa26425f091fc61e1f23f4 (diff) | |
parent | 157dd363955b961ef378eb1f7817c31a7fa94d10 (diff) |
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>
Diffstat (limited to 'scripts')
-rw-r--r-- | scripts/qapi/common.py | 243 | ||||
-rwxr-xr-x | scripts/qapi/doc.py | 15 | ||||
-rw-r--r-- | scripts/qapi/introspect.py | 6 | ||||
-rw-r--r-- | scripts/qapi/types.py | 3 | ||||
-rw-r--r-- | scripts/qapi/visit.py | 3 |
5 files changed, 229 insertions, 41 deletions
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 |