| # -*- 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 | |
| import sys | |
| from typing import ( | |
| Dict, | |
| Iterator, | |
| Optional, | |
| Sequence, | |
| Tuple, | |
| ) | |
| from .common import ( | |
| c_enum_const, | |
| c_fname, | |
| c_name, | |
| guardend, | |
| guardstart, | |
| mcgen, | |
| ) | |
| from .schema import ( | |
| QAPISchemaFeature, | |
| QAPISchemaIfCond, | |
| QAPISchemaModule, | |
| QAPISchemaObjectType, | |
| QAPISchemaVisitor, | |
| ) | |
| from .source import QAPISourceInfo | |
| def gen_features(features: Sequence[QAPISchemaFeature]) -> str: | |
| feats = [f"1u << {c_enum_const('qapi_feature', feat.name)}" | |
| for feat in features] | |
| return ' | '.join(feats) 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-existent 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.branches | |
| for memb in arg_type.members: | |
| assert not memb.ifcond.is_present() | |
| ret += sep | |
| sep = ', ' | |
| if memb.need_has(): | |
| 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 by %(tool)s 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. | |
| */ | |
| ''', | |
| tool=os.path.basename(sys.argv[0]), | |
| 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) | |
| class QAPIGenTrace(QAPIGen): | |
| def _top(self) -> str: | |
| return (super()._top() | |
| + '# AUTOMATICALLY GENERATED by ' | |
| + os.path.basename(sys.argv[0]) | |
| + ', DO NOT MODIFY\n\n') | |
| @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, | |
| gen_tracing: bool = False): | |
| 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, | |
| Optional[QAPIGenTrace]]] = {} | |
| self._main_module: Optional[str] = None | |
| self._gen_tracing = gen_tracing | |
| @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] | |
| @property | |
| def _gen_trace_events(self) -> QAPIGenTrace: | |
| assert self._gen_tracing | |
| assert self._current_module is not None | |
| gent = self._module[self._current_module][2] | |
| assert gent is not None | |
| return gent | |
| @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) | |
| gent: Optional[QAPIGenTrace] = None | |
| if self._gen_tracing: | |
| gent = QAPIGenTrace(basename + '.trace-events') | |
| self._module[name] = (genc, genh, gent) | |
| 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, gent) in self._module.items(): | |
| if QAPISchemaModule.is_builtin_module(name) and not opt_builtins: | |
| continue | |
| genc.write(output_dir) | |
| genh.write(output_dir) | |
| if gent is not None: | |
| gent.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)) |