292 lines
7.8 KiB
Python
292 lines
7.8 KiB
Python
|
# -*- 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))
|