qapi: type 'info' as Optional[QAPISourceInfo]
[qemu/ar7.git] / scripts / qapi / gen.py
blob63549cc8d47eb940c1763a881fbba6ba5bbd7dab
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 List,
21 Optional,
22 Tuple,
25 from .common import (
26 c_fname,
27 c_name,
28 gen_endif,
29 gen_if,
30 guardend,
31 guardstart,
32 mcgen,
34 from .schema import (
35 QAPISchemaModule,
36 QAPISchemaObjectType,
37 QAPISchemaVisitor,
39 from .source import QAPISourceInfo
42 class QAPIGen:
43 def __init__(self, fname: str):
44 self.fname = fname
45 self._preamble = ''
46 self._body = ''
48 def preamble_add(self, text: str) -> None:
49 self._preamble += text
51 def add(self, text: str) -> None:
52 self._body += text
54 def get_content(self) -> str:
55 return self._top() + self._preamble + self._body + self._bottom()
57 def _top(self) -> str:
58 # pylint: disable=no-self-use
59 return ''
61 def _bottom(self) -> str:
62 # pylint: disable=no-self-use
63 return ''
65 def write(self, output_dir: str) -> None:
66 # Include paths starting with ../ are used to reuse modules of the main
67 # schema in specialised schemas. Don't overwrite the files that are
68 # already generated for the main schema.
69 if self.fname.startswith('../'):
70 return
71 pathname = os.path.join(output_dir, self.fname)
72 odir = os.path.dirname(pathname)
74 if odir:
75 os.makedirs(odir, exist_ok=True)
77 # use os.open for O_CREAT to create and read a non-existant file
78 fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666)
79 with os.fdopen(fd, 'r+', encoding='utf-8') as fp:
80 text = self.get_content()
81 oldtext = fp.read(len(text) + 1)
82 if text != oldtext:
83 fp.seek(0)
84 fp.truncate(0)
85 fp.write(text)
88 def _wrap_ifcond(ifcond: List[str], before: str, after: str) -> str:
89 if before == after:
90 return after # suppress empty #if ... #endif
92 assert after.startswith(before)
93 out = before
94 added = after[len(before):]
95 if added[0] == '\n':
96 out += '\n'
97 added = added[1:]
98 out += gen_if(ifcond)
99 out += added
100 out += gen_endif(ifcond)
101 return out
104 def build_params(arg_type: Optional[QAPISchemaObjectType],
105 boxed: bool,
106 extra: Optional[str] = None) -> str:
107 ret = ''
108 sep = ''
109 if boxed:
110 assert arg_type
111 ret += '%s arg' % arg_type.c_param_type()
112 sep = ', '
113 elif arg_type:
114 assert not arg_type.variants
115 for memb in arg_type.members:
116 ret += sep
117 sep = ', '
118 if memb.optional:
119 ret += 'bool has_%s, ' % c_name(memb.name)
120 ret += '%s %s' % (memb.type.c_param_type(),
121 c_name(memb.name))
122 if extra:
123 ret += sep + extra
124 return ret if ret else 'void'
127 class QAPIGenCCode(QAPIGen):
128 def __init__(self, fname: str):
129 super().__init__(fname)
130 self._start_if: Optional[Tuple[List[str], str, str]] = None
132 def start_if(self, ifcond: List[str]) -> None:
133 assert self._start_if is None
134 self._start_if = (ifcond, self._body, self._preamble)
136 def end_if(self) -> None:
137 assert self._start_if is not None
138 self._body = _wrap_ifcond(self._start_if[0],
139 self._start_if[1], self._body)
140 self._preamble = _wrap_ifcond(self._start_if[0],
141 self._start_if[2], self._preamble)
142 self._start_if = None
144 def get_content(self) -> str:
145 assert self._start_if is None
146 return super().get_content()
149 class QAPIGenC(QAPIGenCCode):
150 def __init__(self, fname: str, blurb: str, pydoc: str):
151 super().__init__(fname)
152 self._blurb = blurb
153 self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc,
154 re.MULTILINE))
156 def _top(self) -> str:
157 return mcgen('''
158 /* AUTOMATICALLY GENERATED, DO NOT MODIFY */
161 %(blurb)s
163 * %(copyright)s
165 * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
166 * See the COPYING.LIB file in the top-level directory.
169 ''',
170 blurb=self._blurb, copyright=self._copyright)
172 def _bottom(self) -> str:
173 return mcgen('''
175 /* Dummy declaration to prevent empty .o file */
176 char qapi_dummy_%(name)s;
177 ''',
178 name=c_fname(self.fname))
181 class QAPIGenH(QAPIGenC):
182 def _top(self) -> str:
183 return super()._top() + guardstart(self.fname)
185 def _bottom(self) -> str:
186 return guardend(self.fname)
189 @contextmanager
190 def ifcontext(ifcond: List[str], *args: QAPIGenCCode) -> Iterator[None]:
192 A with-statement context manager that wraps with `start_if()` / `end_if()`.
194 :param ifcond: A list of conditionals, passed to `start_if()`.
195 :param args: any number of `QAPIGenCCode`.
197 Example::
199 with ifcontext(ifcond, self._genh, self._genc):
200 modify self._genh and self._genc ...
202 Is equivalent to calling::
204 self._genh.start_if(ifcond)
205 self._genc.start_if(ifcond)
206 modify self._genh and self._genc ...
207 self._genh.end_if()
208 self._genc.end_if()
210 for arg in args:
211 arg.start_if(ifcond)
212 yield
213 for arg in args:
214 arg.end_if()
217 class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
218 def __init__(self,
219 prefix: str,
220 what: str,
221 blurb: str,
222 pydoc: str):
223 self._prefix = prefix
224 self._what = what
225 self._genc = QAPIGenC(self._prefix + self._what + '.c',
226 blurb, pydoc)
227 self._genh = QAPIGenH(self._prefix + self._what + '.h',
228 blurb, pydoc)
230 def write(self, output_dir: str) -> None:
231 self._genc.write(output_dir)
232 self._genh.write(output_dir)
235 class QAPISchemaModularCVisitor(QAPISchemaVisitor):
236 def __init__(self,
237 prefix: str,
238 what: str,
239 user_blurb: str,
240 builtin_blurb: Optional[str],
241 pydoc: str):
242 self._prefix = prefix
243 self._what = what
244 self._user_blurb = user_blurb
245 self._builtin_blurb = builtin_blurb
246 self._pydoc = pydoc
247 self._current_module: Optional[str] = None
248 self._module: Dict[str, Tuple[QAPIGenC, QAPIGenH]] = {}
249 self._main_module: Optional[str] = None
251 @property
252 def _genc(self) -> QAPIGenC:
253 assert self._current_module is not None
254 return self._module[self._current_module][0]
256 @property
257 def _genh(self) -> QAPIGenH:
258 assert self._current_module is not None
259 return self._module[self._current_module][1]
261 @staticmethod
262 def _module_dirname(name: str) -> str:
263 if QAPISchemaModule.is_user_module(name):
264 return os.path.dirname(name)
265 return ''
267 def _module_basename(self, what: str, name: str) -> str:
268 ret = '' if QAPISchemaModule.is_builtin_module(name) else self._prefix
269 if QAPISchemaModule.is_user_module(name):
270 basename = os.path.basename(name)
271 ret += what
272 if name != self._main_module:
273 ret += '-' + os.path.splitext(basename)[0]
274 else:
275 assert QAPISchemaModule.is_system_module(name)
276 ret += re.sub(r'-', '-' + name[2:] + '-', what)
277 return ret
279 def _module_filename(self, what: str, name: str) -> str:
280 return os.path.join(self._module_dirname(name),
281 self._module_basename(what, name))
283 def _add_module(self, name: str, blurb: str) -> None:
284 if QAPISchemaModule.is_user_module(name):
285 if self._main_module is None:
286 self._main_module = name
287 basename = self._module_filename(self._what, name)
288 genc = QAPIGenC(basename + '.c', blurb, self._pydoc)
289 genh = QAPIGenH(basename + '.h', blurb, self._pydoc)
290 self._module[name] = (genc, genh)
291 self._current_module = name
293 @contextmanager
294 def _temp_module(self, name: str) -> Iterator[None]:
295 old_module = self._current_module
296 self._current_module = name
297 yield
298 self._current_module = old_module
300 def write(self, output_dir: str, opt_builtins: bool = False) -> None:
301 for name in self._module:
302 if QAPISchemaModule.is_builtin_module(name) and not opt_builtins:
303 continue
304 (genc, genh) = self._module[name]
305 genc.write(output_dir)
306 genh.write(output_dir)
308 def _begin_builtin_module(self) -> None:
309 pass
311 def _begin_user_module(self, name: str) -> None:
312 pass
314 def visit_module(self, name: str) -> None:
315 if QAPISchemaModule.is_builtin_module(name):
316 if self._builtin_blurb:
317 self._add_module(name, self._builtin_blurb)
318 self._begin_builtin_module()
319 else:
320 # The built-in module has not been created. No code may
321 # be generated.
322 self._current_module = None
323 else:
324 assert QAPISchemaModule.is_user_module(name)
325 self._add_module(name, self._user_blurb)
326 self._begin_user_module(name)
328 def visit_include(self, name: str, info: Optional[QAPISourceInfo]) -> None:
329 relname = os.path.relpath(self._module_filename(self._what, name),
330 os.path.dirname(self._genh.fname))
331 self._genh.preamble_add(mcgen('''
332 #include "%(relname)s.h"
333 ''',
334 relname=relname))