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 14from contextlib import contextmanager 15import os 16import re 17from typing import ( 18 Dict, 19 Iterator, 20 Optional, 21 Sequence, 22 Tuple, 23) 24 25from .common import ( 26 c_fname, 27 c_name, 28 guardend, 29 guardstart, 30 mcgen, 31) 32from .schema import ( 33 QAPISchemaFeature, 34 QAPISchemaIfCond, 35 QAPISchemaModule, 36 QAPISchemaObjectType, 37 QAPISchemaVisitor, 38) 39from .source import QAPISourceInfo 40 41 42def 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' 46 47 48class QAPIGen: 49 def __init__(self, fname: str): 50 self.fname = fname 51 self._preamble = '' 52 self._body = '' 53 54 def preamble_add(self, text: str) -> None: 55 self._preamble += text 56 57 def add(self, text: str) -> None: 58 self._body += text 59 60 def get_content(self) -> str: 61 return self._top() + self._preamble + self._body + self._bottom() 62 63 def _top(self) -> str: 64 # pylint: disable=no-self-use 65 return '' 66 67 def _bottom(self) -> str: 68 # pylint: disable=no-self-use 69 return '' 70 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) 79 80 if odir: 81 os.makedirs(odir, exist_ok=True) 82 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) 92 93 94def _wrap_ifcond(ifcond: QAPISchemaIfCond, before: str, after: str) -> str: 95 if before == after: 96 return after # suppress empty #if ... #endif 97 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 108 109 110def 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 assert not memb.ifcond.is_present() 123 ret += sep 124 sep = ', ' 125 if memb.need_has(): 126 ret += 'bool has_%s, ' % c_name(memb.name) 127 ret += '%s %s' % (memb.type.c_param_type(), 128 c_name(memb.name)) 129 if extra: 130 ret += sep + extra 131 return ret if ret else 'void' 132 133 134class QAPIGenCCode(QAPIGen): 135 def __init__(self, fname: str): 136 super().__init__(fname) 137 self._start_if: Optional[Tuple[QAPISchemaIfCond, str, str]] = None 138 139 def start_if(self, ifcond: QAPISchemaIfCond) -> None: 140 assert self._start_if is None 141 self._start_if = (ifcond, self._body, self._preamble) 142 143 def end_if(self) -> None: 144 assert self._start_if is not None 145 self._body = _wrap_ifcond(self._start_if[0], 146 self._start_if[1], self._body) 147 self._preamble = _wrap_ifcond(self._start_if[0], 148 self._start_if[2], self._preamble) 149 self._start_if = None 150 151 def get_content(self) -> str: 152 assert self._start_if is None 153 return super().get_content() 154 155 156class QAPIGenC(QAPIGenCCode): 157 def __init__(self, fname: str, blurb: str, pydoc: str): 158 super().__init__(fname) 159 self._blurb = blurb 160 self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc, 161 re.MULTILINE)) 162 163 def _top(self) -> str: 164 return mcgen(''' 165/* AUTOMATICALLY GENERATED, DO NOT MODIFY */ 166 167/* 168%(blurb)s 169 * 170 * %(copyright)s 171 * 172 * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. 173 * See the COPYING.LIB file in the top-level directory. 174 */ 175 176''', 177 blurb=self._blurb, copyright=self._copyright) 178 179 def _bottom(self) -> str: 180 return mcgen(''' 181 182/* Dummy declaration to prevent empty .o file */ 183char qapi_dummy_%(name)s; 184''', 185 name=c_fname(self.fname)) 186 187 188class QAPIGenH(QAPIGenC): 189 def _top(self) -> str: 190 return super()._top() + guardstart(self.fname) 191 192 def _bottom(self) -> str: 193 return guardend(self.fname) 194 195 196class QAPIGenTrace(QAPIGen): 197 def _top(self) -> str: 198 return super()._top() + '# AUTOMATICALLY GENERATED, DO NOT MODIFY\n\n' 199 200 201@contextmanager 202def ifcontext(ifcond: QAPISchemaIfCond, *args: QAPIGenCCode) -> Iterator[None]: 203 """ 204 A with-statement context manager that wraps with `start_if()` / `end_if()`. 205 206 :param ifcond: A sequence of conditionals, passed to `start_if()`. 207 :param args: any number of `QAPIGenCCode`. 208 209 Example:: 210 211 with ifcontext(ifcond, self._genh, self._genc): 212 modify self._genh and self._genc ... 213 214 Is equivalent to calling:: 215 216 self._genh.start_if(ifcond) 217 self._genc.start_if(ifcond) 218 modify self._genh and self._genc ... 219 self._genh.end_if() 220 self._genc.end_if() 221 """ 222 for arg in args: 223 arg.start_if(ifcond) 224 yield 225 for arg in args: 226 arg.end_if() 227 228 229class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor): 230 def __init__(self, 231 prefix: str, 232 what: str, 233 blurb: str, 234 pydoc: str): 235 self._prefix = prefix 236 self._what = what 237 self._genc = QAPIGenC(self._prefix + self._what + '.c', 238 blurb, pydoc) 239 self._genh = QAPIGenH(self._prefix + self._what + '.h', 240 blurb, pydoc) 241 242 def write(self, output_dir: str) -> None: 243 self._genc.write(output_dir) 244 self._genh.write(output_dir) 245 246 247class QAPISchemaModularCVisitor(QAPISchemaVisitor): 248 def __init__(self, 249 prefix: str, 250 what: str, 251 user_blurb: str, 252 builtin_blurb: Optional[str], 253 pydoc: str, 254 gen_tracing: bool = False): 255 self._prefix = prefix 256 self._what = what 257 self._user_blurb = user_blurb 258 self._builtin_blurb = builtin_blurb 259 self._pydoc = pydoc 260 self._current_module: Optional[str] = None 261 self._module: Dict[str, Tuple[QAPIGenC, QAPIGenH, 262 Optional[QAPIGenTrace]]] = {} 263 self._main_module: Optional[str] = None 264 self._gen_tracing = gen_tracing 265 266 @property 267 def _genc(self) -> QAPIGenC: 268 assert self._current_module is not None 269 return self._module[self._current_module][0] 270 271 @property 272 def _genh(self) -> QAPIGenH: 273 assert self._current_module is not None 274 return self._module[self._current_module][1] 275 276 @property 277 def _gen_trace_events(self) -> QAPIGenTrace: 278 assert self._gen_tracing 279 assert self._current_module is not None 280 gent = self._module[self._current_module][2] 281 assert gent is not None 282 return gent 283 284 @staticmethod 285 def _module_dirname(name: str) -> str: 286 if QAPISchemaModule.is_user_module(name): 287 return os.path.dirname(name) 288 return '' 289 290 def _module_basename(self, what: str, name: str) -> str: 291 ret = '' if QAPISchemaModule.is_builtin_module(name) else self._prefix 292 if QAPISchemaModule.is_user_module(name): 293 basename = os.path.basename(name) 294 ret += what 295 if name != self._main_module: 296 ret += '-' + os.path.splitext(basename)[0] 297 else: 298 assert QAPISchemaModule.is_system_module(name) 299 ret += re.sub(r'-', '-' + name[2:] + '-', what) 300 return ret 301 302 def _module_filename(self, what: str, name: str) -> str: 303 return os.path.join(self._module_dirname(name), 304 self._module_basename(what, name)) 305 306 def _add_module(self, name: str, blurb: str) -> None: 307 if QAPISchemaModule.is_user_module(name): 308 if self._main_module is None: 309 self._main_module = name 310 basename = self._module_filename(self._what, name) 311 genc = QAPIGenC(basename + '.c', blurb, self._pydoc) 312 genh = QAPIGenH(basename + '.h', blurb, self._pydoc) 313 314 gent: Optional[QAPIGenTrace] = None 315 if self._gen_tracing: 316 gent = QAPIGenTrace(basename + '.trace-events') 317 318 self._module[name] = (genc, genh, gent) 319 self._current_module = name 320 321 @contextmanager 322 def _temp_module(self, name: str) -> Iterator[None]: 323 old_module = self._current_module 324 self._current_module = name 325 yield 326 self._current_module = old_module 327 328 def write(self, output_dir: str, opt_builtins: bool = False) -> None: 329 for name, (genc, genh, gent) in self._module.items(): 330 if QAPISchemaModule.is_builtin_module(name) and not opt_builtins: 331 continue 332 genc.write(output_dir) 333 genh.write(output_dir) 334 if gent is not None: 335 gent.write(output_dir) 336 337 def _begin_builtin_module(self) -> None: 338 pass 339 340 def _begin_user_module(self, name: str) -> None: 341 pass 342 343 def visit_module(self, name: str) -> None: 344 if QAPISchemaModule.is_builtin_module(name): 345 if self._builtin_blurb: 346 self._add_module(name, self._builtin_blurb) 347 self._begin_builtin_module() 348 else: 349 # The built-in module has not been created. No code may 350 # be generated. 351 self._current_module = None 352 else: 353 assert QAPISchemaModule.is_user_module(name) 354 self._add_module(name, self._user_blurb) 355 self._begin_user_module(name) 356 357 def visit_include(self, name: str, info: Optional[QAPISourceInfo]) -> None: 358 relname = os.path.relpath(self._module_filename(self._what, name), 359 os.path.dirname(self._genh.fname)) 360 self._genh.preamble_add(mcgen(''' 361#include "%(relname)s.h" 362''', 363 relname=relname)) 364