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