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