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