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