blob: 6a8abe004154b87dce3abde6c853d128efa78de2 [file] [log] [blame]
Markus Armbrustere6c42b92019-10-18 09:43:44 +02001# -*- coding: utf-8 -*-
2#
3# QAPI code generation
4#
John Snowe6a34cd2020-10-09 12:15:39 -04005# Copyright (c) 2015-2019 Red Hat Inc.
Markus Armbrustere6c42b92019-10-18 09:43:44 +02006#
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
John Snow5af82632020-10-09 12:15:28 -040014from contextlib import contextmanager
Markus Armbrustere6c42b92019-10-18 09:43:44 +020015import os
16import re
John Snowa6c5d152023-10-04 19:05:32 -040017import sys
John Snow17d40c32020-10-09 12:15:49 -040018from typing import (
19 Dict,
20 Iterator,
John Snow17d40c32020-10-09 12:15:49 -040021 Optional,
Markus Armbrusterc67db1e2021-10-28 12:25:15 +020022 Sequence,
John Snow17d40c32020-10-09 12:15:49 -040023 Tuple,
24)
Markus Armbrustere6c42b92019-10-18 09:43:44 +020025
John Snow5af82632020-10-09 12:15:28 -040026from .common import (
27 c_fname,
John Snowe6a34cd2020-10-09 12:15:39 -040028 c_name,
John Snow5af82632020-10-09 12:15:28 -040029 guardend,
30 guardstart,
31 mcgen,
32)
John Snow98967c22021-02-01 14:37:36 -050033from .schema import (
Markus Armbrusterc67db1e2021-10-28 12:25:15 +020034 QAPISchemaFeature,
Marc-André Lureauf17539c2021-08-04 12:30:57 +040035 QAPISchemaIfCond,
John Snow98967c22021-02-01 14:37:36 -050036 QAPISchemaModule,
37 QAPISchemaObjectType,
38 QAPISchemaVisitor,
39)
John Snow17d40c32020-10-09 12:15:49 -040040from .source import QAPISourceInfo
Markus Armbrustere6c42b92019-10-18 09:43:44 +020041
42
Markus Armbrusterc67db1e2021-10-28 12:25:15 +020043def 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
Markus Armbrusterbaa310f2020-03-04 16:59:29 +010049class QAPIGen:
Markus Armbrustercc0747f2021-02-01 14:37:45 -050050 def __init__(self, fname: str):
Markus Armbrustere6c42b92019-10-18 09:43:44 +020051 self.fname = fname
52 self._preamble = ''
53 self._body = ''
54
John Snow17d40c32020-10-09 12:15:49 -040055 def preamble_add(self, text: str) -> None:
Markus Armbrustere6c42b92019-10-18 09:43:44 +020056 self._preamble += text
57
John Snow17d40c32020-10-09 12:15:49 -040058 def add(self, text: str) -> None:
Markus Armbrustere6c42b92019-10-18 09:43:44 +020059 self._body += text
60
John Snow17d40c32020-10-09 12:15:49 -040061 def get_content(self) -> str:
Markus Armbrustere6c42b92019-10-18 09:43:44 +020062 return self._top() + self._preamble + self._body + self._bottom()
63
John Snow17d40c32020-10-09 12:15:49 -040064 def _top(self) -> str:
John Snow9abddb52020-10-09 12:15:53 -040065 # pylint: disable=no-self-use
Markus Armbrustere6c42b92019-10-18 09:43:44 +020066 return ''
67
John Snow17d40c32020-10-09 12:15:49 -040068 def _bottom(self) -> str:
John Snow9abddb52020-10-09 12:15:53 -040069 # pylint: disable=no-self-use
Markus Armbrustere6c42b92019-10-18 09:43:44 +020070 return ''
71
John Snow17d40c32020-10-09 12:15:49 -040072 def write(self, output_dir: str) -> None:
Kevin Wolf2af282e2020-02-24 15:30:08 +010073 # 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
Markus Armbrustere6c42b92019-10-18 09:43:44 +020078 pathname = os.path.join(output_dir, self.fname)
Markus Armbruster8ec0e1a2020-03-04 16:59:32 +010079 odir = os.path.dirname(pathname)
John Snowcc6263c2020-10-09 12:15:52 -040080
Markus Armbruster8ec0e1a2020-03-04 16:59:32 +010081 if odir:
John Snowcc6263c2020-10-09 12:15:52 -040082 os.makedirs(odir, exist_ok=True)
83
Michael Tokarevd30b5bc2023-07-14 14:33:18 +030084 # use os.open for O_CREAT to create and read a non-existent file
Markus Armbrustere6c42b92019-10-18 09:43:44 +020085 fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666)
John Snowcc6263c2020-10-09 12:15:52 -040086 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)
Markus Armbrustere6c42b92019-10-18 09:43:44 +020093
94
Marc-André Lureauf17539c2021-08-04 12:30:57 +040095def _wrap_ifcond(ifcond: QAPISchemaIfCond, before: str, after: str) -> str:
Markus Armbrustere6c42b92019-10-18 09:43:44 +020096 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:]
Markus Armbruster1889e572021-08-31 14:37:58 +0200105 out += ifcond.gen_if()
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200106 out += added
Markus Armbruster1889e572021-08-31 14:37:58 +0200107 out += ifcond.gen_endif()
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200108 return out
109
110
John Snowe6a34cd2020-10-09 12:15:39 -0400111def 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:
Markus Armbruster3ff2a5a2024-03-15 16:33:23 +0100121 assert not arg_type.branches
John Snowe6a34cd2020-10-09 12:15:39 -0400122 for memb in arg_type.members:
Markus Armbrusterde3b3f52023-03-16 08:13:25 +0100123 assert not memb.ifcond.is_present()
John Snowe6a34cd2020-10-09 12:15:39 -0400124 ret += sep
125 sep = ', '
Markus Armbruster44ea9d92022-11-04 17:06:46 +0100126 if memb.need_has():
John Snowe6a34cd2020-10-09 12:15:39 -0400127 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
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200135class QAPIGenCCode(QAPIGen):
Markus Armbrustercc0747f2021-02-01 14:37:45 -0500136 def __init__(self, fname: str):
Markus Armbruster2cae67b2020-03-04 16:59:31 +0100137 super().__init__(fname)
Marc-André Lureauf17539c2021-08-04 12:30:57 +0400138 self._start_if: Optional[Tuple[QAPISchemaIfCond, str, str]] = None
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200139
Marc-André Lureauf17539c2021-08-04 12:30:57 +0400140 def start_if(self, ifcond: QAPISchemaIfCond) -> None:
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200141 assert self._start_if is None
142 self._start_if = (ifcond, self._body, self._preamble)
143
John Snow17d40c32020-10-09 12:15:49 -0400144 def end_if(self) -> None:
John Snowa253b3e2021-02-01 14:37:35 -0500145 assert self._start_if is not None
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200146 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)
John Snowa253b3e2021-02-01 14:37:35 -0500150 self._start_if = None
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200151
John Snow17d40c32020-10-09 12:15:49 -0400152 def get_content(self) -> str:
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200153 assert self._start_if is None
Markus Armbruster2cae67b2020-03-04 16:59:31 +0100154 return super().get_content()
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200155
156
157class QAPIGenC(QAPIGenCCode):
John Snow17d40c32020-10-09 12:15:49 -0400158 def __init__(self, fname: str, blurb: str, pydoc: str):
Markus Armbruster2cae67b2020-03-04 16:59:31 +0100159 super().__init__(fname)
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200160 self._blurb = blurb
161 self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc,
162 re.MULTILINE))
163
John Snow17d40c32020-10-09 12:15:49 -0400164 def _top(self) -> str:
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200165 return mcgen('''
Alex Bennée9deb9992023-05-26 17:53:54 +0100166/* AUTOMATICALLY GENERATED by %(tool)s DO NOT MODIFY */
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200167
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''',
Alex Bennée9deb9992023-05-26 17:53:54 +0100178 tool=os.path.basename(sys.argv[0]),
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200179 blurb=self._blurb, copyright=self._copyright)
180
John Snow17d40c32020-10-09 12:15:49 -0400181 def _bottom(self) -> str:
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200182 return mcgen('''
183
184/* Dummy declaration to prevent empty .o file */
185char qapi_dummy_%(name)s;
186''',
187 name=c_fname(self.fname))
188
189
190class QAPIGenH(QAPIGenC):
John Snow17d40c32020-10-09 12:15:49 -0400191 def _top(self) -> str:
Markus Armbruster2cae67b2020-03-04 16:59:31 +0100192 return super()._top() + guardstart(self.fname)
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200193
John Snow17d40c32020-10-09 12:15:49 -0400194 def _bottom(self) -> str:
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200195 return guardend(self.fname)
196
197
Vladimir Sementsov-Ogievskiy4e86df12022-01-26 17:11:24 +0100198class QAPIGenTrace(QAPIGen):
199 def _top(self) -> str:
Alex Bennée9deb9992023-05-26 17:53:54 +0100200 return (super()._top()
201 + '# AUTOMATICALLY GENERATED by '
202 + os.path.basename(sys.argv[0])
203 + ', DO NOT MODIFY\n\n')
Vladimir Sementsov-Ogievskiy4e86df12022-01-26 17:11:24 +0100204
205
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200206@contextmanager
Marc-André Lureauf17539c2021-08-04 12:30:57 +0400207def ifcontext(ifcond: QAPISchemaIfCond, *args: QAPIGenCCode) -> Iterator[None]:
John Snowadcb9b32020-10-09 12:15:24 -0400208 """
209 A with-statement context manager that wraps with `start_if()` / `end_if()`.
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200210
John Snow2184bca2021-02-15 21:17:51 -0500211 :param ifcond: A sequence of conditionals, passed to `start_if()`.
John Snowadcb9b32020-10-09 12:15:24 -0400212 :param args: any number of `QAPIGenCCode`.
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200213
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
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200234class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
John Snow17d40c32020-10-09 12:15:49 -0400235 def __init__(self,
236 prefix: str,
237 what: str,
238 blurb: str,
239 pydoc: str):
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200240 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
John Snow17d40c32020-10-09 12:15:49 -0400247 def write(self, output_dir: str) -> None:
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200248 self._genc.write(output_dir)
249 self._genh.write(output_dir)
250
251
252class QAPISchemaModularCVisitor(QAPISchemaVisitor):
John Snow17d40c32020-10-09 12:15:49 -0400253 def __init__(self,
254 prefix: str,
255 what: str,
256 user_blurb: str,
257 builtin_blurb: Optional[str],
Vladimir Sementsov-Ogievskiy4e86df12022-01-26 17:11:24 +0100258 pydoc: str,
259 gen_tracing: bool = False):
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200260 self._prefix = prefix
261 self._what = what
Markus Armbruster3bef3aa2019-11-20 19:25:51 +0100262 self._user_blurb = user_blurb
263 self._builtin_blurb = builtin_blurb
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200264 self._pydoc = pydoc
John Snowfd9b1602021-02-01 14:37:42 -0500265 self._current_module: Optional[str] = None
Vladimir Sementsov-Ogievskiy4e86df12022-01-26 17:11:24 +0100266 self._module: Dict[str, Tuple[QAPIGenC, QAPIGenH,
267 Optional[QAPIGenTrace]]] = {}
John Snow17d40c32020-10-09 12:15:49 -0400268 self._main_module: Optional[str] = None
Vladimir Sementsov-Ogievskiy4e86df12022-01-26 17:11:24 +0100269 self._gen_tracing = gen_tracing
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200270
John Snowfd9b1602021-02-01 14:37:42 -0500271 @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
Vladimir Sementsov-Ogievskiy4e86df12022-01-26 17:11:24 +0100281 @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
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200289 @staticmethod
John Snowe2bbc4e2021-02-01 14:37:39 -0500290 def _module_dirname(name: str) -> str:
John Snow98967c22021-02-01 14:37:36 -0500291 if QAPISchemaModule.is_user_module(name):
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200292 return os.path.dirname(name)
293 return ''
294
John Snowe2bbc4e2021-02-01 14:37:39 -0500295 def _module_basename(self, what: str, name: str) -> str:
John Snow98967c22021-02-01 14:37:36 -0500296 ret = '' if QAPISchemaModule.is_builtin_module(name) else self._prefix
297 if QAPISchemaModule.is_user_module(name):
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200298 basename = os.path.basename(name)
299 ret += what
300 if name != self._main_module:
301 ret += '-' + os.path.splitext(basename)[0]
302 else:
John Snowe2bbc4e2021-02-01 14:37:39 -0500303 assert QAPISchemaModule.is_system_module(name)
304 ret += re.sub(r'-', '-' + name[2:] + '-', what)
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200305 return ret
306
John Snowe2bbc4e2021-02-01 14:37:39 -0500307 def _module_filename(self, what: str, name: str) -> str:
John Snow0cbd5b02020-10-09 12:15:51 -0400308 return os.path.join(self._module_dirname(name),
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200309 self._module_basename(what, name))
310
John Snowe2bbc4e2021-02-01 14:37:39 -0500311 def _add_module(self, name: str, blurb: str) -> None:
Markus Armbruster4ab0ff62021-02-01 14:37:40 -0500312 if QAPISchemaModule.is_user_module(name):
313 if self._main_module is None:
314 self._main_module = name
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200315 basename = self._module_filename(self._what, name)
316 genc = QAPIGenC(basename + '.c', blurb, self._pydoc)
317 genh = QAPIGenH(basename + '.h', blurb, self._pydoc)
Vladimir Sementsov-Ogievskiy4e86df12022-01-26 17:11:24 +0100318
319 gent: Optional[QAPIGenTrace] = None
320 if self._gen_tracing:
321 gent = QAPIGenTrace(basename + '.trace-events')
322
323 self._module[name] = (genc, genh, gent)
John Snowfd9b1602021-02-01 14:37:42 -0500324 self._current_module = name
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200325
Markus Armbrusterd921d272021-02-01 14:37:43 -0500326 @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
John Snow17d40c32020-10-09 12:15:49 -0400333 def write(self, output_dir: str, opt_builtins: bool = False) -> None:
Vladimir Sementsov-Ogievskiy4e86df12022-01-26 17:11:24 +0100334 for name, (genc, genh, gent) in self._module.items():
John Snow98967c22021-02-01 14:37:36 -0500335 if QAPISchemaModule.is_builtin_module(name) and not opt_builtins:
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200336 continue
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200337 genc.write(output_dir)
338 genh.write(output_dir)
Vladimir Sementsov-Ogievskiy4e86df12022-01-26 17:11:24 +0100339 if gent is not None:
340 gent.write(output_dir)
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200341
Markus Armbrusterf3a70592021-02-01 14:37:37 -0500342 def _begin_builtin_module(self) -> None:
Markus Armbruster8ec0e1a2020-03-04 16:59:32 +0100343 pass
344
John Snow17d40c32020-10-09 12:15:49 -0400345 def _begin_user_module(self, name: str) -> None:
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200346 pass
347
John Snowe2bbc4e2021-02-01 14:37:39 -0500348 def visit_module(self, name: str) -> None:
Markus Armbrusterf3a70592021-02-01 14:37:37 -0500349 if QAPISchemaModule.is_builtin_module(name):
Markus Armbruster3bef3aa2019-11-20 19:25:51 +0100350 if self._builtin_blurb:
Markus Armbruster4ab0ff62021-02-01 14:37:40 -0500351 self._add_module(name, self._builtin_blurb)
Markus Armbrusterf3a70592021-02-01 14:37:37 -0500352 self._begin_builtin_module()
Markus Armbruster3bef3aa2019-11-20 19:25:51 +0100353 else:
354 # The built-in module has not been created. No code may
355 # be generated.
John Snowfd9b1602021-02-01 14:37:42 -0500356 self._current_module = None
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200357 else:
Markus Armbrusterf3a70592021-02-01 14:37:37 -0500358 assert QAPISchemaModule.is_user_module(name)
Markus Armbruster4ab0ff62021-02-01 14:37:40 -0500359 self._add_module(name, self._user_blurb)
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200360 self._begin_user_module(name)
361
John Snow4a82e462021-02-01 14:37:46 -0500362 def visit_include(self, name: str, info: Optional[QAPISourceInfo]) -> None:
Markus Armbrustere6c42b92019-10-18 09:43:44 +0200363 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))