gen.py 10 KB

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