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