]>
Commit | Line | Data |
---|---|---|
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 | 14 | from contextlib import contextmanager |
e6c42b96 MA |
15 | import os |
16 | import re | |
a6c5d159 | 17 | import sys |
17d40c39 JS |
18 | from typing import ( |
19 | Dict, | |
20 | Iterator, | |
17d40c39 | 21 | Optional, |
c67db1ed | 22 | Sequence, |
17d40c39 JS |
23 | Tuple, |
24 | ) | |
e6c42b96 | 25 | |
5af8263d JS |
26 | from .common import ( |
27 | c_fname, | |
e6a34cd7 | 28 | c_name, |
5af8263d JS |
29 | guardend, |
30 | guardstart, | |
31 | mcgen, | |
32 | ) | |
98967c24 | 33 | from .schema import ( |
c67db1ed | 34 | QAPISchemaFeature, |
f17539c8 | 35 | QAPISchemaIfCond, |
98967c24 JS |
36 | QAPISchemaModule, |
37 | QAPISchemaObjectType, | |
38 | QAPISchemaVisitor, | |
39 | ) | |
17d40c39 | 40 | from .source import QAPISourceInfo |
e6c42b96 MA |
41 | |
42 | ||
c67db1ed MA |
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' | |
47 | ||
48 | ||
baa310f1 | 49 | class 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 | 95 | def _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 |
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: | |
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 | 135 | class 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 | ||
157 | class 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 */ | |
185 | char qapi_dummy_%(name)s; | |
186 | ''', | |
187 | name=c_fname(self.fname)) | |
188 | ||
189 | ||
190 | class 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 |
198 | class 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 | 207 | def 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 | 234 | class 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 | ||
252 | class 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)) |