QAPI patches patches for 2020-10-10

-----BEGIN PGP SIGNATURE-----
 
 iQJGBAABCAAwFiEENUvIs9frKmtoZ05fOHC0AOuRhlMFAl+BgjISHGFybWJydUBy
 ZWRoYXQuY29tAAoJEDhwtADrkYZT3zEP/iud3fGwcKlt+et2ZnKwsD2xpiT+L8CL
 r4qpKDDoqWFufNpCzLhoGNZjNJq8JlgOdjp/RufDFl+QN5aVJmogX/EKW3dan0dK
 qm67Mz7EItm3ssBUitHeipZgHTsLwtt7Bu2VWUcJ/z6UD3QJBNoJ4Bypv4stXtuG
 5OQ3Afwu1ctkmQDiRoG+pXtDVk53SGp8aRrouH4ORGasujS6vAz/gXvBbg+yuD3t
 7dXpOMtbJL3haY/P5fB3pMSwn7Kql09YPe2REcWyvz6zl+SvFcypd5j7Ag5RXj7U
 WK6vkgOgIO4YlTfrr9sHv3jQnYLT9OkcDxDiSi4Pql4EptQydyFfQMGsR3TI7yuM
 LOTLLnDTX/uxCPocVwGT9bSrrbCgUg4adc95r0RrncurJdtldtdJ6idtuB/xZQV2
 O4qTeyCZdbuMjN4GiaGmhrJy9ySWQkJTDjpeJWPrZYIAQyfUz4BBtNmfuGG+gsEK
 A5WIfPVmuI+FM3T/Ck51DXcEGgf+ezcFtzYzWDLsWGOB9m9QB4H0yAwwmqHqZBMP
 mezLuzs6A7T/38xdNGkwj4dXcUKiKz06C00Z9F6jdXS6LnonNh+PZ01Buqkhpg7F
 JQofKIsLcNLzQDyflUluBfC4wDw51BrCRs2ynHNbYLdOgBNNylCQLxxfjOAO5iDE
 LjUcXT5Dg0BU
 =/96J
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/armbru/tags/pull-qapi-2020-10-10' into staging

QAPI patches patches for 2020-10-10

# gpg: Signature made Sat 10 Oct 2020 10:43:14 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-2020-10-10: (34 commits)
  qapi/visit.py: add type hint annotations
  qapi/visit.py: remove unused parameters from gen_visit_object
  qapi/visit.py: assert tag_member contains a QAPISchemaEnumType
  qapi/types.py: remove one-letter variables
  qapi/types.py: add type hint annotations
  qapi/gen.py: delint with pylint
  qapi/gen.py: update write() to be more idiomatic
  qapi/gen.py: Remove unused parameter
  qapi/gen.py: add type hint annotations
  qapi/gen: Make _is_user_module() return bool
  qapi/source.py: delint with pylint
  qapi/source.py: add type hint annotations
  qapi/commands.py: add type hint annotations
  qapi/commands.py: Don't re-bind to variable of different type
  qapi/events.py: Move comments into docstrings
  qapi/events.py: add type hint annotations
  qapi: establish mypy type-checking baseline
  qapi/common.py: move build_params into gen.py
  qapi/common.py: Convert comments into docstrings, and elaborate
  qapi/common.py: add type hint annotations
  ...

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2020-10-12 11:29:41 +01:00
commit 2387df497b
19 changed files with 764 additions and 341 deletions

View file

