aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/qapi/gen.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/qapi/gen.py')
-rw-r--r--scripts/qapi/gen.py339
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))