Merge tag 'v9.0.0-rc3'
[qemu/ar7.git] / scripts / qapi / gen.py
blob5412716617aad5ad4e4685e2aedd5431cb3636f0
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 import sys
18 from typing import (
19 Dict,
20 Iterator,
21 Optional,
22 Sequence,
23 Tuple,
26 from .common import (
27 c_fname,
28 c_name,
29 guardend,
30 guardstart,
31 mcgen,
33 from .schema import (
34 QAPISchemaFeature,
35 QAPISchemaIfCond,
36 QAPISchemaModule,
37 QAPISchemaObjectType,
38 QAPISchemaVisitor,
40 from .source import QAPISourceInfo
43 def 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'
49 class QAPIGen:
50 def __init__(self, fname: str):
51 self.fname = fname
52 self._preamble = ''
53 self._body = ''
55 def preamble_add(self, text: str) -> None:
56 self._preamble += text
58 def add(self, text: str) -> None:
59 self._body += text
61 def get_content(self) -> str:
62 return self._top() + self._preamble + self._body + self._bottom()
64 def _top(self) -> str:
65 # pylint: disable=no-self-use
66 return ''
68 def _bottom(self) -> str:
69 # pylint: disable=no-self-use
70 return ''
72 def write(self, output_dir: str) -> None:
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
78 pathname = os.path.join(output_dir, self.fname)
79 odir = os.path.dirname(pathname)
81 if odir:
82 os.makedirs(odir, exist_ok=True)
84 # use os.open for O_CREAT to create and read a non-existent file
85 fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666)
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)
95 def _wrap_ifcond(ifcond: QAPISchemaIfCond, before: str, after: str) -> str:
96 if before == after:
97 return after # suppress empty #if ... #endif
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:]
105 out += ifcond.gen_if()
106 out += added
107 out += ifcond.gen_endif()
108 return out
111 def 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:
121 assert not arg_type.variants
122 for memb in arg_type.members:
123 assert not memb.ifcond.is_present()
124 ret += sep
125 sep = ', '
126 if memb.need_has():
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'
135 class QAPIGenCCode(QAPIGen):
136 def __init__(self, fname: str):
137 super().__init__(fname)
138 self._start_if: Optional[Tuple[QAPISchemaIfCond, str, str]] = None
140 def start_if(self, ifcond: QAPISchemaIfCond) -> None:
141 assert self._start_if is None
142 self._start_if = (ifcond, self._body, self._preamble)
144 def end_if(self) -> None:
145 assert self._start_if is not None
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)
150 self._start_if = None
152 def get_content(self) -> str:
153 assert self._start_if is None
154 return super().get_content()
157 class QAPIGenC(QAPIGenCCode):
158 def __init__(self, fname: str, blurb: str, pydoc: str):
159 super().__init__(fname)
160 self._blurb = blurb
161 self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc,
162 re.MULTILINE))
164 def _top(self) -> str:
165 return mcgen('''
166 /* AUTOMATICALLY GENERATED by %(tool)s DO NOT MODIFY */
169 %(blurb)s
171 * %(copyright)s
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.
177 ''',
178 tool=os.path.basename(sys.argv[0]),
179 blurb=self._blurb, copyright=self._copyright)
181 def _bottom(self) -> str:
182 return mcgen('''
184 /* Dummy declaration to prevent empty .o file */
185 char qapi_dummy_%(name)s;
186 ''',
187 name=c_fname(self.fname))
190 class QAPIGenH(QAPIGenC):
191 def _top(self) -> str:
192 return super()._top() + guardstart(self.fname)
194 def _bottom(self) -> str:
195 return guardend(self.fname)
198 class QAPIGenTrace(QAPIGen):
199 def _top(self) -> str:
200 return (super()._top()
201 + '# AUTOMATICALLY GENERATED by '
202 + os.path.basename(sys.argv[0])
203 + ', DO NOT MODIFY\n\n')
206 @contextmanager
207 def ifcontext(ifcond: QAPISchemaIfCond, *args: QAPIGenCCode) -> Iterator[None]:
209 A with-statement context manager that wraps with `start_if()` / `end_if()`.
211 :param ifcond: A sequence of conditionals, passed to `start_if()`.
212 :param args: any number of `QAPIGenCCode`.
214 Example::
216 with ifcontext(ifcond, self._genh, self._genc):
217 modify self._genh and self._genc ...
219 Is equivalent to calling::
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()
227 for arg in args:
228 arg.start_if(ifcond)
229 yield
230 for arg in args:
231 arg.end_if()
234 class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
235 def __init__(self,
236 prefix: str,
237 what: str,
238 blurb: str,
239 pydoc: str):
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)
247 def write(self, output_dir: str) -> None:
248 self._genc.write(output_dir)
249 self._genh.write(output_dir)
252 class QAPISchemaModularCVisitor(QAPISchemaVisitor):
253 def __init__(self,
254 prefix: str,
255 what: str,
256 user_blurb: str,
257 builtin_blurb: Optional[str],
258 pydoc: str,
259 gen_tracing: bool = False):
260 self._prefix = prefix
261 self._what = what
262 self._user_blurb = user_blurb
263 self._builtin_blurb = builtin_blurb
264 self._pydoc = pydoc
265 self._current_module: Optional[str] = None
266 self._module: Dict[str, Tuple[QAPIGenC, QAPIGenH,
267 Optional[QAPIGenTrace]]] = {}
268 self._main_module: Optional[str] = None
269 self._gen_tracing = gen_tracing
271 @property
272 def _genc(self) -> QAPIGenC:
273 assert self._current_module is not None
274 return self._module[self._current_module][0]
276 @property
277 def _genh(self) -> QAPIGenH:
278 assert self._current_module is not None
279 return self._module[self._current_module][1]
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
289 @staticmethod
290 def _module_dirname(name: str) -> str:
291 if QAPISchemaModule.is_user_module(name):
292 return os.path.dirname(name)
293 return ''
295 def _module_basename(self, what: str, name: str) -> str:
296 ret = '' if QAPISchemaModule.is_builtin_module(name) else self._prefix
297 if QAPISchemaModule.is_user_module(name):
298 basename = os.path.basename(name)
299 ret += what
300 if name != self._main_module:
301 ret += '-' + os.path.splitext(basename)[0]
302 else:
303 assert QAPISchemaModule.is_system_module(name)
304 ret += re.sub(r'-', '-' + name[2:] + '-', what)
305 return ret
307 def _module_filename(self, what: str, name: str) -> str:
308 return os.path.join(self._module_dirname(name),
309 self._module_basename(what, name))
311 def _add_module(self, name: str, blurb: str) -> None:
312 if QAPISchemaModule.is_user_module(name):
313 if self._main_module is None:
314 self._main_module = name
315 basename = self._module_filename(self._what, name)
316 genc = QAPIGenC(basename + '.c', blurb, self._pydoc)
317 genh = QAPIGenH(basename + '.h', blurb, self._pydoc)
319 gent: Optional[QAPIGenTrace] = None
320 if self._gen_tracing:
321 gent = QAPIGenTrace(basename + '.trace-events')
323 self._module[name] = (genc, genh, gent)
324 self._current_module = name
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
333 def write(self, output_dir: str, opt_builtins: bool = False) -> None:
334 for name, (genc, genh, gent) in self._module.items():
335 if QAPISchemaModule.is_builtin_module(name) and not opt_builtins:
336 continue
337 genc.write(output_dir)
338 genh.write(output_dir)
339 if gent is not None:
340 gent.write(output_dir)
342 def _begin_builtin_module(self) -> None:
343 pass
345 def _begin_user_module(self, name: str) -> None:
346 pass
348 def visit_module(self, name: str) -> None:
349 if QAPISchemaModule.is_builtin_module(name):
350 if self._builtin_blurb:
351 self._add_module(name, self._builtin_blurb)
352 self._begin_builtin_module()
353 else:
354 # The built-in module has not been created. No code may
355 # be generated.
356 self._current_module = None
357 else:
358 assert QAPISchemaModule.is_user_module(name)
359 self._add_module(name, self._user_blurb)
360 self._begin_user_module(name)
362 def visit_include(self, name: str, info: Optional[QAPISourceInfo]) -> None:
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))