qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

gen.py (11130B)


      1 # -*- coding: utf-8 -*-
      2 #
      3 # QAPI code generation
      4 #
      5 # Copyright (c) 2015-2019 Red Hat Inc.
      6 #
      7 # Authors:
      8 #  Markus Armbruster <armbru@redhat.com>
      9 #  Marc-André Lureau <marcandre.lureau@redhat.com>
     10 #
     11 # This work is licensed under the terms of the GNU GPL, version 2.
     12 # See the COPYING file in the top-level directory.
     13 
     14 from contextlib import contextmanager
     15 import os
     16 import re
     17 from typing import (
     18     Dict,
     19     Iterator,
     20     Optional,
     21     Sequence,
     22     Tuple,
     23 )
     24 
     25 from .common import (
     26     c_fname,
     27     c_name,
     28     guardend,
     29     guardstart,
     30     mcgen,
     31 )
     32 from .schema import (
     33     QAPISchemaFeature,
     34     QAPISchemaIfCond,
     35     QAPISchemaModule,
     36     QAPISchemaObjectType,
     37     QAPISchemaVisitor,
     38 )
     39 from .source import QAPISourceInfo
     40 
     41 
     42 def gen_special_features(features: Sequence[QAPISchemaFeature]) -> str:
     43     special_features = [f"1u << QAPI_{feat.name.upper()}"
     44                         for feat in features if feat.is_special()]
     45     return ' | '.join(special_features) or '0'
     46 
     47 
     48 class QAPIGen:
     49     def __init__(self, fname: str):
     50         self.fname = fname
     51         self._preamble = ''
     52         self._body = ''
     53 
     54     def preamble_add(self, text: str) -> None:
     55         self._preamble += text
     56 
     57     def add(self, text: str) -> None:
     58         self._body += text
     59 
     60     def get_content(self) -> str:
     61         return self._top() + self._preamble + self._body + self._bottom()
     62 
     63     def _top(self) -> str:
     64         # pylint: disable=no-self-use
     65         return ''
     66 
     67     def _bottom(self) -> str:
     68         # pylint: disable=no-self-use
     69         return ''
     70 
     71     def write(self, output_dir: str) -> None:
     72         # Include paths starting with ../ are used to reuse modules of the main
     73         # schema in specialised schemas. Don't overwrite the files that are
     74         # already generated for the main schema.
     75         if self.fname.startswith('../'):
     76             return
     77         pathname = os.path.join(output_dir, self.fname)
     78         odir = os.path.dirname(pathname)
     79 
     80         if odir:
     81             os.makedirs(odir, exist_ok=True)
     82 
     83         # use os.open for O_CREAT to create and read a non-existant file
     84         fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666)
     85         with os.fdopen(fd, 'r+', encoding='utf-8') as fp:
     86             text = self.get_content()
     87             oldtext = fp.read(len(text) + 1)
     88             if text != oldtext:
     89                 fp.seek(0)
     90                 fp.truncate(0)
     91                 fp.write(text)
     92 
     93 
     94 def _wrap_ifcond(ifcond: QAPISchemaIfCond, before: str, after: str) -> str:
     95     if before == after:
     96         return after   # suppress empty #if ... #endif
     97 
     98     assert after.startswith(before)
     99     out = before
    100     added = after[len(before):]
    101     if added[0] == '\n':
    102         out += '\n'
    103         added = added[1:]
    104     out += ifcond.gen_if()
    105     out += added
    106     out += ifcond.gen_endif()
    107     return out
    108 
    109 
    110 def build_params(arg_type: Optional[QAPISchemaObjectType],
    111                  boxed: bool,
    112                  extra: Optional[str] = None) -> str:
    113     ret = ''
    114     sep = ''
    115     if boxed:
    116         assert arg_type
    117         ret += '%s arg' % arg_type.c_param_type()
    118         sep = ', '
    119     elif arg_type:
    120         assert not arg_type.variants
    121         for memb in arg_type.members:
    122             ret += sep
    123             sep = ', '
    124             if memb.optional:
    125                 ret += 'bool has_%s, ' % c_name(memb.name)
    126             ret += '%s %s' % (memb.type.c_param_type(),
    127                               c_name(memb.name))
    128     if extra:
    129         ret += sep + extra
    130     return ret if ret else 'void'
    131 
    132 
    133 class QAPIGenCCode(QAPIGen):
    134     def __init__(self, fname: str):
    135         super().__init__(fname)
    136         self._start_if: Optional[Tuple[QAPISchemaIfCond, str, str]] = None
    137 
    138     def start_if(self, ifcond: QAPISchemaIfCond) -> None:
    139         assert self._start_if is None
    140         self._start_if = (ifcond, self._body, self._preamble)
    141 
    142     def end_if(self) -> None:
    143         assert self._start_if is not None
    144         self._body = _wrap_ifcond(self._start_if[0],
    145                                   self._start_if[1], self._body)
    146         self._preamble = _wrap_ifcond(self._start_if[0],
    147                                       self._start_if[2], self._preamble)
    148         self._start_if = None
    149 
    150     def get_content(self) -> str:
    151         assert self._start_if is None
    152         return super().get_content()
    153 
    154 
    155 class QAPIGenC(QAPIGenCCode):
    156     def __init__(self, fname: str, blurb: str, pydoc: str):
    157         super().__init__(fname)
    158         self._blurb = blurb
    159         self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc,
    160                                                   re.MULTILINE))
    161 
    162     def _top(self) -> str:
    163         return mcgen('''
    164 /* AUTOMATICALLY GENERATED, DO NOT MODIFY */
    165 
    166 /*
    167 %(blurb)s
    168  *
    169  * %(copyright)s
    170  *
    171  * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
    172  * See the COPYING.LIB file in the top-level directory.
    173  */
    174 
    175 ''',
    176                      blurb=self._blurb, copyright=self._copyright)
    177 
    178     def _bottom(self) -> str:
    179         return mcgen('''
    180 
    181 /* Dummy declaration to prevent empty .o file */
    182 char qapi_dummy_%(name)s;
    183 ''',
    184                      name=c_fname(self.fname))
    185 
    186 
    187 class QAPIGenH(QAPIGenC):
    188     def _top(self) -> str:
    189         return super()._top() + guardstart(self.fname)
    190 
    191     def _bottom(self) -> str:
    192         return guardend(self.fname)
    193 
    194 
    195 class QAPIGenTrace(QAPIGen):
    196     def _top(self) -> str:
    197         return super()._top() + '# AUTOMATICALLY GENERATED, DO NOT MODIFY\n\n'
    198 
    199 
    200 @contextmanager
    201 def ifcontext(ifcond: QAPISchemaIfCond, *args: QAPIGenCCode) -> Iterator[None]:
    202     """
    203     A with-statement context manager that wraps with `start_if()` / `end_if()`.
    204 
    205     :param ifcond: A sequence of conditionals, passed to `start_if()`.
    206     :param args: any number of `QAPIGenCCode`.
    207 
    208     Example::
    209 
    210         with ifcontext(ifcond, self._genh, self._genc):
    211             modify self._genh and self._genc ...
    212 
    213     Is equivalent to calling::
    214 
    215         self._genh.start_if(ifcond)
    216         self._genc.start_if(ifcond)
    217         modify self._genh and self._genc ...
    218         self._genh.end_if()
    219         self._genc.end_if()
    220     """
    221     for arg in args:
    222         arg.start_if(ifcond)
    223     yield
    224     for arg in args:
    225         arg.end_if()
    226 
    227 
    228 class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
    229     def __init__(self,
    230                  prefix: str,
    231                  what: str,
    232                  blurb: str,
    233                  pydoc: str):
    234         self._prefix = prefix
    235         self._what = what
    236         self._genc = QAPIGenC(self._prefix + self._what + '.c',
    237                               blurb, pydoc)
    238         self._genh = QAPIGenH(self._prefix + self._what + '.h',
    239                               blurb, pydoc)
    240 
    241     def write(self, output_dir: str) -> None:
    242         self._genc.write(output_dir)
    243         self._genh.write(output_dir)
    244 
    245 
    246 class QAPISchemaModularCVisitor(QAPISchemaVisitor):
    247     def __init__(self,
    248                  prefix: str,
    249                  what: str,
    250                  user_blurb: str,
    251                  builtin_blurb: Optional[str],
    252                  pydoc: str,
    253                  gen_tracing: bool = False):
    254         self._prefix = prefix
    255         self._what = what
    256         self._user_blurb = user_blurb
    257         self._builtin_blurb = builtin_blurb
    258         self._pydoc = pydoc
    259         self._current_module: Optional[str] = None
    260         self._module: Dict[str, Tuple[QAPIGenC, QAPIGenH,
    261                                       Optional[QAPIGenTrace]]] = {}
    262         self._main_module: Optional[str] = None
    263         self._gen_tracing = gen_tracing
    264 
    265     @property
    266     def _genc(self) -> QAPIGenC:
    267         assert self._current_module is not None
    268         return self._module[self._current_module][0]
    269 
    270     @property
    271     def _genh(self) -> QAPIGenH:
    272         assert self._current_module is not None
    273         return self._module[self._current_module][1]
    274 
    275     @property
    276     def _gen_trace_events(self) -> QAPIGenTrace:
    277         assert self._gen_tracing
    278         assert self._current_module is not None
    279         gent = self._module[self._current_module][2]
    280         assert gent is not None
    281         return gent
    282 
    283     @staticmethod
    284     def _module_dirname(name: str) -> str:
    285         if QAPISchemaModule.is_user_module(name):
    286             return os.path.dirname(name)
    287         return ''
    288 
    289     def _module_basename(self, what: str, name: str) -> str:
    290         ret = '' if QAPISchemaModule.is_builtin_module(name) else self._prefix
    291         if QAPISchemaModule.is_user_module(name):
    292             basename = os.path.basename(name)
    293             ret += what
    294             if name != self._main_module:
    295                 ret += '-' + os.path.splitext(basename)[0]
    296         else:
    297             assert QAPISchemaModule.is_system_module(name)
    298             ret += re.sub(r'-', '-' + name[2:] + '-', what)
    299         return ret
    300 
    301     def _module_filename(self, what: str, name: str) -> str:
    302         return os.path.join(self._module_dirname(name),
    303                             self._module_basename(what, name))
    304 
    305     def _add_module(self, name: str, blurb: str) -> None:
    306         if QAPISchemaModule.is_user_module(name):
    307             if self._main_module is None:
    308                 self._main_module = name
    309         basename = self._module_filename(self._what, name)
    310         genc = QAPIGenC(basename + '.c', blurb, self._pydoc)
    311         genh = QAPIGenH(basename + '.h', blurb, self._pydoc)
    312 
    313         gent: Optional[QAPIGenTrace] = None
    314         if self._gen_tracing:
    315             gent = QAPIGenTrace(basename + '.trace-events')
    316 
    317         self._module[name] = (genc, genh, gent)
    318         self._current_module = name
    319 
    320     @contextmanager
    321     def _temp_module(self, name: str) -> Iterator[None]:
    322         old_module = self._current_module
    323         self._current_module = name
    324         yield
    325         self._current_module = old_module
    326 
    327     def write(self, output_dir: str, opt_builtins: bool = False) -> None:
    328         for name, (genc, genh, gent) in self._module.items():
    329             if QAPISchemaModule.is_builtin_module(name) and not opt_builtins:
    330                 continue
    331             genc.write(output_dir)
    332             genh.write(output_dir)
    333             if gent is not None:
    334                 gent.write(output_dir)
    335 
    336     def _begin_builtin_module(self) -> None:
    337         pass
    338 
    339     def _begin_user_module(self, name: str) -> None:
    340         pass
    341 
    342     def visit_module(self, name: str) -> None:
    343         if QAPISchemaModule.is_builtin_module(name):
    344             if self._builtin_blurb:
    345                 self._add_module(name, self._builtin_blurb)
    346                 self._begin_builtin_module()
    347             else:
    348                 # The built-in module has not been created.  No code may
    349                 # be generated.
    350                 self._current_module = None
    351         else:
    352             assert QAPISchemaModule.is_user_module(name)
    353             self._add_module(name, self._user_blurb)
    354             self._begin_user_module(name)
    355 
    356     def visit_include(self, name: str, info: Optional[QAPISourceInfo]) -> None:
    357         relname = os.path.relpath(self._module_filename(self._what, name),
    358                                   os.path.dirname(self._genh.fname))
    359         self._genh.preamble_add(mcgen('''
    360 #include "%(relname)s.h"
    361 ''',
    362                                       relname=relname))