gen.py 9.8 KB

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