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