| # -*- 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: |
| 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, 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) |
| |
| |
| class QAPIGenTrace(QAPIGen): |
| def _top(self) -> str: |
| return super()._top() + '# AUTOMATICALLY GENERATED, 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)) |