master
py 367 lines 11 KB
Raw
1 # QAPI code generation
2 #
3 # Copyright (c) 2015-2019 Red Hat Inc.
4 #
5 # Authors:
6 # Markus Armbruster <armbru@redhat.com>
7 # Marc-André Lureau <marcandre.lureau@redhat.com>
8 #
9 # This work is licensed under the terms of the GNU GPL, version 2.
10 # See the COPYING file in the top-level directory.
11
12 from contextlib import contextmanager
13 import os
14 import re
15 import sys
16 from typing import (
17 Dict,
18 Iterator,
19 Optional,
20 Sequence,
21 Tuple,
22 )
23
24 from .common import (
25 c_enum_const,
26 c_fname,
27 c_name,
28 guardend,
29 guardstart,
30 mcgen,
31 )
32 from .schema import (
33 QAPISchemaFeature,
34 QAPISchemaIfCond,
35 QAPISchemaModule,
36 QAPISchemaObjectType,
37 QAPISchemaVisitor,
38 )
39 from .source import QAPISourceInfo
40
41
42 def gen_features(features: Sequence[QAPISchemaFeature]) -> str:
43 feats = [f"1u << {c_enum_const('qapi_feature', feat.name)}"
44 for feat in features]
45 return ' | '.join(feats) or '0'
46
47
48 class QAPIGen:
49 def __init__(self, fname: str):
50 self.fname = fname
51 self._preamble = ''
52 self._body = ''
53
54 def preamble_add(self, text: str) -> None:
55 self._preamble += text
56
57 def add(self, text: str) -> None:
58 self._body += text
59
60 def get_content(self) -> str:
61 return self._top() + self._preamble + self._body + self._bottom()
62
63 def _top(self) -> str:
64 # pylint: disable=no-self-use
65 return ''
66
67 def _bottom(self) -> str:
68 # pylint: disable=no-self-use
69 return ''
70
71 def write(self, output_dir: str) -> None:
72 # Include paths starting with ../ are used to reuse modules of the main
73 # schema in specialised schemas. Don't overwrite the files that are
74 # already generated for the main schema.
75 if self.fname.startswith('../'):
76 return
77 pathname = os.path.join(output_dir, self.fname)
78 odir = os.path.dirname(pathname)
79
80 if odir:
81 os.makedirs(odir, exist_ok=True)
82
83 # use os.open for O_CREAT to create and read a non-existent file
84 fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666)
85 with os.fdopen(fd, 'r+', encoding='utf-8') as fp:
86 text = self.get_content()
87 oldtext = fp.read(len(text) + 1)
88 if text != oldtext:
89 fp.seek(0)
90 fp.truncate(0)
91 fp.write(text)
92
93
94 def _wrap_ifcond(ifcond: QAPISchemaIfCond, before: str, after: str) -> str:
95 if before == after:
96 return after # suppress empty #if ... #endif
97
98 assert after.startswith(before)
99 out = before
100 added = after[len(before):]
101 if added[0] == '\n':
102 out += '\n'
103 added = added[1:]
104 out += ifcond.gen_if()
105 out += added
106 out += ifcond.gen_endif()
107 return out
108
109
110 def build_params(arg_type: Optional[QAPISchemaObjectType],
111 boxed: bool,
112 extra: Optional[str] = None) -> str:
113 ret = ''
114 sep = ''
115 if boxed:
116 assert arg_type
117 ret += '%s arg' % arg_type.c_param_type()
118 sep = ', '
119 elif arg_type:
120 assert not arg_type.branches
121 for memb in arg_type.members:
122 assert not memb.ifcond.is_present()
123 ret += sep
124 sep = ', '
125 if memb.need_has():
126 ret += 'bool has_%s, ' % c_name(memb.name)
127 ret += '%s %s' % (memb.type.c_param_type(),
128 c_name(memb.name))
129 if extra:
130 ret += sep + extra
131 return ret if ret else 'void'
132
133
134 class QAPIGenCCode(QAPIGen):
135 def __init__(self, fname: str):
136 super().__init__(fname)
137 self._start_if: Optional[Tuple[QAPISchemaIfCond, str, str]] = None
138
139 def start_if(self, ifcond: QAPISchemaIfCond) -> None:
140 assert self._start_if is None
141 self._start_if = (ifcond, self._body, self._preamble)
142
143 def end_if(self) -> None:
144 assert self._start_if is not None
145 self._body = _wrap_ifcond(self._start_if[0],
146 self._start_if[1], self._body)
147 self._preamble = _wrap_ifcond(self._start_if[0],
148 self._start_if[2], self._preamble)
149 self._start_if = None
150
151 def get_content(self) -> str:
152 assert self._start_if is None
153 return super().get_content()
154
155
156 class QAPIGenC(QAPIGenCCode):
157 def __init__(self, fname: str, blurb: str, pydoc: str):
158 super().__init__(fname)
159 self._blurb = blurb
160 self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc,
161 re.MULTILINE))
162
163 def _top(self) -> str:
164 return mcgen('''
165 /* AUTOMATICALLY GENERATED by %(tool)s DO NOT MODIFY */
166
167 /*
168 %(blurb)s
169 *
170 * %(copyright)s
171 *
172 * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
173 * See the COPYING.LIB file in the top-level directory.
174 */
175
176 ''',
177 tool=os.path.basename(sys.argv[0]),
178 blurb=self._blurb, copyright=self._copyright)
179
180 def _bottom(self) -> str:
181 return mcgen('''
182
183 /* Dummy declaration to prevent empty .o file */
184 char qapi_dummy_%(name)s;
185 ''',
186 name=c_fname(self.fname))
187
188
189 class QAPIGenH(QAPIGenC):
190 def _top(self) -> str:
191 return super()._top() + guardstart(self.fname)
192
193 def _bottom(self) -> str:
194 return guardend(self.fname)
195
196
197 class QAPIGenTrace(QAPIGen):
198 def _top(self) -> str:
199 return (super()._top()
200 + '# AUTOMATICALLY GENERATED by '
201 + os.path.basename(sys.argv[0])
202 + ', DO NOT MODIFY\n\n')
203
204
205 @contextmanager
206 def ifcontext(ifcond: QAPISchemaIfCond, *args: QAPIGenCCode) -> Iterator[None]:
207 """
208 A with-statement context manager that wraps with `start_if()` / `end_if()`.
209
210 :param ifcond: A sequence of conditionals, passed to `start_if()`.
211 :param args: any number of `QAPIGenCCode`.
212
213 Example::
214
215 with ifcontext(ifcond, self._genh, self._genc):
216 modify self._genh and self._genc ...
217
218 Is equivalent to calling::
219
220 self._genh.start_if(ifcond)
221 self._genc.start_if(ifcond)
222 modify self._genh and self._genc ...
223 self._genh.end_if()
224 self._genc.end_if()
225 """
226 for arg in args:
227 arg.start_if(ifcond)
228 yield
229 for arg in args:
230 arg.end_if()
231
232
233 class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
234 def __init__(self,
235 prefix: str,
236 what: str,
237 blurb: str,
238 pydoc: str):
239 self._prefix = prefix
240 self._what = what
241 self._genc = QAPIGenC(self._prefix + self._what + '.c',
242 blurb, pydoc)
243 self._genh = QAPIGenH(self._prefix + self._what + '.h',
244 blurb, pydoc)
245
246 def write(self, output_dir: str) -> None:
247 self._genc.write(output_dir)
248 self._genh.write(output_dir)
249
250
251 class QAPISchemaModularCVisitor(QAPISchemaVisitor):
252 def __init__(self,
253 prefix: str,
254 what: str,
255 user_blurb: str,
256 builtin_blurb: Optional[str],
257 pydoc: str,
258 gen_tracing: bool = False):
259 self._prefix = prefix
260 self._what = what
261 self._user_blurb = user_blurb
262 self._builtin_blurb = builtin_blurb
263 self._pydoc = pydoc
264 self._current_module: Optional[str] = None
265 self._module: Dict[str, Tuple[QAPIGenC, QAPIGenH,
266 Optional[QAPIGenTrace]]] = {}
267 self._main_module: Optional[str] = None
268 self._gen_tracing = gen_tracing
269
270 @property
271 def _genc(self) -> QAPIGenC:
272 assert self._current_module is not None
273 return self._module[self._current_module][0]
274
275 @property
276 def _genh(self) -> QAPIGenH:
277 assert self._current_module is not None
278 return self._module[self._current_module][1]
279
280 @property
281 def _gen_trace_events(self) -> QAPIGenTrace:
282 assert self._gen_tracing
283 assert self._current_module is not None
284 gent = self._module[self._current_module][2]
285 assert gent is not None
286 return gent
287
288 @staticmethod
289 def _module_dirname(name: str) -> str:
290 if QAPISchemaModule.is_user_module(name):
291 return os.path.dirname(name)
292 return ''
293
294 def _module_basename(self, what: str, name: str) -> str:
295 ret = '' if QAPISchemaModule.is_builtin_module(name) else self._prefix
296 if QAPISchemaModule.is_user_module(name):
297 basename = os.path.basename(name)
298 ret += what
299 if name != self._main_module:
300 ret += '-' + os.path.splitext(basename)[0]
301 else:
302 assert QAPISchemaModule.is_system_module(name)
303 ret += re.sub(r'-', '-' + name[2:] + '-', what)
304 return ret
305
306 def _module_filename(self, what: str, name: str) -> str:
307 return os.path.join(self._module_dirname(name),
308 self._module_basename(what, name))
309
310 def _add_module(self, name: str, blurb: str) -> None:
311 if QAPISchemaModule.is_user_module(name):
312 if self._main_module is None:
313 self._main_module = name
314 basename = self._module_filename(self._what, name)
315 genc = QAPIGenC(basename + '.c', blurb, self._pydoc)
316 genh = QAPIGenH(basename + '.h', blurb, self._pydoc)
317
318 gent: Optional[QAPIGenTrace] = None
319 if self._gen_tracing:
320 gent = QAPIGenTrace(basename + '.trace-events')
321
322 self._module[name] = (genc, genh, gent)
323 self._current_module = name
324
325 @contextmanager
326 def _temp_module(self, name: str) -> Iterator[None]:
327 old_module = self._current_module
328 self._current_module = name
329 yield
330 self._current_module = old_module
331
332 def write(self, output_dir: str, opt_builtins: bool = False) -> None:
333 for name, (genc, genh, gent) in self._module.items():
334 if QAPISchemaModule.is_builtin_module(name) and not opt_builtins:
335 continue
336 genc.write(output_dir)
337 genh.write(output_dir)
338 if gent is not None:
339 gent.write(output_dir)
340
341 def _begin_builtin_module(self) -> None:
342 pass
343
344 def _begin_user_module(self, name: str) -> None:
345 pass
346
347 def visit_module(self, name: str) -> None:
348 if QAPISchemaModule.is_builtin_module(name):
349 if self._builtin_blurb:
350 self._add_module(name, self._builtin_blurb)
351 self._begin_builtin_module()
352 else:
353 # The built-in module has not been created. No code may
354 # be generated.
355 self._current_module = None
356 else:
357 assert QAPISchemaModule.is_user_module(name)
358 self._add_module(name, self._user_blurb)
359 self._begin_user_module(name)
360
361 def visit_include(self, name: str, info: Optional[QAPISourceInfo]) -> None:
362 relname = os.path.relpath(self._module_filename(self._what, name),
363 os.path.dirname(self._genh.fname))
364 self._genh.preamble_add(mcgen('''
365 #include "%(relname)s.h"
366 ''',
367 relname=relname))