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: Lluís Vilanova <vilanova@ac.upc.edu>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Luiz Capitulino <lcapitulino@redhat.com>
This commit is contained in:
Lluís Vilanova 2014-05-07 20:46:15 +02:00 committed by Luiz Capitulino
parent 33aaad529e
commit a719a27c82
44 changed files with 110 additions and 13 deletions

View File

@ -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

View File

@ -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 = {
'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):
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,10 +91,33 @@ 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)
'info': expr_info} if isinstance(expr, dict) and "include" in expr:
self.exprs.append(expr_elem) 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}
self.exprs.append(expr_elem)
def accept(self): def accept(self):
while True: while True:
@ -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)

View File

@ -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

View File

@ -0,0 +1 @@
tests/qapi-schema/include-before-err.json:2:13: Expected ":"

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,2 @@
{ 'include': 'include-simple-sub.json' }
{ 'command' 'missing-colon' }

View File

View File

@ -0,0 +1 @@
{ 'include': 'include-cycle-c.json' }

View File

@ -0,0 +1 @@
{ 'include': 'include-cycle.json' }

View File

@ -0,0 +1,3 @@
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

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1 @@
{ 'include': 'include-cycle-b.json' }

View File

View File

@ -0,0 +1 @@
tests/qapi-schema/include-format-err.json:1: Invalid 'include' directive

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,2 @@
{ 'include': 'include-simple-sub.json',
'foo': 'bar' }

View File

View File

@ -0,0 +1,2 @@
In file included from tests/qapi-schema/include-nested-err.json:1:
missing-colon.json:1:10: Expected ":"

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1 @@
{ 'include': 'missing-colon.json' }

View File

View File

@ -0,0 +1 @@
tests/qapi-schema/include-no-file.json:1: No such file or directory: include-no-file-sub.json

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1 @@
{ 'include': 'include-no-file-sub.json' }

View File

View File

@ -0,0 +1 @@
tests/qapi-schema/include-non-file.json:1: Expected a file name (string), got: ['foo', 'bar']

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1 @@
{ 'include': [ 'foo', 'bar' ] }

View File

View File

@ -0,0 +1,2 @@
{ 'enum': 'Status',
'data': [ 'good', 'bad', 'ugly' ] }

View File

View File

@ -0,0 +1 @@
0

View File

@ -0,0 +1 @@
{ 'include': 'include/relpath.json' }

View File

@ -0,0 +1,3 @@
[OrderedDict([('enum', 'Status'), ('data', ['good', 'bad', 'ugly'])])]
[{'enum_name': 'Status', 'enum_values': ['good', 'bad', 'ugly']}]
[]

View File

@ -0,0 +1 @@
tests/qapi-schema/include-self-cycle.json:1: Inclusion loop for include-self-cycle.json

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1 @@
{ 'include': 'include-self-cycle.json' }

View File

View File

@ -0,0 +1,2 @@
{ 'enum': 'Status',
'data': [ 'good', 'bad', 'ugly' ] }

View File

View File

@ -0,0 +1 @@
0

View File

@ -0,0 +1 @@
{ 'include': 'include-simple-sub.json' }

View File

@ -0,0 +1,3 @@
[OrderedDict([('enum', 'Status'), ('data', ['good', 'bad', 'ugly'])])]
[{'enum_name': 'Status', 'enum_values': ['good', 'bad', 'ugly']}]
[]

View File

@ -0,0 +1 @@
{ 'include': '../include-relpath-sub.json' }