gen.py 7.8 KB

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