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