@ -267,7 +267,7 @@ of view of external observers (e.g. another processor core). They can
apply to any memory operations as well as just loads or stores.
The Linux kernel has an excellent `write-up
<https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/plain/Documentation/memory-barriers.txt>`
<https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/plain/Documentation/memory-barriers.txt>`_
on the various forms of memory barrier and the guarantees they can
provide.

View file

@ -953,7 +953,7 @@ compiler flags are needed to build for a given target.
If you have the ability to run containers as the user you can also
take advantage of the build systems "Docker" support. It will then use
containers to build any test case for an enabled guest where there is
no system compiler available. See :ref: `_docker-ref` for details.
no system compiler available. See :ref:`docker-ref` for details.
Running subset of tests
-----------------------

View file

@ -1,56 +1,19 @@
#!/usr/bin/env python3
# QAPI generator
#
# This work is licensed under the terms of the GNU GPL, version 2 or later.
# See the COPYING file in the top-level directory.
"""
QAPI code generation execution shim.
This standalone script exists primarily to facilitate the running of the QAPI
code generator without needing to install the python module to the current
execution environment.
"""
import argparse
import re
import sys
from qapi.commands import gen_commands
from qapi.events import gen_events
from qapi.introspect import gen_introspect
from qapi.schema import QAPIError, QAPISchema
from qapi.types import gen_types
from qapi.visit import gen_visit
def main(argv):
parser = argparse.ArgumentParser(
description='Generate code from a QAPI schema')
parser.add_argument('-b', '--builtins', action='store_true',
help="generate code for built-in types")
parser.add_argument('-o', '--output-dir', action='store', default='',
help="write output to directory OUTPUT_DIR")
parser.add_argument('-p', '--prefix', action='store', default='',
help="prefix for symbols")
parser.add_argument('-u', '--unmask-non-abi-names', action='store_true',
dest='unmask',
help="expose non-ABI names in introspection")
parser.add_argument('schema', action='store')
args = parser.parse_args()
match = re.match(r'([A-Za-z_.-][A-Za-z0-9_.-]*)?', args.prefix)
if match.end() != len(args.prefix):
print("%s: 'funny character '%s' in argument of --prefix"
% (sys.argv[0], args.prefix[match.end()]),
file=sys.stderr)
sys.exit(1)
try:
schema = QAPISchema(args.schema)
except QAPIError as err:
print(err, file=sys.stderr)
exit(1)
gen_types(schema, args.output_dir, args.prefix, args.builtins)
gen_visit(schema, args.output_dir, args.prefix, args.builtins)
gen_commands(schema, args.output_dir, args.prefix)
gen_events(schema, args.output_dir, args.prefix)
gen_introspect(schema, args.output_dir, args.prefix, args.unmask)
from qapi import main
if __name__ == '__main__':
main(sys.argv)
sys.exit(main.main())

2
scripts/qapi/.flake8 Normal file
View file

@ -0,0 +1,2 @@
[flake8]
extend-ignore = E722 # Prefer pylint's bare-except checks to flake8's

7
scripts/qapi/.isort.cfg Normal file
View file

@ -0,0 +1,7 @@
[settings]
force_grid_wrap=4
force_sort_within_sections=True
include_trailing_comma=True
line_length=72
lines_after_imports=2
multi_line_output=3

View file

@ -13,11 +13,34 @@ This work is licensed under the terms of the GNU GPL, version 2.
See the COPYING file in the top-level directory.
"""
from qapi.common import *
from qapi.gen import QAPIGenCCode, QAPISchemaModularCVisitor, ifcontext
from typing import (
Dict,
List,
Optional,
Set,
)
from .common import c_name, mcgen
from .gen import (
QAPIGenC,
QAPIGenCCode,
QAPISchemaModularCVisitor,
build_params,
ifcontext,
)
from .schema import (
QAPISchema,
QAPISchemaFeature,
QAPISchemaObjectType,
QAPISchemaType,
)
from .source import QAPISourceInfo
def gen_command_decl(name, arg_type, boxed, ret_type):
def gen_command_decl(name: str,
arg_type: Optional[QAPISchemaObjectType],
boxed: bool,
ret_type: Optional[QAPISchemaType]) -> str:
return mcgen('''
%(c_type)s qmp_%(c_name)s(%(params)s);
''',
@ -26,7 +49,10 @@ def gen_command_decl(name, arg_type, boxed, ret_type):
params=build_params(arg_type, boxed, 'Error **errp'))
def gen_call(name, arg_type, boxed, ret_type):
def gen_call(name: str,
arg_type: Optional[QAPISchemaObjectType],
boxed: bool,
ret_type: Optional[QAPISchemaType]) -> str:
ret = ''
argstr = ''
@ -62,10 +88,11 @@ def gen_call(name, arg_type, boxed, ret_type):
return ret
def gen_marshal_output(ret_type):
def gen_marshal_output(ret_type: QAPISchemaType) -> str:
return mcgen('''
static void qmp_marshal_output_%(c_name)s(%(c_type)s ret_in, QObject **ret_out, Error **errp)
static void qmp_marshal_output_%(c_name)s(%(c_type)s ret_in,
QObject **ret_out, Error **errp)
{
Visitor *v;
@ -82,19 +109,22 @@ static void qmp_marshal_output_%(c_name)s(%(c_type)s ret_in, QObject **ret_out,
c_type=ret_type.c_type(), c_name=ret_type.c_name())
def build_marshal_proto(name):
def build_marshal_proto(name: str) -> str:
return ('void qmp_marshal_%s(QDict *args, QObject **ret, Error **errp)'
% c_name(name))
def gen_marshal_decl(name):
def gen_marshal_decl(name: str) -> str:
return mcgen('''
%(proto)s;
''',
proto=build_marshal_proto(name))
def gen_marshal(name, arg_type, boxed, ret_type):
def gen_marshal(name: str,
arg_type: Optional[QAPISchemaObjectType],
boxed: bool,
ret_type: Optional[QAPISchemaType]) -> str:
have_args = boxed or (arg_type and not arg_type.is_empty())
ret = mcgen('''
@ -176,8 +206,11 @@ out:
return ret
def gen_register_command(name, success_response, allow_oob, allow_preconfig,
coroutine):
def gen_register_command(name: str,
success_response: bool,
allow_oob: bool,
allow_preconfig: bool,
coroutine: bool) -> str:
options = []
if not success_response:
@ -192,18 +225,16 @@ def gen_register_command(name, success_response, allow_oob, allow_preconfig,
if not options:
options = ['QCO_NO_OPTIONS']
options = " | ".join(options)
ret = mcgen('''
qmp_register_command(cmds, "%(name)s",
qmp_marshal_%(c_name)s, %(opts)s);
''',
name=name, c_name=c_name(name),
opts=options)
opts=" | ".join(options))
return ret
def gen_registry(registry, prefix):
def gen_registry(registry: str, prefix: str) -> str:
ret = mcgen('''
void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds)
@ -220,15 +251,14 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds)
class QAPISchemaGenCommandVisitor(QAPISchemaModularCVisitor):
def __init__(self, prefix):
def __init__(self, prefix: str):
super().__init__(
prefix, 'qapi-commands',
' * Schema-defined QAPI/QMP commands', None, __doc__)
self._regy = QAPIGenCCode(None)
self._visited_ret_types = {}
self._visited_ret_types: Dict[QAPIGenC, Set[QAPISchemaType]] = {}
def _begin_user_module(self, name):
def _begin_user_module(self, name: str) -> None:
self._visited_ret_types[self._genc] = set()
commands = self._module_basename('qapi-commands', name)
types = self._module_basename('qapi-types', name)
@ -252,7 +282,7 @@ class QAPISchemaGenCommandVisitor(QAPISchemaModularCVisitor):
''',
types=types))
def visit_end(self):
def visit_end(self) -> None:
self._add_system_module('init', ' * QAPI Commands initialization')
self._genh.add(mcgen('''
#include "qapi/qmp/dispatch.h"
@ -268,9 +298,19 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
prefix=self._prefix))
self._genc.add(gen_registry(self._regy.get_content(), self._prefix))
def visit_command(self, name, info, ifcond, features,
arg_type, ret_type, gen, success_response, boxed,
allow_oob, allow_preconfig, coroutine):
def visit_command(self,
name: str,
info: QAPISourceInfo,
ifcond: List[str],
features: List[QAPISchemaFeature],
arg_type: Optional[QAPISchemaObjectType],
ret_type: Optional[QAPISchemaType],
gen: bool,
success_response: bool,
boxed: bool,
allow_oob: bool,
allow_preconfig: bool,
coroutine: bool) -> None:
if not gen:
return
# FIXME: If T is a user-defined type, the user is responsible
@ -292,7 +332,9 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
coroutine))
def gen_commands(schema, output_dir, prefix):
def gen_commands(schema: QAPISchema,
output_dir: str,
prefix: str) -> None:
vis = QAPISchemaGenCommandVisitor(prefix)
schema.visit(vis)
vis.write(output_dir)

View file

@ -12,12 +12,28 @@
# See the COPYING file in the top-level directory.
import re
from typing import Optional, Sequence
# ENUMName -> ENUM_NAME, EnumName1 -> ENUM_NAME1
# ENUM_NAME -> ENUM_NAME, ENUM_NAME1 -> ENUM_NAME1, ENUM_Name2 -> ENUM_NAME2
# ENUM24_Name -> ENUM24_NAME
def camel_to_upper(value):
#: Magic string that gets removed along with all space to its right.
EATSPACE = '\033EATSPACE.'
POINTER_SUFFIX = ' *' + EATSPACE
_C_NAME_TRANS = str.maketrans('.-', '__')
def camel_to_upper(value: str) -> str:
"""
Converts CamelCase to CAMEL_CASE.
Examples::
ENUMName -> ENUM_NAME
EnumName1 -> ENUM_NAME1
ENUM_NAME -> ENUM_NAME
ENUM_NAME1 -> ENUM_NAME1
ENUM_Name2 -> ENUM_NAME2
ENUM24_Name -> ENUM24_NAME
"""
c_fun_str = c_name(value, False)
if value.isupper():
return c_fun_str
@ -25,36 +41,47 @@ def camel_to_upper(value):
new_name = ''
length = len(c_fun_str)
for i in range(length):
c = c_fun_str[i]
# When c is upper and no '_' appears before, do more checks
if c.isupper() and (i > 0) and c_fun_str[i - 1] != '_':
char = c_fun_str[i]
# When char is upper case and no '_' appears before, do more checks
if char.isupper() and (i > 0) and c_fun_str[i - 1] != '_':
if i < length - 1 and c_fun_str[i + 1].islower():
new_name += '_'
elif c_fun_str[i - 1].isdigit():
new_name += '_'
new_name += c
new_name += char
return new_name.lstrip('_').upper()
def c_enum_const(type_name, const_name, prefix=None):
def c_enum_const(type_name: str,
const_name: str,
prefix: Optional[str] = None) -> str:
"""
Generate a C enumeration constant name.
:param type_name: The name of the enumeration.
:param const_name: The name of this constant.
:param prefix: Optional, prefix that overrides the type_name.
"""
if prefix is not None:
type_name = prefix
return camel_to_upper(type_name) + '_' + c_name(const_name, False).upper()
c_name_trans = str.maketrans('.-', '__')
def c_name(name: str, protect: bool = True) -> str:
"""
Map ``name`` to a valid C identifier.
Used for converting 'name' from a 'name':'type' qapi definition
into a generated struct member, as well as converting type names
into substrings of a generated C function name.
# Map @name to a valid C identifier.
# If @protect, avoid returning certain ticklish identifiers (like
# C keywords) by prepending 'q_'.
#
# Used for converting 'name' from a 'name':'type' qapi definition
# into a generated struct member, as well as converting type names
# into substrings of a generated C function name.
# '__a.b_c' -> '__a_b_c', 'x-foo' -> 'x_foo'
# protect=True: 'int' -> 'q_int'; protect=False: 'int' -> 'int'
def c_name(name, protect=True):
'__a.b_c' -> '__a_b_c', 'x-foo' -> 'x_foo'
protect=True: 'int' -> 'q_int'; protect=False: 'int' -> 'int'
:param name: The name to map.
:param protect: If true, avoid returning certain ticklish identifiers
(like C keywords) by prepending ``q_``.
"""
# ANSI X3J11/88-090, 3.1.1
c89_words = set(['auto', 'break', 'case', 'char', 'const', 'continue',
'default', 'do', 'double', 'else', 'enum', 'extern',
@ -82,61 +109,75 @@ def c_name(name, protect=True):
'not_eq', 'or', 'or_eq', 'xor', 'xor_eq'])
# namespace pollution:
polluted_words = set(['unix', 'errno', 'mips', 'sparc', 'i386'])
name = name.translate(c_name_trans)
name = name.translate(_C_NAME_TRANS)
if protect and (name in c89_words | c99_words | c11_words | gcc_words
| cpp_words | polluted_words):
return 'q_' + name
return name
eatspace = '\033EATSPACE.'
pointer_suffix = ' *' + eatspace
class Indentation:
"""
Indentation level management.
:param initial: Initial number of spaces, default 0.
"""
def __init__(self, initial: int = 0) -> None:
self._level = initial
def __int__(self) -> int:
return self._level
def __repr__(self) -> str:
return "{}({:d})".format(type(self).__name__, self._level)
def __str__(self) -> str:
"""Return the current indentation as a string of spaces."""
return ' ' * self._level
def __bool__(self) -> bool:
"""True when there is a non-zero indentation."""
return bool(self._level)
def increase(self, amount: int = 4) -> None:
"""Increase the indentation level by ``amount``, default 4."""
self._level += amount
def decrease(self, amount: int = 4) -> None:
"""Decrease the indentation level by ``amount``, default 4."""
if self._level < amount:
raise ArithmeticError(
f"Can't remove {amount:d} spaces from {self!r}")
self._level -= amount
def genindent(count):
ret = ''
for _ in range(count):
ret += ' '
return ret
#: Global, current indent level for code generation.
indent = Indentation()
indent_level = 0
def cgen(code: str, **kwds: object) -> str:
"""
Generate ``code`` with ``kwds`` interpolated.
def push_indent(indent_amount=4):
global indent_level
indent_level += indent_amount
def pop_indent(indent_amount=4):
global indent_level
indent_level -= indent_amount
# Generate @code with @kwds interpolated.
# Obey indent_level, and strip eatspace.
def cgen(code, **kwds):
Obey `indent`, and strip `EATSPACE`.
"""
raw = code % kwds
if indent_level:
indent = genindent(indent_level)
# re.subn() lacks flags support before Python 2.7, use re.compile()
raw = re.subn(re.compile(r'^(?!(#|$))', re.MULTILINE),
indent, raw)
raw = raw[0]
return re.sub(re.escape(eatspace) + r' *', '', raw)
if indent:
raw = re.sub(r'^(?!(#|$))', str(indent), raw, flags=re.MULTILINE)
return re.sub(re.escape(EATSPACE) + r' *', '', raw)
def mcgen(code, **kwds):
def mcgen(code: str, **kwds: object) -> str:
if code[0] == '\n':
code = code[1:]
return cgen(code, **kwds)
def c_fname(filename):
def c_fname(filename: str) -> str:
return re.sub(r'[^A-Za-z0-9_]', '_', filename)
def guardstart(name):
def guardstart(name: str) -> str:
return mcgen('''
#ifndef %(name)s
#define %(name)s
@ -145,7 +186,7 @@ def guardstart(name):
name=c_fname(name).upper())
def guardend(name):
def guardend(name: str) -> str:
return mcgen('''
#endif /* %(name)s */
@ -153,7 +194,7 @@ def guardend(name):
name=c_fname(name).upper())
def gen_if(ifcond):
def gen_if(ifcond: Sequence[str]) -> str:
ret = ''
for ifc in ifcond:
ret += mcgen('''
@ -162,31 +203,10 @@ def gen_if(ifcond):
return ret
def gen_endif(ifcond):
def gen_endif(ifcond: Sequence[str]) -> str:
ret = ''
for ifc in reversed(ifcond):
ret += mcgen('''
#endif /* %(cond)s */
''', cond=ifc)
return ret
def build_params(arg_type, boxed, extra=None):
ret = ''
sep = ''
if boxed:
assert arg_type
ret += '%s arg' % arg_type.c_param_type()
sep = ', '
elif arg_type:
assert not arg_type.variants
for memb in arg_type.members:
ret += sep
sep = ', '
if memb.optional:
ret += 'bool has_%s, ' % c_name(memb.name)
ret += '%s %s' % (memb.type.c_param_type(),
c_name(memb.name))
if extra:
ret += sep + extra
return ret if ret else 'void'

View file

@ -12,19 +12,31 @@ This work is licensed under the terms of the GNU GPL, version 2.
See the COPYING file in the top-level directory.
"""
from qapi.common import *
from qapi.gen import QAPISchemaModularCVisitor, ifcontext
from qapi.schema import QAPISchemaEnumMember
from qapi.types import gen_enum, gen_enum_lookup
from typing import List
from .common import c_enum_const, c_name, mcgen
from .gen import QAPISchemaModularCVisitor, build_params, ifcontext
from .schema import (
QAPISchema,
QAPISchemaEnumMember,
QAPISchemaFeature,
QAPISchemaObjectType,
)
from .source import QAPISourceInfo
from .types import gen_enum, gen_enum_lookup
def build_event_send_proto(name, arg_type, boxed):
def build_event_send_proto(name: str,
arg_type: QAPISchemaObjectType,
boxed: bool) -> str:
return 'void qapi_event_send_%(c_name)s(%(param)s)' % {
'c_name': c_name(name.lower()),
'param': build_params(arg_type, boxed)}
def gen_event_send_decl(name, arg_type, boxed):
def gen_event_send_decl(name: str,
arg_type: QAPISchemaObjectType,
boxed: bool) -> str:
return mcgen('''
%(proto)s;
@ -32,8 +44,12 @@ def gen_event_send_decl(name, arg_type, boxed):
proto=build_event_send_proto(name, arg_type, boxed))
# Declare and initialize an object 'qapi' using parameters from build_params()
def gen_param_var(typ):
def gen_param_var(typ: QAPISchemaObjectType) -> str:
"""
Generate a struct variable holding the event parameters.
Initialize it with the function arguments defined in `gen_event_send`.
"""
assert not typ.variants
ret = mcgen('''
%(c_name)s param = {
@ -61,7 +77,11 @@ def gen_param_var(typ):
return ret
def gen_event_send(name, arg_type, boxed, event_enum_name, event_emit):
def gen_event_send(name: str,
arg_type: QAPISchemaObjectType,
boxed: bool,
event_enum_name: str,
event_emit: str) -> str:
# FIXME: Our declaration of local variables (and of 'errp' in the
# parameter list) can collide with exploded members of the event's
# data type passed in as parameters. If this collision ever hits in
@ -137,15 +157,15 @@ def gen_event_send(name, arg_type, boxed, event_enum_name, event_emit):
class QAPISchemaGenEventVisitor(QAPISchemaModularCVisitor):
def __init__(self, prefix):
def __init__(self, prefix: str):
super().__init__(
prefix, 'qapi-events',
' * Schema-defined QAPI/QMP events', None, __doc__)
self._event_enum_name = c_name(prefix + 'QAPIEvent', protect=False)
self._event_enum_members = []
self._event_enum_members: List[QAPISchemaEnumMember] = []
self._event_emit_name = c_name(prefix + 'qapi_event_emit')
def _begin_user_module(self, name):
def _begin_user_module(self, name: str) -> None:
events = self._module_basename('qapi-events', name)
types = self._module_basename('qapi-types', name)
visit = self._module_basename('qapi-visit', name)
@ -168,7 +188,7 @@ class QAPISchemaGenEventVisitor(QAPISchemaModularCVisitor):
''',
types=types))
def visit_end(self):
def visit_end(self) -> None:
self._add_system_module('emit', ' * QAPI Events emission')
self._genc.preamble_add(mcgen('''
#include "qemu/osdep.h"
@ -189,7 +209,13 @@ void %(event_emit)s(%(event_enum)s event, QDict *qdict);
event_emit=self._event_emit_name,
event_enum=self._event_enum_name))
def visit_event(self, name, info, ifcond, features, arg_type, boxed):
def visit_event(self,
name: str,
info: QAPISourceInfo,
ifcond: List[str],
features: List[QAPISchemaFeature],
arg_type: QAPISchemaObjectType,
boxed: bool) -> None:
with ifcontext(ifcond, self._genh, self._genc):
self._genh.add(gen_event_send_decl(name, arg_type, boxed))
self._genc.add(gen_event_send(name, arg_type, boxed,
@ -200,7 +226,9 @@ void %(event_emit)s(%(event_enum)s event, QDict *qdict);
self._event_enum_members.append(QAPISchemaEnumMember(name, None))
def gen_events(schema, output_dir, prefix):
def gen_events(schema: QAPISchema,
output_dir: str,
prefix: str) -> None:
vis = QAPISchemaGenEventVisitor(prefix)
schema.visit(vis)
vis.write(output_dir)

View file

@ -14,10 +14,11 @@
# This work is licensed under the terms of the GNU GPL, version 2.
# See the COPYING file in the top-level directory.
import re
from collections import OrderedDict
from qapi.common import c_name
from qapi.error import QAPISemError
import re
from .common import c_name
from .error import QAPISemError
# Names must be letters, numbers, -, and _. They must start with letter,

View file

@ -2,7 +2,7 @@
#
# QAPI code generation
#
# Copyright (c) 2018-2019 Red Hat Inc.
# Copyright (c) 2015-2019 Red Hat Inc.
#
# Authors:
# Markus Armbruster <armbru@redhat.com>
@ -11,39 +11,54 @@
# This work is licensed under the terms of the GNU GPL, version 2.
# See the COPYING file in the top-level directory.
import errno
from contextlib import contextmanager
import os
import re
from contextlib import contextmanager
from typing import (
Dict,
Iterator,
List,
Optional,
Tuple,
)
from qapi.common import *
from qapi.schema import QAPISchemaVisitor
from .common import (
c_fname,
c_name,
gen_endif,
gen_if,
guardend,
guardstart,
mcgen,
)
from .schema import QAPISchemaObjectType, QAPISchemaVisitor
from .source import QAPISourceInfo
class QAPIGen:
def __init__(self, fname):
def __init__(self, fname: Optional[str]):
self.fname = fname
self._preamble = ''
self._body = ''
def preamble_add(self, text):
def preamble_add(self, text: str) -> None:
self._preamble += text
def add(self, text):
def add(self, text: str) -> None:
self._body += text
def get_content(self):
def get_content(self) -> str:
return self._top() + self._preamble + self._body + self._bottom()
def _top(self):
def _top(self) -> str:
# pylint: disable=no-self-use
return ''
def _bottom(self):
def _bottom(self) -> str:
# pylint: disable=no-self-use
return ''
def write(self, output_dir):
def write(self, output_dir: str) -> None:
# Include paths starting with ../ are used to reuse modules of the main
# schema in specialised schemas. Don't overwrite the files that are
# already generated for the main schema.
@ -51,24 +66,22 @@ class QAPIGen:
return
pathname = os.path.join(output_dir, self.fname)
odir = os.path.dirname(pathname)
if odir:
try:
os.makedirs(odir)
except os.error as e:
if e.errno != errno.EEXIST:
raise
os.makedirs(odir, exist_ok=True)
# use os.open for O_CREAT to create and read a non-existant file
fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666)
f = open(fd, 'r+', encoding='utf-8')
text = self.get_content()
oldtext = f.read(len(text) + 1)
if text != oldtext:
f.seek(0)
f.truncate(0)
f.write(text)
f.close()
with os.fdopen(fd, 'r+', encoding='utf-8') as fp:
text = self.get_content()
oldtext = fp.read(len(text) + 1)
if text != oldtext:
fp.seek(0)
fp.truncate(0)
fp.write(text)
def _wrap_ifcond(ifcond, before, after):
def _wrap_ifcond(ifcond: List[str], before: str, after: str) -> str:
if before == after:
return after # suppress empty #if ... #endif
@ -84,41 +97,62 @@ def _wrap_ifcond(ifcond, before, after):
return out
def build_params(arg_type: Optional[QAPISchemaObjectType],
boxed: bool,
extra: Optional[str] = None) -> str:
ret = ''
sep = ''
if boxed:
assert arg_type
ret += '%s arg' % arg_type.c_param_type()
sep = ', '
elif arg_type:
assert not arg_type.variants
for memb in arg_type.members:
ret += sep
sep = ', '
if memb.optional:
ret += 'bool has_%s, ' % c_name(memb.name)
ret += '%s %s' % (memb.type.c_param_type(),
c_name(memb.name))
if extra:
ret += sep + extra
return ret if ret else 'void'
class QAPIGenCCode(QAPIGen):
def __init__(self, fname):
def __init__(self, fname: Optional[str]):
super().__init__(fname)
self._start_if = None
self._start_if: Optional[Tuple[List[str], str, str]] = None
def start_if(self, ifcond):
def start_if(self, ifcond: List[str]) -> None:
assert self._start_if is None
self._start_if = (ifcond, self._body, self._preamble)
def end_if(self):
def end_if(self) -> None:
assert self._start_if
self._wrap_ifcond()
self._start_if = None
def _wrap_ifcond(self):
def _wrap_ifcond(self) -> None:
self._body = _wrap_ifcond(self._start_if[0],
self._start_if[1], self._body)
self._preamble = _wrap_ifcond(self._start_if[0],
self._start_if[2], self._preamble)
def get_content(self):
def get_content(self) -> str:
assert self._start_if is None
return super().get_content()
class QAPIGenC(QAPIGenCCode):
def __init__(self, fname, blurb, pydoc):
def __init__(self, fname: str, blurb: str, pydoc: str):
super().__init__(fname)
self._blurb = blurb
self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc,
re.MULTILINE))
def _top(self):
def _top(self) -> str:
return mcgen('''
/* AUTOMATICALLY GENERATED, DO NOT MODIFY */
@ -134,7 +168,7 @@ class QAPIGenC(QAPIGenCCode):
''',
blurb=self._blurb, copyright=self._copyright)
def _bottom(self):
def _bottom(self) -> str:
return mcgen('''
/* Dummy declaration to prevent empty .o file */
@ -144,19 +178,20 @@ char qapi_dummy_%(name)s;
class QAPIGenH(QAPIGenC):
def _top(self):
def _top(self) -> str:
return super()._top() + guardstart(self.fname)
def _bottom(self):
def _bottom(self) -> str:
return guardend(self.fname)
@contextmanager
def ifcontext(ifcond, *args):
"""A 'with' statement context manager to wrap with start_if()/end_if()
def ifcontext(ifcond: List[str], *args: QAPIGenCCode) -> Iterator[None]:
"""
A with-statement context manager that wraps with `start_if()` / `end_if()`.
*args: any number of QAPIGenCCode
:param ifcond: A list of conditionals, passed to `start_if()`.
:param args: any number of `QAPIGenCCode`.
Example::
@ -179,8 +214,11 @@ def ifcontext(ifcond, *args):
class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
def __init__(self, prefix, what, blurb, pydoc):
def __init__(self,
prefix: str,
what: str,
blurb: str,
pydoc: str):
self._prefix = prefix
self._what = what
self._genc = QAPIGenC(self._prefix + self._what + '.c',
@ -188,38 +226,42 @@ class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
self._genh = QAPIGenH(self._prefix + self._what + '.h',
blurb, pydoc)
def write(self, output_dir):
def write(self, output_dir: str) -> None:
self._genc.write(output_dir)
self._genh.write(output_dir)
class QAPISchemaModularCVisitor(QAPISchemaVisitor):
def __init__(self, prefix, what, user_blurb, builtin_blurb, pydoc):
def __init__(self,
prefix: str,
what: str,
user_blurb: str,
builtin_blurb: Optional[str],
pydoc: str):
self._prefix = prefix
self._what = what
self._user_blurb = user_blurb
self._builtin_blurb = builtin_blurb
self._pydoc = pydoc
self._genc = None
self._genh = None
self._module = {}
self._main_module = None
self._genc: Optional[QAPIGenC] = None
self._genh: Optional[QAPIGenH] = None
self._module: Dict[Optional[str], Tuple[QAPIGenC, QAPIGenH]] = {}
self._main_module: Optional[str] = None
@staticmethod
def _is_user_module(name):
return name and not name.startswith('./')
def _is_user_module(name: Optional[str]) -> bool:
return bool(name and not name.startswith('./'))
@staticmethod
def _is_builtin_module(name):
def _is_builtin_module(name: Optional[str]) -> bool:
return not name
def _module_dirname(self, what, name):
def _module_dirname(self, name: Optional[str]) -> str:
if self._is_user_module(name):
return os.path.dirname(name)
return ''
def _module_basename(self, what, name):
def _module_basename(self, what: str, name: Optional[str]) -> str:
ret = '' if self._is_builtin_module(name) else self._prefix
if self._is_user_module(name):
basename = os.path.basename(name)
@ -231,27 +273,27 @@ class QAPISchemaModularCVisitor(QAPISchemaVisitor):
ret += re.sub(r'-', '-' + name + '-', what)
return ret
def _module_filename(self, what, name):
return os.path.join(self._module_dirname(what, name),
def _module_filename(self, what: str, name: Optional[str]) -> str:
return os.path.join(self._module_dirname(name),
self._module_basename(what, name))
def _add_module(self, name, blurb):
def _add_module(self, name: Optional[str], blurb: str) -> None:
basename = self._module_filename(self._what, name)
genc = QAPIGenC(basename + '.c', blurb, self._pydoc)
genh = QAPIGenH(basename + '.h', blurb, self._pydoc)
self._module[name] = (genc, genh)
self._genc, self._genh = self._module[name]
def _add_user_module(self, name, blurb):
def _add_user_module(self, name: str, blurb: str) -> None:
assert self._is_user_module(name)
if self._main_module is None:
self._main_module = name
self._add_module(name, blurb)
def _add_system_module(self, name, blurb):
def _add_system_module(self, name: Optional[str], blurb: str) -> None:
self._add_module(name and './' + name, blurb)
def write(self, output_dir, opt_builtins=False):
def write(self, output_dir: str, opt_builtins: bool = False) -> None:
for name in self._module:
if self._is_builtin_module(name) and not opt_builtins:
continue
@ -259,13 +301,13 @@ class QAPISchemaModularCVisitor(QAPISchemaVisitor):
genc.write(output_dir)
genh.write(output_dir)
def _begin_system_module(self, name):
def _begin_system_module(self, name: None) -> None:
pass
def _begin_user_module(self, name):
def _begin_user_module(self, name: str) -> None:
pass
def visit_module(self, name):
def visit_module(self, name: Optional[str]) -> None:
if name is None:
if self._builtin_blurb:
self._add_system_module(None, self._builtin_blurb)
@ -279,7 +321,7 @@ class QAPISchemaModularCVisitor(QAPISchemaVisitor):
self._add_user_module(name, self._user_blurb)
self._begin_user_module(name)
def visit_include(self, name, info):
def visit_include(self, name: str, info: QAPISourceInfo) -> None:
relname = os.path.relpath(self._module_filename(self._what, name),
os.path.dirname(self._genh.fname))
self._genh.preamble_add(mcgen('''

View file

@ -10,10 +10,18 @@ This work is licensed under the terms of the GNU GPL, version 2.
See the COPYING file in the top-level directory.
"""
from qapi.common import *
from qapi.gen import QAPISchemaMonolithicCVisitor
from qapi.schema import (QAPISchemaArrayType, QAPISchemaBuiltinType,
QAPISchemaType)
from .common import (
c_name,
gen_endif,
gen_if,
mcgen,
)
from .gen import QAPISchemaMonolithicCVisitor
from .schema import (
QAPISchemaArrayType,
QAPISchemaBuiltinType,
QAPISchemaType,
)
def _make_tree(obj, ifcond, features, extra=None):

95
scripts/qapi/main.py Normal file
View file

@ -0,0 +1,95 @@
# This work is licensed under the terms of the GNU GPL, version 2 or later.
# See the COPYING file in the top-level directory.
"""
QAPI Generator
This is the main entry point for generating C code from the QAPI schema.
"""
import argparse
import re
import sys
from typing import Optional
from .commands import gen_commands
from .error import QAPIError
from .events import gen_events
from .introspect import gen_introspect
from .schema import QAPISchema
from .types import gen_types
from .visit import gen_visit
def invalid_prefix_char(prefix: str) -> Optional[str]:
match = re.match(r'([A-Za-z_.-][A-Za-z0-9_.-]*)?', prefix)
if match.end() != len(prefix):
return prefix[match.end()]
return None
def generate(schema_file: str,
output_dir: str,
prefix: str,
unmask: bool = False,
builtins: bool = False) -> None:
"""
Generate C code for the given schema into the target directory.
:param schema_file: The primary QAPI schema file.
:param output_dir: The output directory to store generated code.
:param prefix: Optional C-code prefix for symbol names.
:param unmask: Expose non-ABI names through introspection?
:param builtins: Generate code for built-in types?
:raise QAPIError: On failures.
"""
assert invalid_prefix_char(prefix) is None
schema = QAPISchema(schema_file)
gen_types(schema, output_dir, prefix, builtins)
gen_visit(schema, output_dir, prefix, builtins)
gen_commands(schema, output_dir, prefix)
gen_events(schema, output_dir, prefix)
gen_introspect(schema, output_dir, prefix, unmask)
def main() -> int:
"""
gapi-gen executable entry point.
Expects arguments via sys.argv, see --help for details.
:return: int, 0 on success, 1 on failure.
"""
parser = argparse.ArgumentParser(
description='Generate code from a QAPI schema')
parser.add_argument('-b', '--builtins', action='store_true',
help="generate code for built-in types")
parser.add_argument('-o', '--output-dir', action='store',
default='',
help="write output to directory OUTPUT_DIR")
parser.add_argument('-p', '--prefix', action='store',
default='',
help="prefix for symbols")
parser.add_argument('-u', '--unmask-non-abi-names', action='store_true',
dest='unmask',
help="expose non-ABI names in introspection")
parser.add_argument('schema', action='store')
args = parser.parse_args()
funny_char = invalid_prefix_char(args.prefix)
if funny_char:
msg = f"funny character '{funny_char}' in argument of --prefix"
print(f"{sys.argv[0]}: {msg}", file=sys.stderr)
return 1
try:
generate(args.schema,
output_dir=args.output_dir,
prefix=args.prefix,
unmask=args.unmask,
builtins=args.builtins)
except QAPIError as err:
print(f"{sys.argv[0]}: {str(err)}", file=sys.stderr)
return 1
return 0

30
scripts/qapi/mypy.ini Normal file
View file

@ -0,0 +1,30 @@
[mypy]
strict = True
strict_optional = False
disallow_untyped_calls = False
python_version = 3.6
[mypy-qapi.error]
disallow_untyped_defs = False
disallow_incomplete_defs = False
check_untyped_defs = False
[mypy-qapi.expr]
disallow_untyped_defs = False
disallow_incomplete_defs = False
check_untyped_defs = False
[mypy-qapi.introspect]
disallow_untyped_defs = False
disallow_incomplete_defs = False
check_untyped_defs = False
[mypy-qapi.parser]
disallow_untyped_defs = False
disallow_incomplete_defs = False
check_untyped_defs = False
[mypy-qapi.schema]
disallow_untyped_defs = False
disallow_incomplete_defs = False
check_untyped_defs = False

View file

@ -14,12 +14,12 @@
# This work is licensed under the terms of the GNU GPL, version 2.
# See the COPYING file in the top-level directory.
from collections import OrderedDict
import os
import re
from collections import OrderedDict
from qapi.error import QAPIParseError, QAPISemError
from qapi.source import QAPISourceInfo
from .error import QAPIParseError, QAPISemError
from .source import QAPISourceInfo
class QAPISchemaParser:

70
scripts/qapi/pylintrc Normal file
View file

@ -0,0 +1,70 @@
[MASTER]
# Add files or directories matching the regex patterns to the ignore list.
# The regex matches against base names, not paths.
ignore-patterns=error.py,
expr.py,
parser.py,
schema.py,
[MESSAGES CONTROL]
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once). You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
disable=fixme,
missing-docstring,
too-many-arguments,
too-many-branches,
too-many-statements,
too-many-instance-attributes,
[REPORTS]
[REFACTORING]
[MISCELLANEOUS]
[LOGGING]
[BASIC]
# Good variable names which should always be accepted, separated by a comma.
good-names=i,
j,
k,
ex,
Run,
_,
fp, # fp = open(...)
fd, # fd = os.open(...)
[VARIABLES]
[STRING]
[SPELLING]
[FORMAT]
[SIMILARITIES]
# Ignore import statements themselves when computing similarities.
ignore-imports=yes
[TYPECHECK]
[CLASSES]
[IMPORTS]
[DESIGN]
[EXCEPTIONS]

View file

@ -14,18 +14,19 @@
# TODO catching name collisions in generated code would be nice
from collections import OrderedDict
import os
import re
from collections import OrderedDict
from typing import Optional
from qapi.common import c_name, pointer_suffix
from qapi.error import QAPIError, QAPISemError
from qapi.expr import check_exprs
from qapi.parser import QAPISchemaParser
from .common import POINTER_SUFFIX, c_name
from .error import QAPIError, QAPISemError
from .expr import check_exprs
from .parser import QAPISchemaParser
class QAPISchemaEntity:
meta = None
meta: Optional[str] = None
def __init__(self, name, info, doc, ifcond=None, features=None):
assert name is None or isinstance(name, str)
@ -309,7 +310,7 @@ class QAPISchemaArrayType(QAPISchemaType):
return True
def c_type(self):
return c_name(self.name) + pointer_suffix
return c_name(self.name) + POINTER_SUFFIX
def json_type(self):
return 'array'
@ -430,7 +431,7 @@ class QAPISchemaObjectType(QAPISchemaType):
def c_type(self):
assert not self.is_implicit()
return c_name(self.name) + pointer_suffix
return c_name(self.name) + POINTER_SUFFIX
def c_unboxed_type(self):
return c_name(self.name)
@ -504,7 +505,7 @@ class QAPISchemaAlternateType(QAPISchemaType):
v.connect_doc(doc)
def c_type(self):
return c_name(self.name) + pointer_suffix
return c_name(self.name) + POINTER_SUFFIX
def json_type(self):
return 'value'
@ -536,7 +537,7 @@ class QAPISchemaVariants:
v.set_defined_in(name)
def check(self, schema, seen):
if not self.tag_member: # flat union
if not self.tag_member: # flat union
self.tag_member = seen.get(c_name(self._tag_name))
base = "'base'"
# Pointing to the base type when not implicit would be
@ -824,7 +825,7 @@ class QAPISchema:
self._entity_dict = {}
self._module_dict = OrderedDict()
self._schema_dir = os.path.dirname(fname)
self._make_module(None) # built-ins
self._make_module(None) # built-ins
self._make_module(fname)
self._predefining = True
self._def_predefineds()
@ -899,7 +900,7 @@ class QAPISchema:
self._make_array_type(name, None)
def _def_predefineds(self):
for t in [('str', 'string', 'char' + pointer_suffix),
for t in [('str', 'string', 'char' + POINTER_SUFFIX),
('number', 'number', 'double'),
('int', 'int', 'int64_t'),
('int8', 'int', 'int8_t'),
@ -912,8 +913,8 @@ class QAPISchema:
('uint64', 'int', 'uint64_t'),
('size', 'int', 'uint64_t'),
('bool', 'boolean', 'bool'),
('any', 'value', 'QObject' + pointer_suffix),
('null', 'null', 'QNull' + pointer_suffix)]:
('any', 'value', 'QObject' + POINTER_SUFFIX),
('null', 'null', 'QNull' + POINTER_SUFFIX)]:
self._def_builtin_type(*t)
self.the_empty_object_type = QAPISchemaObjectType(
'q_empty', None, None, None, None, None, [], None)
@ -968,7 +969,9 @@ class QAPISchema:
# But it's not tight: the disjunction need not imply it. We
# may end up compiling useless wrapper types.
# TODO kill simple unions or implement the disjunction
assert (ifcond or []) == typ._ifcond # pylint: disable=protected-access
# pylint: disable=protected-access
assert (ifcond or []) == typ._ifcond
else:
self._def_entity(QAPISchemaObjectType(
name, info, None, ifcond, None, None, members, None))

View file

@ -11,37 +11,46 @@
import copy
import sys
from typing import List, Optional, TypeVar
class QAPISchemaPragma:
def __init__(self):
# Replace with @dataclass in Python 3.7+
# pylint: disable=too-few-public-methods
def __init__(self) -> None:
# Are documentation comments required?
self.doc_required = False
# Whitelist of commands allowed to return a non-dictionary
self.returns_whitelist = []
self.returns_whitelist: List[str] = []
# Whitelist of entities allowed to violate case conventions
self.name_case_whitelist = []
self.name_case_whitelist: List[str] = []
class QAPISourceInfo:
def __init__(self, fname, line, parent):
T = TypeVar('T', bound='QAPISourceInfo')
def __init__(self, fname: str, line: int,
parent: Optional['QAPISourceInfo']):
self.fname = fname
self.line = line
self.parent = parent
self.pragma = parent.pragma if parent else QAPISchemaPragma()
self.defn_meta = None
self.defn_name = None
self.pragma: QAPISchemaPragma = (
parent.pragma if parent else QAPISchemaPragma()
)
self.defn_meta: Optional[str] = None
self.defn_name: Optional[str] = None
def set_defn(self, meta, name):
def set_defn(self, meta: str, name: str) -> None:
self.defn_meta = meta
self.defn_name = name
def next_line(self):
def next_line(self: T) -> T:
info = copy.copy(self)
info.line += 1
return info
def loc(self):
def loc(self) -> str:
if self.fname is None:
return sys.argv[0]
ret = self.fname
@ -49,13 +58,13 @@ class QAPISourceInfo:
ret += ':%d' % self.line
return ret
def in_defn(self):
def in_defn(self) -> str:
if self.defn_name:
return "%s: In %s '%s':\n" % (self.fname,
self.defn_meta, self.defn_name)
return ''
def include_path(self):
def include_path(self) -> str:
ret = ''
parent = self.parent
while parent:
@ -63,5 +72,5 @@ class QAPISourceInfo:
parent = parent.parent
return ret
def __str__(self):
def __str__(self) -> str:
return self.include_path() + self.in_defn() + self.loc()

View file

@ -13,9 +13,26 @@ This work is licensed under the terms of the GNU GPL, version 2.
# See the COPYING file in the top-level directory.
"""
from qapi.common import *
from qapi.gen import QAPISchemaModularCVisitor, ifcontext
from qapi.schema import QAPISchemaEnumMember, QAPISchemaObjectType
from typing import List, Optional
from .common import (
c_enum_const,
c_name,
gen_endif,
gen_if,
mcgen,
)
from .gen import QAPISchemaModularCVisitor, ifcontext
from .schema import (
QAPISchema,
QAPISchemaEnumMember,
QAPISchemaFeature,
QAPISchemaObjectType,
QAPISchemaObjectTypeMember,
QAPISchemaType,
QAPISchemaVariants,
)
from .source import QAPISourceInfo
# variants must be emitted before their container; track what has already
@ -23,21 +40,23 @@ from qapi.schema import QAPISchemaEnumMember, QAPISchemaObjectType
objects_seen = set()
def gen_enum_lookup(name, members, prefix=None):
def gen_enum_lookup(name: str,
members: List[QAPISchemaEnumMember],
prefix: Optional[str] = None) -> str:
ret = mcgen('''
const QEnumLookup %(c_name)s_lookup = {
.array = (const char *const[]) {
''',
c_name=c_name(name))
for m in members:
ret += gen_if(m.ifcond)
index = c_enum_const(name, m.name, prefix)
for memb in members:
ret += gen_if(memb.ifcond)
index = c_enum_const(name, memb.name, prefix)
ret += mcgen('''
[%(index)s] = "%(name)s",
''',
index=index, name=m.name)
ret += gen_endif(m.ifcond)
index=index, name=memb.name)
ret += gen_endif(memb.ifcond)
ret += mcgen('''
},
@ -48,7 +67,9 @@ const QEnumLookup %(c_name)s_lookup = {
return ret
def gen_enum(name, members, prefix=None):
def gen_enum(name: str,
members: List[QAPISchemaEnumMember],
prefix: Optional[str] = None) -> str:
# append automatically generated _MAX value
enum_members = members + [QAPISchemaEnumMember('_MAX', None)]
@ -58,13 +79,13 @@ typedef enum %(c_name)s {
''',
c_name=c_name(name))
for m in enum_members:
ret += gen_if(m.ifcond)
for memb in enum_members:
ret += gen_if(memb.ifcond)
ret += mcgen('''
%(c_enum)s,
''',
c_enum=c_enum_const(name, m.name, prefix))
ret += gen_endif(m.ifcond)
c_enum=c_enum_const(name, memb.name, prefix))
ret += gen_endif(memb.ifcond)
ret += mcgen('''
} %(c_name)s;
@ -82,7 +103,7 @@ extern const QEnumLookup %(c_name)s_lookup;
return ret
def gen_fwd_object_or_array(name):
def gen_fwd_object_or_array(name: str) -> str:
return mcgen('''
typedef struct %(c_name)s %(c_name)s;
@ -90,7 +111,7 @@ typedef struct %(c_name)s %(c_name)s;
c_name=c_name(name))
def gen_array(name, element_type):
def gen_array(name: str, element_type: QAPISchemaType) -> str:
return mcgen('''
struct %(c_name)s {
@ -101,7 +122,7 @@ struct %(c_name)s {
c_name=c_name(name), c_type=element_type.c_type())
def gen_struct_members(members):
def gen_struct_members(members: List[QAPISchemaObjectTypeMember]) -> str:
ret = ''
for memb in members:
ret += gen_if(memb.ifcond)
@ -118,17 +139,21 @@ def gen_struct_members(members):
return ret
def gen_object(name, ifcond, base, members, variants):
def gen_object(name: str, ifcond: List[str],
base: Optional[QAPISchemaObjectType],
members: List[QAPISchemaObjectTypeMember],
variants: Optional[QAPISchemaVariants]) -> str:
if name in objects_seen:
return ''
objects_seen.add(name)
ret = ''
if variants:
for v in variants.variants:
if isinstance(v.type, QAPISchemaObjectType):
ret += gen_object(v.type.name, v.type.ifcond, v.type.base,
v.type.local_members, v.type.variants)
for var in variants.variants if variants else ():
obj = var.type
if not isinstance(obj, QAPISchemaObjectType):
continue
ret += gen_object(obj.name, obj.ifcond, obj.base,
obj.local_members, obj.variants)
ret += mcgen('''
@ -172,7 +197,7 @@ struct %(c_name)s {
return ret
def gen_upcast(name, base):
def gen_upcast(name: str, base: QAPISchemaObjectType) -> str:
# C makes const-correctness ugly. We have to cast away const to let
# this function work for both const and non-const obj.
return mcgen('''
@ -185,7 +210,7 @@ static inline %(base)s *qapi_%(c_name)s_base(const %(c_name)s *obj)
c_name=c_name(name), base=base.c_name())
def gen_variants(variants):
def gen_variants(variants: QAPISchemaVariants) -> str:
ret = mcgen('''
union { /* union tag is @%(c_name)s */
''',
@ -209,7 +234,7 @@ def gen_variants(variants):
return ret
def gen_type_cleanup_decl(name):
def gen_type_cleanup_decl(name: str) -> str:
ret = mcgen('''
void qapi_free_%(c_name)s(%(c_name)s *obj);
@ -219,7 +244,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(%(c_name)s, qapi_free_%(c_name)s)
return ret
def gen_type_cleanup(name):
def gen_type_cleanup(name: str) -> str:
ret = mcgen('''
void qapi_free_%(c_name)s(%(c_name)s *obj)
@ -241,12 +266,12 @@ void qapi_free_%(c_name)s(%(c_name)s *obj)
class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor):
def __init__(self, prefix):
def __init__(self, prefix: str):
super().__init__(
prefix, 'qapi-types', ' * Schema-defined QAPI types',
' * Built-in QAPI types', __doc__)
def _begin_system_module(self, name):
def _begin_system_module(self, name: None) -> None:
self._genc.preamble_add(mcgen('''
#include "qemu/osdep.h"
#include "qapi/dealloc-visitor.h"
@ -257,7 +282,7 @@ class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor):
#include "qapi/util.h"
'''))
def _begin_user_module(self, name):
def _begin_user_module(self, name: str) -> None:
types = self._module_basename('qapi-types', name)
visit = self._module_basename('qapi-visit', name)
self._genc.preamble_add(mcgen('''
@ -271,27 +296,43 @@ class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor):
#include "qapi/qapi-builtin-types.h"
'''))
def visit_begin(self, schema):
def visit_begin(self, schema: QAPISchema) -> None:
# gen_object() is recursive, ensure it doesn't visit the empty type
objects_seen.add(schema.the_empty_object_type.name)
def _gen_type_cleanup(self, name):
def _gen_type_cleanup(self, name: str) -> None:
self._genh.add(gen_type_cleanup_decl(name))
self._genc.add(gen_type_cleanup(name))
def visit_enum_type(self, name, info, ifcond, features, members, prefix):
def visit_enum_type(self,
name: str,
info: Optional[QAPISourceInfo],
ifcond: List[str],
features: List[QAPISchemaFeature],
members: List[QAPISchemaEnumMember],
prefix: Optional[str]) -> None:
with ifcontext(ifcond, self._genh, self._genc):
self._genh.preamble_add(gen_enum(name, members, prefix))
self._genc.add(gen_enum_lookup(name, members, prefix))
def visit_array_type(self, name, info, ifcond, element_type):
def visit_array_type(self,
name: str,
info: Optional[QAPISourceInfo],
ifcond: List[str],
element_type: QAPISchemaType) -> None:
with ifcontext(ifcond, self._genh, self._genc):
self._genh.preamble_add(gen_fwd_object_or_array(name))
self._genh.add(gen_array(name, element_type))
self._gen_type_cleanup(name)
def visit_object_type(self, name, info, ifcond, features,
base, members, variants):
def visit_object_type(self,
name: str,
info: Optional[QAPISourceInfo],
ifcond: List[str],
features: List[QAPISchemaFeature],
base: Optional[QAPISchemaObjectType],
members: List[QAPISchemaObjectTypeMember],
variants: Optional[QAPISchemaVariants]) -> None:
# Nothing to do for the special empty builtin
if name == 'q_empty':
return
@ -307,7 +348,12 @@ class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor):
# implicit types won't be directly allocated/freed
self._gen_type_cleanup(name)
def visit_alternate_type(self, name, info, ifcond, features, variants):
def visit_alternate_type(self,
name: str,
info: QAPISourceInfo,
ifcond: List[str],
features: List[QAPISchemaFeature],
variants: QAPISchemaVariants) -> None:
with ifcontext(ifcond, self._genh):
self._genh.preamble_add(gen_fwd_object_or_array(name))
self._genh.add(gen_object(name, ifcond, None,
@ -316,7 +362,10 @@ class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor):
self._gen_type_cleanup(name)
def gen_types(schema, output_dir, prefix, opt_builtins):
def gen_types(schema: QAPISchema,
output_dir: str,
prefix: str,
opt_builtins: bool) -> None:
vis = QAPISchemaGenTypeVisitor(prefix)
schema.visit(vis)
vis.write(output_dir, opt_builtins)

View file

@ -13,22 +13,43 @@ This work is licensed under the terms of the GNU GPL, version 2.
See the COPYING file in the top-level directory.
"""
from qapi.common import *
from qapi.gen import QAPISchemaModularCVisitor, ifcontext
from qapi.schema import QAPISchemaObjectType
from typing import List, Optional
from .common import (
c_enum_const,
c_name,
gen_endif,
gen_if,
indent,
mcgen,
)
from .gen import QAPISchemaModularCVisitor, ifcontext
from .schema import (
QAPISchema,
QAPISchemaEnumMember,
QAPISchemaEnumType,
QAPISchemaFeature,
QAPISchemaObjectType,
QAPISchemaObjectTypeMember,
QAPISchemaType,
QAPISchemaVariants,
)
from .source import QAPISourceInfo
def gen_visit_decl(name, scalar=False):
def gen_visit_decl(name: str, scalar: bool = False) -> str:
c_type = c_name(name) + ' *'
if not scalar:
c_type += '*'
return mcgen('''
bool visit_type_%(c_name)s(Visitor *v, const char *name, %(c_type)sobj, Error **errp);
bool visit_type_%(c_name)s(Visitor *v, const char *name,
%(c_type)sobj, Error **errp);
''',
c_name=c_name(name), c_type=c_type)
def gen_visit_members_decl(name):
def gen_visit_members_decl(name: str) -> str:
return mcgen('''
bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp);
@ -36,7 +57,10 @@ bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp);
c_name=c_name(name))
def gen_visit_object_members(name, base, members, variants):
def gen_visit_object_members(name: str,
base: Optional[QAPISchemaObjectType],
members: List[QAPISchemaObjectTypeMember],
variants: Optional[QAPISchemaVariants]) -> str:
ret = mcgen('''
bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp)
@ -59,7 +83,7 @@ bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp)
if (visit_optional(v, "%(name)s", &obj->has_%(c_name)s)) {
''',
name=memb.name, c_name=c_name(memb.name))
push_indent()
indent.increase()
ret += mcgen('''
if (!visit_type_%(c_type)s(v, "%(name)s", &obj->%(c_name)s, errp)) {
return false;
@ -68,22 +92,24 @@ bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp)
c_type=memb.type.c_name(), name=memb.name,
c_name=c_name(memb.name))
if memb.optional:
pop_indent()
indent.decrease()
ret += mcgen('''
}
''')
ret += gen_endif(memb.ifcond)
if variants:
tag_member = variants.tag_member
assert isinstance(tag_member.type, QAPISchemaEnumType)
ret += mcgen('''
switch (obj->%(c_name)s) {
''',
c_name=c_name(variants.tag_member.name))
c_name=c_name(tag_member.name))
for var in variants.variants:
case_str = c_enum_const(variants.tag_member.type.name,
var.name,
variants.tag_member.type.prefix)
case_str = c_enum_const(tag_member.type.name, var.name,
tag_member.type.prefix)
ret += gen_if(var.ifcond)
if var.type.name == 'q_empty':
# valid variant and nothing to do
@ -114,10 +140,11 @@ bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp)
return ret
def gen_visit_list(name, element_type):
def gen_visit_list(name: str, element_type: QAPISchemaType) -> str:
return mcgen('''
bool visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error **errp)
bool visit_type_%(c_name)s(Visitor *v, const char *name,
%(c_name)s **obj, Error **errp)
{
bool ok = false;
%(c_name)s *tail;
@ -147,10 +174,11 @@ out_obj:
c_name=c_name(name), c_elt_type=element_type.c_name())
def gen_visit_enum(name):
def gen_visit_enum(name: str) -> str:
return mcgen('''
bool visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s *obj, Error **errp)
bool visit_type_%(c_name)s(Visitor *v, const char *name,
%(c_name)s *obj, Error **errp)
{
int value = *obj;
bool ok = visit_type_enum(v, name, &value, &%(c_name)s_lookup, errp);
@ -161,10 +189,11 @@ bool visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s *obj, Error
c_name=c_name(name))
def gen_visit_alternate(name, variants):
def gen_visit_alternate(name: str, variants: QAPISchemaVariants) -> str:
ret = mcgen('''
bool visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error **errp)
bool visit_type_%(c_name)s(Visitor *v, const char *name,
%(c_name)s **obj, Error **errp)
{
bool ok = false;
@ -236,10 +265,11 @@ out_obj:
return ret
def gen_visit_object(name, base, members, variants):
def gen_visit_object(name: str) -> str:
return mcgen('''
bool visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error **errp)
bool visit_type_%(c_name)s(Visitor *v, const char *name,
%(c_name)s **obj, Error **errp)
{
bool ok = false;
@ -270,12 +300,12 @@ out_obj:
class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor):
def __init__(self, prefix):
def __init__(self, prefix: str):
super().__init__(
prefix, 'qapi-visit', ' * Schema-defined QAPI visitors',
' * Built-in QAPI visitors', __doc__)
def _begin_system_module(self, name):
def _begin_system_module(self, name: None) -> None:
self._genc.preamble_add(mcgen('''
#include "qemu/osdep.h"
#include "qapi/error.h"
@ -287,7 +317,7 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor):
'''))
def _begin_user_module(self, name):
def _begin_user_module(self, name: str) -> None:
types = self._module_basename('qapi-types', name)
visit = self._module_basename('qapi-visit', name)
self._genc.preamble_add(mcgen('''
@ -304,18 +334,34 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor):
''',
types=types))
def visit_enum_type(self, name, info, ifcond, features, members, prefix):
def visit_enum_type(self,
name: str,
info: QAPISourceInfo,
ifcond: List[str],
features: List[QAPISchemaFeature],
members: List[QAPISchemaEnumMember],
prefix: Optional[str]) -> None:
with ifcontext(ifcond, self._genh, self._genc):
self._genh.add(gen_visit_decl(name, scalar=True))
self._genc.add(gen_visit_enum(name))
def visit_array_type(self, name, info, ifcond, element_type):
def visit_array_type(self,
name: str,
info: Optional[QAPISourceInfo],
ifcond: List[str],
element_type: QAPISchemaType) -> None:
with ifcontext(ifcond, self._genh, self._genc):
self._genh.add(gen_visit_decl(name))
self._genc.add(gen_visit_list(name, element_type))
def visit_object_type(self, name, info, ifcond, features,
base, members, variants):
def visit_object_type(self,
name: str,
info: Optional[QAPISourceInfo],
ifcond: List[str],
features: List[QAPISchemaFeature],
base: Optional[QAPISchemaObjectType],
members: List[QAPISchemaObjectTypeMember],
variants: Optional[QAPISchemaVariants]) -> None:
# Nothing to do for the special empty builtin
if name == 'q_empty':
return
@ -328,15 +374,23 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor):
if not name.startswith('q_'):
# only explicit types need an allocating visit
self._genh.add(gen_visit_decl(name))
self._genc.add(gen_visit_object(name, base, members, variants))
self._genc.add(gen_visit_object(name))
def visit_alternate_type(self, name, info, ifcond, features, variants):
def visit_alternate_type(self,
name: str,
info: QAPISourceInfo,
ifcond: List[str],
features: List[QAPISchemaFeature],
variants: QAPISchemaVariants) -> None:
with ifcontext(ifcond, self._genh, self._genc):
self._genh.add(gen_visit_decl(name))
self._genc.add(gen_visit_alternate(name, variants))
def gen_visit(schema, output_dir, prefix, opt_builtins):
def gen_visit(schema: QAPISchema,
output_dir: str,
prefix: str,
opt_builtins: bool) -> None:
vis = QAPISchemaGenVisitVisitor(prefix)
schema.visit(vis)
vis.write(output_dir, opt_builtins)