]> Git Repo - qemu.git/blob - scripts/qapi/gen.py
Merge remote-tracking branch 'remotes/armbru/tags/pull-qapi-2020-10-10' into staging
[qemu.git] / scripts / qapi / gen.py
1 # -*- coding: utf-8 -*-
2 #
3 # QAPI code generation
4 #
5 # Copyright (c) 2015-2019 Red Hat Inc.
6 #
7 # Authors:
8 #  Markus Armbruster <[email protected]>
9 #  Marc-AndrĂ© Lureau <[email protected]>
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
14 from contextlib import contextmanager
15 import os
16 import re
17 from typing import (
18     Dict,
19     Iterator,
20     List,
21     Optional,
22     Tuple,
23 )
24
25 from .common import (
26     c_fname,
27     c_name,
28     gen_endif,
29     gen_if,
30     guardend,
31     guardstart,
32     mcgen,
33 )
34 from .schema import QAPISchemaObjectType, QAPISchemaVisitor
35 from .source import QAPISourceInfo
36
37
38 class QAPIGen:
39     def __init__(self, fname: Optional[str]):
40         self.fname = fname
41         self._preamble = ''
42         self._body = ''
43
44     def preamble_add(self, text: str) -> None:
45         self._preamble += text
46
47     def add(self, text: str) -> None:
48         self._body += text
49
50     def get_content(self) -> str:
51         return self._top() + self._preamble + self._body + self._bottom()
52
53     def _top(self) -> str:
54         # pylint: disable=no-self-use
55         return ''
56
57     def _bottom(self) -> str:
58         # pylint: disable=no-self-use
59         return ''
60
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
70         if odir:
71             os.makedirs(odir, exist_ok=True)
72
73         # use os.open for O_CREAT to create and read a non-existant file
74         fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666)
75         with os.fdopen(fd, 'r+', encoding='utf-8') as fp:
76             text = self.get_content()
77             oldtext = fp.read(len(text) + 1)
78             if text != oldtext:
79                 fp.seek(0)
80                 fp.truncate(0)
81                 fp.write(text)
82
83
84 def _wrap_ifcond(ifcond: List[str], before: str, after: str) -> str:
85     if before == after:
86         return after   # suppress empty #if ... #endif
87
88     assert after.startswith(before)
89     out = before
90     added = after[len(before):]
91     if added[0] == '\n':
92         out += '\n'
93         added = added[1:]
94     out += gen_if(ifcond)
95     out += added
96     out += gen_endif(ifcond)
97     return out
98
99
100 def build_params(arg_type: Optional[QAPISchemaObjectType],
101                  boxed: bool,
102                  extra: Optional[str] = None) -> str:
103     ret = ''
104     sep = ''
105     if boxed:
106         assert arg_type
107         ret += '%s arg' % arg_type.c_param_type()
108         sep = ', '
109     elif arg_type:
110         assert not arg_type.variants
111         for memb in arg_type.members:
112             ret += sep
113             sep = ', '
114             if memb.optional:
115                 ret += 'bool has_%s, ' % c_name(memb.name)
116             ret += '%s %s' % (memb.type.c_param_type(),
117                               c_name(memb.name))
118     if extra:
119         ret += sep + extra
120     return ret if ret else 'void'
121
122
123 class QAPIGenCCode(QAPIGen):
124     def __init__(self, fname: Optional[str]):
125         super().__init__(fname)
126         self._start_if: Optional[Tuple[List[str], str, str]] = None
127
128     def start_if(self, ifcond: List[str]) -> None:
129         assert self._start_if is None
130         self._start_if = (ifcond, self._body, self._preamble)
131
132     def end_if(self) -> None:
133         assert self._start_if
134         self._wrap_ifcond()
135         self._start_if = None
136
137     def _wrap_ifcond(self) -> None:
138         self._body = _wrap_ifcond(self._start_if[0],
139                                   self._start_if[1], self._body)
140         self._preamble = _wrap_ifcond(self._start_if[0],
141                                       self._start_if[2], self._preamble)
142
143     def get_content(self) -> str:
144         assert self._start_if is None
145         return super().get_content()
146
147
148 class QAPIGenC(QAPIGenCCode):
149     def __init__(self, fname: str, blurb: str, pydoc: str):
150         super().__init__(fname)
151         self._blurb = blurb
152         self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc,
153                                                   re.MULTILINE))
154
155     def _top(self) -> str:
156         return mcgen('''
157 /* AUTOMATICALLY GENERATED, DO NOT MODIFY */
158
159 /*
160 %(blurb)s
161  *
162  * %(copyright)s
163  *
164  * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
165  * See the COPYING.LIB file in the top-level directory.
166  */
167
168 ''',
169                      blurb=self._blurb, copyright=self._copyright)
170
171     def _bottom(self) -> str:
172         return mcgen('''
173
174 /* Dummy declaration to prevent empty .o file */
175 char qapi_dummy_%(name)s;
176 ''',
177                      name=c_fname(self.fname))
178
179
180 class QAPIGenH(QAPIGenC):
181     def _top(self) -> str:
182         return super()._top() + guardstart(self.fname)
183
184     def _bottom(self) -> str:
185         return guardend(self.fname)
186
187
188 @contextmanager
189 def ifcontext(ifcond: List[str], *args: QAPIGenCCode) -> Iterator[None]:
190     """
191     A with-statement context manager that wraps with `start_if()` / `end_if()`.
192
193     :param ifcond: A list of conditionals, passed to `start_if()`.
194     :param args: any number of `QAPIGenCCode`.
195
196     Example::
197
198         with ifcontext(ifcond, self._genh, self._genc):
199             modify self._genh and self._genc ...
200
201     Is equivalent to calling::
202
203         self._genh.start_if(ifcond)
204         self._genc.start_if(ifcond)
205         modify self._genh and self._genc ...
206         self._genh.end_if()
207         self._genc.end_if()
208     """
209     for arg in args:
210         arg.start_if(ifcond)
211     yield
212     for arg in args:
213         arg.end_if()
214
215
216 class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
217     def __init__(self,
218                  prefix: str,
219                  what: str,
220                  blurb: str,
221                  pydoc: str):
222         self._prefix = prefix
223         self._what = what
224         self._genc = QAPIGenC(self._prefix + self._what + '.c',
225                               blurb, pydoc)
226         self._genh = QAPIGenH(self._prefix + self._what + '.h',
227                               blurb, pydoc)
228
229     def write(self, output_dir: str) -> None:
230         self._genc.write(output_dir)
231         self._genh.write(output_dir)
232
233
234 class QAPISchemaModularCVisitor(QAPISchemaVisitor):
235     def __init__(self,
236                  prefix: str,
237                  what: str,
238                  user_blurb: str,
239                  builtin_blurb: Optional[str],
240                  pydoc: str):
241         self._prefix = prefix
242         self._what = what
243         self._user_blurb = user_blurb
244         self._builtin_blurb = builtin_blurb
245         self._pydoc = pydoc
246         self._genc: Optional[QAPIGenC] = None
247         self._genh: Optional[QAPIGenH] = None
248         self._module: Dict[Optional[str], Tuple[QAPIGenC, QAPIGenH]] = {}
249         self._main_module: Optional[str] = None
250
251     @staticmethod
252     def _is_user_module(name: Optional[str]) -> bool:
253         return bool(name and not name.startswith('./'))
254
255     @staticmethod
256     def _is_builtin_module(name: Optional[str]) -> bool:
257         return not name
258
259     def _module_dirname(self, name: Optional[str]) -> str:
260         if self._is_user_module(name):
261             return os.path.dirname(name)
262         return ''
263
264     def _module_basename(self, what: str, name: Optional[str]) -> str:
265         ret = '' if self._is_builtin_module(name) else self._prefix
266         if self._is_user_module(name):
267             basename = os.path.basename(name)
268             ret += what
269             if name != self._main_module:
270                 ret += '-' + os.path.splitext(basename)[0]
271         else:
272             name = name[2:] if name else 'builtin'
273             ret += re.sub(r'-', '-' + name + '-', what)
274         return ret
275
276     def _module_filename(self, what: str, name: Optional[str]) -> str:
277         return os.path.join(self._module_dirname(name),
278                             self._module_basename(what, name))
279
280     def _add_module(self, name: Optional[str], blurb: str) -> None:
281         basename = self._module_filename(self._what, name)
282         genc = QAPIGenC(basename + '.c', blurb, self._pydoc)
283         genh = QAPIGenH(basename + '.h', blurb, self._pydoc)
284         self._module[name] = (genc, genh)
285         self._genc, self._genh = self._module[name]
286
287     def _add_user_module(self, name: str, blurb: str) -> None:
288         assert self._is_user_module(name)
289         if self._main_module is None:
290             self._main_module = name
291         self._add_module(name, blurb)
292
293     def _add_system_module(self, name: Optional[str], blurb: str) -> None:
294         self._add_module(name and './' + name, blurb)
295
296     def write(self, output_dir: str, opt_builtins: bool = False) -> None:
297         for name in self._module:
298             if self._is_builtin_module(name) and not opt_builtins:
299                 continue
300             (genc, genh) = self._module[name]
301             genc.write(output_dir)
302             genh.write(output_dir)
303
304     def _begin_system_module(self, name: None) -> None:
305         pass
306
307     def _begin_user_module(self, name: str) -> None:
308         pass
309
310     def visit_module(self, name: Optional[str]) -> None:
311         if name is None:
312             if self._builtin_blurb:
313                 self._add_system_module(None, self._builtin_blurb)
314                 self._begin_system_module(name)
315             else:
316                 # The built-in module has not been created.  No code may
317                 # be generated.
318                 self._genc = None
319                 self._genh = None
320         else:
321             self._add_user_module(name, self._user_blurb)
322             self._begin_user_module(name)
323
324     def visit_include(self, name: str, info: QAPISourceInfo) -> None:
325         relname = os.path.relpath(self._module_filename(self._what, name),
326                                   os.path.dirname(self._genh.fname))
327         self._genh.preamble_add(mcgen('''
328 #include "%(relname)s.h"
329 ''',
330                                       relname=relname))
This page took 0.044987 seconds and 4 git commands to generate.