icount: Take iothread lock when running QEMU timers
[qemu/ar7.git] / scripts / qapi / gen.py
blob113b49134de470930c8bb43daca3a15d9ac8a0eb
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 ret += sep
123 sep = ', '
124 if memb.optional:
125 ret += 'bool has_%s, ' % c_name(memb.name)
126 ret += '%s %s' % (memb.type.c_param_type(),
127 c_name(memb.name))
128 if extra:
129 ret += sep + extra
130 return ret if ret else 'void'
133 class QAPIGenCCode(QAPIGen):
134 def __init__(self, fname: str):
135 super().__init__(fname)
136 self._start_if: Optional[Tuple[QAPISchemaIfCond, str, str]] = None
138 def start_if(self, ifcond: QAPISchemaIfCond) -> None:
139 assert self._start_if is None
140 self._start_if = (ifcond, self._body, self._preamble)
142 def end_if(self) -> None:
143 assert self._start_if is not None
144 self._body = _wrap_ifcond(self._start_if[0],
145 self._start_if[1], self._body)
146 self._preamble = _wrap_ifcond(self._start_if[0],
147 self._start_if[2], self._preamble)
148 self._start_if = None
150 def get_content(self) -> str:
151 assert self._start_if is None
152 return super().get_content()
155 class QAPIGenC(QAPIGenCCode):
156 def __init__(self, fname: str, blurb: str, pydoc: str):
157 super().__init__(fname)
158 self._blurb = blurb
159 self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc,
160 re.MULTILINE))
162 def _top(self) -> str:
163 return mcgen('''
164 /* AUTOMATICALLY GENERATED, DO NOT MODIFY */
167 %(blurb)s
169 * %(copyright)s
171 * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
172 * See the COPYING.LIB file in the top-level directory.
175 ''',
176 blurb=self._blurb, copyright=self._copyright)
178 def _bottom(self) -> str:
179 return mcgen('''
181 /* Dummy declaration to prevent empty .o file */
182 char qapi_dummy_%(name)s;
183 ''',
184 name=c_fname(self.fname))
187 class QAPIGenH(QAPIGenC):
188 def _top(self) -> str:
189 return super()._top() + guardstart(self.fname)
191 def _bottom(self) -> str:
192 return guardend(self.fname)
195 class QAPIGenTrace(QAPIGen):
196 def _top(self) -> str:
197 return super()._top() + '# AUTOMATICALLY GENERATED, DO NOT MODIFY\n\n'
200 @contextmanager
201 def ifcontext(ifcond: QAPISchemaIfCond, *args: QAPIGenCCode) -> Iterator[None]:
203 A with-statement context manager that wraps with `start_if()` / `end_if()`.
205 :param ifcond: A sequence of conditionals, passed to `start_if()`.
206 :param args: any number of `QAPIGenCCode`.
208 Example::
210 with ifcontext(ifcond, self._genh, self._genc):
211 modify self._genh and self._genc ...
213 Is equivalent to calling::
215 self._genh.start_if(ifcond)
216 self._genc.start_if(ifcond)
217 modify self._genh and self._genc ...
218 self._genh.end_if()
219 self._genc.end_if()
221 for arg in args:
222 arg.start_if(ifcond)
223 yield
224 for arg in args:
225 arg.end_if()
228 class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
229 def __init__(self,
230 prefix: str,
231 what: str,
232 blurb: str,
233 pydoc: str):
234 self._prefix = prefix
235 self._what = what
236 self._genc = QAPIGenC(self._prefix + self._what + '.c',
237 blurb, pydoc)
238 self._genh = QAPIGenH(self._prefix + self._what + '.h',
239 blurb, pydoc)
241 def write(self, output_dir: str) -> None:
242 self._genc.write(output_dir)
243 self._genh.write(output_dir)
246 class QAPISchemaModularCVisitor(QAPISchemaVisitor):
247 def __init__(self,
248 prefix: str,
249 what: str,
250 user_blurb: str,
251 builtin_blurb: Optional[str],
252 pydoc: str,
253 gen_tracing: bool = False):
254 self._prefix = prefix
255 self._what = what
256 self._user_blurb = user_blurb
257 self._builtin_blurb = builtin_blurb
258 self._pydoc = pydoc
259 self._current_module: Optional[str] = None
260 self._module: Dict[str, Tuple[QAPIGenC, QAPIGenH,
261 Optional[QAPIGenTrace]]] = {}
262 self._main_module: Optional[str] = None
263 self._gen_tracing = gen_tracing
265 @property
266 def _genc(self) -> QAPIGenC:
267 assert self._current_module is not None
268 return self._module[self._current_module][0]
270 @property
271 def _genh(self) -> QAPIGenH:
272 assert self._current_module is not None
273 return self._module[self._current_module][1]
275 @property
276 def _gen_trace_events(self) -> QAPIGenTrace:
277 assert self._gen_tracing
278 assert self._current_module is not None
279 gent = self._module[self._current_module][2]
280 assert gent is not None
281 return gent
283 @staticmethod
284 def _module_dirname(name: str) -> str:
285 if QAPISchemaModule.is_user_module(name):
286 return os.path.dirname(name)
287 return ''
289 def _module_basename(self, what: str, name: str) -> str:
290 ret = '' if QAPISchemaModule.is_builtin_module(name) else self._prefix
291 if QAPISchemaModule.is_user_module(name):
292 basename = os.path.basename(name)
293 ret += what
294 if name != self._main_module:
295 ret += '-' + os.path.splitext(basename)[0]
296 else:
297 assert QAPISchemaModule.is_system_module(name)
298 ret += re.sub(r'-', '-' + name[2:] + '-', what)
299 return ret
301 def _module_filename(self, what: str, name: str) -> str:
302 return os.path.join(self._module_dirname(name),
303 self._module_basename(what, name))
305 def _add_module(self, name: str, blurb: str) -> None:
306 if QAPISchemaModule.is_user_module(name):
307 if self._main_module is None:
308 self._main_module = name
309 basename = self._module_filename(self._what, name)
310 genc = QAPIGenC(basename + '.c', blurb, self._pydoc)
311 genh = QAPIGenH(basename + '.h', blurb, self._pydoc)
313 gent: Optional[QAPIGenTrace] = None
314 if self._gen_tracing:
315 gent = QAPIGenTrace(basename + '.trace-events')
317 self._module[name] = (genc, genh, gent)
318 self._current_module = name
320 @contextmanager
321 def _temp_module(self, name: str) -> Iterator[None]:
322 old_module = self._current_module
323 self._current_module = name
324 yield
325 self._current_module = old_module
327 def write(self, output_dir: str, opt_builtins: bool = False) -> None:
328 for name, (genc, genh, gent) in self._module.items():
329 if QAPISchemaModule.is_builtin_module(name) and not opt_builtins:
330 continue
331 genc.write(output_dir)
332 genh.write(output_dir)
333 if gent is not None:
334 gent.write(output_dir)
336 def _begin_builtin_module(self) -> None:
337 pass
339 def _begin_user_module(self, name: str) -> None:
340 pass
342 def visit_module(self, name: str) -> None:
343 if QAPISchemaModule.is_builtin_module(name):
344 if self._builtin_blurb:
345 self._add_module(name, self._builtin_blurb)
346 self._begin_builtin_module()
347 else:
348 # The built-in module has not been created. No code may
349 # be generated.
350 self._current_module = None
351 else:
352 assert QAPISchemaModule.is_user_module(name)
353 self._add_module(name, self._user_blurb)
354 self._begin_user_module(name)
356 def visit_include(self, name: str, info: Optional[QAPISourceInfo]) -> None:
357 relname = os.path.relpath(self._module_filename(self._what, name),
358 os.path.dirname(self._genh.fname))
359 self._genh.preamble_add(mcgen('''
360 #include "%(relname)s.h"
361 ''',
362 relname=relname))