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