diff mbox series

[v3,40/49] RFC: qapi: learn to split schema by 'top-unit'

Message ID 20180321115211.17937-41-marcandre.lureau@redhat.com
State New
Headers show
Series qapi: add #if pre-processor conditions to generated code | expand

Commit Message

Marc-André Lureau March 21, 2018, 11:52 a.m. UTC
Another take at making the schema modular, this time by introducing
the concept of a 'top-unit'. With this approach, each module is part
of a top-unit, which is visited first before visiting the module
themselfs. The default 'top-unit' is None. Else, a module belong to a
'top-unit' whose name is given by the 'top-unit' pragma value.

This gives a chance to the generators to break generated output into
different files. The point of this excercice is to have modules that
can be compiled per qemu targets, instead of everything belonging to
the top schema without the right to use poisoin defines for
conditional compilation.

Generated types, visitors, events and commands are split by
'top-unit'.

The generated introspection and documentation remain monolithic.

TODO: if this approach is acceptable, write tests & doc.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 scripts/qapi/commands.py |  22 +++++---
 scripts/qapi/common.py   | 105 +++++++++++++++++++++++++++++++--------
 scripts/qapi/events.py   |  33 ++++++++----
 scripts/qapi/types.py    |   8 +--
 scripts/qapi/visit.py    |   8 +--
 5 files changed, 129 insertions(+), 47 deletions(-)
diff mbox series

Patch

diff --git a/scripts/qapi/commands.py b/scripts/qapi/commands.py
index 3d3e97f737..3ca36fc33b 100644
--- a/scripts/qapi/commands.py
+++ b/scripts/qapi/commands.py
@@ -235,14 +235,20 @@  class QAPISchemaGenCommandVisitor(QAPISchemaModularCVisitor):
         QAPISchemaModularCVisitor.__init__(
             self, prefix, 'qapi-commands',
             ' * Schema-defined QAPI/QMP commands', __doc__)
+
+    def visit_unit_begin(self, unit):
+        super(self.__class__, self).visit_unit_begin(unit)
         self._regy = QAPIGenCSnippet()
         self._visited_ret_types = {}
 
-    def _begin_module(self, name):
+    def _begin_module(self, name, main_module):
         self._visited_ret_types[self._genc] = set()
-        commands = self._module_basename('qapi-commands', name)
-        types = self._module_basename('qapi-types', name)
-        visit = self._module_basename('qapi-visit', name)
+        commands = self._module_basename('qapi-commands', name,
+                                         self._unit, main_module)
+        types = self._module_basename('qapi-types', name,
+                                      self._unit, main_module)
+        visit = self._module_basename('qapi-visit', name,
+                                      self._unit, main_module)
         self._genc.add(mcgen('''
 #include "qemu/osdep.h"
 #include "qemu-common.h"
@@ -265,13 +271,13 @@  class QAPISchemaGenCommandVisitor(QAPISchemaModularCVisitor):
 ''',
                              types=types))
 
-    def visit_end(self):
-        (genc, genh) = self._module[self._main_module]
+    def visit_unit_end(self):
+        (genc, genh) = self.get_module_gen(self._main_module)
         genh.add(mcgen('''
 void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
 ''',
-                       c_prefix=c_name(self._prefix, protect=False)))
-        genc.add(gen_registry(self._regy.get_content(), self._prefix))
+                       c_prefix=c_name(self._prefix_unit(), protect=False)))
+        genc.add(gen_registry(self._regy.get_content(), self._prefix_unit()))
 
     def visit_command(self, name, info, ifcond, arg_type, ret_type,
                       gen, success_response, boxed, allow_oob):
diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index 174b39ea50..bff8de05d1 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -271,11 +271,12 @@  class QAPISchemaParser(object):
         self.exprs = []
         self.docs = []
         self.accept()
+        self._top_unit = None
         cur_doc = None
 
         while self.tok is not None:
             info = {'file': self.fname, 'line': self.line,
-                    'parent': self.incl_info}
+                    'parent': self.incl_info, 'top-unit': self._top_unit}
             if self.tok == '#':
                 self.reject_expr_doc(cur_doc)
                 cur_doc = self.get_doc(info)
