adcb9b36c9
A precise style guide and a package-wide overhaul is forthcoming pending further discussion and consensus. For now, merely avoid obvious errors that cause Sphinx documentation build problems, using a style loosely based on PEP 257 and Sphinx Autodoc. It is chosen for interoperability with our existing Sphinx framework, and because it has loose recognition in the Pycharm IDE. See also: https://www.python.org/dev/peps/pep-0257/ https://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html#info-field-lists Signed-off-by: John Snow <jsnow@redhat.com> Message-Id: <20201009161558.107041-3-jsnow@redhat.com> Reviewed-by: Markus Armbruster <armbru@redhat.com> Signed-off-by: Markus Armbruster <armbru@redhat.com>
291 lines
8.0 KiB
Python
291 lines
8.0 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
|
|
from contextlib import contextmanager
|
|
|
|
from qapi.common import *
|
|
from qapi.schema import QAPISchemaVisitor
|
|
|
|
|
|
class QAPIGen:
|
|
|
|
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):
|
|
# Include paths starting with ../ are used to reuse modules of the main
|
|
# schema in specialised schemas. Don't overwrite the files that are
|
|
# already generated for the main schema.
|
|
if self.fname.startswith('../'):
|
|
return
|
|
pathname = os.path.join(output_dir, self.fname)
|
|
odir = os.path.dirname(pathname)
|
|
if odir:
|
|
try:
|
|
os.makedirs(odir)
|
|
except os.error as e:
|
|
if e.errno != errno.EEXIST:
|
|
raise
|
|
fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666)
|
|
f = open(fd, 'r+', encoding='utf-8')
|
|
text = self.get_content()
|
|
oldtext = f.read(len(text) + 1)
|
|
if text != oldtext:
|
|
f.seek(0)
|
|
f.truncate(0)
|
|
f.write(text)
|
|
f.close()
|
|
|
|
|
|
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):
|
|
super().__init__(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 super().get_content()
|
|
|
|
|
|
class QAPIGenC(QAPIGenCCode):
|
|
|
|
def __init__(self, fname, blurb, pydoc):
|
|
super().__init__(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 super()._top() + guardstart(self.fname)
|
|
|
|
def _bottom(self):
|
|
return guardend(self.fname)
|
|
|
|
|
|
@contextmanager
|
|
def ifcontext(ifcond, *args):
|
|
"""
|
|
A with-statement context manager that wraps with `start_if()` / `end_if()`.
|
|
|
|
:param ifcond: A list of conditionals, passed to `start_if()`.
|
|
:param 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 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, user_blurb, builtin_blurb, pydoc):
|
|
self._prefix = prefix
|
|
self._what = what
|
|
self._user_blurb = user_blurb
|
|
self._builtin_blurb = builtin_blurb
|
|
self._pydoc = pydoc
|
|
self._genc = None
|
|
self._genh = None
|
|
self._module = {}
|
|
self._main_module = None
|
|
|
|
@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._genc, self._genh = self._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 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_system_module(self, name):
|
|
pass
|
|
|
|
def _begin_user_module(self, name):
|
|
pass
|
|
|
|
def visit_module(self, name):
|
|
if name is None:
|
|
if self._builtin_blurb:
|
|
self._add_system_module(None, self._builtin_blurb)
|
|
self._begin_system_module(name)
|
|
else:
|
|
# 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._user_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))
|