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 sys 17import re 18from typing import ( 19 Dict, 20 Iterator, 21 Optional, 22 Sequence, 23 Tuple, 24) 25 26from .common import ( 27 c_fname, 28 c_name, 29 guardend, 30 guardstart, 31 mcgen, 32) 33from .schema import ( 34 QAPISchemaFeature, 35 QAPISchemaIfCond, 36 QAPISchemaModule, 37 QAPISchemaObjectType, 38 QAPISchemaVisitor, 39) 40from .source import QAPISourceInfo 41 42 43def 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 49class QAPIGen: 50 def __init__(self, fname: str): 51 self.fname = fname 52 self._preamble = '' 53 self._body = '' 54 55 def preamble_add(self, text: str) -> None: 56 self._preamble += text 57 58 def add(self, text: str) -> None: 59 self._body += text 60 61 def get_content(self) -> str: 62 return self._top() + self._preamble + self._body + self._bottom() 63 64 def _top(self) -> str: 65 # pylint: disable=no-self-use 66 return '' 67 68 def _bottom(self) -> str: 69 # pylint: disable=no-self-use 70 return '' 71 72 def write(self, output_dir: str) -> None: 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 78 pathname = os.path.join(output_dir, self.fname) 79 odir = os.path.dirname(pathname) 80 81 if odir: 82 os.makedirs(odir, exist_ok=True) 83 84 # use os.open for O_CREAT to create and read a non-existent file 85 fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666) 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) 93 94 95def _wrap_ifcond(ifcond: QAPISchemaIfCond, before: str, after: str) -> str: 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:] 105 out += ifcond.gen_if() 106 out += added 107 out += ifcond.gen_endif() 108 return out 109 110 111def 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: 121 assert not arg_type.variants 122 for memb in arg_type.members: 123 assert not memb.ifcond.is_present() 124 ret += sep 125 sep = ', ' 126 if memb.need_has(): 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 135class QAPIGenCCode(QAPIGen): 136 def __init__(self, fname: str): 137 super().__init__(fname) 138 self._start_if: Optional[Tuple[QAPISchemaIfCond, str, str]] = None 139 140 def start_if(self, ifcond: QAPISchemaIfCond) -> None: 141 assert self._start_if is None 142 self._start_if = (ifcond, self._body, self._preamble) 143 144 def end_if(self) -> None: 145 assert self._start_if is not None 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) 150 self._start_if = None 151 152 def get_content(self) -> str: 153 assert self._start_if is None 154 return super().get_content() 155 156 157class QAPIGenC(QAPIGenCCode): 158 def __init__(self, fname: str, blurb: str, pydoc: str): 159 super().__init__(fname) 160 self._blurb = blurb 161 self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc, 162 re.MULTILINE)) 163 164 def _top(self) -> str: 165 return mcgen(''' 166/* AUTOMATICALLY GENERATED by %(tool)s DO NOT MODIFY */ 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''', 178 tool=os.path.basename(sys.argv[0]), 179 blurb=self._blurb, copyright=self._copyright) 180 181 def _bottom(self) -> str: 182 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): 191 def _top(self) -> str: 192 return super()._top() + guardstart(self.fname) 193 194 def _bottom(self) -> str: 195 return guardend(self.fname) 196 197 198class QAPIGenTrace(QAPIGen): 199 def _top(self) -> str: 200 return (super()._top() 201 + '# AUTOMATICALLY GENERATED by ' 202 + os.path.basename(sys.argv[0]) 203 + ', DO NOT MODIFY\n\n') 204 205 206@contextmanager 207def ifcontext(ifcond: QAPISchemaIfCond, *args: QAPIGenCCode) -> Iterator[None]: 208 """ 209 A with-statement context manager that wraps with `start_if()` / `end_if()`. 210 211 :param ifcond: A sequence of conditionals, passed to `start_if()`. 212 :param args: any number of `QAPIGenCCode`. 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 234class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor): 235 def __init__(self, 236 prefix: str, 237 what: str, 238 blurb: str, 239 pydoc: str): 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 247 def write(self, output_dir: str) -> None: 248 self._genc.write(output_dir) 249 self._genh.write(output_dir) 250 251 252class QAPISchemaModularCVisitor(QAPISchemaVisitor): 253 def __init__(self, 254 prefix: str, 255 what: str, 256 user_blurb: str, 257 builtin_blurb: Optional[str], 258 pydoc: str, 259 gen_tracing: bool = False): 260 self._prefix = prefix 261 self._what = what 262 self._user_blurb = user_blurb 263 self._builtin_blurb = builtin_blurb 264 self._pydoc = pydoc 265 self._current_module: Optional[str] = None 266 self._module: Dict[str, Tuple[QAPIGenC, QAPIGenH, 267 Optional[QAPIGenTrace]]] = {} 268 self._main_module: Optional[str] = None 269 self._gen_tracing = gen_tracing 270 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 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 289 @staticmethod 290 def _module_dirname(name: str) -> str: 291 if QAPISchemaModule.is_user_module(name): 292 return os.path.dirname(name) 293 return '' 294 295 def _module_basename(self, what: str, name: str) -> str: 296 ret = '' if QAPISchemaModule.is_builtin_module(name) else self._prefix 297 if QAPISchemaModule.is_user_module(name): 298 basename = os.path.basename(name) 299 ret += what 300 if name != self._main_module: 301 ret += '-' + os.path.splitext(basename)[0] 302 else: 303 assert QAPISchemaModule.is_system_module(name) 304 ret += re.sub(r'-', '-' + name[2:] + '-', what) 305 return ret 306 307 def _module_filename(self, what: str, name: str) -> str: 308 return os.path.join(self._module_dirname(name), 309 self._module_basename(what, name)) 310 311 def _add_module(self, name: str, blurb: str) -> None: 312 if QAPISchemaModule.is_user_module(name): 313 if self._main_module is None: 314 self._main_module = name 315 basename = self._module_filename(self._what, name) 316 genc = QAPIGenC(basename + '.c', blurb, self._pydoc) 317 genh = QAPIGenH(basename + '.h', blurb, self._pydoc) 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) 324 self._current_module = name 325 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 333 def write(self, output_dir: str, opt_builtins: bool = False) -> None: 334 for name, (genc, genh, gent) in self._module.items(): 335 if QAPISchemaModule.is_builtin_module(name) and not opt_builtins: 336 continue 337 genc.write(output_dir) 338 genh.write(output_dir) 339 if gent is not None: 340 gent.write(output_dir) 341 342 def _begin_builtin_module(self) -> None: 343 pass 344 345 def _begin_user_module(self, name: str) -> None: 346 pass 347 348 def visit_module(self, name: str) -> None: 349 if QAPISchemaModule.is_builtin_module(name): 350 if self._builtin_blurb: 351 self._add_module(name, self._builtin_blurb) 352 self._begin_builtin_module() 353 else: 354 # The built-in module has not been created. No code may 355 # be generated. 356 self._current_module = None 357 else: 358 assert QAPISchemaModule.is_user_module(name) 359 self._add_module(name, self._user_blurb) 360 self._begin_user_module(name) 361 362 def visit_include(self, name: str, info: Optional[QAPISourceInfo]) -> None: 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)) 369