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