From 99b64d4014f4cba8f9ae90733dc6dd41b21f09e6 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 bfc69f596..2b721bfbf 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 @@ -356,6 +383,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) + class Interface: def __init__(self, name): self.name = name @@ -429,3 +459,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) From 8916874ee6f3ff0f887dbe1eda55c23c2c0097ee 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 032a29ed5..307018571 100644 --- a/gio/gdbus-2.0/codegen/codegen.py +++ b/gio/gdbus-2.0/codegen/codegen.py @@ -613,6 +613,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, input_files_basenames, docbook_gen, outfile): diff --git a/gio/gdbus-2.0/codegen/codegen_main.py b/gio/gdbus-2.0/codegen/codegen_main.py index 65876a2e8..344cb4cc2 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 = [] input_files_basenames = [] @@ -254,6 +276,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__": From 3a1536ba345c2c1c1f1c80eb9b5ad636c42e4904 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 344cb4cc2..801c67f38 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='', From 0e3b13721088d9f87ad9918c54265a2bda0e498b 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 801c67f38..10ecf7785 100755 --- a/gio/gdbus-2.0/codegen/codegen_main.py +++ b/gio/gdbus-2.0/codegen/codegen_main.py @@ -242,7 +242,7 @@ def codegen_main(): all_ifaces.extend(parsed_ifaces) input_files_basenames.append(os.path.basename(fname)) - if args.annotate != None: + if args.annotate is not None: apply_annotations(all_ifaces, args.annotate) for i in all_ifaces: