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