# -*- coding: utf-8 -*- # # QAPI code generation # # Copyright (c) 2018-2019 Red Hat Inc. # # Authors: # Markus Armbruster # Marc-André Lureau # # 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 errno import os import re from .common import ( c_fname, gen_endif, gen_if, guardend, guardstart, mcgen, ) from .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))