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