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