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