@@ -298,6 +299,9 @@  class QAPISchemaParser(object):
                 exprs_include = self._include(include, info, incl_fname,
                                               previously_included)
                 if exprs_include:
+                    incl_info = self.exprs[-1]['info']
+                    if exprs_include._top_unit:
+                        incl_info['has-pragma-top-unit'] = exprs_include._top_unit
                     self.exprs.extend(exprs_include.exprs)
                     self.docs.extend(exprs_include.docs)
             elif "pragma" in expr:
@@ -356,6 +360,11 @@  class QAPISchemaParser(object):
                 raise QAPISemError(info,
                                    "Pragma 'doc-required' must be boolean")
             doc_required = value
+        elif name == 'top-unit':
+            if not isinstance(value, str):
+                raise QAPISemError(info,
+                                   "Pragma 'top-unit' must be a string")
+            self._top_unit = value
         elif name == 'returns-whitelist':
             if (not isinstance(value, list)
                     or any([not isinstance(elt, str) for elt in value])):
@@ -1098,6 +1107,11 @@  class QAPISchemaEntity(object):
     def c_name(self):
         return c_name(self.name)
 
+    def get_top_unit(self):
+        if self.info:
+            return self.info['top-unit']
+        return None
+
     def check(self, schema):
         if isinstance(self._ifcond, QAPISchemaType):
             # inherit the condition from a type
@@ -1118,6 +1132,12 @@  class QAPISchemaVisitor(object):
     def visit_begin(self, schema):
         pass
 
+    def visit_unit_begin(self, unit):
+        pass
+
+    def visit_unit_end(self):
+        pass
+
     def visit_end(self):
         pass
 
@@ -1613,7 +1633,7 @@  class QAPISchema(object):
         parser = QAPISchemaParser(open(fname, 'r'))
         exprs = check_exprs(parser.exprs)
         self.docs = parser.docs
-        self._entity_list = []
+        self._entity_list = {} # dict of unit name -> list of entity
         self._entity_dict = {}
         self._predefining = True
         self._def_predefineds()
@@ -1625,7 +1645,8 @@  class QAPISchema(object):
         # Only the predefined types are allowed to not have info
         assert ent.info or self._predefining
         assert ent.name is None or ent.name not in self._entity_dict
-        self._entity_list.append(ent)
+        entity_list = self._entity_list.setdefault(ent.get_top_unit(), [])
+        entity_list.append(ent)
         if ent.name is not None:
             self._entity_dict[ent.name] = ent
         if ent.info:
@@ -1870,18 +1891,23 @@  class QAPISchema(object):
                 assert False
 
     def check(self):
-        for ent in self._entity_list:
-            ent.check(self)
+        for unit in self._entity_list:
+            for ent in self._entity_list[unit]:
+                ent.check(self)
 
     def visit(self, visitor):
         visitor.visit_begin(self)
-        module = None
-        for entity in self._entity_list:
-            if visitor.visit_needed(entity):
+        for unit in self._entity_list:
+            module = None
+            visitor.visit_unit_begin(unit)
+            for entity in self._entity_list[unit]:
+                if not visitor.visit_needed(entity):
+                    continue
                 if entity.module != module:
                     module = entity.module
                     visitor.visit_module(module)
                 entity.visit(visitor)
+            visitor.visit_unit_end()
         visitor.visit_end()
 
 
@@ -2301,6 +2327,19 @@  class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
         self._genh.write(output_dir, self._prefix + self._what + '.h')
 
 
+class QAPIGenCModule(object):
+
+    def __init__(self, blurb, pydoc, unit, main_module=False):
+        self.genc = QAPIGenC(blurb, pydoc)
+        self.genh = QAPIGenH(blurb, pydoc)
+        self.unit = unit
+        self.main_module = main_module
+
+    def write(self, output_dir, basename):
+        self.genc.write(output_dir, basename + '.c')
+        self.genh.write(output_dir, basename + '.h')
+
+
 class QAPISchemaModularCVisitor(QAPISchemaVisitor):
 
     def __init__(self, prefix, what, blurb, pydoc):
