gen.py 9.9 KB

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