From fe823e3cfe25c96de5e453d1acbdc036892a9c36 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Tue, 17 Apr 2018 14:07:50 +0100 Subject: [PATCH 1/4] codegen: Support Since and name changing annotations on annotations Recursive annotations do seem to be supported, so we should support them properly in the type system representation. This currently introduces no behavioural changes, but will be used in upcoming commits. Signed-off-by: Philip Withnall https://bugzilla.gnome.org/show_bug.cgi?id=795304 --- gio/gdbus-2.0/codegen/dbustypes.py | 33 ++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/gio/gdbus-2.0/codegen/dbustypes.py b/gio/gdbus-2.0/codegen/dbustypes.py index 359880ff7..29222f987 100644 --- a/gio/gdbus-2.0/codegen/dbustypes.py +++ b/gio/gdbus-2.0/codegen/dbustypes.py @@ -27,6 +27,25 @@ class Annotation: self.key = key self.value = value self.annotations = [] + self.since = '' + + def post_process(self, interface_prefix, cns, cns_upper, cns_lower, container): + key = self.key + overridden_key = utils.lookup_annotation(self.annotations, 'org.gtk.GDBus.C.Name') + if utils.is_ugly_case(overridden_key): + self.key_lower = overridden_key.lower() + else: + if overridden_key: + key = overridden_key + self.key_lower = utils.camel_case_to_uscore(key).lower().replace('-', '_').replace('.', '_') + + if len(self.since) == 0: + self.since = utils.lookup_since(self.annotations) + if len(self.since) == 0: + self.since = container.since + + for a in self.annotations: + a.post_process(interface_prefix, cns, cns_upper, cns_lower, self) class Arg: def __init__(self, name, signature): @@ -229,6 +248,8 @@ class Arg: self.gvalue_get = 'g_value_get_boxed' self.array_annotation = '(array zero-terminated=1)' + for a in self.annotations: + a.post_process(interface_prefix, cns, cns_upper, cns_lower, self) class Method: def __init__(self, name): @@ -270,6 +291,9 @@ class Method: if utils.lookup_annotation(self.annotations, 'org.freedesktop.DBus.Deprecated') == 'true': self.deprecated = True + for a in self.annotations: + a.post_process(interface_prefix, cns, cns_upper, cns_lower, self) + class Signal: def __init__(self, name): self.name = name @@ -305,6 +329,9 @@ class Signal: if utils.lookup_annotation(self.annotations, 'org.freedesktop.DBus.Deprecated') == 'true': self.deprecated = True + for a in self.annotations: + a.post_process(interface_prefix, cns, cns_upper, cns_lower, self) + class Property: def __init__(self, name, signature, access): self.name = name @@ -357,6 +384,9 @@ class Property: if utils.lookup_annotation(self.annotations, 'org.freedesktop.DBus.Deprecated') == 'true': self.deprecated = True + for a in self.annotations: + a.post_process(interface_prefix, cns, cns_upper, cns_lower, self) + # FIXME: for now we only support 'false' and 'const' on the signal itself, see #674913 and # http://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format # for details @@ -436,3 +466,6 @@ class Interface: for p in self.properties: p.post_process(interface_prefix, cns, cns_upper, cns_lower, self) + + for a in self.annotations: + a.post_process(interface_prefix, cns, cns_upper, cns_lower, self) -- 2.35.1 From dcb1c3fbd588dcf5cdcaeb65547fdbe176312e10 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Tue, 17 Apr 2018 14:10:07 +0100 Subject: [PATCH 2/4] codegen: Add --interface-info-[body|header] modes These generate basic .c and .h files containing the GDBusInterfaceInfo for a D-Bus introspection XML file, but no other code (no skeletons, proxies, GObjects, etc.). This is useful for projects who want to describe their D-Bus interfaces using introspection XML, but who wish to implement the interfaces manually (for various reasons, typically because the skeletons generated by gdbus-codegen are too simplistic and limiting). Previously, these projects would have had to write the GDBusInterfaceInfo manually, which is painstaking and error-prone. The new --interface-info-[body|header] options are very similar to --[body|header], but mutually exclusive with them. Signed-off-by: Philip Withnall https://bugzilla.gnome.org/show_bug.cgi?id=795304 --- docs/reference/gio/gdbus-codegen.xml | 65 +++++- gio/gdbus-2.0/codegen/codegen.py | 280 ++++++++++++++++++++++++++ gio/gdbus-2.0/codegen/codegen_main.py | 39 ++++ 3 files changed, 377 insertions(+), 7 deletions(-) diff --git a/docs/reference/gio/gdbus-codegen.xml b/docs/reference/gio/gdbus-codegen.xml index b1145e5ef..3e1a9d668 100644 --- a/docs/reference/gio/gdbus-codegen.xml +++ b/docs/reference/gio/gdbus-codegen.xml @@ -39,6 +39,8 @@ FILE + + OUTFILE @@ -69,7 +71,11 @@ arguments on the command line and generates output files. It currently supports generating C source code (via ) or header (via ) - and Docbook XML (via ). + and Docbook XML (via ). Alternatively, + more restricted C source code and headers can be generated, which just + contain the interface information (as GDBusInterfaceInfo + structures) using and + . @@ -90,8 +96,11 @@ For C code generation either that - generates source code, or that - generates headers, can be used. These options must be used along with + generates source code, that + generates headers, that generates + interface information source code, or + that generates interface information + headers, can be used. These options must be used along with , which is used to specify the file to output to. @@ -282,8 +291,10 @@ Directory to output generated source to. Equivalent to changing directory before generation. - This option cannot be used with neither nor - , and must be used. + This option cannot be used with , + , or + ; and + must be used. @@ -321,12 +332,52 @@ + + + + + If this option is passed, it will generate the header code for the + GDBusInterfaceInfo structures only and will write it to + the disk by using the path and file name provided by + . + + + Using , or + are not allowed to be used along with + the and + options, because these options + are used to generate only one file. + + + + + + + + + If this option is passed, it will generate the source code for the + GDBusInterfaceInfo structures only and will write it to + the disk by using the path and file name provided by + . + + + Using , or + are not allowed to be used along with + the and + options, because these options + are used to generate only one file. + + + + OUTFILE - The full path where the header () or the source code - () will be written, using the path and filename provided by + The full path where the header (, + ) or the source code + (, ) will + be written, using the path and filename provided by . The full path could be something like $($OUTFILE).{c,h}. diff --git a/gio/gdbus-2.0/codegen/codegen.py b/gio/gdbus-2.0/codegen/codegen.py index 442bd3f5d..4e258332d 100644 --- a/gio/gdbus-2.0/codegen/codegen.py +++ b/gio/gdbus-2.0/codegen/codegen.py @@ -610,6 +610,286 @@ class HeaderCodeGenerator: # ---------------------------------------------------------------------------------------------------- +class InterfaceInfoHeaderCodeGenerator: + def __init__(self, ifaces, namespace, header_name, use_pragma, outfile): + self.ifaces = ifaces + self.namespace, self.ns_upper, self.ns_lower = generate_namespace(namespace) + self.header_guard = header_name.upper().replace('.', '_').replace('-', '_').replace('/', '_').replace(':', '_') + self.use_pragma = use_pragma + self.outfile = outfile + + # ---------------------------------------------------------------------------------------------------- + + def generate_header_preamble(self): + self.outfile.write(LICENSE_STR.format(config.VERSION)) + self.outfile.write('\n') + + if self.use_pragma: + self.outfile.write('#pragma once\n') + else: + self.outfile.write('#ifndef __{!s}__\n'.format(self.header_guard)) + self.outfile.write('#define __{!s}__\n'.format(self.header_guard)) + + self.outfile.write('\n') + self.outfile.write('#include \n') + self.outfile.write('\n') + self.outfile.write('G_BEGIN_DECLS\n') + self.outfile.write('\n') + + # ---------------------------------------------------------------------------------------------------- + + def declare_infos(self): + for i in self.ifaces: + self.outfile.write('extern const GDBusInterfaceInfo %s_interface;\n' % i.name_lower) + + # ---------------------------------------------------------------------------------------------------- + + def generate_header_postamble(self): + self.outfile.write('\n') + self.outfile.write('G_END_DECLS\n') + + if not self.use_pragma: + self.outfile.write('\n') + self.outfile.write('#endif /* __{!s}__ */\n'.format(self.header_guard)) + + # ---------------------------------------------------------------------------------------------------- + + def generate(self): + self.generate_header_preamble() + self.declare_infos() + self.generate_header_postamble() + +# ---------------------------------------------------------------------------------------------------- + +class InterfaceInfoBodyCodeGenerator: + def __init__(self, ifaces, namespace, header_name, outfile): + self.ifaces = ifaces + self.namespace, self.ns_upper, self.ns_lower = generate_namespace(namespace) + self.header_name = header_name + self.outfile = outfile + + # ---------------------------------------------------------------------------------------------------- + + def generate_body_preamble(self): + self.outfile.write(LICENSE_STR.format(config.VERSION)) + self.outfile.write('\n') + self.outfile.write('#ifdef HAVE_CONFIG_H\n' + '# include "config.h"\n' + '#endif\n' + '\n' + '#include "%s"\n' + '\n' + '#include \n' + % (self.header_name)) + self.outfile.write('\n') + + # ---------------------------------------------------------------------------------------------------- + + def generate_array(self, array_name_lower, element_type, elements): + self.outfile.write('const %s * const %s[] =\n' % (element_type, array_name_lower)) + self.outfile.write('{\n') + for (_, name) in sorted(elements, key=utils.version_cmp_key): + self.outfile.write(' &%s,\n' % name) + self.outfile.write(' NULL,\n') + self.outfile.write('};\n') + self.outfile.write('\n') + + def define_annotations(self, array_name_lower, annotations): + if len(annotations) == 0: + return + + annotation_pointers = [] + + for a in annotations: + # Skip internal annotations. + if a.key.startswith('org.gtk.GDBus'): + continue + + self.define_annotations('%s__%s_annotations' % (array_name_lower, a.key_lower), a.annotations) + + self.outfile.write('const GDBusAnnotationInfo %s__%s_annotation =\n' % (array_name_lower, a.key_lower)) + self.outfile.write('{\n') + self.outfile.write(' -1, /* ref count */\n') + self.outfile.write(' (gchar *) "%s",\n' % a.key) + self.outfile.write(' (gchar *) "%s",\n' % a.value) + if len(a.annotations) > 0: + self.outfile.write(' (GDBusAnnotationInfo **) %s__%s_annotations,\n' % (array_name_lower, a.key_lower)) + else: + self.outfile.write(' NULL, /* no annotations */\n') + self.outfile.write('};\n') + self.outfile.write('\n') + + key = (a.since, '%s__%s_annotation' % (array_name_lower, a.key_lower)) + annotation_pointers.append(key) + + self.generate_array(array_name_lower, 'GDBusAnnotationInfo', + annotation_pointers) + + def define_args(self, array_name_lower, args): + if len(args) == 0: + return + + arg_pointers = [] + + for a in args: + self.define_annotations('%s__%s_arg_annotations' % (array_name_lower, a.name), a.annotations) + + self.outfile.write('const GDBusArgInfo %s__%s_arg =\n' % (array_name_lower, a.name)) + self.outfile.write('{\n') + self.outfile.write(' -1, /* ref count */\n') + self.outfile.write(' (gchar *) "%s",\n' % a.name) + self.outfile.write(' (gchar *) "%s",\n' % a.signature) + if len(a.annotations) > 0: + self.outfile.write(' (GDBusAnnotationInfo **) %s__%s_arg_annotations,\n' % (array_name_lower, a.name)) + else: + self.outfile.write(' NULL, /* no annotations */\n') + self.outfile.write('};\n') + self.outfile.write('\n') + + key = (a.since, '%s__%s_arg' % (array_name_lower, a.name)) + arg_pointers.append(key) + + self.generate_array(array_name_lower, 'GDBusArgInfo', arg_pointers) + + def define_infos(self): + for i in self.ifaces: + self.outfile.write('/* ------------------------------------------------------------------------ */\n') + self.outfile.write('/* Definitions for %s */\n' % i.name) + self.outfile.write('\n') + + # GDBusMethodInfos. + if len(i.methods) > 0: + method_pointers = [] + + for m in i.methods: + self.define_args('%s_interface__%s_method_in_args' % (i.name_lower, m.name_lower), m.in_args) + self.define_args('%s_interface__%s_method_out_args' % (i.name_lower, m.name_lower), m.out_args) + self.define_annotations('%s_interface__%s_method_annotations' % (i.name_lower, m.name_lower), m.annotations) + + self.outfile.write('const GDBusMethodInfo %s_interface__%s_method =\n' % (i.name_lower, m.name_lower)) + self.outfile.write('{\n') + self.outfile.write(' -1, /* ref count */\n') + self.outfile.write(' (gchar *) "%s",\n' % m.name) + if len(m.in_args) > 0: + self.outfile.write(' (GDBusArgInfo **) %s_interface__%s_method_in_args,\n' % (i.name_lower, m.name_lower)) + else: + self.outfile.write(' NULL, /* no in args */\n') + if len(m.out_args) > 0: + self.outfile.write(' (GDBusArgInfo **) %s_interface__%s_method_out_args,\n' % (i.name_lower, m.name_lower)) + else: + self.outfile.write(' NULL, /* no out args */\n') + if len(m.annotations) > 0: + self.outfile.write(' (GDBusAnnotationInfo **) %s_interface__%s_method_annotations,\n' % (i.name_lower, m.name_lower)) + else: + self.outfile.write(' NULL, /* no annotations */\n') + self.outfile.write('};\n') + self.outfile.write('\n') + + key = (m.since, '%s_interface__%s_method' % (i.name_lower, m.name_lower)) + method_pointers.append(key) + + self.generate_array('%s_interface_methods' % i.name_lower, + 'GDBusMethodInfo', method_pointers) + + # GDBusSignalInfos. + if len(i.signals) > 0: + signal_pointers = [] + + for s in i.signals: + self.define_args('%s_interface__%s_signal_args' % (i.name_lower, s.name_lower), s.args) + self.define_annotations('%s_interface__%s_signal_annotations' % (i.name_lower, s.name_lower), s.annotations) + + self.outfile.write('const GDBusSignalInfo %s_interface__%s_signal =\n' % (i.name_lower, s.name_lower)) + self.outfile.write('{\n') + self.outfile.write(' -1, /* ref count */\n') + self.outfile.write(' (gchar *) "%s",\n' % s.name) + if len(s.args) > 0: + self.outfile.write(' (GDBusArgInfo **) %s_interface__%s_signal_args,\n' % (i.name_lower, s.name_lower)) + else: + self.outfile.write(' NULL, /* no args */\n') + if len(s.annotations) > 0: + self.outfile.write(' (GDBusAnnotationInfo **) %s_interface__%s_signal_annotations,\n' % (i.name_lower, s.name_lower)) + else: + self.outfile.write(' NULL, /* no annotations */\n') + self.outfile.write('};\n') + self.outfile.write('\n') + + key = (m.since, '%s_interface__%s_signal' % (i.name_lower, s.name_lower)) + signal_pointers.append(key) + + self.generate_array('%s_interface_signals' % i.name_lower, + 'GDBusSignalInfo', signal_pointers) + + # GDBusPropertyInfos. + if len(i.properties) > 0: + property_pointers = [] + + for p in i.properties: + if p.readable and p.writable: + flags = 'G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE' + elif p.readable: + flags = 'G_DBUS_PROPERTY_INFO_FLAGS_READABLE' + elif p.writable: + flags = 'G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE' + else: + flags = 'G_DBUS_PROPERTY_INFO_FLAGS_NONE' + + self.define_annotations('%s_interface__%s_property_annotations' % (i.name_lower, p.name_lower), p.annotations) + + self.outfile.write('const GDBusPropertyInfo %s_interface__%s_property =\n' % (i.name_lower, p.name_lower)) + self.outfile.write('{\n') + self.outfile.write(' -1, /* ref count */\n') + self.outfile.write(' (gchar *) "%s",\n' % p.name) + self.outfile.write(' (gchar *) "%s",\n' % p.signature) + self.outfile.write(' %s,\n' % flags) + if len(p.annotations) > 0: + self.outfile.write(' (GDBusAnnotationInfo **) %s_interface__%s_property_annotations,\n' % (i.name_lower, p.name_lower)) + else: + self.outfile.write(' NULL, /* no annotations */\n') + self.outfile.write('};\n') + self.outfile.write('\n') + + key = (m.since, '%s_interface__%s_property' % (i.name_lower, p.name_lower)) + property_pointers.append(key) + + self.generate_array('%s_interface_properties' % i.name_lower, + 'GDBusPropertyInfo', property_pointers) + + # Finally the GDBusInterfaceInfo. + self.define_annotations('%s_interface_annotations' % i.name_lower, + i.annotations) + + self.outfile.write('const GDBusInterfaceInfo %s_interface =\n' % i.name_lower) + self.outfile.write('{\n') + self.outfile.write(' -1, /* ref count */\n') + self.outfile.write(' (gchar *) "%s",\n' % i.name) + if len(i.methods) > 0: + self.outfile.write(' (GDBusMethodInfo **) %s_interface_methods,\n' % i.name_lower) + else: + self.outfile.write(' NULL, /* no methods */\n') + if len(i.signals) > 0: + self.outfile.write(' (GDBusSignalInfo **) %s_interface_signals,\n' % i.name_lower) + else: + self.outfile.write(' NULL, /* no signals */\n') + if len(i.properties) > 0: + self.outfile.write(' (GDBusPropertyInfo **) %s_interface_properties,\n' % i.name_lower) + else: + self.outfile.write( 'NULL, /* no properties */\n') + if len(i.annotations) > 0: + self.outfile.write(' (GDBusAnnotationInfo **) %s_interface_annotations,\n' % i.name_lower) + else: + self.outfile.write(' NULL, /* no annotations */\n') + self.outfile.write('};\n') + self.outfile.write('\n') + + # ---------------------------------------------------------------------------------------------------- + + def generate(self): + self.generate_body_preamble() + self.define_infos() + +# ---------------------------------------------------------------------------------------------------- + class CodeGenerator: def __init__(self, ifaces, namespace, generate_objmanager, header_name, docbook_gen, outfile): diff --git a/gio/gdbus-2.0/codegen/codegen_main.py b/gio/gdbus-2.0/codegen/codegen_main.py index 1cfe7c1bb..37efb3bcf 100755 --- a/gio/gdbus-2.0/codegen/codegen_main.py +++ b/gio/gdbus-2.0/codegen/codegen_main.py @@ -175,6 +175,10 @@ def codegen_main(): 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', @@ -210,6 +214,24 @@ def codegen_main(): c_file = args.output 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 + header_name = os.path.splitext(os.path.basename(c_file))[0] + '.h' all_ifaces = [] for fname in args.files + args.xml_files: @@ -250,6 +272,23 @@ def codegen_main(): outfile) gen.generate() + if args.interface_info_header: + with open(h_file, 'w') as outfile: + gen = codegen.InterfaceInfoHeaderCodeGenerator(all_ifaces, + args.c_namespace, + header_name, + args.pragma_once, + outfile) + gen.generate() + + if args.interface_info_body: + with open(c_file, 'w') as outfile: + gen = codegen.InterfaceInfoBodyCodeGenerator(all_ifaces, + args.c_namespace, + header_name, + outfile) + gen.generate() + sys.exit(0) if __name__ == "__main__": -- 2.35.1 From 11de9adfe6f57521ea5ed881b6862480c742414c Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Tue, 17 Apr 2018 14:12:18 +0100 Subject: [PATCH 3/4] codegen: Suppress the old --xml-files option in the --help output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since it’s deprecated in favour of positional arguments, including it in the help output is confusing. Signed-off-by: Philip Withnall https://bugzilla.gnome.org/show_bug.cgi?id=795304 --- gio/gdbus-2.0/codegen/codegen_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gio/gdbus-2.0/codegen/codegen_main.py b/gio/gdbus-2.0/codegen/codegen_main.py index 37efb3bcf..d3763eb0f 100755 --- a/gio/gdbus-2.0/codegen/codegen_main.py +++ b/gio/gdbus-2.0/codegen/codegen_main.py @@ -152,7 +152,7 @@ def codegen_main(): 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='D-Bus introspection XML file') + 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='', -- 2.35.1 From b2b72837b0545e297db7ded8773377b4b6473a55 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Tue, 17 Apr 2018 14:13:05 +0100 Subject: [PATCH 4/4] codegen: Fix a minor Python linting warning This introduces no functional changes. Signed-off-by: Philip Withnall https://bugzilla.gnome.org/show_bug.cgi?id=795304 --- gio/gdbus-2.0/codegen/codegen_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gio/gdbus-2.0/codegen/codegen_main.py b/gio/gdbus-2.0/codegen/codegen_main.py index d3763eb0f..fa9c71373 100755 --- a/gio/gdbus-2.0/codegen/codegen_main.py +++ b/gio/gdbus-2.0/codegen/codegen_main.py @@ -240,7 +240,7 @@ def codegen_main(): parsed_ifaces = parser.parse_dbus_xml(xml_data) all_ifaces.extend(parsed_ifaces) - if args.annotate != None: + if args.annotate is not None: apply_annotations(all_ifaces, args.annotate) for i in all_ifaces: -- 2.35.1