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