xref: /qemu/scripts/qapi/gen.py (revision 5daa6bfd)
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    """A 'with' statement context manager to wrap with start_if()/end_if()
158
159    *args: any number of QAPIGenCCode
160
161    Example::
162
163        with ifcontext(ifcond, self._genh, self._genc):
164            modify self._genh and self._genc ...
165
166    Is equivalent to calling::
167
168        self._genh.start_if(ifcond)
169        self._genc.start_if(ifcond)
170        modify self._genh and self._genc ...
171        self._genh.end_if()
172        self._genc.end_if()
173    """
174    for arg in args:
175        arg.start_if(ifcond)
176    yield
177    for arg in args:
178        arg.end_if()
179
180
181class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
182
183    def __init__(self, prefix, what, blurb, pydoc):
184        self._prefix = prefix
185        self._what = what
186        self._genc = QAPIGenC(self._prefix + self._what + '.c',
187                              blurb, pydoc)
188        self._genh = QAPIGenH(self._prefix + self._what + '.h',
189                              blurb, pydoc)
190
191    def write(self, output_dir):
192        self._genc.write(output_dir)
193        self._genh.write(output_dir)
194
195
196class QAPISchemaModularCVisitor(QAPISchemaVisitor):
197
198    def __init__(self, prefix, what, user_blurb, builtin_blurb, pydoc):
199        self._prefix = prefix
200        self._what = what
201        self._user_blurb = user_blurb
202        self._builtin_blurb = builtin_blurb
203        self._pydoc = pydoc
204        self._genc = None
205        self._genh = None
206        self._module = {}
207        self._main_module = None
208
209    @staticmethod
210    def _is_user_module(name):
211        return name and not name.startswith('./')
212
213    @staticmethod
214    def _is_builtin_module(name):
215        return not name
216
217    def _module_dirname(self, what, name):
218        if self._is_user_module(name):
219            return os.path.dirname(name)
220        return ''
221
222    def _module_basename(self, what, name):
223        ret = '' if self._is_builtin_module(name) else self._prefix
224        if self._is_user_module(name):
225            basename = os.path.basename(name)
226            ret += what
227            if name != self._main_module:
228                ret += '-' + os.path.splitext(basename)[0]
229        else:
230            name = name[2:] if name else 'builtin'
231            ret += re.sub(r'-', '-' + name + '-', what)
232        return ret
233
234    def _module_filename(self, what, name):
235        return os.path.join(self._module_dirname(what, name),
236                            self._module_basename(what, name))
237
238    def _add_module(self, name, blurb):
239        basename = self._module_filename(self._what, name)
240        genc = QAPIGenC(basename + '.c', blurb, self._pydoc)
241        genh = QAPIGenH(basename + '.h', blurb, self._pydoc)
242        self._module[name] = (genc, genh)
243        self._genc, self._genh = self._module[name]
244
245    def _add_user_module(self, name, blurb):
246        assert self._is_user_module(name)
247        if self._main_module is None:
248            self._main_module = name
249        self._add_module(name, blurb)
250
251    def _add_system_module(self, name, blurb):
252        self._add_module(name and './' + name, blurb)
253
254    def write(self, output_dir, opt_builtins=False):
255        for name in self._module:
256            if self._is_builtin_module(name) and not opt_builtins:
257                continue
258            (genc, genh) = self._module[name]
259            genc.write(output_dir)
260            genh.write(output_dir)
261
262    def _begin_system_module(self, name):
263        pass
264
265    def _begin_user_module(self, name):
266        pass
267
268    def visit_module(self, name):
269        if name is None:
270            if self._builtin_blurb:
271                self._add_system_module(None, self._builtin_blurb)
272                self._begin_system_module(name)
273            else:
274                # The built-in module has not been created.  No code may
275                # be generated.
276                self._genc = None
277                self._genh = None
278        else:
279            self._add_user_module(name, self._user_blurb)
280            self._begin_user_module(name)
281
282    def visit_include(self, name, info):
283        relname = os.path.relpath(self._module_filename(self._what, name),
284                                  os.path.dirname(self._genh.fname))
285        self._genh.preamble_add(mcgen('''
286#include "%(relname)s.h"
287''',
288                                      relname=relname))
289