gen.py 11 KB

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