1 # -*- Mode: Python -*-
2 # coding=utf-8
3
4 # GDBus - GLib D-Bus Library
5 #
6 # Copyright (C) 2008-2011 Red Hat, Inc.
7 # Copyright (C) 2018 Iñigo Martínez <inigomartinez@gmail.com>
8 #
9 # This library is free software; you can redistribute it and/or
10 # modify it under the terms of the GNU Lesser General Public
11 # License as published by the Free Software Foundation; either
12 # version 2.1 of the License, or (at your option) any later version.
13 #
14 # This library is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 # Lesser General Public License for more details.
18 #
19 # You should have received a copy of the GNU Lesser General
20 # Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
21 #
22 # Author: David Zeuthen <davidz@redhat.com>
23
24 import argparse
25 import os
26 import sys
27
28 from . import config
29 from . import dbustypes
30 from . import parser
31 from . import codegen
32 from . import codegen_docbook
33 from . import codegen_md
34 from . import codegen_rst
35 from .utils import print_error, print_warning
36
37
38 def find_arg(arg_list, arg_name):
39 for a in arg_list:
40 if a.name == arg_name:
41 return a
42 return None
43
44
45 def find_method(iface, method):
46 for m in iface.methods:
47 if m.name == method:
48 return m
49 return None
50
51
52 def find_signal(iface, signal):
53 for m in iface.signals:
54 if m.name == signal:
55 return m
56 return None
57
58
59 def find_prop(iface, prop):
60 for m in iface.properties:
61 if m.name == prop:
62 return m
63 return None
64
65
66 def apply_annotation(iface_list, iface, method, signal, prop, arg, key, value):
67 iface_obj = None
68 for i in iface_list:
69 if i.name == iface:
70 iface_obj = i
71 break
72
73 if iface_obj is None:
74 print_error('No interface "{}"'.format(iface))
75
76 target_obj = None
77
78 if method:
79 method_obj = find_method(iface_obj, method)
80 if method_obj is None:
81 print_error('No method "{}" on interface "{}"'.format(method, iface))
82 if arg:
83 arg_obj = find_arg(method_obj.in_args, arg)
84 if arg_obj is None:
85 arg_obj = find_arg(method_obj.out_args, arg)
86 if arg_obj is None:
87 print_error(
88 'No arg "{}" on method "{}" on interface "{}"'.format(
89 arg, method, iface
90 )
91 )
92 target_obj = arg_obj
93 else:
94 target_obj = method_obj
95 elif signal:
96 signal_obj = find_signal(iface_obj, signal)
97 if signal_obj is None:
98 print_error('No signal "{}" on interface "{}"'.format(signal, iface))
99 if arg:
100 arg_obj = find_arg(signal_obj.args, arg)
101 if arg_obj is None:
102 print_error(
103 'No arg "{}" on signal "{}" on interface "{}"'.format(
104 arg, signal, iface
105 )
106 )
107 target_obj = arg_obj
108 else:
109 target_obj = signal_obj
110 elif prop:
111 prop_obj = find_prop(iface_obj, prop)
112 if prop_obj is None:
113 print_error('No property "{}" on interface "{}"'.format(prop, iface))
114 target_obj = prop_obj
115 else:
116 target_obj = iface_obj
117 target_obj.annotations.insert(0, dbustypes.Annotation(key, value))
118
119
120 def apply_annotations(iface_list, annotation_list):
121 # apply annotations given on the command line
122 for what, key, value in annotation_list:
123 pos = what.find("::")
124 if pos != -1:
125 # signal
126 iface = what[0:pos]
127 signal = what[pos + 2 :]
128 pos = signal.find("[")
129 if pos != -1:
130 arg = signal[pos + 1 :]
131 signal = signal[0:pos]
132 pos = arg.find("]")
133 arg = arg[0:pos]
134 apply_annotation(iface_list, iface, None, signal, None, arg, key, value)
135 else:
136 apply_annotation(
137 iface_list, iface, None, signal, None, None, key, value
138 )
139 else:
140 pos = what.find(":")
141 if pos != -1:
142 # property
143 iface = what[0:pos]
144 prop = what[pos + 1 :]
145 apply_annotation(iface_list, iface, None, None, prop, None, key, value)
146 else:
147 pos = what.find("()")
148 if pos != -1:
149 # method
150 combined = what[0:pos]
151 pos = combined.rfind(".")
152 iface = combined[0:pos]
153 method = combined[pos + 1 :]
154 pos = what.find("[")
155 if pos != -1:
156 arg = what[pos + 1 :]
157 pos = arg.find("]")
158 arg = arg[0:pos]
159 apply_annotation(
160 iface_list, iface, method, None, None, arg, key, value
161 )
162 else:
163 apply_annotation(
164 iface_list, iface, method, None, None, None, key, value
165 )
166 else:
167 # must be an interface
168 iface = what
169 apply_annotation(
170 iface_list, iface, None, None, None, None, key, value
171 )
172
173
174 def codegen_main():
175 arg_parser = argparse.ArgumentParser(
176 description="D-Bus code and documentation generator"
177 )
178 arg_parser.add_argument(
179 "files", metavar="FILE", nargs="+", help="D-Bus introspection XML file"
180 )
181 arg_parser.add_argument(
182 "--xml-files",
183 metavar="FILE",
184 action="append",
185 default=[],
186 help=argparse.SUPPRESS,
187 )
188 arg_parser.add_argument(
189 "--interface-prefix",
190 metavar="PREFIX",
191 default="",
192 help="String to strip from D-Bus interface names for code and docs",
193 )
194 arg_parser.add_argument(
195 "--c-namespace",
196 metavar="NAMESPACE",
197 default="",
198 help="The namespace to use for generated C code",
199 )
200 arg_parser.add_argument(
201 "--c-generate-object-manager",
202 action="store_true",
203 help="Generate a GDBusObjectManagerClient subclass when generating C code",
204 )
205 arg_parser.add_argument(
206 "--c-generate-autocleanup",
207 choices=["none", "objects", "all"],
208 default="objects",
209 help="Generate autocleanup support",
210 )
211 arg_parser.add_argument(
212 "--generate-docbook",
213 metavar="OUTFILES",
214 help="Generate Docbook in OUTFILES-org.Project.IFace.xml",
215 )
216 arg_parser.add_argument(
217 "--generate-md",
218 metavar="OUTFILES",
219 help="Generate Markdown in OUTFILES-org.Project.IFace.md",
220 )
221 arg_parser.add_argument(
222 "--generate-rst",
223 metavar="OUTFILES",
224 help="Generate reStructuredText in OUTFILES-org.Project.IFace.rst",
225 )
226 arg_parser.add_argument(
227 "--pragma-once",
228 action="store_true",
229 help='Use "pragma once" as the inclusion guard',
230 )
231 arg_parser.add_argument(
232 "--annotate",
233 nargs=3,
234 action="append",
235 metavar="WHAT KEY VALUE",
236 help="Add annotation (may be used several times)",
237 )
238 arg_parser.add_argument(
239 "--glib-min-required",
240 metavar="VERSION",
241 help="Minimum version of GLib to be supported by the outputted code "
242 "(default: 2.30)",
243 )
244 arg_parser.add_argument(
245 "--glib-max-allowed",
246 metavar="VERSION",
247 help="Maximum version of GLib to be used by the outputted code "
248 "(default: current GLib version)",
249 )
250 arg_parser.add_argument(
251 "--symbol-decorator",
252 help="Macro used to decorate a symbol in the outputted header, "
253 "possibly to export symbols",
254 )
255 arg_parser.add_argument(
256 "--symbol-decorator-header",
257 help="Additional header required for decorator specified by "
258 "--symbol-decorator",
259 )
260 arg_parser.add_argument(
261 "--symbol-decorator-define",
262 help="Additional define required for decorator specified by "
263 "--symbol-decorator",
264 )
265
266 group = arg_parser.add_mutually_exclusive_group()
267 group.add_argument(
268 "--generate-c-code", metavar="OUTFILES", help="Generate C code in OUTFILES.[ch]"
269 )
270 group.add_argument("--header", action="store_true", help="Generate C headers")
271 group.add_argument("--body", action="store_true", help="Generate C code")
272 group.add_argument(
273 "--interface-info-header",
274 action="store_true",
275 help="Generate GDBusInterfaceInfo C header",
276 )
277 group.add_argument(
278 "--interface-info-body",
279 action="store_true",
280 help="Generate GDBusInterfaceInfo C code",
281 )
282
283 group = arg_parser.add_mutually_exclusive_group()
284 group.add_argument(
285 "--output", metavar="FILE", help="Write output into the specified file"
286 )
287 group.add_argument(
288 "--output-directory",
289 metavar="OUTDIR",
290 default="",
291 help="Location to output generated files",
292 )
293
294 args = arg_parser.parse_args()
295
296 if len(args.xml_files) > 0:
297 print_warning(
298 'The "--xml-files" option is deprecated; use positional arguments instead'
299 )
300
301 if (
302 args.generate_c_code is not None
303 or args.generate_docbook is not None
304 or args.generate_md is not None
305 or args.generate_rst is not None
306 ) and args.output is not None:
307 print_error(
308 "Using --generate-c-code or --generate-{docbook,md,rst} and "
309 "--output at the same time is not allowed"
310 )
311
312 if args.generate_c_code:
313 header_name = args.generate_c_code + ".h"
314 h_file = os.path.join(args.output_directory, header_name)
315 args.header = True
316 c_file = os.path.join(args.output_directory, args.generate_c_code + ".c")
317 args.body = True
318 elif args.header:
319 if args.output is None:
320 print_error("Using --header requires --output")
321
322 h_file = args.output
323 header_name = os.path.basename(h_file)
324 elif args.body:
325 if args.output is None:
326 print_error("Using --body requires --output")
327
328 c_file = args.output
329 header_name = os.path.splitext(os.path.basename(c_file))[0] + ".h"
330 elif args.interface_info_header:
331 if args.output is None:
332 print_error("Using --interface-info-header requires --output")
333 if args.c_generate_object_manager:
334 print_error(
335 "--c-generate-object-manager is incompatible with "
336 "--interface-info-header"
337 )
338
339 h_file = args.output
340 header_name = os.path.basename(h_file)
341 elif args.interface_info_body:
342 if args.output is None:
343 print_error("Using --interface-info-body requires --output")
344 if args.c_generate_object_manager:
345 print_error(
346 "--c-generate-object-manager is incompatible with "
347 "--interface-info-body"
348 )
349
350 c_file = args.output
351 header_name = os.path.splitext(os.path.basename(c_file))[0] + ".h"
352
353 # Check the minimum GLib version. The minimum --glib-min-required is 2.30,
354 # because that’s when gdbus-codegen was introduced. Support 1, 2 or 3
355 # component versions, but ignore the micro component if it’s present.
356 if args.glib_min_required:
357 try:
358 parts = args.glib_min_required.split(".", 3)
359 glib_min_required = (int(parts[0]), int(parts[1] if len(parts) > 1 else 0))
360 # Ignore micro component, but still validate it:
361 _ = int(parts[2] if len(parts) > 2 else 0) # noqa: F841
362 except (ValueError, IndexError):
363 print_error(
364 "Unrecognized --glib-min-required string ‘{}’".format(
365 args.glib_min_required
366 )
367 )
368
369 if glib_min_required < (2, 30):
370 print_error(
371 "Invalid --glib-min-required string ‘{}’: minimum "
372 "version is 2.30".format(args.glib_min_required)
373 )
374 else:
375 glib_min_required = (2, 30)
376
377 # And the maximum GLib version.
378 if args.glib_max_allowed:
379 try:
380 parts = args.glib_max_allowed.split(".", 3)
381 glib_max_allowed = (int(parts[0]), int(parts[1] if len(parts) > 1 else 0))
382 # Ignore micro component, but still validate it:
383 _ = int(parts[2] if len(parts) > 2 else 0) # noqa: F841
384 except (ValueError, IndexError):
385 print_error(
386 "Unrecognized --glib-max-allowed string ‘{}’".format(
387 args.glib_max_allowed
388 )
389 )
390 else:
391 glib_max_allowed = (config.MAJOR_VERSION, config.MINOR_VERSION)
392
393 # Only allow --symbol-decorator-define and --symbol-decorator-header if
394 # --symbol-decorator is used
395 if args.symbol_decorator is None:
396 if args.symbol_decorator_header or args.symbol_decorator_define:
397 print_error(
398 "--symbol-decorator-define and --symbol-decorator-header must "
399 "be used with --symbol-decorator"
400 )
401
402 # Round --glib-max-allowed up to the next stable release.
403 glib_max_allowed = (
404 glib_max_allowed[0],
405 glib_max_allowed[1] + (glib_max_allowed[1] % 2),
406 )
407
408 if glib_max_allowed < glib_min_required:
409 print_error(
410 "Invalid versions: --glib-min-required ({}) must be "
411 "less than or equal to --glib-max-allowed ({})".format(
412 glib_min_required, glib_max_allowed
413 )
414 )
415
416 all_ifaces = []
417 input_files_basenames = []
418 for fname in sorted(args.files + args.xml_files):
419 with open(fname, "rb") as f:
420 xml_data = f.read()
421 parsed_ifaces = parser.parse_dbus_xml(
422 xml_data, h_type_implies_unix_fd=(glib_min_required >= (2, 64))
423 )
424 all_ifaces.extend(parsed_ifaces)
425 input_files_basenames.append(os.path.basename(fname))
426
427 if args.annotate is not None:
428 apply_annotations(all_ifaces, args.annotate)
429
430 for i in all_ifaces:
431 i.post_process(args.interface_prefix, args.c_namespace)
432
433 docbook = args.generate_docbook
434 docbook_gen = codegen_docbook.DocbookCodeGenerator(all_ifaces)
435 if docbook:
436 docbook_gen.generate(docbook, args.output_directory)
437
438 md = args.generate_md
439 md_gen = codegen_md.MdCodeGenerator(all_ifaces)
440 if md:
441 md_gen.generate(md, args.output_directory)
442
443 rst = args.generate_rst
444 rst_gen = codegen_rst.RstCodeGenerator(all_ifaces)
445 if rst:
446 rst_gen.generate(rst, args.output_directory)
447
448 if args.header:
449 with open(h_file, "w") as outfile:
450 gen = codegen.HeaderCodeGenerator(
451 all_ifaces,
452 args.c_namespace,
453 args.c_generate_object_manager,
454 args.c_generate_autocleanup,
455 header_name,
456 input_files_basenames,
457 args.pragma_once,
458 glib_min_required,
459 args.symbol_decorator,
460 args.symbol_decorator_header,
461 outfile,
462 )
463 gen.generate()
464
465 if args.body:
466 with open(c_file, "w") as outfile:
467 gen = codegen.CodeGenerator(
468 all_ifaces,
469 args.c_namespace,
470 args.c_generate_object_manager,
471 header_name,
472 input_files_basenames,
473 docbook_gen,
474 glib_min_required,
475 args.symbol_decorator_define,
476 outfile,
477 )
478 gen.generate()
479
480 if args.interface_info_header:
481 with open(h_file, "w") as outfile:
482 gen = codegen.InterfaceInfoHeaderCodeGenerator(
483 all_ifaces,
484 args.c_namespace,
485 header_name,
486 input_files_basenames,
487 args.pragma_once,
488 glib_min_required,
489 args.symbol_decorator,
490 args.symbol_decorator_header,
491 outfile,
492 )
493 gen.generate()
494
495 if args.interface_info_body:
496 with open(c_file, "w") as outfile:
497 gen = codegen.InterfaceInfoBodyCodeGenerator(
498 all_ifaces,
499 args.c_namespace,
500 header_name,
501 input_files_basenames,
502 glib_min_required,
503 args.symbol_decorator_define,
504 outfile,
505 )
506 gen.generate()
507
508 sys.exit(0)
509
510
511 if __name__ == "__main__":
512 codegen_main()