@@ -2309,48 +2348,70 @@  class QAPISchemaModularCVisitor(QAPISchemaVisitor):
         self._blurb = blurb
         self._pydoc = pydoc
         self._module = {}
+        self._unit = None
         self._main_module = None
 
-    def _module_basename(self, what, name):
+    def _module_basename(self, what, name, unit=None, main_module=False):
         if name is None:
             return re.sub(r'-', '-builtin-', what)
         basename = os.path.join(os.path.dirname(name),
                                 self._prefix + what)
-        if name == self._main_module:
+        if unit:
+            basename = unit + '-' + basename
+        if main_module:
             return basename
         return basename + '-' + os.path.splitext(os.path.basename(name))[0]
 
+    def _prefix_unit(self):
+        if self._unit:
+            return self._prefix + self._unit + '-'
+        return self._prefix
+
+    def visit_unit_begin(self, unit):
+        self._unit = unit
+        self._main_module = None
+
     def _add_module(self, name, blurb):
-        if self._main_module is None and name is not None:
+        main_module = False
+        if (name is not None and
+            ((self._unit is None and self._main_module is None) or
+             (self._unit == os.path.splitext(os.path.basename(name))[0]))):
             self._main_module = name
-        genc = QAPIGenC(blurb, self._pydoc)
-        genh = QAPIGenH(blurb, self._pydoc)
-        self._module[name] = (genc, genh)
+            main_module = True
+        self._module[name] = QAPIGenCModule(blurb, self._pydoc,
+                                            self._unit, main_module)
         self._set_module(name)
+        return main_module
+
+    def get_module_gen(self, name):
+        mod = self._module[name]
+        return mod.genc, mod.genh
 
     def _set_module(self, name):
-        self._genc, self._genh = self._module[name]
+        self._genc, self._genh = self.get_module_gen(name)
 
     def write(self, output_dir, opt_builtins=False):
         for name in self._module:
             if name is None and not opt_builtins:
                 continue
-            basename = self._module_basename(self._what, name)
-            (genc, genh) = self._module[name]
-            genc.write(output_dir, basename + '.c')
-            genh.write(output_dir, basename + '.h')
+            module = self._module[name]
+            basename = self._module_basename(self._what, name,
+                                             module.unit, module.main_module)
+            module.write(output_dir, basename)
 
-    def _begin_module(self, name):
+    def _begin_module(self, name, main_module):
         pass
 
     def visit_module(self, name):
         if name in self._module:
             self._set_module(name)
             return
-        self._add_module(name, self._blurb)
-        self._begin_module(name)
+        main_module = self._add_module(name, self._blurb)
+        self._begin_module(name, main_module)
 
     def visit_include(self, name, info):
