| # -*- Mode: Python -*- | |
| # coding=utf-8 | |
| # GDBus - GLib D-Bus Library | |
| # | |
| # Copyright (C) 2008-2011 Red Hat, Inc. | |
| # Copyright (C) 2018 Iñigo Martínez <inigomartinez@gmail.com> | |
| # | |
| # SPDX-License-Identifier: LGPL-2.1-or-later | |
| # | |
| # This library is free software; you can redistribute it and/or | |
| # modify it under the terms of the GNU Lesser General Public | |
| # License as published by the Free Software Foundation; either | |
| # version 2.1 of the License, or (at your option) any later version. | |
| # | |
| # This library is distributed in the hope that it will be useful, | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| # Lesser General Public License for more details. | |
| # | |
| # You should have received a copy of the GNU Lesser General | |
| # Public License along with this library; if not, see <http://www.gnu.org/licenses/>. | |
| # | |
| # Author: David Zeuthen <davidz@redhat.com> | |
| import argparse | |
| import os | |
| import sys | |
| from contextlib import contextmanager | |
| from . import config | |
| from . import dbustypes | |
| from . import parser | |
| from . import codegen | |
| from . import codegen_docbook | |
| from . import codegen_md | |
| from . import codegen_rst | |
| from .utils import print_error, print_warning | |
| def find_arg(arg_list, arg_name): | |
| for a in arg_list: | |
| if a.name == arg_name: | |
| return a | |
| return None | |
| def find_method(iface, method): | |
| for m in iface.methods: | |
| if m.name == method: | |
| return m | |
| return None | |
| def find_signal(iface, signal): | |
| for m in iface.signals: | |
| if m.name == signal: | |
| return m | |
| return None | |
| def find_prop(iface, prop): | |
| for m in iface.properties: | |
| if m.name == prop: | |
| return m | |
| return None | |
| @contextmanager | |
| def file_or_stdout(filename): | |
| if filename is None or filename == "-": | |
| yield sys.stdout | |
| else: | |
| with open(filename, "w") as outfile: | |
| yield outfile | |
| def apply_annotation(iface_list, iface, method, signal, prop, arg, key, value): | |
| iface_obj = None | |
| for i in iface_list: | |
| if i.name == iface: | |
| iface_obj = i | |
| break | |
| if iface_obj is None: | |
| print_error('No interface "{}"'.format(iface)) | |
| target_obj = None | |
| if method: | |
| method_obj = find_method(iface_obj, method) | |
| if method_obj is None: | |
| print_error('No method "{}" on interface "{}"'.format(method, iface)) | |
| if arg: | |
| arg_obj = find_arg(method_obj.in_args, arg) | |
| if arg_obj is None: | |
| arg_obj = find_arg(method_obj.out_args, arg) | |
| if arg_obj is None: | |
| print_error( | |
| 'No arg "{}" on method "{}" on interface "{}"'.format( | |
| arg, method, iface | |
| ) | |
| ) | |
| target_obj = arg_obj | |
| else: | |
| target_obj = method_obj | |
| elif signal: | |
| signal_obj = find_signal(iface_obj, signal) | |
| if signal_obj is None: | |
| print_error('No signal "{}" on interface "{}"'.format(signal, iface)) | |
| if arg: | |
| arg_obj = find_arg(signal_obj.args, arg) | |
| if arg_obj is None: | |
| print_error( | |
| 'No arg "{}" on signal "{}" on interface "{}"'.format( | |
| arg, signal, iface | |
| ) | |
| ) | |
| target_obj = arg_obj | |
| else: | |
| target_obj = signal_obj | |
| elif prop: | |
| prop_obj = find_prop(iface_obj, prop) | |
| if prop_obj is None: | |
| print_error('No property "{}" on interface "{}"'.format(prop, iface)) | |
| target_obj = prop_obj | |
| else: | |
| target_obj = iface_obj | |
| target_obj.annotations.insert(0, dbustypes.Annotation(key, value)) | |
| def apply_annotations(iface_list, annotation_list): | |
| # apply annotations given on the command line | |
| for what, key, value in annotation_list: | |
| pos = what.find("::") | |
| if pos != -1: | |
| # signal | |
| iface = what[0:pos] | |
| signal = what[pos + 2 :] | |
| pos = signal.find("[") | |
| if pos != -1: | |
| arg = signal[pos + 1 :] | |
| signal = signal[0:pos] | |
| pos = arg.find("]") | |
| arg = arg[0:pos] | |
| apply_annotation(iface_list, iface, None, signal, None, arg, key, value) | |
| else: | |
| apply_annotation( | |
| iface_list, iface, None, signal, None, None, key, value | |
| ) | |
| else: | |
| pos = what.find(":") | |
| if pos != -1: | |
| # property | |
| iface = what[0:pos] | |
| prop = what[pos + 1 :] | |
| apply_annotation(iface_list, iface, None, None, prop, None, key, value) | |
| else: | |
| pos = what.find("()") | |
| if pos != -1: | |
| # method | |
| combined = what[0:pos] | |
| pos = combined.rfind(".") | |
| iface = combined[0:pos] | |
| method = combined[pos + 1 :] | |
| pos = what.find("[") | |
| if pos != -1: | |
| arg = what[pos + 1 :] | |
| pos = arg.find("]") | |
| arg = arg[0:pos] | |
| apply_annotation( | |
| iface_list, iface, method, None, None, arg, key, value | |
| ) | |
| else: | |
| apply_annotation( | |
| iface_list, iface, method, None, None, None, key, value | |
| ) | |
| else: | |
| # must be an interface | |
| iface = what | |
| apply_annotation( | |
| iface_list, iface, None, None, None, None, key, value | |
| ) | |
| def codegen_main(): | |
| arg_parser = argparse.ArgumentParser( | |
| description="D-Bus code and documentation generator" | |
| ) | |
| arg_parser.add_argument( | |
| "files", metavar="FILE", nargs="+", help="D-Bus introspection XML file" | |
| ) | |
| arg_parser.add_argument( | |
| "--xml-files", | |
| metavar="FILE", | |
| action="append", | |
| default=[], | |
| help=argparse.SUPPRESS, | |
| ) | |
| arg_parser.add_argument( | |
| "--interface-prefix", | |
| metavar="PREFIX", | |
| default="", | |
| help="String to strip from D-Bus interface names for code and docs", | |
| ) | |
| arg_parser.add_argument( | |
| "--c-namespace", | |
| metavar="NAMESPACE", | |
| default="", | |
| help="The namespace to use for generated C code", | |
| ) | |
| arg_parser.add_argument( | |
| "--c-generate-object-manager", | |
| action="store_true", | |
| help="Generate a GDBusObjectManagerClient subclass when generating C code", | |
| ) | |
| arg_parser.add_argument( | |
| "--c-generate-autocleanup", | |
| choices=["none", "objects", "all"], | |
| default="objects", | |
| help="Generate autocleanup support", | |
| ) | |
| arg_parser.add_argument( | |
| "--generate-docbook", | |
| metavar="OUTFILES", | |
| help="Generate Docbook in OUTFILES-org.Project.IFace.xml", | |
| ) | |
| arg_parser.add_argument( | |
| "--generate-md", | |
| metavar="OUTFILES", | |
| help="Generate Markdown in OUTFILES-org.Project.IFace.md", | |
| ) | |
| arg_parser.add_argument( | |
| "--generate-rst", | |
| metavar="OUTFILES", | |
| help="Generate reStructuredText in OUTFILES-org.Project.IFace.rst", | |
| ) | |
| arg_parser.add_argument( | |
| "--pragma-once", | |
| action="store_true", | |
| help='Use "pragma once" as the inclusion guard', | |
| ) | |
| arg_parser.add_argument( | |
| "--annotate", | |
| nargs=3, | |
| action="append", | |
| metavar="WHAT KEY VALUE", | |
| help="Add annotation (may be used several times)", | |
| ) | |
| arg_parser.add_argument( | |
| "--glib-min-required", | |
| metavar="VERSION", | |
| help="Minimum version of GLib to be supported by the outputted code " | |
| "(default: 2.30)", | |
| ) | |
| arg_parser.add_argument( | |
| "--glib-max-allowed", | |
| metavar="VERSION", | |
| help="Maximum version of GLib to be used by the outputted code " | |
| "(default: current GLib version)", | |
| ) | |
| arg_parser.add_argument( | |
| "--symbol-decorator", | |
| help="Macro used to decorate a symbol in the outputted header, " | |
| "possibly to export symbols", | |
| ) | |
| arg_parser.add_argument( | |
| "--symbol-decorator-header", | |
| help="Additional header required for decorator specified by " | |
| "--symbol-decorator", | |
| ) | |
| arg_parser.add_argument( | |
| "--symbol-decorator-define", | |
| help="Additional define required for decorator specified by " | |
| "--symbol-decorator", | |
| ) | |
| group = arg_parser.add_mutually_exclusive_group() | |
| group.add_argument( | |
| "--generate-c-code", metavar="OUTFILES", help="Generate C code in OUTFILES.[ch]" | |
| ) | |
| group.add_argument("--header", action="store_true", help="Generate C headers") | |
| group.add_argument("--body", action="store_true", help="Generate C code") | |
| group.add_argument( | |
| "--interface-info-header", | |
| action="store_true", | |
| help="Generate GDBusInterfaceInfo C header", | |
| ) | |
| group.add_argument( | |
| "--interface-info-body", | |
| action="store_true", | |
| help="Generate GDBusInterfaceInfo C code", | |
| ) | |
| group = arg_parser.add_mutually_exclusive_group() | |
| group.add_argument( | |
| "--output", metavar="FILE", help="Write output into the specified file" | |
| ) | |
| group.add_argument( | |
| "--output-directory", | |
| metavar="OUTDIR", | |
| default="", | |
| help="Location to output generated files", | |
| ) | |
| args = arg_parser.parse_args() | |
| if len(args.xml_files) > 0: | |
| print_warning( | |
| 'The "--xml-files" option is deprecated; use positional arguments instead' | |
| ) | |
| if ( | |
| args.generate_c_code is not None | |
| or args.generate_docbook is not None | |
| or args.generate_md is not None | |
| or args.generate_rst is not None | |
| ) and args.output is not None: | |
| print_error( | |
| "Using --generate-c-code or --generate-{docbook,md,rst} and " | |
| "--output at the same time is not allowed" | |
| ) | |
| if args.generate_c_code: | |
| header_name = args.generate_c_code + ".h" | |
| h_file = os.path.join(args.output_directory, header_name) | |
| args.header = True | |
| c_file = os.path.join(args.output_directory, args.generate_c_code + ".c") | |
| args.body = True | |
| elif args.header: | |
| if args.output is None: | |
| print_error("Using --header requires --output") | |
| h_file = args.output | |
| header_name = os.path.basename(h_file) | |
| elif args.body: | |
| if args.output is None: | |
| print_error("Using --body requires --output") | |
| c_file = args.output | |
| if c_file == "-": | |
| header_name = "" | |
| else: | |
| header_name = os.path.splitext(os.path.basename(c_file))[0] + ".h" | |
| elif args.interface_info_header: | |
| if args.output is None: | |
| print_error("Using --interface-info-header requires --output") | |
| if args.c_generate_object_manager: | |
| print_error( | |
| "--c-generate-object-manager is incompatible with " | |
| "--interface-info-header" | |
| ) | |
| h_file = args.output | |
| header_name = os.path.basename(h_file) | |
| elif args.interface_info_body: | |
| if args.output is None: | |
| print_error("Using --interface-info-body requires --output") | |
| if args.c_generate_object_manager: | |
| print_error( | |
| "--c-generate-object-manager is incompatible with " | |
| "--interface-info-body" | |
| ) | |
| c_file = args.output | |
| if c_file == "-": | |
| header_name = "" | |
| else: | |
| header_name = os.path.splitext(os.path.basename(c_file))[0] + ".h" | |
| # Check the minimum GLib version. The minimum --glib-min-required is 2.30, | |
| # because that’s when gdbus-codegen was introduced. Support 1, 2 or 3 | |
| # component versions, but ignore the micro component if it’s present. | |
| if args.glib_min_required: | |
| try: | |
| parts = args.glib_min_required.split(".", 3) | |
| glib_min_required = (int(parts[0]), int(parts[1] if len(parts) > 1 else 0)) | |
| # Ignore micro component, but still validate it: | |
| _ = int(parts[2] if len(parts) > 2 else 0) # noqa: F841 | |
| except (ValueError, IndexError): | |
| print_error( | |
| "Unrecognized --glib-min-required string ‘{}’".format( | |
| args.glib_min_required | |
| ) | |
| ) | |
| if glib_min_required < (2, 30): | |
| print_error( | |
| "Invalid --glib-min-required string ‘{}’: minimum " | |
| "version is 2.30".format(args.glib_min_required) | |
| ) | |
| else: | |
| glib_min_required = (2, 30) | |
| # And the maximum GLib version. | |
| if args.glib_max_allowed: | |
| try: | |
| parts = args.glib_max_allowed.split(".", 3) | |
| glib_max_allowed = (int(parts[0]), int(parts[1] if len(parts) > 1 else 0)) | |
| # Ignore micro component, but still validate it: | |
| _ = int(parts[2] if len(parts) > 2 else 0) # noqa: F841 | |
| except (ValueError, IndexError): | |
| print_error( | |
| "Unrecognized --glib-max-allowed string ‘{}’".format( | |
| args.glib_max_allowed | |
| ) | |
| ) | |
| else: | |
| glib_max_allowed = (config.MAJOR_VERSION, config.MINOR_VERSION) | |
| # Only allow --symbol-decorator-define and --symbol-decorator-header if | |
| # --symbol-decorator is used | |
| if args.symbol_decorator is None: | |
| if args.symbol_decorator_header or args.symbol_decorator_define: | |
| print_error( | |
| "--symbol-decorator-define and --symbol-decorator-header must " | |
| "be used with --symbol-decorator" | |
| ) | |
| # Round --glib-max-allowed up to the next stable release. | |
| glib_max_allowed = ( | |
| glib_max_allowed[0], | |
| glib_max_allowed[1] + (glib_max_allowed[1] % 2), | |
| ) | |
| if glib_max_allowed < glib_min_required: | |
| print_error( | |
| "Invalid versions: --glib-min-required ({}) must be " | |
| "less than or equal to --glib-max-allowed ({})".format( | |
| glib_min_required, glib_max_allowed | |
| ) | |
| ) | |
| all_ifaces = [] | |
| input_files_basenames = [] | |
| for fname in sorted(args.files + args.xml_files): | |
| with open(fname, "rb") as f: | |
| xml_data = f.read() | |
| parsed_ifaces = parser.parse_dbus_xml( | |
| xml_data, h_type_implies_unix_fd=(glib_min_required >= (2, 64)) | |
| ) | |
| all_ifaces.extend(parsed_ifaces) | |
| input_files_basenames.append(os.path.basename(fname)) | |
| if args.annotate is not None: | |
| apply_annotations(all_ifaces, args.annotate) | |
| for i in all_ifaces: | |
| i.post_process(args.interface_prefix, args.c_namespace) | |
| docbook = args.generate_docbook | |
| docbook_gen = codegen_docbook.DocbookCodeGenerator(all_ifaces) | |
| if docbook: | |
| docbook_gen.generate(docbook, args.output_directory) | |
| md = args.generate_md | |
| md_gen = codegen_md.MdCodeGenerator(all_ifaces) | |
| if md: | |
| md_gen.generate(md, args.output_directory) | |
| rst = args.generate_rst | |
| rst_gen = codegen_rst.RstCodeGenerator(all_ifaces) | |
| if rst: | |
| rst_gen.generate(rst, args.output_directory) | |
| if args.header: | |
| with file_or_stdout(h_file) as outfile: | |
| gen = codegen.HeaderCodeGenerator( | |
| all_ifaces, | |
| args.c_namespace, | |
| args.c_generate_object_manager, | |
| args.c_generate_autocleanup, | |
| header_name, | |
| input_files_basenames, | |
| args.pragma_once, | |
| glib_min_required, | |
| args.symbol_decorator, | |
| args.symbol_decorator_header, | |
| outfile, | |
| ) | |
| gen.generate() | |
| if args.body: | |
| with file_or_stdout(c_file) as outfile: | |
| gen = codegen.CodeGenerator( | |
| all_ifaces, | |
| args.c_namespace, | |
| args.c_generate_object_manager, | |
| header_name, | |
| input_files_basenames, | |
| docbook_gen, | |
| glib_min_required, | |
| args.symbol_decorator_define, | |
| outfile, | |
| ) | |
| gen.generate() | |
| if args.interface_info_header: | |
| with file_or_stdout(h_file) as outfile: | |
| gen = codegen.InterfaceInfoHeaderCodeGenerator( | |
| all_ifaces, | |
| args.c_namespace, | |
| header_name, | |
| input_files_basenames, | |
| args.pragma_once, | |
| glib_min_required, | |
| args.symbol_decorator, | |
| args.symbol_decorator_header, | |
| outfile, | |
| ) | |
| gen.generate() | |
| if args.interface_info_body: | |
| with file_or_stdout(c_file) as outfile: | |
| gen = codegen.InterfaceInfoBodyCodeGenerator( | |
| all_ifaces, | |
| args.c_namespace, | |
| header_name, | |
| input_files_basenames, | |
| glib_min_required, | |
| args.symbol_decorator_define, | |
| outfile, | |
| ) | |
| gen.generate() | |
| sys.exit(0) | |
| if __name__ == "__main__": | |
| codegen_main() |