1fb0bc835SMarkus Armbruster""" 2fb0bc835SMarkus ArmbrusterQAPI introspection generator 3fb0bc835SMarkus Armbruster 4fb0bc835SMarkus ArmbrusterCopyright (C) 2015-2018 Red Hat, Inc. 5fb0bc835SMarkus Armbruster 6fb0bc835SMarkus ArmbrusterAuthors: 7fb0bc835SMarkus Armbruster Markus Armbruster <armbru@redhat.com> 8fb0bc835SMarkus Armbruster 9fb0bc835SMarkus ArmbrusterThis work is licensed under the terms of the GNU GPL, version 2. 10fb0bc835SMarkus ArmbrusterSee the COPYING file in the top-level directory. 11fb0bc835SMarkus Armbruster""" 12fb0bc835SMarkus Armbruster 139db27346SJohn Snowfrom typing import ( 149db27346SJohn Snow Any, 159db27346SJohn Snow Dict, 164f7f97a7SJohn Snow Generic, 174f7f97a7SJohn Snow Iterable, 189db27346SJohn Snow List, 199db27346SJohn Snow Optional, 204f7f97a7SJohn Snow Tuple, 214f7f97a7SJohn Snow TypeVar, 229db27346SJohn Snow Union, 239db27346SJohn Snow) 245f50cedeSJohn Snow 255af8263dSJohn Snowfrom .common import ( 265af8263dSJohn Snow c_name, 275af8263dSJohn Snow gen_endif, 285af8263dSJohn Snow gen_if, 295af8263dSJohn Snow mcgen, 305af8263dSJohn Snow) 317137a960SJohn Snowfrom .gen import QAPISchemaMonolithicCVisitor 3267fea575SJohn Snowfrom .schema import ( 3367fea575SJohn Snow QAPISchemaArrayType, 3467fea575SJohn Snow QAPISchemaBuiltinType, 3567fea575SJohn Snow QAPISchemaType, 3667fea575SJohn Snow) 37fb0bc835SMarkus Armbruster 38fb0bc835SMarkus Armbruster 399db27346SJohn Snow# This module constructs a tree data structure that is used to 409db27346SJohn Snow# generate the introspection information for QEMU. It is shaped 419db27346SJohn Snow# like a JSON value. 429db27346SJohn Snow# 439db27346SJohn Snow# A complexity over JSON is that our values may or may not be annotated. 449db27346SJohn Snow# 459db27346SJohn Snow# Un-annotated values may be: 469db27346SJohn Snow# Scalar: str, bool, None. 479db27346SJohn Snow# Non-scalar: List, Dict 489db27346SJohn Snow# _value = Union[str, bool, None, Dict[str, JSONValue], List[JSONValue]] 499db27346SJohn Snow# 509db27346SJohn Snow# With optional annotations, the type of all values is: 519db27346SJohn Snow# JSONValue = Union[_Value, Annotated[_Value]] 529db27346SJohn Snow# 539db27346SJohn Snow# Sadly, mypy does not support recursive types; so the _Stub alias is used to 549db27346SJohn Snow# mark the imprecision in the type model where we'd otherwise use JSONValue. 559db27346SJohn Snow_Stub = Any 569db27346SJohn Snow_Scalar = Union[str, bool, None] 579db27346SJohn Snow_NonScalar = Union[Dict[str, _Stub], List[_Stub]] 589db27346SJohn Snow_Value = Union[_Scalar, _NonScalar] 594f7f97a7SJohn SnowJSONValue = Union[_Value, 'Annotated[_Value]'] 609db27346SJohn Snow 619db27346SJohn Snow 624f7f97a7SJohn Snow_ValueT = TypeVar('_ValueT', bound=_Value) 634f7f97a7SJohn Snow 644f7f97a7SJohn Snow 654f7f97a7SJohn Snowclass Annotated(Generic[_ValueT]): 664f7f97a7SJohn Snow """ 674f7f97a7SJohn Snow Annotated generally contains a SchemaInfo-like type (as a dict), 684f7f97a7SJohn Snow But it also used to wrap comments/ifconds around scalar leaf values, 694f7f97a7SJohn Snow for the benefit of features and enums. 704f7f97a7SJohn Snow """ 714f7f97a7SJohn Snow # TODO: Remove after Python 3.7 adds @dataclass: 724f7f97a7SJohn Snow # pylint: disable=too-few-public-methods 734f7f97a7SJohn Snow def __init__(self, value: _ValueT, ifcond: Iterable[str], 744f7f97a7SJohn Snow comment: Optional[str] = None): 754f7f97a7SJohn Snow self.value = value 764f7f97a7SJohn Snow self.comment: Optional[str] = comment 774f7f97a7SJohn Snow self.ifcond: Tuple[str, ...] = tuple(ifcond) 7824cfd6adSMarkus Armbruster 7924cfd6adSMarkus Armbruster 8005556960SJohn Snowdef _tree_to_qlit(obj, level=0, dict_value=False): 817d0f982bSMarc-André Lureau 827d0f982bSMarc-André Lureau def indent(level): 837d0f982bSMarc-André Lureau return level * 4 * ' ' 847d0f982bSMarc-André Lureau 854f7f97a7SJohn Snow if isinstance(obj, Annotated): 8605556960SJohn Snow # NB: _tree_to_qlit is called recursively on the values of a 8705556960SJohn Snow # key:value pair; those values can't be decorated with 8805556960SJohn Snow # comments or conditionals. 8905556960SJohn Snow msg = "dict values cannot have attached comments or if-conditionals." 9005556960SJohn Snow assert not dict_value, msg 9105556960SJohn Snow 928c643361SEric Blake ret = '' 934f7f97a7SJohn Snow if obj.comment: 944f7f97a7SJohn Snow ret += indent(level) + '/* %s */\n' % obj.comment 954f7f97a7SJohn Snow if obj.ifcond: 964f7f97a7SJohn Snow ret += gen_if(obj.ifcond) 974f7f97a7SJohn Snow ret += _tree_to_qlit(obj.value, level) 984f7f97a7SJohn Snow if obj.ifcond: 994f7f97a7SJohn Snow ret += '\n' + gen_endif(obj.ifcond) 100d626b6c1SMarc-André Lureau return ret 101d626b6c1SMarc-André Lureau 1027d0f982bSMarc-André Lureau ret = '' 10305556960SJohn Snow if not dict_value: 1047d0f982bSMarc-André Lureau ret += indent(level) 105fb0bc835SMarkus Armbruster if obj is None: 1067d0f982bSMarc-André Lureau ret += 'QLIT_QNULL' 107fb0bc835SMarkus Armbruster elif isinstance(obj, str): 1087d0f982bSMarc-André Lureau ret += 'QLIT_QSTR(' + to_c_string(obj) + ')' 109fb0bc835SMarkus Armbruster elif isinstance(obj, list): 1102e8a843dSMarkus Armbruster elts = [_tree_to_qlit(elt, level + 1).strip('\n') 111fb0bc835SMarkus Armbruster for elt in obj] 1127d0f982bSMarc-André Lureau elts.append(indent(level + 1) + "{}") 1137d0f982bSMarc-André Lureau ret += 'QLIT_QLIST(((QLitObject[]) {\n' 11440bb1376SMarc-André Lureau ret += '\n'.join(elts) + '\n' 1157d0f982bSMarc-André Lureau ret += indent(level) + '}))' 116fb0bc835SMarkus Armbruster elif isinstance(obj, dict): 1177d0f982bSMarc-André Lureau elts = [] 1187d0f982bSMarc-André Lureau for key, value in sorted(obj.items()): 1197d0f982bSMarc-André Lureau elts.append(indent(level + 1) + '{ %s, %s }' % 1202e8a843dSMarkus Armbruster (to_c_string(key), 1212e8a843dSMarkus Armbruster _tree_to_qlit(value, level + 1, True))) 1227d0f982bSMarc-André Lureau elts.append(indent(level + 1) + '{}') 1237d0f982bSMarc-André Lureau ret += 'QLIT_QDICT(((QLitDictEntry[]) {\n' 1247d0f982bSMarc-André Lureau ret += ',\n'.join(elts) + '\n' 1257d0f982bSMarc-André Lureau ret += indent(level) + '}))' 126876c6751SPeter Xu elif isinstance(obj, bool): 127876c6751SPeter Xu ret += 'QLIT_QBOOL(%s)' % ('true' if obj else 'false') 128fb0bc835SMarkus Armbruster else: 129*2a6c161bSJohn Snow raise NotImplementedError( 130*2a6c161bSJohn Snow f"type '{type(obj).__name__}' not implemented" 131*2a6c161bSJohn Snow ) 13240bb1376SMarc-André Lureau if level > 0: 13340bb1376SMarc-André Lureau ret += ',' 134fb0bc835SMarkus Armbruster return ret 135fb0bc835SMarkus Armbruster 136fb0bc835SMarkus Armbruster 137fb0bc835SMarkus Armbrusterdef to_c_string(string): 138fb0bc835SMarkus Armbruster return '"' + string.replace('\\', r'\\').replace('"', r'\"') + '"' 139fb0bc835SMarkus Armbruster 140fb0bc835SMarkus Armbruster 14171b3f045SMarkus Armbrusterclass QAPISchemaGenIntrospectVisitor(QAPISchemaMonolithicCVisitor): 142fb0bc835SMarkus Armbruster 14371b3f045SMarkus Armbruster def __init__(self, prefix, unmask): 1442cae67bcSMarkus Armbruster super().__init__( 1452cae67bcSMarkus Armbruster prefix, 'qapi-introspect', 14671b3f045SMarkus Armbruster ' * QAPI/QMP schema introspection', __doc__) 14771b3f045SMarkus Armbruster self._unmask = unmask 14871b3f045SMarkus Armbruster self._schema = None 1492e8a843dSMarkus Armbruster self._trees = [] 150fb0bc835SMarkus Armbruster self._used_types = [] 151fb0bc835SMarkus Armbruster self._name_map = {} 15271b3f045SMarkus Armbruster self._genc.add(mcgen(''' 15371b3f045SMarkus Armbruster#include "qemu/osdep.h" 154eb815e24SMarkus Armbruster#include "%(prefix)sqapi-introspect.h" 15571b3f045SMarkus Armbruster 15671b3f045SMarkus Armbruster''', 15771b3f045SMarkus Armbruster prefix=prefix)) 15871b3f045SMarkus Armbruster 15971b3f045SMarkus Armbruster def visit_begin(self, schema): 16071b3f045SMarkus Armbruster self._schema = schema 161fb0bc835SMarkus Armbruster 162fb0bc835SMarkus Armbruster def visit_end(self): 163fb0bc835SMarkus Armbruster # visit the types that are actually used 164fb0bc835SMarkus Armbruster for typ in self._used_types: 165fb0bc835SMarkus Armbruster typ.visit(self) 166fb0bc835SMarkus Armbruster # generate C 1677d0f982bSMarc-André Lureau name = c_name(self._prefix, protect=False) + 'qmp_schema_qlit' 16871b3f045SMarkus Armbruster self._genh.add(mcgen(''' 1697d0f982bSMarc-André Lureau#include "qapi/qmp/qlit.h" 1707d0f982bSMarc-André Lureau 1717d0f982bSMarc-André Lureauextern const QLitObject %(c_name)s; 172fb0bc835SMarkus Armbruster''', 17371b3f045SMarkus Armbruster c_name=c_name(name))) 17471b3f045SMarkus Armbruster self._genc.add(mcgen(''' 1757d0f982bSMarc-André Lureauconst QLitObject %(c_name)s = %(c_string)s; 176fb0bc835SMarkus Armbruster''', 177fb0bc835SMarkus Armbruster c_name=c_name(name), 1782e8a843dSMarkus Armbruster c_string=_tree_to_qlit(self._trees))) 179fb0bc835SMarkus Armbruster self._schema = None 1802e8a843dSMarkus Armbruster self._trees = [] 18171b3f045SMarkus Armbruster self._used_types = [] 18271b3f045SMarkus Armbruster self._name_map = {} 183fb0bc835SMarkus Armbruster 184fb0bc835SMarkus Armbruster def visit_needed(self, entity): 185fb0bc835SMarkus Armbruster # Ignore types on first pass; visit_end() will pick up used types 186fb0bc835SMarkus Armbruster return not isinstance(entity, QAPISchemaType) 187fb0bc835SMarkus Armbruster 188fb0bc835SMarkus Armbruster def _name(self, name): 189fb0bc835SMarkus Armbruster if self._unmask: 190fb0bc835SMarkus Armbruster return name 191fb0bc835SMarkus Armbruster if name not in self._name_map: 192fb0bc835SMarkus Armbruster self._name_map[name] = '%d' % len(self._name_map) 193fb0bc835SMarkus Armbruster return self._name_map[name] 194fb0bc835SMarkus Armbruster 195fb0bc835SMarkus Armbruster def _use_type(self, typ): 1966b67bcacSJohn Snow assert self._schema is not None 1976b67bcacSJohn Snow 198fb0bc835SMarkus Armbruster # Map the various integer types to plain int 199fb0bc835SMarkus Armbruster if typ.json_type() == 'int': 200fb0bc835SMarkus Armbruster typ = self._schema.lookup_type('int') 201fb0bc835SMarkus Armbruster elif (isinstance(typ, QAPISchemaArrayType) and 202fb0bc835SMarkus Armbruster typ.element_type.json_type() == 'int'): 203fb0bc835SMarkus Armbruster typ = self._schema.lookup_type('intList') 204fb0bc835SMarkus Armbruster # Add type to work queue if new 205fb0bc835SMarkus Armbruster if typ not in self._used_types: 206fb0bc835SMarkus Armbruster self._used_types.append(typ) 207fb0bc835SMarkus Armbruster # Clients should examine commands and events, not types. Hide 2081aa806ccSEric Blake # type names as integers to reduce the temptation. Also, it 2091aa806ccSEric Blake # saves a few characters on the wire. 210fb0bc835SMarkus Armbruster if isinstance(typ, QAPISchemaBuiltinType): 211fb0bc835SMarkus Armbruster return typ.name 212fb0bc835SMarkus Armbruster if isinstance(typ, QAPISchemaArrayType): 213fb0bc835SMarkus Armbruster return '[' + self._use_type(typ.element_type) + ']' 214fb0bc835SMarkus Armbruster return self._name(typ.name) 215fb0bc835SMarkus Armbruster 21684bece7dSJohn Snow @staticmethod 21784bece7dSJohn Snow def _gen_features(features): 2184f7f97a7SJohn Snow return [Annotated(f.name, f.ifcond) for f in features] 21984bece7dSJohn Snow 2202e8a843dSMarkus Armbruster def _gen_tree(self, name, mtype, obj, ifcond, features): 2215f50cedeSJohn Snow comment: Optional[str] = None 222fb0bc835SMarkus Armbruster if mtype not in ('command', 'event', 'builtin', 'array'): 2238c643361SEric Blake if not self._unmask: 2248c643361SEric Blake # Output a comment to make it easy to map masked names 2258c643361SEric Blake # back to the source when reading the generated output. 2265f50cedeSJohn Snow comment = f'"{self._name(name)}" = {name}' 227fb0bc835SMarkus Armbruster name = self._name(name) 228fb0bc835SMarkus Armbruster obj['name'] = name 229fb0bc835SMarkus Armbruster obj['meta-type'] = mtype 23084bece7dSJohn Snow if features: 23184bece7dSJohn Snow obj['features'] = self._gen_features(features) 2324f7f97a7SJohn Snow self._trees.append(Annotated(obj, ifcond, comment)) 233fb0bc835SMarkus Armbruster 234fb0bc835SMarkus Armbruster def _gen_member(self, member): 23524cfd6adSMarkus Armbruster obj = {'name': member.name, 'type': self._use_type(member.type)} 236fb0bc835SMarkus Armbruster if member.optional: 23724cfd6adSMarkus Armbruster obj['default'] = None 23884bece7dSJohn Snow if member.features: 23984bece7dSJohn Snow obj['features'] = self._gen_features(member.features) 2404f7f97a7SJohn Snow return Annotated(obj, member.ifcond) 241fb0bc835SMarkus Armbruster 242fb0bc835SMarkus Armbruster def _gen_variants(self, tag_name, variants): 243fb0bc835SMarkus Armbruster return {'tag': tag_name, 244fb0bc835SMarkus Armbruster 'variants': [self._gen_variant(v) for v in variants]} 245fb0bc835SMarkus Armbruster 246fb0bc835SMarkus Armbruster def _gen_variant(self, variant): 24724cfd6adSMarkus Armbruster obj = {'case': variant.name, 'type': self._use_type(variant.type)} 2484f7f97a7SJohn Snow return Annotated(obj, variant.ifcond) 249fb0bc835SMarkus Armbruster 250fb0bc835SMarkus Armbruster def visit_builtin_type(self, name, info, json_type): 2512e8a843dSMarkus Armbruster self._gen_tree(name, 'builtin', {'json-type': json_type}, [], None) 252fb0bc835SMarkus Armbruster 253013b4efcSMarkus Armbruster def visit_enum_type(self, name, info, ifcond, features, members, prefix): 2544f7f97a7SJohn Snow self._gen_tree( 2554f7f97a7SJohn Snow name, 'enum', 2564f7f97a7SJohn Snow {'values': [Annotated(m.name, m.ifcond) for m in members]}, 2574f7f97a7SJohn Snow ifcond, features 2584f7f97a7SJohn Snow ) 259fb0bc835SMarkus Armbruster 260fbf09a2fSMarc-André Lureau def visit_array_type(self, name, info, ifcond, element_type): 261fb0bc835SMarkus Armbruster element = self._use_type(element_type) 2622e8a843dSMarkus Armbruster self._gen_tree('[' + element + ']', 'array', {'element-type': element}, 263013b4efcSMarkus Armbruster ifcond, None) 264fb0bc835SMarkus Armbruster 2657b3bc9e2SMarkus Armbruster def visit_object_type_flat(self, name, info, ifcond, features, 2667b3bc9e2SMarkus Armbruster members, variants): 267fb0bc835SMarkus Armbruster obj = {'members': [self._gen_member(m) for m in members]} 268fb0bc835SMarkus Armbruster if variants: 269fb0bc835SMarkus Armbruster obj.update(self._gen_variants(variants.tag_member.name, 270fb0bc835SMarkus Armbruster variants.variants)) 2716a8c0b51SKevin Wolf 2722e8a843dSMarkus Armbruster self._gen_tree(name, 'object', obj, ifcond, features) 273fb0bc835SMarkus Armbruster 274013b4efcSMarkus Armbruster def visit_alternate_type(self, name, info, ifcond, features, variants): 2754f7f97a7SJohn Snow self._gen_tree( 2764f7f97a7SJohn Snow name, 'alternate', 2774f7f97a7SJohn Snow {'members': [Annotated({'type': self._use_type(m.type)}, 2784f7f97a7SJohn Snow m.ifcond) 279013b4efcSMarkus Armbruster for m in variants.variants]}, 2804f7f97a7SJohn Snow ifcond, features 2814f7f97a7SJohn Snow ) 282fb0bc835SMarkus Armbruster 2837b3bc9e2SMarkus Armbruster def visit_command(self, name, info, ifcond, features, 2847b3bc9e2SMarkus Armbruster arg_type, ret_type, gen, success_response, boxed, 28504f22362SKevin Wolf allow_oob, allow_preconfig, coroutine): 2866b67bcacSJohn Snow assert self._schema is not None 2876b67bcacSJohn Snow 288fb0bc835SMarkus Armbruster arg_type = arg_type or self._schema.the_empty_object_type 289fb0bc835SMarkus Armbruster ret_type = ret_type or self._schema.the_empty_object_type 29025b1ef31SMarkus Armbruster obj = {'arg-type': self._use_type(arg_type), 29125b1ef31SMarkus Armbruster 'ret-type': self._use_type(ret_type)} 29225b1ef31SMarkus Armbruster if allow_oob: 29325b1ef31SMarkus Armbruster obj['allow-oob'] = allow_oob 2942e8a843dSMarkus Armbruster self._gen_tree(name, 'command', obj, ifcond, features) 29523394b4cSPeter Krempa 296013b4efcSMarkus Armbruster def visit_event(self, name, info, ifcond, features, arg_type, boxed): 2976b67bcacSJohn Snow assert self._schema is not None 298fb0bc835SMarkus Armbruster arg_type = arg_type or self._schema.the_empty_object_type 2992e8a843dSMarkus Armbruster self._gen_tree(name, 'event', {'arg-type': self._use_type(arg_type)}, 300013b4efcSMarkus Armbruster ifcond, features) 301fb0bc835SMarkus Armbruster 302fb0bc835SMarkus Armbruster 303fb0bc835SMarkus Armbrusterdef gen_introspect(schema, output_dir, prefix, opt_unmask): 304fb0bc835SMarkus Armbruster vis = QAPISchemaGenIntrospectVisitor(prefix, opt_unmask) 305fb0bc835SMarkus Armbruster schema.visit(vis) 30671b3f045SMarkus Armbruster vis.write(output_dir) 307