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