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