QAPI patches for 2019-10-22
-----BEGIN PGP SIGNATURE----- iQJGBAABCAAwFiEENUvIs9frKmtoZ05fOHC0AOuRhlMFAl2vGKQSHGFybWJydUBy ZWRoYXQuY29tAAoJEDhwtADrkYZTRx8QALT/lq55N4Z2cHmVtNsdPLIsHtWr/lrY p7PTlzw0GSkUc4PGFtVmkul+hHs5Veu6AXAKCOKId/h2LbqIN9aNdrhCX4cAMQxi joQXQhksNtibdEr/Hvomx5J6HW6vAh6eCUqAPbEhOCsoCmV1qvwLhOGCXxy/LDtI h8a9E7AXjDqQhpjlqpWmJyiOFA8jpFUJGGA0KzcC+1qWAC+CIZOYMVaI5Zg12zol cmW9tXW36eVaebVLdVZRPsgEK5kARHITq8dj9Mf5IlNx+LzRCHWcQHZVqH2nlU4B B5Vu0rWXZQveD32EkRmbB6h4fquXe5vnTAZsCCXy8e3XWLgmo0Sf9Y8+O5cbZ82e RlPsKbLRGbu9PFU1vaZ7K53dts9Fq8Ah69HjIl9sK8WW8FcSm1NJAPq+5vJf5iZF ilKtlWNqbr61+twfS02ZTpD6lrAEhpQQmBS8cFodPtTdggtrufl3NZQ3M+Y79DQL RXD8i/BBDutdw7GXquoHhPeY1iehcRHmKWVX0us4b8q1uxOBe58V24RueBnANyVZ WWufBB3lZvo3mEzK2IOOiKPoQ46OfndXc9SkqUPvaEkoX47MaT+4CxFuX6DBDlkT bVJa2BoAipV2NMfNsKbRG0iDx91lk45knt0uQ66v0Q5KDFfE06J+riaC/Uaz7ISq iXVz3JiB6Fka =BC95 -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/armbru/tags/pull-qapi-2019-10-22-v3' into staging QAPI patches for 2019-10-22 # gpg: Signature made Tue 22 Oct 2019 15:56:36 BST # gpg: using RSA key 354BC8B3D7EB2A6B68674E5F3870B400EB918653 # gpg: issuer "armbru@redhat.com" # gpg: Good signature from "Markus Armbruster <armbru@redhat.com>" [full] # gpg: aka "Markus Armbruster <armbru@pond.sub.org>" [full] # Primary key fingerprint: 354B C8B3 D7EB 2A6B 6867 4E5F 3870 B400 EB91 8653 * remotes/armbru/tags/pull-qapi-2019-10-22-v3: qapi: Allow introspecting fix for savevm's cooperation with blockdev tests/qapi-schema: Cover feature documentation comments tests: qapi: Test 'features' of commands qapi: Add feature flags to commands tests/qapi-schema: Tidy up test output indentation qapi: Clear scripts/qapi/doc.py executable bits again qapi: Split up scripts/qapi/common.py qapi: Move gen_enum(), gen_enum_lookup() back to qapi/types.py qapi: Speed up frontend tests qapi: Eliminate accidental global frontend state qapi: Store pragma state in QAPISourceInfo, not global state qapi: Don't suppress doc generation without pragma doc-required Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
69717d0f89
17
Makefile
17
Makefile
@ -582,13 +582,20 @@ qemu-ga$(EXESUF): QEMU_CFLAGS += -I qga/qapi-generated
|
||||
qemu-keymap$(EXESUF): LIBS += $(XKBCOMMON_LIBS)
|
||||
qemu-keymap$(EXESUF): QEMU_CFLAGS += $(XKBCOMMON_CFLAGS)
|
||||
|
||||
qapi-py = $(SRC_PATH)/scripts/qapi/commands.py \
|
||||
$(SRC_PATH)/scripts/qapi/events.py \
|
||||
$(SRC_PATH)/scripts/qapi/introspect.py \
|
||||
$(SRC_PATH)/scripts/qapi/types.py \
|
||||
$(SRC_PATH)/scripts/qapi/visit.py \
|
||||
qapi-py = $(SRC_PATH)/scripts/qapi/__init__.py \
|
||||
$(SRC_PATH)/scripts/qapi/commands.py \
|
||||
$(SRC_PATH)/scripts/qapi/common.py \
|
||||
$(SRC_PATH)/scripts/qapi/doc.py \
|
||||
$(SRC_PATH)/scripts/qapi/error.py \
|
||||
$(SRC_PATH)/scripts/qapi/events.py \
|
||||
$(SRC_PATH)/scripts/qapi/expr.py \
|
||||
$(SRC_PATH)/scripts/qapi/gen.py \
|
||||
$(SRC_PATH)/scripts/qapi/introspect.py \
|
||||
$(SRC_PATH)/scripts/qapi/parser.py \
|
||||
$(SRC_PATH)/scripts/qapi/schema.py \
|
||||
$(SRC_PATH)/scripts/qapi/source.py \
|
||||
$(SRC_PATH)/scripts/qapi/types.py \
|
||||
$(SRC_PATH)/scripts/qapi/visit.py \
|
||||
$(SRC_PATH)/scripts/qapi-gen.py
|
||||
|
||||
qga/qapi-generated/qga-qapi-types.c qga/qapi-generated/qga-qapi-types.h \
|
||||
|
@ -457,7 +457,8 @@ Syntax:
|
||||
'*gen': false,
|
||||
'*allow-oob': true,
|
||||
'*allow-preconfig': true,
|
||||
'*if': COND }
|
||||
'*if': COND,
|
||||
'*features': FEATURES }
|
||||
|
||||
Member 'command' names the command.
|
||||
|
||||
@ -640,9 +641,10 @@ change in the QMP syntax (usually by allowing values or operations
|
||||
that previously resulted in an error). QMP clients may still need to
|
||||
know whether the extension is available.
|
||||
|
||||
For this purpose, a list of features can be specified for a struct type.
|
||||
This is exposed to the client as a list of string, where each string
|
||||
signals that this build of QEMU shows a certain behaviour.
|
||||
For this purpose, a list of features can be specified for a command or
|
||||
struct type. This is exposed to the client as a list of strings,
|
||||
where each string signals that this build of QEMU shows a certain
|
||||
behaviour.
|
||||
|
||||
Each member of the 'features' array defines a feature. It can either
|
||||
be { 'name': STRING, '*if': COND }, or STRING, which is shorthand for
|
||||
|
@ -266,13 +266,17 @@
|
||||
# @allow-oob: whether the command allows out-of-band execution,
|
||||
# defaults to false (Since: 2.12)
|
||||
#
|
||||
# @features: names of features associated with the command, in no particular
|
||||
# order. (since 4.2)
|
||||
#
|
||||
# TODO: @success-response (currently irrelevant, because it's QGA, not QMP)
|
||||
#
|
||||
# Since: 2.5
|
||||
##
|
||||
{ 'struct': 'SchemaInfoCommand',
|
||||
'data': { 'arg-type': 'str', 'ret-type': 'str',
|
||||
'*allow-oob': 'bool' } }
|
||||
'*allow-oob': 'bool',
|
||||
'*features': [ 'str' ] } }
|
||||
|
||||
##
|
||||
# @SchemaInfoEvent:
|
||||
|
@ -1020,6 +1020,12 @@
|
||||
#
|
||||
# @cpu-index: The CPU to use for commands that require an implicit CPU
|
||||
#
|
||||
# Features:
|
||||
# @savevm-monitor-nodes: If present, HMP command savevm only snapshots
|
||||
# monitor-owned nodes if they have no parents.
|
||||
# This allows the use of 'savevm' with
|
||||
# -blockdev. (since 4.2)
|
||||
#
|
||||
# Returns: the output of the command as a string
|
||||
#
|
||||
# Since: 0.14.0
|
||||
@ -1047,7 +1053,8 @@
|
||||
##
|
||||
{ 'command': 'human-monitor-command',
|
||||
'data': {'command-line': 'str', '*cpu-index': 'int'},
|
||||
'returns': 'str' }
|
||||
'returns': 'str',
|
||||
'features': [ 'savevm-monitor-nodes' ] }
|
||||
|
||||
##
|
||||
# @change:
|
||||
|
@ -5,16 +5,18 @@
|
||||
# See the COPYING file in the top-level directory.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
from qapi.common import QAPIError, QAPISchema
|
||||
from qapi.types import gen_types
|
||||
from qapi.visit import gen_visit
|
||||
|
||||
from qapi.commands import gen_commands
|
||||
from qapi.doc import gen_doc
|
||||
from qapi.events import gen_events
|
||||
from qapi.introspect import gen_introspect
|
||||
from qapi.doc import gen_doc
|
||||
from qapi.schema import QAPIError, QAPISchema
|
||||
from qapi.types import gen_types
|
||||
from qapi.visit import gen_visit
|
||||
|
||||
|
||||
def main(argv):
|
||||
|
@ -14,6 +14,7 @@ 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):
|
||||
@ -276,7 +277,8 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
|
||||
genc.add(gen_registry(self._regy.get_content(), self._prefix))
|
||||
|
||||
def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
|
||||
success_response, boxed, allow_oob, allow_preconfig):
|
||||
success_response, boxed, allow_oob, allow_preconfig,
|
||||
features):
|
||||
if not gen:
|
||||
return
|
||||
# FIXME: If T is a user-defined type, the user is responsible
|
||||
|
File diff suppressed because it is too large
Load Diff
14
scripts/qapi/doc.py
Executable file → Normal file
14
scripts/qapi/doc.py
Executable file → Normal file
@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# QAPI texi generator
|
||||
#
|
||||
# This work is licensed under the terms of the GNU LGPL, version 2+.
|
||||
@ -7,7 +6,8 @@
|
||||
|
||||
from __future__ import print_function
|
||||
import re
|
||||
import qapi.common
|
||||
from qapi.gen import QAPIGenDoc, QAPISchemaVisitor
|
||||
|
||||
|
||||
MSG_FMT = """
|
||||
@deftypefn {type} {{}} {name}
|
||||
@ -216,10 +216,10 @@ def texi_entity(doc, what, ifcond, base=None, variants=None,
|
||||
+ texi_sections(doc, ifcond))
|
||||
|
||||
|
||||
class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor):
|
||||
class QAPISchemaGenDocVisitor(QAPISchemaVisitor):
|
||||
def __init__(self, prefix):
|
||||
self._prefix = prefix
|
||||
self._gen = qapi.common.QAPIGenDoc(self._prefix + 'qapi-doc.texi')
|
||||
self._gen = QAPIGenDoc(self._prefix + 'qapi-doc.texi')
|
||||
self.cur_doc = None
|
||||
|
||||
def write(self, output_dir):
|
||||
@ -249,12 +249,14 @@ class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor):
|
||||
body=texi_entity(doc, 'Members', ifcond)))
|
||||
|
||||
def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
|
||||
success_response, boxed, allow_oob, allow_preconfig):
|
||||
success_response, boxed, allow_oob, allow_preconfig,
|
||||
features):
|
||||
doc = self.cur_doc
|
||||
if boxed:
|
||||
body = texi_body(doc)
|
||||
body += ('\n@b{Arguments:} the members of @code{%s}\n'
|
||||
% arg_type.name)
|
||||
body += texi_features(doc)
|
||||
body += texi_sections(doc, ifcond)
|
||||
else:
|
||||
body = texi_entity(doc, 'Arguments', ifcond)
|
||||
@ -283,8 +285,6 @@ class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor):
|
||||
|
||||
|
||||
def gen_doc(schema, output_dir, prefix):
|
||||
if not qapi.common.doc_required:
|
||||
return
|
||||
vis = QAPISchemaGenDocVisitor(prefix)
|
||||
vis.visit_begin(schema)
|
||||
for doc in schema.docs:
|
||||
|
43
scripts/qapi/error.py
Normal file
43
scripts/qapi/error.py
Normal file
@ -0,0 +1,43 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# QAPI error classes
|
||||
#
|
||||
# Copyright (c) 2017-2019 Red Hat Inc.
|
||||
#
|
||||
# Authors:
|
||||
# Markus Armbruster <armbru@redhat.com>
|
||||
# Marc-André Lureau <marcandre.lureau@redhat.com>
|
||||
#
|
||||
# This work is licensed under the terms of the GNU GPL, version 2.
|
||||
# See the COPYING file in the top-level directory.
|
||||
|
||||
|
||||
class QAPIError(Exception):
|
||||
def __init__(self, info, col, msg):
|
||||
Exception.__init__(self)
|
||||
self.info = info
|
||||
self.col = col
|
||||
self.msg = msg
|
||||
|
||||
def __str__(self):
|
||||
loc = str(self.info)
|
||||
if self.col is not None:
|
||||
assert self.info.line is not None
|
||||
loc += ':%s' % self.col
|
||||
return loc + ': ' + self.msg
|
||||
|
||||
|
||||
class QAPIParseError(QAPIError):
|
||||
def __init__(self, parser, msg):
|
||||
col = 1
|
||||
for ch in parser.src[parser.line_pos:parser.pos]:
|
||||
if ch == '\t':
|
||||
col = (col + 7) % 8 + 1
|
||||
else:
|
||||
col += 1
|
||||
QAPIError.__init__(self, parser.info, col, msg)
|
||||
|
||||
|
||||
class QAPISemError(QAPIError):
|
||||
def __init__(self, info, msg):
|
||||
QAPIError.__init__(self, info, None, msg)
|
@ -13,6 +13,9 @@ 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):
|
||||
|
383
scripts/qapi/expr.py
Normal file
383
scripts/qapi/expr.py
Normal file
@ -0,0 +1,383 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Check (context-free) QAPI schema expression structure
|
||||
#
|
||||
# Copyright IBM, Corp. 2011
|
||||
# Copyright (c) 2013-2019 Red Hat Inc.
|
||||
#
|
||||
# Authors:
|
||||
# Anthony Liguori <aliguori@us.ibm.com>
|
||||
# Markus Armbruster <armbru@redhat.com>
|
||||
# Eric Blake <eblake@redhat.com>
|
||||
# Marc-André Lureau <marcandre.lureau@redhat.com>
|
||||
#
|
||||
# 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
|
||||
|
||||
|
||||
# Names must be letters, numbers, -, and _. They must start with letter,
|
||||
# except for downstream extensions which must start with __RFQDN_.
|
||||
# Dots are only valid in the downstream extension prefix.
|
||||
valid_name = re.compile(r'^(__[a-zA-Z0-9.-]+_)?'
|
||||
'[a-zA-Z][a-zA-Z0-9_-]*$')
|
||||
|
||||
|
||||
def check_name_is_str(name, info, source):
|
||||
if not isinstance(name, str):
|
||||
raise QAPISemError(info, "%s requires a string name" % source)
|
||||
|
||||
|
||||
def check_name_str(name, info, source,
|
||||
allow_optional=False, enum_member=False,
|
||||
permit_upper=False):
|
||||
global valid_name
|
||||
membername = name
|
||||
|
||||
if allow_optional and name.startswith('*'):
|
||||
membername = name[1:]
|
||||
# Enum members can start with a digit, because the generated C
|
||||
# code always prefixes it with the enum name
|
||||
if enum_member and membername[0].isdigit():
|
||||
membername = 'D' + membername
|
||||
# Reserve the entire 'q_' namespace for c_name(), and for 'q_empty'
|
||||
# and 'q_obj_*' implicit type names.
|
||||
if not valid_name.match(membername) or \
|
||||
c_name(membername, False).startswith('q_'):
|
||||
raise QAPISemError(info, "%s has an invalid name" % source)
|
||||
if not permit_upper and name.lower() != name:
|
||||
raise QAPISemError(
|
||||
info, "%s uses uppercase in name" % source)
|
||||
assert not membername.startswith('*')
|
||||
|
||||
|
||||
def check_defn_name_str(name, info, meta):
|
||||
check_name_str(name, info, meta, permit_upper=True)
|
||||
if name.endswith('Kind') or name.endswith('List'):
|
||||
raise QAPISemError(
|
||||
info, "%s name should not end in '%s'" % (meta, name[-4:]))
|
||||
|
||||
|
||||
def check_keys(value, info, source, required, optional):
|
||||
|
||||
def pprint(elems):
|
||||
return ', '.join("'" + e + "'" for e in sorted(elems))
|
||||
|
||||
missing = set(required) - set(value)
|
||||
if missing:
|
||||
raise QAPISemError(
|
||||
info,
|
||||
"%s misses key%s %s"
|
||||
% (source, 's' if len(missing) > 1 else '',
|
||||
pprint(missing)))
|
||||
allowed = set(required + optional)
|
||||
unknown = set(value) - allowed
|
||||
if unknown:
|
||||
raise QAPISemError(
|
||||
info,
|
||||
"%s has unknown key%s %s\nValid keys are %s."
|
||||
% (source, 's' if len(unknown) > 1 else '',
|
||||
pprint(unknown), pprint(allowed)))
|
||||
|
||||
|
||||
def check_flags(expr, info):
|
||||
for key in ['gen', 'success-response']:
|
||||
if key in expr and expr[key] is not False:
|
||||
raise QAPISemError(
|
||||
info, "flag '%s' may only use false value" % key)
|
||||
for key in ['boxed', 'allow-oob', 'allow-preconfig']:
|
||||
if key in expr and expr[key] is not True:
|
||||
raise QAPISemError(
|
||||
info, "flag '%s' may only use true value" % key)
|
||||
|
||||
|
||||
def normalize_if(expr):
|
||||
ifcond = expr.get('if')
|
||||
if isinstance(ifcond, str):
|
||||
expr['if'] = [ifcond]
|
||||
|
||||
|
||||
def check_if(expr, info, source):
|
||||
|
||||
def check_if_str(ifcond, info):
|
||||
if not isinstance(ifcond, str):
|
||||
raise QAPISemError(
|
||||
info,
|
||||
"'if' condition of %s must be a string or a list of strings"
|
||||
% source)
|
||||
if ifcond.strip() == '':
|
||||
raise QAPISemError(
|
||||
info,
|
||||
"'if' condition '%s' of %s makes no sense"
|
||||
% (ifcond, source))
|
||||
|
||||
ifcond = expr.get('if')
|
||||
if ifcond is None:
|
||||
return
|
||||
if isinstance(ifcond, list):
|
||||
if ifcond == []:
|
||||
raise QAPISemError(
|
||||
info, "'if' condition [] of %s is useless" % source)
|
||||
for elt in ifcond:
|
||||
check_if_str(elt, info)
|
||||
else:
|
||||
check_if_str(ifcond, info)
|
||||
|
||||
|
||||
def normalize_members(members):
|
||||
if isinstance(members, OrderedDict):
|
||||
for key, arg in members.items():
|
||||
if isinstance(arg, dict):
|
||||
continue
|
||||
members[key] = {'type': arg}
|
||||
|
||||
|
||||
def check_type(value, info, source,
|
||||
allow_array=False, allow_dict=False):
|
||||
if value is None:
|
||||
return
|
||||
|
||||
# Array type
|
||||
if isinstance(value, list):
|
||||
if not allow_array:
|
||||
raise QAPISemError(info, "%s cannot be an array" % source)
|
||||
if len(value) != 1 or not isinstance(value[0], str):
|
||||
raise QAPISemError(info,
|
||||
"%s: array type must contain single type name" %
|
||||
source)
|
||||
return
|
||||
|
||||
# Type name
|
||||
if isinstance(value, str):
|
||||
return
|
||||
|
||||
# Anonymous type
|
||||
|
||||
if not allow_dict:
|
||||
raise QAPISemError(info, "%s should be a type name" % source)
|
||||
|
||||
if not isinstance(value, OrderedDict):
|
||||
raise QAPISemError(info,
|
||||
"%s should be an object or type name" % source)
|
||||
|
||||
permit_upper = allow_dict in info.pragma.name_case_whitelist
|
||||
|
||||
# value is a dictionary, check that each member is okay
|
||||
for (key, arg) in value.items():
|
||||
key_source = "%s member '%s'" % (source, key)
|
||||
check_name_str(key, info, key_source,
|
||||
allow_optional=True, permit_upper=permit_upper)
|
||||
if c_name(key, False) == 'u' or c_name(key, False).startswith('has_'):
|
||||
raise QAPISemError(info, "%s uses reserved name" % key_source)
|
||||
check_keys(arg, info, key_source, ['type'], ['if'])
|
||||
check_if(arg, info, key_source)
|
||||
normalize_if(arg)
|
||||
check_type(arg['type'], info, key_source, allow_array=True)
|
||||
|
||||
|
||||
def normalize_features(features):
|
||||
if isinstance(features, list):
|
||||
features[:] = [f if isinstance(f, dict) else {'name': f}
|
||||
for f in features]
|
||||
|
||||
|
||||
def check_features(features, info):
|
||||
if features is None:
|
||||
return
|
||||
if not isinstance(features, list):
|
||||
raise QAPISemError(info, "'features' must be an array")
|
||||
for f in features:
|
||||
source = "'features' member"
|
||||
assert isinstance(f, dict)
|
||||
check_keys(f, info, source, ['name'], ['if'])
|
||||
check_name_is_str(f['name'], info, source)
|
||||
source = "%s '%s'" % (source, f['name'])
|
||||
check_name_str(f['name'], info, source)
|
||||
check_if(f, info, source)
|
||||
normalize_if(f)
|
||||
|
||||
|
||||
def normalize_enum(expr):
|
||||
if isinstance(expr['data'], list):
|
||||
expr['data'] = [m if isinstance(m, dict) else {'name': m}
|
||||
for m in expr['data']]
|
||||
|
||||
|
||||
def check_enum(expr, info):
|
||||
name = expr['enum']
|
||||
members = expr['data']
|
||||
prefix = expr.get('prefix')
|
||||
|
||||
if not isinstance(members, list):
|
||||
raise QAPISemError(info, "'data' must be an array")
|
||||
if prefix is not None and not isinstance(prefix, str):
|
||||
raise QAPISemError(info, "'prefix' must be a string")
|
||||
|
||||
permit_upper = name in info.pragma.name_case_whitelist
|
||||
|
||||
for member in members:
|
||||
source = "'data' member"
|
||||
check_keys(member, info, source, ['name'], ['if'])
|
||||
check_name_is_str(member['name'], info, source)
|
||||
source = "%s '%s'" % (source, member['name'])
|
||||
check_name_str(member['name'], info, source,
|
||||
enum_member=True, permit_upper=permit_upper)
|
||||
check_if(member, info, source)
|
||||
normalize_if(member)
|
||||
|
||||
|
||||
def check_struct(expr, info):
|
||||
name = expr['struct']
|
||||
members = expr['data']
|
||||
|
||||
check_type(members, info, "'data'", allow_dict=name)
|
||||
check_type(expr.get('base'), info, "'base'")
|
||||
check_features(expr.get('features'), info)
|
||||
|
||||
|
||||
def check_union(expr, info):
|
||||
name = expr['union']
|
||||
base = expr.get('base')
|
||||
discriminator = expr.get('discriminator')
|
||||
members = expr['data']
|
||||
|
||||
if discriminator is None: # simple union
|
||||
if base is not None:
|
||||
raise QAPISemError(info, "'base' requires 'discriminator'")
|
||||
else: # flat union
|
||||
check_type(base, info, "'base'", allow_dict=name)
|
||||
if not base:
|
||||
raise QAPISemError(info, "'discriminator' requires 'base'")
|
||||
check_name_is_str(discriminator, info, "'discriminator'")
|
||||
|
||||
for (key, value) in members.items():
|
||||
source = "'data' member '%s'" % key
|
||||
check_name_str(key, info, source)
|
||||
check_keys(value, info, source, ['type'], ['if'])
|
||||
check_if(value, info, source)
|
||||
normalize_if(value)
|
||||
check_type(value['type'], info, source, allow_array=not base)
|
||||
|
||||
|
||||
def check_alternate(expr, info):
|
||||
members = expr['data']
|
||||
|
||||
if len(members) == 0:
|
||||
raise QAPISemError(info, "'data' must not be empty")
|
||||
for (key, value) in members.items():
|
||||
source = "'data' member '%s'" % key
|
||||
check_name_str(key, info, source)
|
||||
check_keys(value, info, source, ['type'], ['if'])
|
||||
check_if(value, info, source)
|
||||
normalize_if(value)
|
||||
check_type(value['type'], info, source)
|
||||
|
||||
|
||||
def check_command(expr, info):
|
||||
args = expr.get('data')
|
||||
rets = expr.get('returns')
|
||||
boxed = expr.get('boxed', False)
|
||||
|
||||
if boxed and args is None:
|
||||
raise QAPISemError(info, "'boxed': true requires 'data'")
|
||||
check_type(args, info, "'data'", allow_dict=not boxed)
|
||||
check_type(rets, info, "'returns'", allow_array=True)
|
||||
check_features(expr.get('features'), info)
|
||||
|
||||
|
||||
def check_event(expr, info):
|
||||
args = expr.get('data')
|
||||
boxed = expr.get('boxed', False)
|
||||
|
||||
if boxed and args is None:
|
||||
raise QAPISemError(info, "'boxed': true requires 'data'")
|
||||
check_type(args, info, "'data'", allow_dict=not boxed)
|
||||
|
||||
|
||||
def check_exprs(exprs):
|
||||
for expr_elem in exprs:
|
||||
expr = expr_elem['expr']
|
||||
info = expr_elem['info']
|
||||
doc = expr_elem.get('doc')
|
||||
|
||||
if 'include' in expr:
|
||||
continue
|
||||
|
||||
if 'enum' in expr:
|
||||
meta = 'enum'
|
||||
elif 'union' in expr:
|
||||
meta = 'union'
|
||||
elif 'alternate' in expr:
|
||||
meta = 'alternate'
|
||||
elif 'struct' in expr:
|
||||
meta = 'struct'
|
||||
elif 'command' in expr:
|
||||
meta = 'command'
|
||||
elif 'event' in expr:
|
||||
meta = 'event'
|
||||
else:
|
||||
raise QAPISemError(info, "expression is missing metatype")
|
||||
|
||||
name = expr[meta]
|
||||
check_name_is_str(name, info, "'%s'" % meta)
|
||||
info.set_defn(meta, name)
|
||||
check_defn_name_str(name, info, meta)
|
||||
|
||||
if doc:
|
||||
if doc.symbol != name:
|
||||
raise QAPISemError(
|
||||
info, "documentation comment is for '%s'" % doc.symbol)
|
||||
doc.check_expr(expr)
|
||||
elif info.pragma.doc_required:
|
||||
raise QAPISemError(info,
|
||||
"documentation comment required")
|
||||
|
||||
if meta == 'enum':
|
||||
check_keys(expr, info, meta,
|
||||
['enum', 'data'], ['if', 'prefix'])
|
||||
normalize_enum(expr)
|
||||
check_enum(expr, info)
|
||||
elif meta == 'union':
|
||||
check_keys(expr, info, meta,
|
||||
['union', 'data'],
|
||||
['base', 'discriminator', 'if'])
|
||||
normalize_members(expr.get('base'))
|
||||
normalize_members(expr['data'])
|
||||
check_union(expr, info)
|
||||
elif meta == 'alternate':
|
||||
check_keys(expr, info, meta,
|
||||
['alternate', 'data'], ['if'])
|
||||
normalize_members(expr['data'])
|
||||
check_alternate(expr, info)
|
||||
elif meta == 'struct':
|
||||
check_keys(expr, info, meta,
|
||||
['struct', 'data'], ['base', 'if', 'features'])
|
||||
normalize_members(expr['data'])
|
||||
normalize_features(expr.get('features'))
|
||||
check_struct(expr, info)
|
||||
elif meta == 'command':
|
||||
check_keys(expr, info, meta,
|
||||
['command'],
|
||||
['data', 'returns', 'boxed', 'if', 'features',
|
||||
'gen', 'success-response', 'allow-oob',
|
||||
'allow-preconfig'])
|
||||
normalize_members(expr.get('data'))
|
||||
normalize_features(expr.get('features'))
|
||||
check_command(expr, info)
|
||||
elif meta == 'event':
|
||||
check_keys(expr, info, meta,
|
||||
['event'], ['data', 'boxed', 'if'])
|
||||
normalize_members(expr.get('data'))
|
||||
check_event(expr, info)
|
||||
else:
|
||||
assert False, 'unexpected meta type'
|
||||
|
||||
normalize_if(expr)
|
||||
check_if(expr, info, meta)
|
||||
check_flags(expr, info)
|
||||
|
||||
return exprs
|
291
scripts/qapi/gen.py
Normal file
291
scripts/qapi/gen.py
Normal file
@ -0,0 +1,291 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# QAPI code generation
|
||||
#
|
||||
# Copyright (c) 2018-2019 Red Hat Inc.
|
||||
#
|
||||
# Authors:
|
||||
# Markus Armbruster <armbru@redhat.com>
|
||||
# Marc-André Lureau <marcandre.lureau@redhat.com>
|
||||
#
|
||||
# This work is licensed under the terms of the GNU GPL, version 2.
|
||||
# See the COPYING file in the top-level directory.
|
||||
|
||||
|
||||
import errno
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
|
||||
from qapi.common import *
|
||||
from qapi.schema import QAPISchemaVisitor
|
||||
|
||||
|
||||
class QAPIGen(object):
|
||||
|
||||
def __init__(self, fname):
|
||||
self.fname = fname
|
||||
self._preamble = ''
|
||||
self._body = ''
|
||||
|
||||
def preamble_add(self, text):
|
||||
self._preamble += text
|
||||
|
||||
def add(self, text):
|
||||
self._body += text
|
||||
|
||||
def get_content(self):
|
||||
return self._top() + self._preamble + self._body + self._bottom()
|
||||
|
||||
def _top(self):
|
||||
return ''
|
||||
|
||||
def _bottom(self):
|
||||
return ''
|
||||
|
||||
def write(self, output_dir):
|
||||
pathname = os.path.join(output_dir, self.fname)
|
||||
dir = os.path.dirname(pathname)
|
||||
if dir:
|
||||
try:
|
||||
os.makedirs(dir)
|
||||
except os.error as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666)
|
||||
if sys.version_info[0] >= 3:
|
||||
f = open(fd, 'r+', encoding='utf-8')
|
||||
else:
|
||||
f = os.fdopen(fd, 'r+')
|
||||
text = self.get_content()
|
||||
oldtext = f.read(len(text) + 1)
|
||||
if text != oldtext:
|
||||
f.seek(0)
|
||||
f.truncate(0)
|
||||
f.write(text)
|
||||
f.close()
|
||||
|
||||
|
||||
def _wrap_ifcond(ifcond, before, after):
|
||||
if before == after:
|
||||
return after # suppress empty #if ... #endif
|
||||
|
||||
assert after.startswith(before)
|
||||
out = before
|
||||
added = after[len(before):]
|
||||
if added[0] == '\n':
|
||||
out += '\n'
|
||||
added = added[1:]
|
||||
out += gen_if(ifcond)
|
||||
out += added
|
||||
out += gen_endif(ifcond)
|
||||
return out
|
||||
|
||||
|
||||
class QAPIGenCCode(QAPIGen):
|
||||
|
||||
def __init__(self, fname):
|
||||
QAPIGen.__init__(self, fname)
|
||||
self._start_if = None
|
||||
|
||||
def start_if(self, ifcond):
|
||||
assert self._start_if is None
|
||||
self._start_if = (ifcond, self._body, self._preamble)
|
||||
|
||||
def end_if(self):
|
||||
assert self._start_if
|
||||
self._wrap_ifcond()
|
||||
self._start_if = None
|
||||
|
||||
def _wrap_ifcond(self):
|
||||
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):
|
||||
assert self._start_if is None
|
||||
return QAPIGen.get_content(self)
|
||||
|
||||
|
||||
class QAPIGenC(QAPIGenCCode):
|
||||
|
||||
def __init__(self, fname, blurb, pydoc):
|
||||
QAPIGenCCode.__init__(self, fname)
|
||||
self._blurb = blurb
|
||||
self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc,
|
||||
re.MULTILINE))
|
||||
|
||||
def _top(self):
|
||||
return mcgen('''
|
||||
/* AUTOMATICALLY GENERATED, DO NOT MODIFY */
|
||||
|
||||
/*
|
||||
%(blurb)s
|
||||
*
|
||||
* %(copyright)s
|
||||
*
|
||||
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
|
||||
* See the COPYING.LIB file in the top-level directory.
|
||||
*/
|
||||
|
||||
''',
|
||||
blurb=self._blurb, copyright=self._copyright)
|
||||
|
||||
def _bottom(self):
|
||||
return mcgen('''
|
||||
|
||||
/* Dummy declaration to prevent empty .o file */
|
||||
char qapi_dummy_%(name)s;
|
||||
''',
|
||||
name=c_fname(self.fname))
|
||||
|
||||
|
||||
class QAPIGenH(QAPIGenC):
|
||||
|
||||
def _top(self):
|
||||
return QAPIGenC._top(self) + guardstart(self.fname)
|
||||
|
||||
def _bottom(self):
|
||||
return guardend(self.fname)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def ifcontext(ifcond, *args):
|
||||
"""A 'with' statement context manager to wrap with start_if()/end_if()
|
||||
|
||||
*args: any number of QAPIGenCCode
|
||||
|
||||
Example::
|
||||
|
||||
with ifcontext(ifcond, self._genh, self._genc):
|
||||
modify self._genh and self._genc ...
|
||||
|
||||
Is equivalent to calling::
|
||||
|
||||
self._genh.start_if(ifcond)
|
||||
self._genc.start_if(ifcond)
|
||||
modify self._genh and self._genc ...
|
||||
self._genh.end_if()
|
||||
self._genc.end_if()
|
||||
"""
|
||||
for arg in args:
|
||||
arg.start_if(ifcond)
|
||||
yield
|
||||
for arg in args:
|
||||
arg.end_if()
|
||||
|
||||
|
||||
class QAPIGenDoc(QAPIGen):
|
||||
|
||||
def _top(self):
|
||||
return (QAPIGen._top(self)
|
||||
+ '@c AUTOMATICALLY GENERATED, DO NOT MODIFY\n\n')
|
||||
|
||||
|
||||
class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
|
||||
|
||||
def __init__(self, prefix, what, blurb, pydoc):
|
||||
self._prefix = prefix
|
||||
self._what = what
|
||||
self._genc = QAPIGenC(self._prefix + self._what + '.c',
|
||||
blurb, pydoc)
|
||||
self._genh = QAPIGenH(self._prefix + self._what + '.h',
|
||||
blurb, pydoc)
|
||||
|
||||
def write(self, output_dir):
|
||||
self._genc.write(output_dir)
|
||||
self._genh.write(output_dir)
|
||||
|
||||
|
||||
class QAPISchemaModularCVisitor(QAPISchemaVisitor):
|
||||
|
||||
def __init__(self, prefix, what, blurb, pydoc):
|
||||
self._prefix = prefix
|
||||
self._what = what
|
||||
self._blurb = blurb
|
||||
self._pydoc = pydoc
|
||||
self._genc = None
|
||||
self._genh = None
|
||||
self._module = {}
|
||||
self._main_module = None
|
||||
|
||||
@staticmethod
|
||||
def _is_user_module(name):
|
||||
return name and not name.startswith('./')
|
||||
|
||||
@staticmethod
|
||||
def _is_builtin_module(name):
|
||||
return not name
|
||||
|
||||
def _module_dirname(self, what, name):
|
||||
if self._is_user_module(name):
|
||||
return os.path.dirname(name)
|
||||
return ''
|
||||
|
||||
def _module_basename(self, what, name):
|
||||
ret = '' if self._is_builtin_module(name) else self._prefix
|
||||
if self._is_user_module(name):
|
||||
basename = os.path.basename(name)
|
||||
ret += what
|
||||
if name != self._main_module:
|
||||
ret += '-' + os.path.splitext(basename)[0]
|
||||
else:
|
||||
name = name[2:] if name else 'builtin'
|
||||
ret += re.sub(r'-', '-' + name + '-', what)
|
||||
return ret
|
||||
|
||||
def _module_filename(self, what, name):
|
||||
return os.path.join(self._module_dirname(what, name),
|
||||
self._module_basename(what, name))
|
||||
|
||||
def _add_module(self, name, blurb):
|
||||
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._set_module(name)
|
||||
|
||||
def _add_user_module(self, name, blurb):
|
||||
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):
|
||||
self._add_module(name and './' + name, blurb)
|
||||
|
||||
def _set_module(self, name):
|
||||
self._genc, self._genh = self._module[name]
|
||||
|
||||
def write(self, output_dir, opt_builtins=False):
|
||||
for name in self._module:
|
||||
if self._is_builtin_module(name) and not opt_builtins:
|
||||
continue
|
||||
(genc, genh) = self._module[name]
|
||||
genc.write(output_dir)
|
||||
genh.write(output_dir)
|
||||
|
||||
def _begin_user_module(self, name):
|
||||
pass
|
||||
|
||||
def visit_module(self, name):
|
||||
if name in self._module:
|
||||
self._set_module(name)
|
||||
elif self._is_builtin_module(name):
|
||||
# The built-in module has not been created. No code may
|
||||
# be generated.
|
||||
self._genc = None
|
||||
self._genh = None
|
||||
else:
|
||||
self._add_user_module(name, self._blurb)
|
||||
self._begin_user_module(name)
|
||||
|
||||
def visit_include(self, name, info):
|
||||
relname = os.path.relpath(self._module_filename(self._what, name),
|
||||
os.path.dirname(self._genh.fname))
|
||||
self._genh.preamble_add(mcgen('''
|
||||
#include "%(relname)s.h"
|
||||
''',
|
||||
relname=relname))
|
@ -10,7 +10,12 @@ This work is licensed under the terms of the GNU GPL, version 2.
|
||||
See the COPYING file in the top-level directory.
|
||||
"""
|
||||
|
||||
import string
|
||||
|
||||
from qapi.common import *
|
||||
from qapi.gen import QAPISchemaMonolithicCVisitor
|
||||
from qapi.schema import (QAPISchemaArrayType, QAPISchemaBuiltinType,
|
||||
QAPISchemaType)
|
||||
|
||||
|
||||
def to_qlit(obj, level=0, suppress_first_indent=False):
|
||||
@ -206,13 +211,18 @@ const QLitObject %(c_name)s = %(c_string)s;
|
||||
for m in variants.variants]}, ifcond)
|
||||
|
||||
def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
|
||||
success_response, boxed, allow_oob, allow_preconfig):
|
||||
success_response, boxed, allow_oob, allow_preconfig,
|
||||
features):
|
||||
arg_type = arg_type or self._schema.the_empty_object_type
|
||||
ret_type = ret_type or self._schema.the_empty_object_type
|
||||
obj = {'arg-type': self._use_type(arg_type),
|
||||
'ret-type': self._use_type(ret_type)}
|
||||
if allow_oob:
|
||||
obj['allow-oob'] = allow_oob
|
||||
|
||||
if features:
|
||||
obj['features'] = [(f.name, {'if': f.ifcond}) for f in features]
|
||||
|
||||
self._gen_qlit(name, 'command', obj, ifcond)
|
||||
|
||||
def visit_event(self, name, info, ifcond, arg_type, boxed):
|
||||
|
570
scripts/qapi/parser.py
Normal file
570
scripts/qapi/parser.py
Normal file
@ -0,0 +1,570 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# QAPI schema parser
|
||||
#
|
||||
# Copyright IBM, Corp. 2011
|
||||
# Copyright (c) 2013-2019 Red Hat Inc.
|
||||
#
|
||||
# Authors:
|
||||
# Anthony Liguori <aliguori@us.ibm.com>
|
||||
# Markus Armbruster <armbru@redhat.com>
|
||||
# Marc-André Lureau <marcandre.lureau@redhat.com>
|
||||
# Kevin Wolf <kwolf@redhat.com>
|
||||
#
|
||||
# This work is licensed under the terms of the GNU GPL, version 2.
|
||||
# See the COPYING file in the top-level directory.
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from collections import OrderedDict
|
||||
|
||||
from qapi.error import QAPIParseError, QAPISemError
|
||||
from qapi.source import QAPISourceInfo
|
||||
|
||||
|
||||
class QAPISchemaParser(object):
|
||||
|
||||
def __init__(self, fname, previously_included=None, incl_info=None):
|
||||
previously_included = previously_included or set()
|
||||
previously_included.add(os.path.abspath(fname))
|
||||
|
||||
try:
|
||||
if sys.version_info[0] >= 3:
|
||||
fp = open(fname, 'r', encoding='utf-8')
|
||||
else:
|
||||
fp = open(fname, 'r')
|
||||
self.src = fp.read()
|
||||
except IOError as e:
|
||||
raise QAPISemError(incl_info or QAPISourceInfo(None, None, None),
|
||||
"can't read %s file '%s': %s"
|
||||
% ("include" if incl_info else "schema",
|
||||
fname,
|
||||
e.strerror))
|
||||
|
||||
if self.src == '' or self.src[-1] != '\n':
|
||||
self.src += '\n'
|
||||
self.cursor = 0
|
||||
self.info = QAPISourceInfo(fname, 1, incl_info)
|
||||
self.line_pos = 0
|
||||
self.exprs = []
|
||||
self.docs = []
|
||||
self.accept()
|
||||
cur_doc = None
|
||||
|
||||
while self.tok is not None:
|
||||
info = self.info
|
||||
if self.tok == '#':
|
||||
self.reject_expr_doc(cur_doc)
|
||||
cur_doc = self.get_doc(info)
|
||||
self.docs.append(cur_doc)
|
||||
continue
|
||||
|
||||
expr = self.get_expr(False)
|
||||
if 'include' in expr:
|
||||
self.reject_expr_doc(cur_doc)
|
||||
if len(expr) != 1:
|
||||
raise QAPISemError(info, "invalid 'include' directive")
|
||||
include = expr['include']
|
||||
if not isinstance(include, str):
|
||||
raise QAPISemError(info,
|
||||
"value of 'include' must be a string")
|
||||
incl_fname = os.path.join(os.path.dirname(fname),
|
||||
include)
|
||||
self.exprs.append({'expr': {'include': incl_fname},
|
||||
'info': info})
|
||||
exprs_include = self._include(include, info, incl_fname,
|
||||
previously_included)
|
||||
if exprs_include:
|
||||
self.exprs.extend(exprs_include.exprs)
|
||||
self.docs.extend(exprs_include.docs)
|
||||
elif "pragma" in expr:
|
||||
self.reject_expr_doc(cur_doc)
|
||||
if len(expr) != 1:
|
||||
raise QAPISemError(info, "invalid 'pragma' directive")
|
||||
pragma = expr['pragma']
|
||||
if not isinstance(pragma, dict):
|
||||
raise QAPISemError(
|
||||
info, "value of 'pragma' must be an object")
|
||||
for name, value in pragma.items():
|
||||
self._pragma(name, value, info)
|
||||
else:
|
||||
expr_elem = {'expr': expr,
|
||||
'info': info}
|
||||
if cur_doc:
|
||||
if not cur_doc.symbol:
|
||||
raise QAPISemError(
|
||||
cur_doc.info, "definition documentation required")
|
||||
expr_elem['doc'] = cur_doc
|
||||
self.exprs.append(expr_elem)
|
||||
cur_doc = None
|
||||
self.reject_expr_doc(cur_doc)
|
||||
|
||||
@staticmethod
|
||||
def reject_expr_doc(doc):
|
||||
if doc and doc.symbol:
|
||||
raise QAPISemError(
|
||||
doc.info,
|
||||
"documentation for '%s' is not followed by the definition"
|
||||
% doc.symbol)
|
||||
|
||||
def _include(self, include, info, incl_fname, previously_included):
|
||||
incl_abs_fname = os.path.abspath(incl_fname)
|
||||
# catch inclusion cycle
|
||||
inf = info
|
||||
while inf:
|
||||
if incl_abs_fname == os.path.abspath(inf.fname):
|
||||
raise QAPISemError(info, "inclusion loop for %s" % include)
|
||||
inf = inf.parent
|
||||
|
||||
# skip multiple include of the same file
|
||||
if incl_abs_fname in previously_included:
|
||||
return None
|
||||
|
||||
return QAPISchemaParser(incl_fname, previously_included, info)
|
||||
|
||||
def _pragma(self, name, value, info):
|
||||
if name == 'doc-required':
|
||||
if not isinstance(value, bool):
|
||||
raise QAPISemError(info,
|
||||
"pragma 'doc-required' must be boolean")
|
||||
info.pragma.doc_required = value
|
||||
elif name == 'returns-whitelist':
|
||||
if (not isinstance(value, list)
|
||||
or any([not isinstance(elt, str) for elt in value])):
|
||||
raise QAPISemError(
|
||||
info,
|
||||
"pragma returns-whitelist must be a list of strings")
|
||||
info.pragma.returns_whitelist = value
|
||||
elif name == 'name-case-whitelist':
|
||||
if (not isinstance(value, list)
|
||||
or any([not isinstance(elt, str) for elt in value])):
|
||||
raise QAPISemError(
|
||||
info,
|
||||
"pragma name-case-whitelist must be a list of strings")
|
||||
info.pragma.name_case_whitelist = value
|
||||
else:
|
||||
raise QAPISemError(info, "unknown pragma '%s'" % name)
|
||||
|
||||
def accept(self, skip_comment=True):
|
||||
while True:
|
||||
self.tok = self.src[self.cursor]
|
||||
self.pos = self.cursor
|
||||
self.cursor += 1
|
||||
self.val = None
|
||||
|
||||
if self.tok == '#':
|
||||
if self.src[self.cursor] == '#':
|
||||
# Start of doc comment
|
||||
skip_comment = False
|
||||
self.cursor = self.src.find('\n', self.cursor)
|
||||
if not skip_comment:
|
||||
self.val = self.src[self.pos:self.cursor]
|
||||
return
|
||||
elif self.tok in '{}:,[]':
|
||||
return
|
||||
elif self.tok == "'":
|
||||
# Note: we accept only printable ASCII
|
||||
string = ''
|
||||
esc = False
|
||||
while True:
|
||||
ch = self.src[self.cursor]
|
||||
self.cursor += 1
|
||||
if ch == '\n':
|
||||
raise QAPIParseError(self, "missing terminating \"'\"")
|
||||
if esc:
|
||||
# Note: we recognize only \\ because we have
|
||||
# no use for funny characters in strings
|
||||
if ch != '\\':
|
||||
raise QAPIParseError(self,
|
||||
"unknown escape \\%s" % ch)
|
||||
esc = False
|
||||
elif ch == '\\':
|
||||
esc = True
|
||||
continue
|
||||
elif ch == "'":
|
||||
self.val = string
|
||||
return
|
||||
if ord(ch) < 32 or ord(ch) >= 127:
|
||||
raise QAPIParseError(
|
||||
self, "funny character in string")
|
||||
string += ch
|
||||
elif self.src.startswith('true', self.pos):
|
||||
self.val = True
|
||||
self.cursor += 3
|
||||
return
|
||||
elif self.src.startswith('false', self.pos):
|
||||
self.val = False
|
||||
self.cursor += 4
|
||||
return
|
||||
elif self.tok == '\n':
|
||||
if self.cursor == len(self.src):
|
||||
self.tok = None
|
||||
return
|
||||
self.info = self.info.next_line()
|
||||
self.line_pos = self.cursor
|
||||
elif not self.tok.isspace():
|
||||
# Show up to next structural, whitespace or quote
|
||||
# character
|
||||
match = re.match('[^[\\]{}:,\\s\'"]+',
|
||||
self.src[self.cursor-1:])
|
||||
raise QAPIParseError(self, "stray '%s'" % match.group(0))
|
||||
|
||||
def get_members(self):
|
||||
expr = OrderedDict()
|
||||
if self.tok == '}':
|
||||
self.accept()
|
||||
return expr
|
||||
if self.tok != "'":
|
||||
raise QAPIParseError(self, "expected string or '}'")
|
||||
while True:
|
||||
key = self.val
|
||||
self.accept()
|
||||
if self.tok != ':':
|
||||
raise QAPIParseError(self, "expected ':'")
|
||||
self.accept()
|
||||
if key in expr:
|
||||
raise QAPIParseError(self, "duplicate key '%s'" % key)
|
||||
expr[key] = self.get_expr(True)
|
||||
if self.tok == '}':
|
||||
self.accept()
|
||||
return expr
|
||||
if self.tok != ',':
|
||||
raise QAPIParseError(self, "expected ',' or '}'")
|
||||
self.accept()
|
||||
if self.tok != "'":
|
||||
raise QAPIParseError(self, "expected string")
|
||||
|
||||
def get_values(self):
|
||||
expr = []
|
||||
if self.tok == ']':
|
||||
self.accept()
|
||||
return expr
|
||||
if self.tok not in "{['tfn":
|
||||
raise QAPIParseError(
|
||||
self, "expected '{', '[', ']', string, boolean or 'null'")
|
||||
while True:
|
||||
expr.append(self.get_expr(True))
|
||||
if self.tok == ']':
|
||||
self.accept()
|
||||
return expr
|
||||
if self.tok != ',':
|
||||
raise QAPIParseError(self, "expected ',' or ']'")
|
||||
self.accept()
|
||||
|
||||
def get_expr(self, nested):
|
||||
if self.tok != '{' and not nested:
|
||||
raise QAPIParseError(self, "expected '{'")
|
||||
if self.tok == '{':
|
||||
self.accept()
|
||||
expr = self.get_members()
|
||||
elif self.tok == '[':
|
||||
self.accept()
|
||||
expr = self.get_values()
|
||||
elif self.tok in "'tfn":
|
||||
expr = self.val
|
||||
self.accept()
|
||||
else:
|
||||
raise QAPIParseError(
|
||||
self, "expected '{', '[', string, boolean or 'null'")
|
||||
return expr
|
||||
|
||||
def get_doc(self, info):
|
||||
if self.val != '##':
|
||||
raise QAPIParseError(
|
||||
self, "junk after '##' at start of documentation comment")
|
||||
|
||||
doc = QAPIDoc(self, info)
|
||||
self.accept(False)
|
||||
while self.tok == '#':
|
||||
if self.val.startswith('##'):
|
||||
# End of doc comment
|
||||
if self.val != '##':
|
||||
raise QAPIParseError(
|
||||
self,
|
||||
"junk after '##' at end of documentation comment")
|
||||
doc.end_comment()
|
||||
self.accept()
|
||||
return doc
|
||||
else:
|
||||
doc.append(self.val)
|
||||
self.accept(False)
|
||||
|
||||
raise QAPIParseError(self, "documentation comment must end with '##'")
|
||||
|
||||
|
||||
class QAPIDoc(object):
|
||||
"""
|
||||
A documentation comment block, either definition or free-form
|
||||
|
||||
Definition documentation blocks consist of
|
||||
|
||||
* a body section: one line naming the definition, followed by an
|
||||
overview (any number of lines)
|
||||
|
||||
* argument sections: a description of each argument (for commands
|
||||
and events) or member (for structs, unions and alternates)
|
||||
|
||||
* features sections: a description of each feature flag
|
||||
|
||||
* additional (non-argument) sections, possibly tagged
|
||||
|
||||
Free-form documentation blocks consist only of a body section.
|
||||
"""
|
||||
|
||||
class Section(object):
|
||||
def __init__(self, name=None):
|
||||
# optional section name (argument/member or section name)
|
||||
self.name = name
|
||||
# the list of lines for this section
|
||||
self.text = ''
|
||||
|
||||
def append(self, line):
|
||||
self.text += line.rstrip() + '\n'
|
||||
|
||||
class ArgSection(Section):
|
||||
def __init__(self, name):
|
||||
QAPIDoc.Section.__init__(self, name)
|
||||
self.member = None
|
||||
|
||||
def connect(self, member):
|
||||
self.member = member
|
||||
|
||||
def __init__(self, parser, info):
|
||||
# self._parser is used to report errors with QAPIParseError. The
|
||||
# resulting error position depends on the state of the parser.
|
||||
# It happens to be the beginning of the comment. More or less
|
||||
# servicable, but action at a distance.
|
||||
self._parser = parser
|
||||
self.info = info
|
||||
self.symbol = None
|
||||
self.body = QAPIDoc.Section()
|
||||
# dict mapping parameter name to ArgSection
|
||||
self.args = OrderedDict()
|
||||
self.features = OrderedDict()
|
||||
# a list of Section
|
||||
self.sections = []
|
||||
# the current section
|
||||
self._section = self.body
|
||||
self._append_line = self._append_body_line
|
||||
|
||||
def has_section(self, name):
|
||||
"""Return True if we have a section with this name."""
|
||||
for i in self.sections:
|
||||
if i.name == name:
|
||||
return True
|
||||
return False
|
||||
|
||||
def append(self, line):
|
||||
"""
|
||||
Parse a comment line and add it to the documentation.
|
||||
|
||||
The way that the line is dealt with depends on which part of
|
||||
the documentation we're parsing right now:
|
||||
* The body section: ._append_line is ._append_body_line
|
||||
* An argument section: ._append_line is ._append_args_line
|
||||
* A features section: ._append_line is ._append_features_line
|
||||
* An additional section: ._append_line is ._append_various_line
|
||||
"""
|
||||
line = line[1:]
|
||||
if not line:
|
||||
self._append_freeform(line)
|
||||
return
|
||||
|
||||
if line[0] != ' ':
|
||||
raise QAPIParseError(self._parser, "missing space after #")
|
||||
line = line[1:]
|
||||
self._append_line(line)
|
||||
|
||||
def end_comment(self):
|
||||
self._end_section()
|
||||
|
||||
@staticmethod
|
||||
def _is_section_tag(name):
|
||||
return name in ('Returns:', 'Since:',
|
||||
# those are often singular or plural
|
||||
'Note:', 'Notes:',
|
||||
'Example:', 'Examples:',
|
||||
'TODO:')
|
||||
|
||||
def _append_body_line(self, line):
|
||||
"""
|
||||
Process a line of documentation text in the body section.
|
||||
|
||||
If this a symbol line and it is the section's first line, this
|
||||
is a definition documentation block for that symbol.
|
||||
|
||||
If it's a definition documentation block, another symbol line
|
||||
begins the argument section for the argument named by it, and
|
||||
a section tag begins an additional section. Start that
|
||||
section and append the line to it.
|
||||
|
||||
Else, append the line to the current section.
|
||||
"""
|
||||
name = line.split(' ', 1)[0]
|
||||
# FIXME not nice: things like '# @foo:' and '# @foo: ' aren't
|
||||
# recognized, and get silently treated as ordinary text
|
||||
if not self.symbol and not self.body.text and line.startswith('@'):
|
||||
if not line.endswith(':'):
|
||||
raise QAPIParseError(self._parser, "line should end with ':'")
|
||||
self.symbol = line[1:-1]
|
||||
# FIXME invalid names other than the empty string aren't flagged
|
||||
if not self.symbol:
|
||||
raise QAPIParseError(self._parser, "invalid name")
|
||||
elif self.symbol:
|
||||
# This is a definition documentation block
|
||||
if name.startswith('@') and name.endswith(':'):
|
||||
self._append_line = self._append_args_line
|
||||
self._append_args_line(line)
|
||||
elif line == 'Features:':
|
||||
self._append_line = self._append_features_line
|
||||
elif self._is_section_tag(name):
|
||||
self._append_line = self._append_various_line
|
||||
self._append_various_line(line)
|
||||
else:
|
||||
self._append_freeform(line.strip())
|
||||
else:
|
||||
# This is a free-form documentation block
|
||||
self._append_freeform(line.strip())
|
||||
|
||||
def _append_args_line(self, line):
|
||||
"""
|
||||
Process a line of documentation text in an argument section.
|
||||
|
||||
A symbol line begins the next argument section, a section tag
|
||||
section or a non-indented line after a blank line begins an
|
||||
additional section. Start that section and append the line to
|
||||
it.
|
||||
|
||||
Else, append the line to the current section.
|
||||
|
||||
"""
|
||||
name = line.split(' ', 1)[0]
|
||||
|
||||
if name.startswith('@') and name.endswith(':'):
|
||||
line = line[len(name)+1:]
|
||||
self._start_args_section(name[1:-1])
|
||||
elif self._is_section_tag(name):
|
||||
self._append_line = self._append_various_line
|
||||
self._append_various_line(line)
|
||||
return
|
||||
elif (self._section.text.endswith('\n\n')
|
||||
and line and not line[0].isspace()):
|
||||
if line == 'Features:':
|
||||
self._append_line = self._append_features_line
|
||||
else:
|
||||
self._start_section()
|
||||
self._append_line = self._append_various_line
|
||||
self._append_various_line(line)
|
||||
return
|
||||
|
||||
self._append_freeform(line.strip())
|
||||
|
||||
def _append_features_line(self, line):
|
||||
name = line.split(' ', 1)[0]
|
||||
|
||||
if name.startswith('@') and name.endswith(':'):
|
||||
line = line[len(name)+1:]
|
||||
self._start_features_section(name[1:-1])
|
||||
elif self._is_section_tag(name):
|
||||
self._append_line = self._append_various_line
|
||||
self._append_various_line(line)
|
||||
return
|
||||
elif (self._section.text.endswith('\n\n')
|
||||
and line and not line[0].isspace()):
|
||||
self._start_section()
|
||||
self._append_line = self._append_various_line
|
||||
self._append_various_line(line)
|
||||
return
|
||||
|
||||
self._append_freeform(line.strip())
|
||||
|
||||
def _append_various_line(self, line):
|
||||
"""
|
||||
Process a line of documentation text in an additional section.
|
||||
|
||||
A symbol line is an error.
|
||||
|
||||
A section tag begins an additional section. Start that
|
||||
section and append the line to it.
|
||||
|
||||
Else, append the line to the current section.
|
||||
"""
|
||||
name = line.split(' ', 1)[0]
|
||||
|
||||
if name.startswith('@') and name.endswith(':'):
|
||||
raise QAPIParseError(self._parser,
|
||||
"'%s' can't follow '%s' section"
|
||||
% (name, self.sections[0].name))
|
||||
elif self._is_section_tag(name):
|
||||
line = line[len(name)+1:]
|
||||
self._start_section(name[:-1])
|
||||
|
||||
if (not self._section.name or
|
||||
not self._section.name.startswith('Example')):
|
||||
line = line.strip()
|
||||
|
||||
self._append_freeform(line)
|
||||
|
||||
def _start_symbol_section(self, symbols_dict, name):
|
||||
# FIXME invalid names other than the empty string aren't flagged
|
||||
if not name:
|
||||
raise QAPIParseError(self._parser, "invalid parameter name")
|
||||
if name in symbols_dict:
|
||||
raise QAPIParseError(self._parser,
|
||||
"'%s' parameter name duplicated" % name)
|
||||
assert not self.sections
|
||||
self._end_section()
|
||||
self._section = QAPIDoc.ArgSection(name)
|
||||
symbols_dict[name] = self._section
|
||||
|
||||
def _start_args_section(self, name):
|
||||
self._start_symbol_section(self.args, name)
|
||||
|
||||
def _start_features_section(self, name):
|
||||
self._start_symbol_section(self.features, name)
|
||||
|
||||
def _start_section(self, name=None):
|
||||
if name in ('Returns', 'Since') and self.has_section(name):
|
||||
raise QAPIParseError(self._parser,
|
||||
"duplicated '%s' section" % name)
|
||||
self._end_section()
|
||||
self._section = QAPIDoc.Section(name)
|
||||
self.sections.append(self._section)
|
||||
|
||||
def _end_section(self):
|
||||
if self._section:
|
||||
text = self._section.text = self._section.text.strip()
|
||||
if self._section.name and (not text or text.isspace()):
|
||||
raise QAPIParseError(
|
||||
self._parser,
|
||||
"empty doc section '%s'" % self._section.name)
|
||||
self._section = None
|
||||
|
||||
def _append_freeform(self, line):
|
||||
match = re.match(r'(@\S+:)', line)
|
||||
if match:
|
||||
raise QAPIParseError(self._parser,
|
||||
"'%s' not allowed in free-form documentation"
|
||||
% match.group(1))
|
||||
self._section.append(line)
|
||||
|
||||
def connect_member(self, member):
|
||||
if member.name not in self.args:
|
||||
# Undocumented TODO outlaw
|
||||
self.args[member.name] = QAPIDoc.ArgSection(member.name)
|
||||
self.args[member.name].connect(member)
|
||||
|
||||
def check_expr(self, expr):
|
||||
if self.has_section('Returns') and 'command' not in expr:
|
||||
raise QAPISemError(self.info,
|
||||
"'Returns:' is only valid for commands")
|
||||
|
||||
def check(self):
|
||||
bogus = [name for name, section in self.args.items()
|
||||
if not section.member]
|
||||
if bogus:
|
||||
raise QAPISemError(
|
||||
self.info,
|
||||
"the following documented members are not in "
|
||||
"the declaration: %s" % ", ".join(bogus))
|
1057
scripts/qapi/schema.py
Normal file
1057
scripts/qapi/schema.py
Normal file
File diff suppressed because it is too large
Load Diff
67
scripts/qapi/source.py
Normal file
67
scripts/qapi/source.py
Normal file
@ -0,0 +1,67 @@
|
||||
#
|
||||
# QAPI frontend source file info
|
||||
#
|
||||
# Copyright (c) 2019 Red Hat Inc.
|
||||
#
|
||||
# Authors:
|
||||
# Markus Armbruster <armbru@redhat.com>
|
||||
#
|
||||
# This work is licensed under the terms of the GNU GPL, version 2.
|
||||
# See the COPYING file in the top-level directory.
|
||||
|
||||
import copy
|
||||
import sys
|
||||
|
||||
|
||||
class QAPISchemaPragma(object):
|
||||
def __init__(self):
|
||||
# Are documentation comments required?
|
||||
self.doc_required = False
|
||||
# Whitelist of commands allowed to return a non-dictionary
|
||||
self.returns_whitelist = []
|
||||
# Whitelist of entities allowed to violate case conventions
|
||||
self.name_case_whitelist = []
|
||||
|
||||
|
||||
class QAPISourceInfo(object):
|
||||
def __init__(self, fname, line, parent):
|
||||
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
|
||||
|
||||
def set_defn(self, meta, name):
|
||||
self.defn_meta = meta
|
||||
self.defn_name = name
|
||||
|
||||
def next_line(self):
|
||||
info = copy.copy(self)
|
||||
info.line += 1
|
||||
return info
|
||||
|
||||
def loc(self):
|
||||
if self.fname is None:
|
||||
return sys.argv[0]
|
||||
ret = self.fname
|
||||
if self.line is not None:
|
||||
ret += ':%d' % self.line
|
||||
return ret
|
||||
|
||||
def in_defn(self):
|
||||
if self.defn_name:
|
||||
return "%s: In %s '%s':\n" % (self.fname,
|
||||
self.defn_meta, self.defn_name)
|
||||
return ''
|
||||
|
||||
def include_path(self):
|
||||
ret = ''
|
||||
parent = self.parent
|
||||
while parent:
|
||||
ret = 'In file included from %s:\n' % parent.loc() + ret
|
||||
parent = parent.parent
|
||||
return ret
|
||||
|
||||
def __str__(self):
|
||||
return self.include_path() + self.in_defn() + self.loc()
|
@ -14,6 +14,8 @@ This work is licensed under the terms of the GNU GPL, version 2.
|
||||
"""
|
||||
|
||||
from qapi.common import *
|
||||
from qapi.gen import QAPISchemaModularCVisitor, ifcontext
|
||||
from qapi.schema import QAPISchemaEnumMember, QAPISchemaObjectType
|
||||
|
||||
|
||||
# variants must be emitted before their container; track what has already
|
||||
@ -21,6 +23,65 @@ from qapi.common import *
|
||||
objects_seen = set()
|
||||
|
||||
|
||||
def gen_enum_lookup(name, members, prefix=None):
|
||||
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)
|
||||
ret += mcgen('''
|
||||
[%(index)s] = "%(name)s",
|
||||
''',
|
||||
index=index, name=m.name)
|
||||
ret += gen_endif(m.ifcond)
|
||||
|
||||
ret += mcgen('''
|
||||
},
|
||||
.size = %(max_index)s
|
||||
};
|
||||
''',
|
||||
max_index=c_enum_const(name, '_MAX', prefix))
|
||||
return ret
|
||||
|
||||
|
||||
def gen_enum(name, members, prefix=None):
|
||||
# append automatically generated _MAX value
|
||||
enum_members = members + [QAPISchemaEnumMember('_MAX', None)]
|
||||
|
||||
ret = mcgen('''
|
||||
|
||||
typedef enum %(c_name)s {
|
||||
''',
|
||||
c_name=c_name(name))
|
||||
|
||||
for m in enum_members:
|
||||
ret += gen_if(m.ifcond)
|
||||
ret += mcgen('''
|
||||
%(c_enum)s,
|
||||
''',
|
||||
c_enum=c_enum_const(name, m.name, prefix))
|
||||
ret += gen_endif(m.ifcond)
|
||||
|
||||
ret += mcgen('''
|
||||
} %(c_name)s;
|
||||
''',
|
||||
c_name=c_name(name))
|
||||
|
||||
ret += mcgen('''
|
||||
|
||||
#define %(c_name)s_str(val) \\
|
||||
qapi_enum_lookup(&%(c_name)s_lookup, (val))
|
||||
|
||||
extern const QEnumLookup %(c_name)s_lookup;
|
||||
''',
|
||||
c_name=c_name(name))
|
||||
return ret
|
||||
|
||||
|
||||
def gen_fwd_object_or_array(name):
|
||||
return mcgen('''
|
||||
|
||||
|
@ -14,6 +14,8 @@ 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):
|
||||
|
@ -31,13 +31,20 @@ ifneq ($(wildcard config-host.mak),)
|
||||
export SRC_PATH
|
||||
|
||||
# TODO don't duplicate $(SRC_PATH)/Makefile's qapi-py here
|
||||
qapi-py = $(SRC_PATH)/scripts/qapi/commands.py \
|
||||
$(SRC_PATH)/scripts/qapi/events.py \
|
||||
$(SRC_PATH)/scripts/qapi/introspect.py \
|
||||
$(SRC_PATH)/scripts/qapi/types.py \
|
||||
$(SRC_PATH)/scripts/qapi/visit.py \
|
||||
qapi-py = $(SRC_PATH)/scripts/qapi/__init__.py \
|
||||
$(SRC_PATH)/scripts/qapi/commands.py \
|
||||
$(SRC_PATH)/scripts/qapi/common.py \
|
||||
$(SRC_PATH)/scripts/qapi/doc.py \
|
||||
$(SRC_PATH)/scripts/qapi/error.py \
|
||||
$(SRC_PATH)/scripts/qapi/events.py \
|
||||
$(SRC_PATH)/scripts/qapi/expr.py \
|
||||
$(SRC_PATH)/scripts/qapi/gen.py \
|
||||
$(SRC_PATH)/scripts/qapi/introspect.py \
|
||||
$(SRC_PATH)/scripts/qapi/parser.py \
|
||||
$(SRC_PATH)/scripts/qapi/schema.py \
|
||||
$(SRC_PATH)/scripts/qapi/source.py \
|
||||
$(SRC_PATH)/scripts/qapi/types.py \
|
||||
$(SRC_PATH)/scripts/qapi/visit.py \
|
||||
$(SRC_PATH)/scripts/qapi-gen.py
|
||||
|
||||
# Get the list of all supported sysemu targets
|
||||
@ -609,6 +616,7 @@ tests/test-qapi-gen-timestamp: \
|
||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-gen.py \
|
||||
-o tests -p "test-" $<, \
|
||||
"GEN","$(@:%-timestamp=%)")
|
||||
@rm -f tests/test-qapi-doc.texi
|
||||
@>$@
|
||||
|
||||
tests/qapi-schema/doc-good.test.texi: $(SRC_PATH)/tests/qapi-schema/doc-good.json $(qapi-py)
|
||||
@ -1101,17 +1109,11 @@ check-tests/check-block.sh: tests/check-block.sh qemu-img$(EXESUF) \
|
||||
$(patsubst %,%/all,$(filter %-softmmu,$(TARGET_DIRS)))
|
||||
@$<
|
||||
|
||||
.PHONY: $(patsubst %, check-%, $(check-qapi-schema-y))
|
||||
$(patsubst %, check-%, $(check-qapi-schema-y)): check-%.json: $(SRC_PATH)/%.json
|
||||
.PHONY: check-tests/qapi-schema/frontend
|
||||
check-tests/qapi-schema/frontend: $(addprefix $(SRC_PATH)/, $(check-qapi-schema-y))
|
||||
$(call quiet-command, PYTHONPATH=$(SRC_PATH)/scripts \
|
||||
PYTHONIOENCODING=utf-8 $(PYTHON) $(SRC_PATH)/tests/qapi-schema/test-qapi.py \
|
||||
$^ >$*.test.out 2>$*.test.err; \
|
||||
echo $$? >$*.test.exit, \
|
||||
"TEST","$*.out")
|
||||
@# Sanitize error messages (make them independent of build directory)
|
||||
@perl -p -e 's|\Q$(SRC_PATH)\E/||g' $*.test.err | diff -u $(SRC_PATH)/$*.err -
|
||||
@diff -u $(SRC_PATH)/$*.out $*.test.out
|
||||
@diff -u $(SRC_PATH)/$*.exit $*.test.exit
|
||||
PYTHONIOENCODING=utf-8 $(PYTHON) $(SRC_PATH)/tests/qapi-schema/test-qapi.py $^, \
|
||||
TEST, check-qapi-schema)
|
||||
|
||||
.PHONY: check-tests/qapi-schema/doc-good.texi
|
||||
check-tests/qapi-schema/doc-good.texi: tests/qapi-schema/doc-good.test.texi
|
||||
@ -1169,7 +1171,7 @@ check-acceptance: check-venv $(TESTS_RESULTS_DIR)
|
||||
# Consolidated targets
|
||||
|
||||
.PHONY: check-block check-qapi-schema check-qtest check-unit check check-clean
|
||||
check-qapi-schema: $(patsubst %,check-%, $(check-qapi-schema-y)) check-tests/qapi-schema/doc-good.texi
|
||||
check-qapi-schema: check-tests/qapi-schema/frontend check-tests/qapi-schema/doc-good.texi
|
||||
check-qtest: $(patsubst %,check-qtest-%, $(QTEST_TARGETS))
|
||||
check-block: $(patsubst %,check-%, $(check-block-y))
|
||||
check: check-block check-qapi-schema check-unit check-softfloat check-qtest check-decodetree
|
||||
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/allow-preconfig-test.json: In command 'allow-preconfig-test':
|
||||
tests/qapi-schema/allow-preconfig-test.json:2: flag 'allow-preconfig' may only use true value
|
||||
allow-preconfig-test.json: In command 'allow-preconfig-test':
|
||||
allow-preconfig-test.json:2: flag 'allow-preconfig' may only use true value
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/alternate-any.json: In alternate 'Alt':
|
||||
tests/qapi-schema/alternate-any.json:2: branch 'one' cannot use built-in type 'any'
|
||||
alternate-any.json: In alternate 'Alt':
|
||||
alternate-any.json:2: branch 'one' cannot use built-in type 'any'
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/alternate-array.json: In alternate 'Alt':
|
||||
tests/qapi-schema/alternate-array.json:5: 'data' member 'two' cannot be an array
|
||||
alternate-array.json: In alternate 'Alt':
|
||||
alternate-array.json:5: 'data' member 'two' cannot be an array
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,3 +1,3 @@
|
||||
tests/qapi-schema/alternate-base.json: In alternate 'Alt':
|
||||
tests/qapi-schema/alternate-base.json:4: alternate has unknown key 'base'
|
||||
alternate-base.json: In alternate 'Alt':
|
||||
alternate-base.json:4: alternate has unknown key 'base'
|
||||
Valid keys are 'alternate', 'data', 'if'.
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/alternate-branch-if-invalid.json: In alternate 'Alt':
|
||||
tests/qapi-schema/alternate-branch-if-invalid.json:2: 'if' condition ' ' of 'data' member 'branch' makes no sense
|
||||
alternate-branch-if-invalid.json: In alternate 'Alt':
|
||||
alternate-branch-if-invalid.json:2: 'if' condition ' ' of 'data' member 'branch' makes no sense
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/alternate-clash.json: In alternate 'Alt1':
|
||||
tests/qapi-schema/alternate-clash.json:7: branch 'a_b' collides with branch 'a-b'
|
||||
alternate-clash.json: In alternate 'Alt1':
|
||||
alternate-clash.json:7: branch 'a_b' collides with branch 'a-b'
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/alternate-conflict-bool-string.json: In alternate 'Alt':
|
||||
tests/qapi-schema/alternate-conflict-bool-string.json:2: branch 'two' can't be distinguished from 'one'
|
||||
alternate-conflict-bool-string.json: In alternate 'Alt':
|
||||
alternate-conflict-bool-string.json:2: branch 'two' can't be distinguished from 'one'
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/alternate-conflict-dict.json: In alternate 'Alt':
|
||||
tests/qapi-schema/alternate-conflict-dict.json:6: branch 'two' can't be distinguished from 'one'
|
||||
alternate-conflict-dict.json: In alternate 'Alt':
|
||||
alternate-conflict-dict.json:6: branch 'two' can't be distinguished from 'one'
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/alternate-conflict-enum-bool.json: In alternate 'Alt':
|
||||
tests/qapi-schema/alternate-conflict-enum-bool.json:4: branch 'two' can't be distinguished from 'one'
|
||||
alternate-conflict-enum-bool.json: In alternate 'Alt':
|
||||
alternate-conflict-enum-bool.json:4: branch 'two' can't be distinguished from 'one'
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/alternate-conflict-enum-int.json: In alternate 'Alt':
|
||||
tests/qapi-schema/alternate-conflict-enum-int.json:4: branch 'two' can't be distinguished from 'one'
|
||||
alternate-conflict-enum-int.json: In alternate 'Alt':
|
||||
alternate-conflict-enum-int.json:4: branch 'two' can't be distinguished from 'one'
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/alternate-conflict-num-string.json: In alternate 'Alt':
|
||||
tests/qapi-schema/alternate-conflict-num-string.json:2: branch 'two' can't be distinguished from 'one'
|
||||
alternate-conflict-num-string.json: In alternate 'Alt':
|
||||
alternate-conflict-num-string.json:2: branch 'two' can't be distinguished from 'one'
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/alternate-conflict-string.json: In alternate 'Alt':
|
||||
tests/qapi-schema/alternate-conflict-string.json:2: branch 'two' can't be distinguished from 'one'
|
||||
alternate-conflict-string.json: In alternate 'Alt':
|
||||
alternate-conflict-string.json:2: branch 'two' can't be distinguished from 'one'
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/alternate-empty.json: In alternate 'Alt':
|
||||
tests/qapi-schema/alternate-empty.json:2: 'data' must not be empty
|
||||
alternate-empty.json: In alternate 'Alt':
|
||||
alternate-empty.json:2: 'data' must not be empty
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/alternate-invalid-dict.json: In alternate 'Alt':
|
||||
tests/qapi-schema/alternate-invalid-dict.json:2: 'data' member 'two' misses key 'type'
|
||||
alternate-invalid-dict.json: In alternate 'Alt':
|
||||
alternate-invalid-dict.json:2: 'data' member 'two' misses key 'type'
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/alternate-nested.json: In alternate 'Alt2':
|
||||
tests/qapi-schema/alternate-nested.json:4: branch 'nested' cannot use alternate type 'Alt1'
|
||||
alternate-nested.json: In alternate 'Alt2':
|
||||
alternate-nested.json:4: branch 'nested' cannot use alternate type 'Alt1'
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/alternate-unknown.json: In alternate 'Alt':
|
||||
tests/qapi-schema/alternate-unknown.json:2: branch 'unknown' uses unknown type 'MissingType'
|
||||
alternate-unknown.json: In alternate 'Alt':
|
||||
alternate-unknown.json:2: branch 'unknown' uses unknown type 'MissingType'
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/args-alternate.json: In command 'oops':
|
||||
tests/qapi-schema/args-alternate.json:3: command's 'data' cannot take alternate type 'Alt'
|
||||
args-alternate.json: In command 'oops':
|
||||
args-alternate.json:3: command's 'data' cannot take alternate type 'Alt'
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/args-any.json: In command 'oops':
|
||||
tests/qapi-schema/args-any.json:2: command's 'data' cannot take built-in type 'any'
|
||||
args-any.json: In command 'oops':
|
||||
args-any.json:2: command's 'data' cannot take built-in type 'any'
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/args-array-empty.json: In command 'oops':
|
||||
tests/qapi-schema/args-array-empty.json:2: 'data' member 'empty': array type must contain single type name
|
||||
args-array-empty.json: In command 'oops':
|
||||
args-array-empty.json:2: 'data' member 'empty': array type must contain single type name
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/args-array-unknown.json: In command 'oops':
|
||||
tests/qapi-schema/args-array-unknown.json:2: command uses unknown type 'NoSuchType'
|
||||
args-array-unknown.json: In command 'oops':
|
||||
args-array-unknown.json:2: command uses unknown type 'NoSuchType'
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/args-bad-boxed.json: In command 'foo':
|
||||
tests/qapi-schema/args-bad-boxed.json:2: flag 'boxed' may only use true value
|
||||
args-bad-boxed.json: In command 'foo':
|
||||
args-bad-boxed.json:2: flag 'boxed' may only use true value
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/args-boxed-anon.json: In command 'foo':
|
||||
tests/qapi-schema/args-boxed-anon.json:2: 'data' should be a type name
|
||||
args-boxed-anon.json: In command 'foo':
|
||||
args-boxed-anon.json:2: 'data' should be a type name
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/args-boxed-string.json: In command 'foo':
|
||||
tests/qapi-schema/args-boxed-string.json:2: command's 'data' cannot take built-in type 'str'
|
||||
args-boxed-string.json: In command 'foo':
|
||||
args-boxed-string.json:2: command's 'data' cannot take built-in type 'str'
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/args-int.json: In command 'oops':
|
||||
tests/qapi-schema/args-int.json:2: command's 'data' cannot take built-in type 'int'
|
||||
args-int.json: In command 'oops':
|
||||
args-int.json:2: command's 'data' cannot take built-in type 'int'
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/args-invalid.json: In command 'foo':
|
||||
tests/qapi-schema/args-invalid.json:1: 'data' should be an object or type name
|
||||
args-invalid.json: In command 'foo':
|
||||
args-invalid.json:1: 'data' should be an object or type name
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/args-member-array-bad.json: In command 'oops':
|
||||
tests/qapi-schema/args-member-array-bad.json:2: 'data' member 'member': array type must contain single type name
|
||||
args-member-array-bad.json: In command 'oops':
|
||||
args-member-array-bad.json:2: 'data' member 'member': array type must contain single type name
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/args-member-case.json: In command 'no-way-this-will-get-whitelisted':
|
||||
tests/qapi-schema/args-member-case.json:2: 'data' member 'Arg' uses uppercase in name
|
||||
args-member-case.json: In command 'no-way-this-will-get-whitelisted':
|
||||
args-member-case.json:2: 'data' member 'Arg' uses uppercase in name
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/args-member-unknown.json: In command 'oops':
|
||||
tests/qapi-schema/args-member-unknown.json:2: parameter 'member' uses unknown type 'NoSuchType'
|
||||
args-member-unknown.json: In command 'oops':
|
||||
args-member-unknown.json:2: parameter 'member' uses unknown type 'NoSuchType'
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/args-name-clash.json: In command 'oops':
|
||||
tests/qapi-schema/args-name-clash.json:4: parameter 'a_b' collides with parameter 'a-b'
|
||||
args-name-clash.json: In command 'oops':
|
||||
args-name-clash.json:4: parameter 'a_b' collides with parameter 'a-b'
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/args-union.json: In command 'oops':
|
||||
tests/qapi-schema/args-union.json:3: command's 'data' can take union type 'Uni' only with 'boxed': true
|
||||
args-union.json: In command 'oops':
|
||||
args-union.json:3: command's 'data' can take union type 'Uni' only with 'boxed': true
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/args-unknown.json: In command 'oops':
|
||||
tests/qapi-schema/args-unknown.json:2: command's 'data' uses unknown type 'NoSuchType'
|
||||
args-unknown.json: In command 'oops':
|
||||
args-unknown.json:2: command's 'data' uses unknown type 'NoSuchType'
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/bad-base.json: In struct 'MyType':
|
||||
tests/qapi-schema/bad-base.json:3: 'base' requires a struct type, union type 'Union' isn't
|
||||
bad-base.json: In struct 'MyType':
|
||||
bad-base.json:3: 'base' requires a struct type, union type 'Union' isn't
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/bad-data.json: In command 'oops':
|
||||
tests/qapi-schema/bad-data.json:2: 'data' cannot be an array
|
||||
bad-data.json: In command 'oops':
|
||||
bad-data.json:2: 'data' cannot be an array
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/bad-ident.json: In struct '*oops':
|
||||
tests/qapi-schema/bad-ident.json:2: struct has an invalid name
|
||||
bad-ident.json: In struct '*oops':
|
||||
bad-ident.json:2: struct has an invalid name
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/bad-if-empty-list.json: In struct 'TestIfStruct':
|
||||
tests/qapi-schema/bad-if-empty-list.json:2: 'if' condition [] of struct is useless
|
||||
bad-if-empty-list.json: In struct 'TestIfStruct':
|
||||
bad-if-empty-list.json:2: 'if' condition [] of struct is useless
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/bad-if-empty.json: In struct 'TestIfStruct':
|
||||
tests/qapi-schema/bad-if-empty.json:2: 'if' condition '' of struct makes no sense
|
||||
bad-if-empty.json: In struct 'TestIfStruct':
|
||||
bad-if-empty.json:2: 'if' condition '' of struct makes no sense
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/bad-if-list.json: In struct 'TestIfStruct':
|
||||
tests/qapi-schema/bad-if-list.json:2: 'if' condition ' ' of struct makes no sense
|
||||
bad-if-list.json: In struct 'TestIfStruct':
|
||||
bad-if-list.json:2: 'if' condition ' ' of struct makes no sense
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1,2 +1,2 @@
|
||||
tests/qapi-schema/bad-if.json: In struct 'TestIfStruct':
|
||||
tests/qapi-schema/bad-if.json:2: 'if' condition of struct must be a string or a list of strings
|
||||
bad-if.json: In struct 'TestIfStruct':
|
||||
bad-if.json:2: 'if' condition of struct must be a string or a list of strings
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1 +1 @@
|
||||
tests/qapi-schema/bad-type-bool.json:2: 'struct' requires a string name
|
||||
bad-type-bool.json:2: 'struct' requires a string name
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1 +1 @@
|
||||
tests/qapi-schema/bad-type-dict.json:2: 'command' requires a string name
|
||||
bad-type-dict.json:2: 'command' requires a string name
|
||||
|
@ -1 +0,0 @@
|
||||
1
|
@ -1 +1 @@
|
||||
tests/qapi-schema/bad-type-int.json:3:13: stray '123'
|
||||
bad-type-int.json:3:13: stray '123'
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user