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