summary refs log tree commit diff stats
path: root/scripts/qapi/gen.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/qapi/gen.py')
-rw-r--r--scripts/qapi/gen.py291
1 files changed, 291 insertions, 0 deletions
diff --git a/scripts/qapi/gen.py b/scripts/qapi/gen.py
new file mode 100644
index 0000000000..112b6d94c5
--- /dev/null
+++ b/scripts/qapi/gen.py
@@ -0,0 +1,291 @@
+# -*- 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
+import sys
+from contextlib import contextmanager
+
+from qapi.common import *
+from qapi.schema import QAPISchemaVisitor
+
+
+class QAPIGen(object):
+
+    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):
+        pathname = os.path.join(output_dir, self.fname)
+        dir = os.path.dirname(pathname)
+        if dir:
+            try:
+                os.makedirs(dir)
+            except os.error as e:
+                if e.errno != errno.EEXIST:
+                    raise
+        fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666)
+        if sys.version_info[0] >= 3:
+            f = open(fd, 'r+', encoding='utf-8')
+        else:
+            f = os.fdopen(fd, 'r+')
+        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):
+        QAPIGen.__init__(self, 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 QAPIGen.get_content(self)
+
+
+class QAPIGenC(QAPIGenCCode):
+
+    def __init__(self, fname, blurb, pydoc):
+        QAPIGenCCode.__init__(self, 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 QAPIGenC._top(self) + guardstart(self.fname)
+
+    def _bottom(self):
+        return guardend(self.fname)
+
+
+@contextmanager
+def ifcontext(ifcond, *args):
+    """A 'with' statement context manager to wrap with start_if()/end_if()
+
+    *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 QAPIGenDoc(QAPIGen):
+
+    def _top(self):
+        return (QAPIGen._top(self)
+                + '@c AUTOMATICALLY GENERATED, DO NOT MODIFY\n\n')
+
+
+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, blurb, pydoc):
+        self._prefix = prefix
+        self._what = what
+        self._blurb = 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._set_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 _set_module(self, name):
+        self._genc, self._genh = self._module[name]
+
+    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_user_module(self, name):
+        pass
+
+    def visit_module(self, name):
+        if name in self._module:
+            self._set_module(name)
+        elif self._is_builtin_module(name):
+            # 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._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))