gen.py 11 KB

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