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