pcie: factor out pcie_cap_slot_unplug()
[qemu/kevin.git] / scripts / qapi / gen.py
blob995a97d2b82840f190d4878edc96886cb5c00c5f
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 @contextmanager
196 def ifcontext(ifcond: QAPISchemaIfCond, *args: QAPIGenCCode) -> Iterator[None]:
198 A with-statement context manager that wraps with `start_if()` / `end_if()`.
200 :param ifcond: A sequence of conditionals, passed to `start_if()`.
201 :param args: any number of `QAPIGenCCode`.
203 Example::
205 with ifcontext(ifcond, self._genh, self._genc):
206 modify self._genh and self._genc ...
208 Is equivalent to calling::
210 self._genh.start_if(ifcond)
211 self._genc.start_if(ifcond)
212 modify self._genh and self._genc ...
213 self._genh.end_if()
214 self._genc.end_if()
216 for arg in args:
217 arg.start_if(ifcond)
218 yield
219 for arg in args:
220 arg.end_if()
223 class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
224 def __init__(self,
225 prefix: str,
226 what: str,
227 blurb: str,
228 pydoc: str):
229 self._prefix = prefix
230 self._what = what
231 self._genc = QAPIGenC(self._prefix + self._what + '.c',
232 blurb, pydoc)
233 self._genh = QAPIGenH(self._prefix + self._what + '.h',
234 blurb, pydoc)
236 def write(self, output_dir: str) -> None:
237 self._genc.write(output_dir)
238 self._genh.write(output_dir)
241 class QAPISchemaModularCVisitor(QAPISchemaVisitor):
242 def __init__(self,
243 prefix: str,
244 what: str,
245 user_blurb: str,
246 builtin_blurb: Optional[str],
247 pydoc: str):
248 self._prefix = prefix
249 self._what = what
250 self._user_blurb = user_blurb
251 self._builtin_blurb = builtin_blurb
252 self._pydoc = pydoc
253 self._current_module: Optional[str] = None
254 self._module: Dict[str, Tuple[QAPIGenC, QAPIGenH]] = {}
255 self._main_module: Optional[str] = None
257 @property
258 def _genc(self) -> QAPIGenC:
259 assert self._current_module is not None
260 return self._module[self._current_module][0]
262 @property
263 def _genh(self) -> QAPIGenH:
264 assert self._current_module is not None
265 return self._module[self._current_module][1]
267 @staticmethod
268 def _module_dirname(name: str) -> str:
269 if QAPISchemaModule.is_user_module(name):
270 return os.path.dirname(name)
271 return ''
273 def _module_basename(self, what: str, name: str) -> str:
274 ret = '' if QAPISchemaModule.is_builtin_module(name) else self._prefix
275 if QAPISchemaModule.is_user_module(name):
276 basename = os.path.basename(name)
277 ret += what
278 if name != self._main_module:
279 ret += '-' + os.path.splitext(basename)[0]
280 else:
281 assert QAPISchemaModule.is_system_module(name)
282 ret += re.sub(r'-', '-' + name[2:] + '-', what)
283 return ret
285 def _module_filename(self, what: str, name: str) -> str:
286 return os.path.join(self._module_dirname(name),
287 self._module_basename(what, name))
289 def _add_module(self, name: str, blurb: str) -> None:
290 if QAPISchemaModule.is_user_module(name):
291 if self._main_module is None:
292 self._main_module = name
293 basename = self._module_filename(self._what, name)
294 genc = QAPIGenC(basename + '.c', blurb, self._pydoc)
295 genh = QAPIGenH(basename + '.h', blurb, self._pydoc)
296 self._module[name] = (genc, genh)
297 self._current_module = name
299 @contextmanager
300 def _temp_module(self, name: str) -> Iterator[None]:
301 old_module = self._current_module
302 self._current_module = name
303 yield
304 self._current_module = old_module
306 def write(self, output_dir: str, opt_builtins: bool = False) -> None:
307 for name, (genc, genh) in self._module.items():
308 if QAPISchemaModule.is_builtin_module(name) and not opt_builtins:
309 continue
310 genc.write(output_dir)
311 genh.write(output_dir)
313 def _begin_builtin_module(self) -> None:
314 pass
316 def _begin_user_module(self, name: str) -> None:
317 pass
319 def visit_module(self, name: str) -> None:
320 if QAPISchemaModule.is_builtin_module(name):
321 if self._builtin_blurb:
322 self._add_module(name, self._builtin_blurb)
323 self._begin_builtin_module()
324 else:
325 # The built-in module has not been created. No code may
326 # be generated.
327 self._current_module = None
328 else:
329 assert QAPISchemaModule.is_user_module(name)
330 self._add_module(name, self._user_blurb)
331 self._begin_user_module(name)
333 def visit_include(self, name: str, info: Optional[QAPISourceInfo]) -> None:
334 relname = os.path.relpath(self._module_filename(self._what, name),
335 os.path.dirname(self._genh.fname))
336 self._genh.preamble_add(mcgen('''
337 #include "%(relname)s.h"
338 ''',
339 relname=relname))