diff options
Diffstat (limited to 'scripts/qapi/gen.py')
-rw-r--r-- | scripts/qapi/gen.py | 339 |
1 files changed, 339 insertions, 0 deletions
diff --git a/scripts/qapi/gen.py b/scripts/qapi/gen.py new file mode 100644 index 000000000..995a97d2b --- /dev/null +++ b/scripts/qapi/gen.py @@ -0,0 +1,339 @@ +# -*- coding: utf-8 -*- +# +# QAPI code generation +# +# Copyright (c) 2015-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. + +from contextlib import contextmanager +import os +import re +from typing import ( + Dict, + Iterator, + Optional, + Sequence, + Tuple, +) + +from .common import ( + c_fname, + c_name, + guardend, + guardstart, + mcgen, +) +from .schema import ( + QAPISchemaFeature, + QAPISchemaIfCond, + QAPISchemaModule, + QAPISchemaObjectType, + QAPISchemaVisitor, +) +from .source import QAPISourceInfo + + +def gen_special_features(features: Sequence[QAPISchemaFeature]) -> str: + special_features = [f"1u << QAPI_{feat.name.upper()}" + for feat in features if feat.is_special()] + return ' | '.join(special_features) or '0' + + +class QAPIGen: + def __init__(self, fname: str): + self.fname = fname + self._preamble = '' + self._body = '' + + def preamble_add(self, text: str) -> None: + self._preamble += text + + def add(self, text: str) -> None: + self._body += text + + def get_content(self) -> str: + return self._top() + self._preamble + self._body + self._bottom() + + def _top(self) -> str: + # pylint: disable=no-self-use + return '' + + def _bottom(self) -> str: + # pylint: disable=no-self-use + return '' + + def write(self, output_dir: str) -> None: + # 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: + os.makedirs(odir, exist_ok=True) + + # use os.open for O_CREAT to create and read a non-existant file + fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666) + with os.fdopen(fd, 'r+', encoding='utf-8') as fp: + text = self.get_content() + oldtext = fp.read(len(text) + 1) + if text != oldtext: + fp.seek(0) + fp.truncate(0) + fp.write(text) + + +def _wrap_ifcond(ifcond: QAPISchemaIfCond, before: str, after: str) -> str: + 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 += ifcond.gen_if() + out += added + out += ifcond.gen_endif() + return out + + +def build_params(arg_type: Optional[QAPISchemaObjectType], + boxed: bool, + extra: Optional[str] = None) -> str: + ret = '' + sep = '' + if boxed: + assert arg_type + ret += '%s arg' % arg_type.c_param_type() + sep = ', ' + elif arg_type: + assert not arg_type.variants + for memb in arg_type.members: + ret += sep + sep = ', ' + if memb.optional: + ret += 'bool has_%s, ' % c_name(memb.name) + ret += '%s %s' % (memb.type.c_param_type(), + c_name(memb.name)) + if extra: + ret += sep + extra + return ret if ret else 'void' + + +class QAPIGenCCode(QAPIGen): + def __init__(self, fname: str): + super().__init__(fname) + self._start_if: Optional[Tuple[QAPISchemaIfCond, str, str]] = None + + def start_if(self, ifcond: QAPISchemaIfCond) -> None: + assert self._start_if is None + self._start_if = (ifcond, self._body, self._preamble) + + def end_if(self) -> None: + assert self._start_if is not None + 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) + self._start_if = None + + def get_content(self) -> str: + assert self._start_if is None + return super().get_content() + + +class QAPIGenC(QAPIGenCCode): + def __init__(self, fname: str, blurb: str, pydoc: str): + super().__init__(fname) + self._blurb = blurb + self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc, + re.MULTILINE)) + + def _top(self) -> str: + 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) -> str: + 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) -> str: + return super()._top() + guardstart(self.fname) + + def _bottom(self) -> str: + return guardend(self.fname) + + +@contextmanager +def ifcontext(ifcond: QAPISchemaIfCond, *args: QAPIGenCCode) -> Iterator[None]: + """ + A with-statement context manager that wraps with `start_if()` / `end_if()`. + + :param ifcond: A sequence 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: str, + what: str, + blurb: str, + pydoc: str): + 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: str) -> None: + self._genc.write(output_dir) + self._genh.write(output_dir) + + +class QAPISchemaModularCVisitor(QAPISchemaVisitor): + def __init__(self, + prefix: str, + what: str, + user_blurb: str, + builtin_blurb: Optional[str], + pydoc: str): + self._prefix = prefix + self._what = what + self._user_blurb = user_blurb + self._builtin_blurb = builtin_blurb + self._pydoc = pydoc + self._current_module: Optional[str] = None + self._module: Dict[str, Tuple[QAPIGenC, QAPIGenH]] = {} + self._main_module: Optional[str] = None + + @property + def _genc(self) -> QAPIGenC: + assert self._current_module is not None + return self._module[self._current_module][0] + + @property + def _genh(self) -> QAPIGenH: + assert self._current_module is not None + return self._module[self._current_module][1] + + @staticmethod + def _module_dirname(name: str) -> str: + if QAPISchemaModule.is_user_module(name): + return os.path.dirname(name) + return '' + + def _module_basename(self, what: str, name: str) -> str: + ret = '' if QAPISchemaModule.is_builtin_module(name) else self._prefix + if QAPISchemaModule.is_user_module(name): + basename = os.path.basename(name) + ret += what + if name != self._main_module: + ret += '-' + os.path.splitext(basename)[0] + else: + assert QAPISchemaModule.is_system_module(name) + ret += re.sub(r'-', '-' + name[2:] + '-', what) + return ret + + def _module_filename(self, what: str, name: str) -> str: + return os.path.join(self._module_dirname(name), + self._module_basename(what, name)) + + def _add_module(self, name: str, blurb: str) -> None: + if QAPISchemaModule.is_user_module(name): + if self._main_module is None: + self._main_module = name + 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._current_module = name + + @contextmanager + def _temp_module(self, name: str) -> Iterator[None]: + old_module = self._current_module + self._current_module = name + yield + self._current_module = old_module + + def write(self, output_dir: str, opt_builtins: bool = False) -> None: + for name, (genc, genh) in self._module.items(): + if QAPISchemaModule.is_builtin_module(name) and not opt_builtins: + continue + genc.write(output_dir) + genh.write(output_dir) + + def _begin_builtin_module(self) -> None: + pass + + def _begin_user_module(self, name: str) -> None: + pass + + def visit_module(self, name: str) -> None: + if QAPISchemaModule.is_builtin_module(name): + if self._builtin_blurb: + self._add_module(name, self._builtin_blurb) + self._begin_builtin_module() + else: + # The built-in module has not been created. No code may + # be generated. + self._current_module = None + else: + assert QAPISchemaModule.is_user_module(name) + self._add_module(name, self._user_blurb) + self._begin_user_module(name) + + def visit_include(self, name: str, info: Optional[QAPISourceInfo]) -> None: + 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)) |