qapi: Require boxed for conditional command and event arguments
[qemu/armbru.git] / scripts / qapi / gen.py
blob8f8f784f4a20f5a56771c54122d171efb9eb17a8
1 # -*- coding: utf-8 -*-
3 # QAPI code generation
5 # Copyright (c) 2015-2019 Red Hat Inc.
7 # Authors:
8 # Markus Armbruster <armbru@redhat.com>
9 # Marc-André Lureau <marcandre.lureau@redhat.com>
11 # This work is licensed under the terms of the GNU GPL, version 2.
12 # See the COPYING file in the top-level directory.
14 from contextlib import contextmanager
15 import os
16 import re
17 from typing import (
18 Dict,
19 Iterator,
20 Optional,
21 Sequence,
22 Tuple,
25 from .common import (
26 c_fname,
27 c_name,
28 guardend,
29 guardstart,
30 mcgen,
32 from .schema import (
33 QAPISchemaFeature,
34 QAPISchemaIfCond,
35 QAPISchemaModule,
36 QAPISchemaObjectType,
37 QAPISchemaVisitor,
39 from .source import QAPISourceInfo
42 def gen_special_features(features: Sequence[QAPISchemaFeature]) -> str:
43 special_features = [f"1u << QAPI_{feat.name.upper()}"
44 for feat in features if feat.is_special()]
45 return ' | '.join(special_features) or '0'
48 class QAPIGen:
49 def __init__(self, fname: str):
50 self.fname = fname
51 self._preamble = ''
52 self._body = ''
54 def preamble_add(self, text: str) -> None:
55 self._preamble += text
57 def add(self, text: str) -> None:
58 self._body += text
60 def get_content(self) -> str:
61 return self._top() + self._preamble + self._body + self._bottom()
63 def _top(self) -> str:
64 # pylint: disable=no-self-use
65 return ''
67 def _bottom(self) -> str:
68 # pylint: disable=no-self-use
69 return ''
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)
80 if odir:
81 os.makedirs(odir, exist_ok=True)
83 # use os.open for O_CREAT to create and read a non-existant 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)
94 def _wrap_ifcond(ifcond: QAPISchemaIfCond, before: str, after: str) -> str:
95 if before == after:
96 return after # suppress empty #if ... #endif
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
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.variants
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'
134 class QAPIGenCCode(QAPIGen):
135 def __init__(self, fname: str):
136 super().__init__(fname)
137 self._start_if: Optional[Tuple[QAPISchemaIfCond, str, str]] = None
139 def start_if(self, ifcond: QAPISchemaIfCond) -> None:
140 assert self._start_if is None
141 self._start_if = (ifcond, self._body, self._preamble)
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
151 def get_content(self) -> str:
152 assert self._start_if is None
153 return super().get_content()
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))
163 def _top(self) -> str:
164 return mcgen('''
165 /* AUTOMATICALLY GENERATED, DO NOT MODIFY */
168 %(blurb)s
170 * %(copyright)s
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.
176 ''',
177 blurb=self._blurb, copyright=self._copyright)
179 def _bottom(self) -> str:
180 return mcgen('''
182 /* Dummy declaration to prevent empty .o file */
183 char qapi_dummy_%(name)s;
184 ''',
185 name=c_fname(self.fname))
188 class QAPIGenH(QAPIGenC):
189 def _top(self) -> str:
190 return super()._top() + guardstart(self.fname)
192 def _bottom(self) -> str:
193 return guardend(self.fname)
196 class QAPIGenTrace(QAPIGen):
197 def _top(self) -> str:
198 return super()._top() + '# AUTOMATICALLY GENERATED, DO NOT MODIFY\n\n'
201 @contextmanager
202 def ifcontext(ifcond: QAPISchemaIfCond, *args: QAPIGenCCode) -> Iterator[None]:
204 A with-statement context manager that wraps with `start_if()` / `end_if()`.
206 :param ifcond: A sequence of conditionals, passed to `start_if()`.
207 :param args: any number of `QAPIGenCCode`.
209 Example::
211 with ifcontext(ifcond, self._genh, self._genc):
212 modify self._genh and self._genc ...
214 Is equivalent to calling::
216 self._genh.start_if(ifcond)
217 self._genc.start_if(ifcond)
218 modify self._genh and self._genc ...
219 self._genh.end_if()
220 self._genc.end_if()
222 for arg in args:
223 arg.start_if(ifcond)
224 yield
225 for arg in args:
226 arg.end_if()
229 class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
230 def __init__(self,
231 prefix: str,
232 what: str,
233 blurb: str,
234 pydoc: str):
235 self._prefix = prefix
236 self._what = what
237 self._genc = QAPIGenC(self._prefix + self._what + '.c',
238 blurb, pydoc)
239 self._genh = QAPIGenH(self._prefix + self._what + '.h',
240 blurb, pydoc)
242 def write(self, output_dir: str) -> None:
243 self._genc.write(output_dir)
244 self._genh.write(output_dir)
247 class QAPISchemaModularCVisitor(QAPISchemaVisitor):
248 def __init__(self,
249 prefix: str,
250 what: str,
251 user_blurb: str,
252 builtin_blurb: Optional[str],
253 pydoc: str,
254 gen_tracing: bool = False):
255 self._prefix = prefix
256 self._what = what
257 self._user_blurb = user_blurb
258 self._builtin_blurb = builtin_blurb
259 self._pydoc = pydoc
260 self._current_module: Optional[str] = None
261 self._module: Dict[str, Tuple[QAPIGenC, QAPIGenH,
262 Optional[QAPIGenTrace]]] = {}
263 self._main_module: Optional[str] = None
264 self._gen_tracing = gen_tracing
266 @property
267 def _genc(self) -> QAPIGenC:
268 assert self._current_module is not None
269 return self._module[self._current_module][0]
271 @property
272 def _genh(self) -> QAPIGenH:
273 assert self._current_module is not None
274 return self._module[self._current_module][1]
276 @property
277 def _gen_trace_events(self) -> QAPIGenTrace:
278 assert self._gen_tracing
279 assert self._current_module is not None
280 gent = self._module[self._current_module][2]
281 assert gent is not None
282 return gent
284 @staticmethod
285 def _module_dirname(name: str) -> str:
286 if QAPISchemaModule.is_user_module(name):
287 return os.path.dirname(name)
288 return ''
290 def _module_basename(self, what: str, name: str) -> str:
291 ret = '' if QAPISchemaModule.is_builtin_module(name) else self._prefix
292 if QAPISchemaModule.is_user_module(name):
293 basename = os.path.basename(name)
294 ret += what
295 if name != self._main_module:
296 ret += '-' + os.path.splitext(basename)[0]
297 else:
298 assert QAPISchemaModule.is_system_module(name)
299 ret += re.sub(r'-', '-' + name[2:] + '-', what)
300 return ret
302 def _module_filename(self, what: str, name: str) -> str:
303 return os.path.join(self._module_dirname(name),
304 self._module_basename(what, name))
306 def _add_module(self, name: str, blurb: str) -> None:
307 if QAPISchemaModule.is_user_module(name):
308 if self._main_module is None:
309 self._main_module = name
310 basename = self._module_filename(self._what, name)
311 genc = QAPIGenC(basename + '.c', blurb, self._pydoc)
312 genh = QAPIGenH(basename + '.h', blurb, self._pydoc)
314 gent: Optional[QAPIGenTrace] = None
315 if self._gen_tracing:
316 gent = QAPIGenTrace(basename + '.trace-events')
318 self._module[name] = (genc, genh, gent)
319 self._current_module = name
321 @contextmanager
322 def _temp_module(self, name: str) -> Iterator[None]:
323 old_module = self._current_module
324 self._current_module = name
325 yield
326 self._current_module = old_module
328 def write(self, output_dir: str, opt_builtins: bool = False) -> None:
329 for name, (genc, genh, gent) in self._module.items():
330 if QAPISchemaModule.is_builtin_module(name) and not opt_builtins:
331 continue
332 genc.write(output_dir)
333 genh.write(output_dir)
334 if gent is not None:
335 gent.write(output_dir)
337 def _begin_builtin_module(self) -> None:
338 pass
340 def _begin_user_module(self, name: str) -> None:
341 pass
343 def visit_module(self, name: str) -> None:
344 if QAPISchemaModule.is_builtin_module(name):
345 if self._builtin_blurb:
346 self._add_module(name, self._builtin_blurb)
347 self._begin_builtin_module()
348 else:
349 # The built-in module has not been created. No code may
350 # be generated.
351 self._current_module = None
352 else:
353 assert QAPISchemaModule.is_user_module(name)
354 self._add_module(name, self._user_blurb)
355 self._begin_user_module(name)
357 def visit_include(self, name: str, info: Optional[QAPISourceInfo]) -> None:
358 relname = os.path.relpath(self._module_filename(self._what, name),
359 os.path.dirname(self._genh.fname))
360 self._genh.preamble_add(mcgen('''
361 #include "%(relname)s.h"
362 ''',
363 relname=relname))