xref: /openbmc/qemu/scripts/qapi/gen.py (revision adcb9b36c9b0a63e3b0cf81994430cfb0d720571)
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        # Include paths starting with ../ are used to reuse modules of the main
48        # schema in specialised schemas. Don't overwrite the files that are
49        # already generated for the main schema.
50        if self.fname.startswith('../'):
51            return
52        pathname = os.path.join(output_dir, self.fname)
53        odir = os.path.dirname(pathname)
54        if odir:
55            try:
56                os.makedirs(odir)
57            except os.error as e:
58                if e.errno != errno.EEXIST:
59                    raise
60        fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666)
61        f = open(fd, 'r+', encoding='utf-8')
62        text = self.get_content()
63        oldtext = f.read(len(text) + 1)
64        if text != oldtext:
65            f.seek(0)
66            f.truncate(0)
67            f.write(text)
68        f.close()
69
70
71def _wrap_ifcond(ifcond, before, after):
72    if before == after:
73        return after   # suppress empty #if ... #endif
74
75    assert after.startswith(before)
76    out = before
77    added = after[len(before):]
78    if added[0] == '\n':
79        out += '\n'
80        added = added[1:]
81    out += gen_if(ifcond)
82    out += added
83    out += gen_endif(ifcond)
84    return out
85
86
87class QAPIGenCCode(QAPIGen):
88
89    def __init__(self, fname):
90        super().__init__(fname)
91        self._start_if = None
92
93    def start_if(self, ifcond):
94        assert self._start_if is None
95        self._start_if = (ifcond, self._body, self._preamble)
96
97    def end_if(self):
98        assert self._start_if
99        self._wrap_ifcond()
100        self._start_if = None
101
102    def _wrap_ifcond(self):
103        self._body = _wrap_ifcond(self._start_if[0],
104                                  self._start_if[1], self._body)
105        self._preamble = _wrap_ifcond(self._start_if[0],
106                                      self._start_if[2], self._preamble)
107
108    def get_content(self):
109        assert self._start_if is None
110        return super().get_content()
111
112
113class QAPIGenC(QAPIGenCCode):
114
115    def __init__(self, fname, blurb, pydoc):
116        super().__init__(fname)
117        self._blurb = blurb
118        self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc,
119                                                  re.MULTILINE))
120
121    def _top(self):
122        return mcgen('''
123/* AUTOMATICALLY GENERATED, DO NOT MODIFY */
124
125/*
126%(blurb)s
127 *
128 * %(copyright)s
129 *
130 * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
131 * See the COPYING.LIB file in the top-level directory.
132 */
133
134''',
135                     blurb=self._blurb, copyright=self._copyright)
136
137    def _bottom(self):
138        return mcgen('''
139
140/* Dummy declaration to prevent empty .o file */
141char qapi_dummy_%(name)s;
142''',
143                     name=c_fname(self.fname))
144
145
146class QAPIGenH(QAPIGenC):
147
148    def _top(self):
149        return super()._top() + guardstart(self.fname)
150
151    def _bottom(self):
152        return guardend(self.fname)
153
154
155@contextmanager
156def ifcontext(ifcond, *args):
157    """
158    A with-statement context manager that wraps with `start_if()` / `end_if()`.
159
160    :param ifcond: A list of conditionals, passed to `start_if()`.
161    :param args: any number of `QAPIGenCCode`.
162
163    Example::
164
165        with ifcontext(ifcond, self._genh, self._genc):
166            modify self._genh and self._genc ...
167
168    Is equivalent to calling::
169
170        self._genh.start_if(ifcond)
171        self._genc.start_if(ifcond)
172        modify self._genh and self._genc ...
173        self._genh.end_if()
174        self._genc.end_if()
175    """
176    for arg in args:
177        arg.start_if(ifcond)
178    yield
179    for arg in args:
180        arg.end_if()
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