提交 a719a27c 编写于 作者: L Lluís Vilanova 提交者: Luiz Capitulino

qapi: Add a primitive to include other files from a QAPI schema file

The primitive uses JSON syntax, and include paths are relative to the file using the directive:

  { 'include': 'path/to/file.json' }
Signed-off-by: NLluís Vilanova <vilanova@ac.upc.edu>
Reviewed-by: NEric Blake <eblake@redhat.com>
Reviewed-by: NMarkus Armbruster <armbru@redhat.com>
Signed-off-by: NLuiz Capitulino <lcapitulino@redhat.com>
上级 33aaad52
...@@ -40,6 +40,17 @@ enumeration types and union types. ...@@ -40,6 +40,17 @@ enumeration types and union types.
Generally speaking, types definitions should always use CamelCase for the type Generally speaking, types definitions should always use CamelCase for the type
names. Command names should be all lower case with words separated by a hyphen. names. Command names should be all lower case with words separated by a hyphen.
=== Includes ===
The QAPI schema definitions can be modularized using the 'include' directive:
{ 'include': 'path/to/file.json'}
The directive is evaluated recursively, and include paths are relative to the
file using the directive.
=== Complex types === === Complex types ===
A complex type is a dictionary containing a single key whose value is a A complex type is a dictionary containing a single key whose value is a
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
# This work is licensed under the terms of the GNU GPL, version 2. # This work is licensed under the terms of the GNU GPL, version 2.
# See the COPYING file in the top-level directory. # See the COPYING file in the top-level directory.
import re
from ordereddict import OrderedDict from ordereddict import OrderedDict
import os import os
import sys import sys
...@@ -36,9 +37,17 @@ builtin_type_qtypes = { ...@@ -36,9 +37,17 @@ builtin_type_qtypes = {
'uint64': 'QTYPE_QINT', 'uint64': 'QTYPE_QINT',
} }
def error_path(parent):
res = ""
while parent:
res = ("In file included from %s:%d:\n" % (parent['file'],
parent['line'])) + res
parent = parent['parent']
return res
class QAPISchemaError(Exception): class QAPISchemaError(Exception):
def __init__(self, schema, msg): def __init__(self, schema, msg):
self.fp = schema.fp self.input_file = schema.input_file
self.msg = msg self.msg = msg
self.col = 1 self.col = 1
self.line = schema.line self.line = schema.line
...@@ -47,23 +56,31 @@ class QAPISchemaError(Exception): ...@@ -47,23 +56,31 @@ class QAPISchemaError(Exception):
self.col = (self.col + 7) % 8 + 1 self.col = (self.col + 7) % 8 + 1
else: else:
self.col += 1 self.col += 1
self.info = schema.parent_info
def __str__(self): def __str__(self):
return "%s:%s:%s: %s" % (self.fp.name, self.line, self.col, self.msg) return error_path(self.info) + \
"%s:%d:%d: %s" % (self.input_file, self.line, self.col, self.msg)
class QAPIExprError(Exception): class QAPIExprError(Exception):
def __init__(self, expr_info, msg): def __init__(self, expr_info, msg):
self.fp = expr_info['fp'] self.info = expr_info
self.line = expr_info['line']
self.msg = msg self.msg = msg
def __str__(self): def __str__(self):
return "%s:%s: %s" % (self.fp.name, self.line, self.msg) return error_path(self.info['parent']) + \
"%s:%d: %s" % (self.info['file'], self.info['line'], self.msg)
class QAPISchema: class QAPISchema:
def __init__(self, fp): def __init__(self, fp, input_relname=None, include_hist=[], parent_info=None):
self.fp = fp input_fname = os.path.abspath(fp.name)
if input_relname is None:
input_relname = fp.name
self.input_dir = os.path.dirname(input_fname)
self.input_file = input_relname
self.include_hist = include_hist + [(input_relname, input_fname)]
self.parent_info = parent_info
self.src = fp.read() self.src = fp.read()
if self.src == '' or self.src[-1] != '\n': if self.src == '' or self.src[-1] != '\n':
self.src += '\n' self.src += '\n'
...@@ -74,8 +91,31 @@ class QAPISchema: ...@@ -74,8 +91,31 @@ class QAPISchema:
self.accept() self.accept()
while self.tok != None: while self.tok != None:
expr_info = {'fp': fp, 'line': self.line} expr_info = {'file': input_relname, 'line': self.line, 'parent': self.parent_info}
expr_elem = {'expr': self.get_expr(False), expr = self.get_expr(False)
if isinstance(expr, dict) and "include" in expr:
if len(expr) != 1:
raise QAPIExprError(expr_info, "Invalid 'include' directive")
include = expr["include"]
if not isinstance(include, str):
raise QAPIExprError(expr_info,
'Expected a file name (string), got: %s'
% include)
include_path = os.path.join(self.input_dir, include)
if any(include_path == elem[1]
for elem in self.include_hist):
raise QAPIExprError(expr_info, "Inclusion loop for %s"
% include)
try:
fobj = open(include_path, 'r')
except IOError as e:
raise QAPIExprError(expr_info,
'%s: %s' % (e.strerror, include))
exprs_include = QAPISchema(fobj, include,
self.include_hist, expr_info)
self.exprs.extend(exprs_include.exprs)
else:
expr_elem = {'expr': expr,
'info': expr_info} 'info': expr_info}
self.exprs.append(expr_elem) self.exprs.append(expr_elem)
...@@ -267,7 +307,7 @@ def check_exprs(schema): ...@@ -267,7 +307,7 @@ def check_exprs(schema):
def parse_schema(input_file): def parse_schema(input_file):
try: try:
schema = QAPISchema(open(input_file, "r")) schema = QAPISchema(open(input_file, "r"))
except QAPISchemaError, e: except (QAPISchemaError, QAPIExprError), e:
print >>sys.stderr, e print >>sys.stderr, e
exit(1) exit(1)
......
...@@ -190,7 +190,10 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \ ...@@ -190,7 +190,10 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \
duplicate-key.json union-invalid-base.json flat-union-no-base.json \ duplicate-key.json union-invalid-base.json flat-union-no-base.json \
flat-union-invalid-discriminator.json \ flat-union-invalid-discriminator.json \
flat-union-invalid-branch-key.json flat-union-reverse-define.json \ flat-union-invalid-branch-key.json flat-union-reverse-define.json \
flat-union-string-discriminator.json) flat-union-string-discriminator.json \
include-simple.json include-relpath.json include-format-err.json \
include-non-file.json include-no-file.json include-before-err.json \
include-nested-err.json include-self-cycle.json include-cycle.json)
GENERATED_HEADERS += tests/test-qapi-types.h tests/test-qapi-visit.h tests/test-qmp-commands.h GENERATED_HEADERS += tests/test-qapi-types.h tests/test-qapi-visit.h tests/test-qmp-commands.h
......
tests/qapi-schema/include-before-err.json:2:13: Expected ":"
{ 'include': 'include-simple-sub.json' }
{ 'command' 'missing-colon' }
{ 'include': 'include-cycle-c.json' }
{ 'include': 'include-cycle.json' }
In file included from tests/qapi-schema/include-cycle.json:1:
In file included from include-cycle-b.json:1:
include-cycle-c.json:1: Inclusion loop for include-cycle.json
{ 'include': 'include-cycle-b.json' }
tests/qapi-schema/include-format-err.json:1: Invalid 'include' directive
{ 'include': 'include-simple-sub.json',
'foo': 'bar' }
In file included from tests/qapi-schema/include-nested-err.json:1:
missing-colon.json:1:10: Expected ":"
{ 'include': 'missing-colon.json' }
tests/qapi-schema/include-no-file.json:1: No such file or directory: include-no-file-sub.json
{ 'include': 'include-no-file-sub.json' }
tests/qapi-schema/include-non-file.json:1: Expected a file name (string), got: ['foo', 'bar']
{ 'include': [ 'foo', 'bar' ] }
{ 'enum': 'Status',
'data': [ 'good', 'bad', 'ugly' ] }
{ 'include': 'include/relpath.json' }
[OrderedDict([('enum', 'Status'), ('data', ['good', 'bad', 'ugly'])])]
[{'enum_name': 'Status', 'enum_values': ['good', 'bad', 'ugly']}]
[]
tests/qapi-schema/include-self-cycle.json:1: Inclusion loop for include-self-cycle.json
{ 'include': 'include-self-cycle.json' }
{ 'enum': 'Status',
'data': [ 'good', 'bad', 'ugly' ] }
{ 'include': 'include-simple-sub.json' }
[OrderedDict([('enum', 'Status'), ('data', ['good', 'bad', 'ugly'])])]
[{'enum_name': 'Status', 'enum_values': ['good', 'bad', 'ugly']}]
[]
{ 'include': '../include-relpath-sub.json' }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册