+        if 'has-pragma-top-unit' in info:
+            return
         basename = self._module_basename(self._what, name)
         self._genh.preamble_add(mcgen('''
 #include "%(basename)s.h"
diff --git a/scripts/qapi/events.py b/scripts/qapi/events.py
index f02b65cf5a..88bfcfcf2a 100644
--- a/scripts/qapi/events.py
+++ b/scripts/qapi/events.py
@@ -58,7 +58,12 @@  def gen_param_var(typ):
     return ret
 
 
-def gen_event_send(name, arg_type, boxed, event_enum_name):
+def gen_event_send(unit, name, arg_type, boxed, event_enum_name):
+    if not unit:
+        unit = ''
+    else:
+        unit += '_'
+
     # FIXME: Our declaration of local variables (and of 'errp' in the
     # parameter list) can collide with exploded members of the event's
     # data type passed in as parameters.  If this collision ever hits in
@@ -87,7 +92,7 @@  def gen_event_send(name, arg_type, boxed, event_enum_name):
 
     ret += mcgen('''
 
-    emit = qmp_event_get_func_emit();
+    emit = %(unit)sqmp_event_get_func_emit();
     if (!emit) {
         return;
     }
@@ -95,7 +100,7 @@  def gen_event_send(name, arg_type, boxed, event_enum_name):
     qmp = qmp_event_build_dict("%(name)s");
 
 ''',
-                 name=name)
+                 name=name, unit=unit)
 
     if arg_type and not arg_type.is_empty():
         ret += mcgen('''
@@ -154,12 +159,17 @@  class QAPISchemaGenEventVisitor(QAPISchemaModularCVisitor):
         QAPISchemaModularCVisitor.__init__(
             self, prefix, 'qapi-events',
             ' * Schema-defined QAPI/QMP events', __doc__)
-        self._enum_name = c_name(prefix + 'QAPIEvent', protect=False)
+
+    def visit_unit_begin(self, unit):
+        super(self.__class__, self).visit_unit_begin(unit)
+        self._enum_name = c_name(self._prefix_unit() + 'QAPIEvent', protect=False)
         self._event_names = []
 
-    def _begin_module(self, name):
-        types = self._module_basename('qapi-types', name)
-        visit = self._module_basename('qapi-visit', name)
+    def _begin_module(self, name, main_module):
+        types = self._module_basename('qapi-types', name,
+                                      self._unit, main_module)
+        visit = self._module_basename('qapi-visit', name,
+                                      self._unit, main_module)
         self._genc.add(mcgen('''
 #include "qemu/osdep.h"
 #include "qemu-common.h"
@@ -171,7 +181,7 @@  class QAPISchemaGenEventVisitor(QAPISchemaModularCVisitor):
 #include "qapi/qmp-event.h"
 
 ''',
-                             visit=visit, prefix=self._prefix))
+                             visit=visit, prefix=self._prefix_unit()))
         self._genh.add(mcgen('''
 #include "qapi/util.h"
 #include "%(types)s.h"
@@ -179,15 +189,16 @@  class QAPISchemaGenEventVisitor(QAPISchemaModularCVisitor):
 ''',
                              types=types))
 
-    def visit_end(self):
-        (genc, genh) = self._module[self._main_module]
+    def visit_unit_end(self):
+        (genc, genh) = self.get_module_gen(self._main_module)
         genh.add(gen_enum(self._enum_name, self._event_names))
         genc.add(gen_enum_lookup(self._enum_name, self._event_names))
 
     def visit_event(self, name, info, ifcond, arg_type, boxed):
         with ifcontext(ifcond, self._genh, self._genc):
             self._genh.add(gen_event_send_decl(name, arg_type, boxed))
-            self._genc.add(gen_event_send(name, arg_type, boxed, self._enum_name))
+            self._genc.add(gen_event_send(self._unit, name,
+                                          arg_type, boxed, self._enum_name))
         self._event_names.append(QAPISchemaMember(name, ifcond))
 
 
diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py
index b0d3ddb596..ac0c4603d4 100644
--- a/scripts/qapi/types.py
+++ b/scripts/qapi/types.py
@@ -194,9 +194,11 @@  class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor):
 #include "qapi/util.h"
 '''))
 
-    def _begin_module(self, name):
-        types = self._module_basename('qapi-types', name)
-        visit = self._module_basename('qapi-visit', name)
+    def _begin_module(self, name, main_module):
+        types = self._module_basename('qapi-types', name,
+                                      self._unit, main_module)
+        visit = self._module_basename('qapi-visit', name,
+                                      self._unit, main_module)
         self._genc.preamble_add(mcgen('''
 #include "qemu/osdep.h"
 #include "qapi/dealloc-visitor.h"
diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py
index dc5a3173fc..60625aae72 100644
--- a/scripts/qapi/visit.py
+++ b/scripts/qapi/visit.py
@@ -289,9 +289,11 @@  class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor):
 ''',
                                       prefix=prefix))
 
-    def _begin_module(self, name):
-        types = self._module_basename('qapi-types', name)
-        visit = self._module_basename('qapi-visit', name)
+    def _begin_module(self, name, main_module):
+        types = self._module_basename('qapi-types', name,
+                                      self._unit, main_module)
+        visit = self._module_basename('qapi-visit', name,
+                                      self._unit, main_module)
         self._genc.preamble_add(mcgen('''
 #include "qemu/osdep.h"
 #include "qemu-common.h"