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