From 75ecee7262548d21a9e20c12f0b3b12f8a51d5c6 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Mon, 25 Oct 2021 06:24:01 +0200 Subject: qapi: Enable enum member introspection to show more than name The next commit will add feature flags to enum members. There's a problem, though: query-qmp-schema shows an enum type's members as an array of member names (SchemaInfoEnum member @values). If it showed an array of objects with a name member, we could simply add more members to these objects. Since it's just strings, we can't. I can see three ways to correct this design mistake: 1. Do it the way we should have done it, plus compatibility goo. We want a ['SchemaInfoEnumMember'] member in SchemaInfoEnum. Since changing @values would be a compatibility break, add a new member @members instead. @values is now redundant. In my testing, output of qemu-system-x86_64's query-qmp-schema grows by 11% (18.5KiB). We can deprecate @values now and drop it later. This will break outmoded clients. Well-behaved clients such as libvirt are expected to break cleanly. 2. Like 1, but omit "boring" elements of @member, and empty @member. @values does not become redundant. @members augments it. Somewhat cumbersome, but output of query-qmp-schema grows only as we make enum members non-boring. There is nothing to deprecate here. 3. Versioned query-qmp-schema. query-qmp-schema provides either @values or @members. The QMP client can select which version it wants. There is no redundant output. We can deprecate old versions and eventually drop them. This will break outmoded clients. Breaking cleanly is easier than for 1. While 1 and 2 operate within the common rules for compatible evolution apply (section "Compatibility considerations" in docs/devel/qapi-code-gen.rst), 3 bypasses them. Attractive when operating within the rules is just too awkward. Not the case here. This commit implements 1. Libvirt developers prefer it. Deprecate @values in favour of @members. Since query-qmp-schema compatibility is pretty fundamental for management applications, an extended grace period is advised. Signed-off-by: Markus Armbruster Reviewed-by: Eric Blake Tested-by: Peter Krempa Acked-by: Peter Krempa Message-Id: <20211025042405.3762351-2-armbru@redhat.com> Reviewed-by: John Snow --- scripts/qapi/introspect.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'scripts/qapi/introspect.py') diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index 4c079ee627..6334546363 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -68,6 +68,7 @@ JSONValue = Union[_Value, 'Annotated[_Value]'] # TypedDict constructs, so they are broadly typed here as simple # Python Dicts. SchemaInfo = Dict[str, object] +SchemaInfoEnumMember = Dict[str, object] SchemaInfoObject = Dict[str, object] SchemaInfoObjectVariant = Dict[str, object] SchemaInfoObjectMember = Dict[str, object] @@ -274,8 +275,16 @@ const QLitObject %(c_name)s = %(c_string)s; obj['features'] = self._gen_features(features) self._trees.append(Annotated(obj, ifcond, comment)) - def _gen_member(self, member: QAPISchemaObjectTypeMember - ) -> Annotated[SchemaInfoObjectMember]: + @staticmethod + def _gen_enum_member(member: QAPISchemaEnumMember + ) -> Annotated[SchemaInfoEnumMember]: + obj: SchemaInfoEnumMember = { + 'name': member.name, + } + return Annotated(obj, member.ifcond) + + def _gen_object_member(self, member: QAPISchemaObjectTypeMember + ) -> Annotated[SchemaInfoObjectMember]: obj: SchemaInfoObjectMember = { 'name': member.name, 'type': self._use_type(member.type) @@ -305,7 +314,8 @@ const QLitObject %(c_name)s = %(c_string)s; prefix: Optional[str]) -> None: self._gen_tree( name, 'enum', - {'values': [Annotated(m.name, m.ifcond) for m in members]}, + {'members': [self._gen_enum_member(m) for m in members], + 'values': [Annotated(m.name, m.ifcond) for m in members]}, ifcond, features ) @@ -322,7 +332,7 @@ const QLitObject %(c_name)s = %(c_string)s; members: List[QAPISchemaObjectTypeMember], variants: Optional[QAPISchemaVariants]) -> None: obj: SchemaInfoObject = { - 'members': [self._gen_member(m) for m in members] + 'members': [self._gen_object_member(m) for m in members] } if variants: obj['tag'] = variants.tag_member.name -- cgit 1.4.1 From b6c18755e41f7b40aad4c2c8188fb1719535699d Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Mon, 25 Oct 2021 06:24:02 +0200 Subject: qapi: Add feature flags to enum members This is quite similar to commit 84ab008687 "qapi: Add feature flags to struct members", only for enums instead of structs. Special feature flag 'deprecated' is silently ignored there. This is okay only because it will be implemented shortly. Signed-off-by: Markus Armbruster Reviewed-by: Eric Blake Message-Id: <20211025042405.3762351-3-armbru@redhat.com> Reviewed-by: John Snow --- docs/devel/qapi-code-gen.rst | 16 +++++++++++----- qapi/compat.json | 2 ++ qapi/introspect.json | 5 ++++- scripts/qapi/expr.py | 3 ++- scripts/qapi/introspect.py | 5 +++-- scripts/qapi/schema.py | 22 ++++++++++++++++++++-- tests/qapi-schema/doc-good.json | 5 ++++- tests/qapi-schema/doc-good.out | 3 +++ tests/qapi-schema/doc-good.txt | 3 +++ tests/qapi-schema/enum-dict-member-unknown.err | 2 +- tests/qapi-schema/qapi-schema-test.json | 3 ++- tests/qapi-schema/qapi-schema-test.out | 1 + tests/qapi-schema/test-qapi.py | 1 + 13 files changed, 57 insertions(+), 14 deletions(-) (limited to 'scripts/qapi/introspect.py') diff --git a/docs/devel/qapi-code-gen.rst b/docs/devel/qapi-code-gen.rst index d267889d2c..4071c9074a 100644 --- a/docs/devel/qapi-code-gen.rst +++ b/docs/devel/qapi-code-gen.rst @@ -200,7 +200,9 @@ Syntax:: '*if': COND, '*features': FEATURES } ENUM-VALUE = STRING - | { 'name': STRING, '*if': COND } + | { 'name': STRING, + '*if': COND, + '*features': FEATURES } Member 'enum' names the enum type. @@ -706,8 +708,10 @@ QEMU shows a certain behaviour. Special features ~~~~~~~~~~~~~~~~ -Feature "deprecated" marks a command, event, or struct member as -deprecated. It is not supported elsewhere so far. +Feature "deprecated" marks a command, event, enum value, or struct +member as deprecated. It is not supported elsewhere so far. +Interfaces so marked may be withdrawn in future releases in accordance +with QEMU's deprecation policy. Naming rules and reserved names @@ -1157,7 +1161,8 @@ and "variants". "members" is a JSON array describing the object's common members, if any. Each element is a JSON object with members "name" (the member's -name), "type" (the name of its type), and optionally "default". The +name), "type" (the name of its type), "features" (a JSON array of +feature strings), and "default". The latter two are optional. The member is optional if "default" is present. Currently, "default" can only have value null. Other values are reserved for future extensions. The "members" array is in no particular order; clients @@ -1234,7 +1239,8 @@ The SchemaInfo for an enumeration type has meta-type "enum" and variant member "members". "members" is a JSON array describing the enumeration values. Each -element is a JSON object with member "name" (the member's name). The +element is a JSON object with member "name" (the member's name), and +optionally "features" (a JSON array of feature strings). The "members" array is in no particular order; clients must search the entire array when learning whether a particular value is supported. diff --git a/qapi/compat.json b/qapi/compat.json index ae3afc22df..1d2b76f00c 100644 --- a/qapi/compat.json +++ b/qapi/compat.json @@ -42,6 +42,8 @@ # with feature 'deprecated'. We may want to extend it to cover # semantic aspects, CLI, and experimental features. # +# Limitation: not implemented for deprecated enumeration values. +# # @deprecated-input: how to handle deprecated input (default 'accept') # @deprecated-output: how to handle deprecated output (default 'accept') # diff --git a/qapi/introspect.json b/qapi/introspect.json index 9683e884f8..183148b2e9 100644 --- a/qapi/introspect.json +++ b/qapi/introspect.json @@ -167,10 +167,13 @@ # # @name: the member's name, as defined in the QAPI schema. # +# @features: names of features associated with the member, in no +# particular order. +# # Since: 6.2 ## { 'struct': 'SchemaInfoEnumMember', - 'data': { 'name': 'str' } } + 'data': { 'name': 'str', '*features': [ 'str' ] } } ## # @SchemaInfoArray: diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py index 819ea6ad97..3cb389e875 100644 --- a/scripts/qapi/expr.py +++ b/scripts/qapi/expr.py @@ -472,7 +472,7 @@ def check_enum(expr: _JSONObject, info: QAPISourceInfo) -> None: for m in members] for member in members: source = "'data' member" - check_keys(member, info, source, ['name'], ['if']) + check_keys(member, info, source, ['name'], ['if', 'features']) member_name = member['name'] check_name_is_str(member_name, info, source) source = "%s '%s'" % (source, member_name) @@ -483,6 +483,7 @@ def check_enum(expr: _JSONObject, info: QAPISourceInfo) -> None: permit_upper=permissive, permit_underscore=permissive) check_if(member, info, source) + check_features(member.get('features'), info) def check_struct(expr: _JSONObject, info: QAPISourceInfo) -> None: diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index 6334546363..67c7d89aae 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -275,12 +275,13 @@ const QLitObject %(c_name)s = %(c_string)s; obj['features'] = self._gen_features(features) self._trees.append(Annotated(obj, ifcond, comment)) - @staticmethod - def _gen_enum_member(member: QAPISchemaEnumMember + def _gen_enum_member(self, member: QAPISchemaEnumMember ) -> Annotated[SchemaInfoEnumMember]: obj: SchemaInfoEnumMember = { 'name': member.name, } + if member.features: + obj['features'] = self._gen_features(member.features) return Annotated(obj, member.ifcond) def _gen_object_member(self, member: QAPISchemaObjectTypeMember diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py index 004d7095ff..6d5f46509a 100644 --- a/scripts/qapi/schema.py +++ b/scripts/qapi/schema.py @@ -708,6 +708,19 @@ class QAPISchemaMember: class QAPISchemaEnumMember(QAPISchemaMember): role = 'value' + def __init__(self, name, info, ifcond=None, features=None): + super().__init__(name, info, ifcond) + for f in features or []: + assert isinstance(f, QAPISchemaFeature) + f.set_defined_in(name) + self.features = features or [] + + def connect_doc(self, doc): + super().connect_doc(doc) + if doc: + for f in self.features: + doc.connect_feature(f) + class QAPISchemaFeature(QAPISchemaMember): role = 'feature' @@ -980,9 +993,14 @@ class QAPISchema: QAPISchemaIfCond(f.get('if'))) for f in features] + def _make_enum_member(self, name, ifcond, features, info): + return QAPISchemaEnumMember(name, info, + QAPISchemaIfCond(ifcond), + self._make_features(features, info)) + def _make_enum_members(self, values, info): - return [QAPISchemaEnumMember(v['name'], info, - QAPISchemaIfCond(v.get('if'))) + return [self._make_enum_member(v['name'], v.get('if'), + v.get('features'), info) for v in values] def _make_array_type(self, element_type, info): diff --git a/tests/qapi-schema/doc-good.json b/tests/qapi-schema/doc-good.json index 86dc25d2bd..74745fb405 100644 --- a/tests/qapi-schema/doc-good.json +++ b/tests/qapi-schema/doc-good.json @@ -58,11 +58,14 @@ # # Features: # @enum-feat: Also _one_ {and only} +# @enum-member-feat: a member feature # # @two is undocumented ## { 'enum': 'Enum', - 'data': [ { 'name': 'one', 'if': 'IFONE' }, 'two' ], + 'data': [ { 'name': 'one', 'if': 'IFONE', + 'features': [ 'enum-member-feat' ] }, + 'two' ], 'features': [ 'enum-feat' ], 'if': 'IFCOND' } diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out index 5a324e2627..9dd65b9d92 100644 --- a/tests/qapi-schema/doc-good.out +++ b/tests/qapi-schema/doc-good.out @@ -13,6 +13,7 @@ module doc-good.json enum Enum member one if IFONE + feature enum-member-feat member two if IFCOND feature enum-feat @@ -108,6 +109,8 @@ The _one_ {and only} feature=enum-feat Also _one_ {and only} + feature=enum-member-feat +a member feature section=None @two is undocumented doc symbol=Base diff --git a/tests/qapi-schema/doc-good.txt b/tests/qapi-schema/doc-good.txt index 701402ee5e..b3b76bd43f 100644 --- a/tests/qapi-schema/doc-good.txt +++ b/tests/qapi-schema/doc-good.txt @@ -56,6 +56,9 @@ Features "enum-feat" Also _one_ {and only} +"enum-member-feat" + a member feature + "two" is undocumented diff --git a/tests/qapi-schema/enum-dict-member-unknown.err b/tests/qapi-schema/enum-dict-member-unknown.err index f8617ea179..235cde0c49 100644 --- a/tests/qapi-schema/enum-dict-member-unknown.err +++ b/tests/qapi-schema/enum-dict-member-unknown.err @@ -1,3 +1,3 @@ enum-dict-member-unknown.json: In enum 'MyEnum': enum-dict-member-unknown.json:2: 'data' member has unknown key 'bad-key' -Valid keys are 'if', 'name'. +Valid keys are 'features', 'if', 'name'. diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index 2ec50109cb..b677ab861d 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -301,7 +301,8 @@ 'TEST_IF_COND_2'] } } ] } { 'enum': 'FeatureEnum1', - 'data': [ 'eins', 'zwei', 'drei' ], + 'data': [ 'eins', 'zwei', + { 'name': 'drei', 'features': [ 'deprecated' ] } ], 'features': [ 'feature1' ] } { 'union': 'FeatureUnion1', diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index 9337adc9ea..16846dbeb8 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -341,6 +341,7 @@ enum FeatureEnum1 member eins member zwei member drei + feature deprecated feature feature1 object q_obj_FeatureUnion1-base member tag: FeatureEnum1 optional=False diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py index c717a7a90b..2160cef082 100755 --- a/tests/qapi-schema/test-qapi.py +++ b/tests/qapi-schema/test-qapi.py @@ -37,6 +37,7 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor): for m in members: print(' member %s' % m.name) self._print_if(m.ifcond, indent=8) + self._print_features(m.features, indent=8) self._print_if(ifcond) self._print_features(features) -- cgit 1.4.1