aboutsummaryrefslogtreecommitdiff
path: root/scripts/qapi
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2020-10-12 11:29:41 +0100
committerPeter Maydell <peter.maydell@linaro.org>2020-10-12 11:29:42 +0100
commit2387df497b4b4bcf754eb7398edca82889e2ef54 (patch)
tree11d3099549e5d67012a4f0818a41ede540940a36 /scripts/qapi
parent48a340d9b23ffcf7704f2de14d1e505481a84a1c (diff)
parentb4c0aa59aff520e2a55edd5fef393058ca6520de (diff)
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>
Diffstat (limited to 'scripts/qapi')
-rw-r--r--scripts/qapi/.flake82
-rw-r--r--scripts/qapi/.isort.cfg7
-rw-r--r--scripts/qapi/commands.py94
-rw-r--r--scripts/qapi/common.py164
-rw-r--r--scripts/qapi/events.py62
-rw-r--r--scripts/qapi/expr.py7
-rw-r--r--scripts/qapi/gen.py182
-rw-r--r--scripts/qapi/introspect.py16
-rw-r--r--scripts/qapi/main.py95
-rw-r--r--scripts/qapi/mypy.ini30
-rw-r--r--scripts/qapi/parser.py6
-rw-r--r--scripts/qapi/pylintrc70
-rw-r--r--scripts/qapi/schema.py33
-rw-r--r--scripts/qapi/source.py35
-rw-r--r--scripts/qapi/types.py125
-rw-r--r--scripts/qapi/visit.py122
16 files changed, 755 insertions, 295 deletions
diff --git a/scripts/qapi/.flake8 b/scripts/qapi/.flake8
new file mode 100644
index 0000000000..6b158c68b8
--- /dev/null
+++ b/scripts/qapi/.flake8
@@ -0,0 +1,2 @@
+[flake8]
+extend-ignore = E722 # Prefer pylint's bare-except checks to flake8's
diff --git a/scripts/qapi/.isort.cfg b/scripts/qapi/.isort.cfg
new file mode 100644
index 0000000000..643caa1fbd
--- /dev/null
+++ b/scripts/qapi/.isort.cfg
@@ -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
diff --git a/scripts/qapi/commands.py b/scripts/qapi/commands.py
index 6e6fc94a14..50978090b4 100644
--- a/scripts/qapi/commands.py
+++ b/scripts/qapi/commands.py
@@ -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
-
-
-def gen_command_decl(name, arg_type, boxed, ret_type):
+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: 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)
diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index ba35abea47..11b86beeab 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -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 genindent(count):
- ret = ''
- for _ in range(count):
- ret += ' '
- return ret
+ 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)
-indent_level = 0
+ 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 push_indent(indent_amount=4):
- global indent_level
- indent_level += indent_amount
+#: Global, current indent level for code generation.
+indent = Indentation()
-def pop_indent(indent_amount=4):
- global indent_level
- indent_level -= indent_amount
+def cgen(code: str, **kwds: object) -> str:
+ """
+ Generate ``code`` with ``kwds`` interpolated.
-# 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'
diff --git a/scripts/qapi/events.py b/scripts/qapi/events.py
index b544af5a1c..599f3d1f56 100644
--- a/scripts/qapi/events.py
+++ b/scripts/qapi/events.py
@@ -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
-
-
-def build_event_send_proto(name, arg_type, boxed):
+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: 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)
diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py
index a15c1fb474..2fcaaa2497 100644
--- a/scripts/qapi/expr.py
+++ b/scripts/qapi/expr.py
@@ -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,
diff --git a/scripts/qapi/gen.py b/scripts/qapi/gen.py
index ca66c82b5b..b40f18eee3 100644
--- a/scripts/qapi/gen.py
+++ b/scripts/qapi/gen.py
@@ -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 qapi.common import *
-from qapi.schema import QAPISchemaVisitor
+from typing import (
+ Dict,
+ Iterator,
+ List,
+ Optional,
+ Tuple,
+)
+
+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
-class QAPIGenCCode(QAPIGen):
+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'
- def __init__(self, fname):
+
+class QAPIGenCCode(QAPIGen):
+ 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('''
diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py
index 5907b09cd5..fafec94e02 100644
--- a/scripts/qapi/introspect.py
+++ b/scripts/qapi/introspect.py
@@ -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):
diff --git a/scripts/qapi/main.py b/scripts/qapi/main.py
new file mode 100644
index 0000000000..42517210b8
--- /dev/null
+++ b/scripts/qapi/main.py
@@ -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
diff --git a/scripts/qapi/mypy.ini b/scripts/qapi/mypy.ini
new file mode 100644
index 0000000000..74fc6c8215
--- /dev/null
+++ b/scripts/qapi/mypy.ini
@@ -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
diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
index 9d1a3e2eea..e7b9d670ad 100644
--- a/scripts/qapi/parser.py
+++ b/scripts/qapi/parser.py
@@ -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:
diff --git a/scripts/qapi/pylintrc b/scripts/qapi/pylintrc
new file mode 100644
index 0000000000..b9e077a164
--- /dev/null
+++ b/scripts/qapi/pylintrc
@@ -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]
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index d1307ec661..720449feee 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -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))
diff --git a/scripts/qapi/source.py b/scripts/qapi/source.py
index e97b9a8e15..d7a79a9b8a 100644
--- a/scripts/qapi/source.py
+++ b/scripts/qapi/source.py
@@ -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()
diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py
index 3640f17cd6..2b4916cdaa 100644
--- a/scripts/qapi/types.py
+++ b/scripts/qapi/types.py
@@ -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)
diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py
index cdabc5fa28..339f152152 100644
--- a/scripts/qapi/visit.py
+++ b/scripts/qapi/visit.py
@@ -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
-
-
-def gen_visit_decl(name, scalar=False):
+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: 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))
-
- def visit_alternate_type(self, name, info, ifcond, features, variants):
+ self._genc.add(gen_visit_object(name))
+
+ 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)