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 QAPIGenDoc(QAPIGen):
182
183    def _top(self):
184        return (super()._top()
185                + '@c AUTOMATICALLY GENERATED, DO NOT MODIFY\n\n')
186
187
188class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
189
190    def __init__(self, prefix, what, blurb, pydoc):
191        self._prefix = prefix
192        self._what = what
193        self._genc = QAPIGenC(self._prefix + self._what + '.c',
194                              blurb, pydoc)
195        self._genh = QAPIGenH(self._prefix + self._what + '.h',
196                              blurb, pydoc)
197
198    def write(self, output_dir):
199        self._genc.write(output_dir)
200        self._genh.write(output_dir)
201
202
203class QAPISchemaModularCVisitor(QAPISchemaVisitor):
204
205    def __init__(self, prefix, what, user_blurb, builtin_blurb, pydoc):
206        self._prefix = prefix
207        self._what = what
208        self._user_blurb = user_blurb
209        self._builtin_blurb = builtin_blurb
210        self._pydoc = pydoc
211        self._genc = None
212        self._genh = None
213        self._module = {}
214        self._main_module = None
215
216    @staticmethod
217    def _is_user_module(name):
218        return name and not name.startswith('./')
219
220    @staticmethod
221    def _is_builtin_module(name):
222        return not name
223
224    def _module_dirname(self, what, name):
225        if self._is_user_module(name):
226            return os.path.dirname(name)
227        return ''
228
229    def _module_basename(self, what, name):
230        ret = '' if self._is_builtin_module(name) else self._prefix
231        if self._is_user_module(name):
232            basename = os.path.basename(name)
233            ret += what
234            if name != self._main_module:
235                ret += '-' + os.path.splitext(basename)[0]
236        else:
237            name = name[2:] if name else 'builtin'
238            ret += re.sub(r'-', '-' + name + '-', what)
239        return ret
240
241    def _module_filename(self, what, name):
242        return os.path.join(self._module_dirname(what, name),
243                            self._module_basename(what, name))
244
245    def _add_module(self, name, blurb):
246        basename = self._module_filename(self._what, name)
247        genc = QAPIGenC(basename + '.c', blurb, self._pydoc)
248        genh = QAPIGenH(basename + '.h', blurb, self._pydoc)
249        self._module[name] = (genc, genh)
250        self._genc, self._genh = self._module[name]
251
252    def _add_user_module(self, name, blurb):
253        assert self._is_user_module(name)
254        if self._main_module is None:
255            self._main_module = name
256        self._add_module(name, blurb)
257
258    def _add_system_module(self, name, blurb):
259        self._add_module(name and './' + name, blurb)
260
261    def write(self, output_dir, opt_builtins=False):
262        for name in self._module:
263            if self._is_builtin_module(name) and not opt_builtins:
264                continue
265            (genc, genh) = self._module[name]
266            genc.write(output_dir)
267            genh.write(output_dir)
268
269    def _begin_system_module(self, name):
270        pass
271
272    def _begin_user_module(self, name):
273        pass
274
275    def visit_module(self, name):
276        if name is None:
277            if self._builtin_blurb:
278                self._add_system_module(None, self._builtin_blurb)
279                self._begin_system_module(name)
280            else:
281                # The built-in module has not been created.  No code may
282                # be generated.
283                self._genc = None
284                self._genh = None
285        else:
286            self._add_user_module(name, self._user_blurb)
287            self._begin_user_module(name)
288
289    def visit_include(self, name, info):
290        relname = os.path.relpath(self._module_filename(self._what, name),
291                                  os.path.dirname(self._genh.fname))
292        self._genh.preamble_add(mcgen('''
293#include "%(relname)s.h"
294''',
295                                      relname=relname))
296