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