• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
24import argparse
25import os
26import sys
27
28from . import config
29from . import dbustypes
30from . import parser
31from . import codegen
32from . import codegen_docbook
33from .utils import print_error, print_warning
34
35
36def 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
43def find_method(iface, method):
44    for m in iface.methods:
45        if m.name == method:
46            return m
47    return None
48
49
50def find_signal(iface, signal):
51    for m in iface.signals:
52        if m.name == signal:
53            return m
54    return None
55
56
57def find_prop(iface, prop):
58    for m in iface.properties:
59        if m.name == prop:
60            return m
61    return None
62
63
64def 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
118def 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
172def 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
486if __name__ == "__main__":
487    codegen_main()
488