qapi: enable use of g_autoptr with QAPI types
[qemu.git] / scripts / qapi / gen.py
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
15 import errno
16 import os
17 import re
18 from contextlib import contextmanager
19
20 from qapi.common import *
21 from qapi.schema import QAPISchemaVisitor
22
23
24 class 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
71 def _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
87 class 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
113 class 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 */
141 char qapi_dummy_%(name)s;
142 ''',
143 name=c_fname(self.fname))
144
145
146 class 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
156 def 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
181 class QAPIGenDoc(QAPIGen):
182
183 def _top(self):
184 return (super()._top()
185 + '@c AUTOMATICALLY GENERATED, DO NOT MODIFY\n\n')
186
187
188 class 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
203 class 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))