]> git.ipfire.org Git - thirdparty/qemu.git/blob - scripts/qapi/gen.py
Merge remote-tracking branch 'remotes/ericb/tags/pull-nbd-2019-10-22' into staging
[thirdparty/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 import sys
19 from contextlib import contextmanager
20
21 from qapi.common import *
22 from qapi.schema import QAPISchemaVisitor
23
24
25 class 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
70 def _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
86 class 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
112 class 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 */
140 char qapi_dummy_%(name)s;
141 ''',
142 name=c_fname(self.fname))
143
144
145 class 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
155 def 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
180 class QAPIGenDoc(QAPIGen):
181
182 def _top(self):
183 return (QAPIGen._top(self)
184 + '@c AUTOMATICALLY GENERATED, DO NOT MODIFY\n\n')
185
186
187 class 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
202 class 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))