Commit 8916874e authored by Philip Withnall's avatar Philip Withnall

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's avatarPhilip Withnall <withnall@endlessm.com>

https://bugzilla.gnome.org/show_bug.cgi?id=795304
parent 99b64d40
...@@ -39,6 +39,8 @@ ...@@ -39,6 +39,8 @@
<arg><option>--xml-files</option> <replaceable>FILE</replaceable></arg> <arg><option>--xml-files</option> <replaceable>FILE</replaceable></arg>
<arg><option>--header</option></arg> <arg><option>--header</option></arg>
<arg><option>--body</option></arg> <arg><option>--body</option></arg>
<arg><option>--interface-info-header</option></arg>
<arg><option>--interface-info-body</option></arg>
<arg><option>--output</option> <replaceable>OUTFILE</replaceable></arg> <arg><option>--output</option> <replaceable>OUTFILE</replaceable></arg>
<group choice="plain" rep="repeat"> <group choice="plain" rep="repeat">
<arg> <arg>
...@@ -69,7 +71,11 @@ ...@@ -69,7 +71,11 @@
arguments on the command line and generates output files. arguments on the command line and generates output files.
It currently supports generating C source code (via It currently supports generating C source code (via
<option>--body</option>) or header (via <option>--header</option>) <option>--body</option>) or header (via <option>--header</option>)
and Docbook XML (via <option>--generate-docbook</option>). and Docbook XML (via <option>--generate-docbook</option>). Alternatively,
more restricted C source code and headers can be generated, which just
contain the interface information (as <type>GDBusInterfaceInfo</type>
structures) using <option>--interface-info-body</option> and
<option>--interface-info-header</option>.
</para> </para>
</refsect1> </refsect1>
...@@ -90,8 +96,11 @@ ...@@ -90,8 +96,11 @@
</para> </para>
<para> <para>
For C code generation either <option>--body</option> that For C code generation either <option>--body</option> that
generates source code, or <option>--header</option> that generates source code, <option>--header</option> that
generates headers, can be used. These options must be used along with generates headers, <option>--interface-info-body</option> that generates
interface information source code, or
<option>--interface-info-header</option> that generates interface information
headers, can be used. These options must be used along with
<option>--output</option>, which is used to specify the file to output to. <option>--output</option>, which is used to specify the file to output to.
</para> </para>
<para> <para>
...@@ -282,8 +291,10 @@ ...@@ -282,8 +291,10 @@
Directory to output generated source to. Equivalent to changing directory before generation. Directory to output generated source to. Equivalent to changing directory before generation.
</para> </para>
<para> <para>
This option cannot be used with neither <option>--body</option> nor This option cannot be used with <option>--body</option>,
<option>--header</option>, and <option>--output</option> must be used. <option>--header</option>, <option>--interface-info-body</option> or
<option>--interface-info-header</option>; and
<option>--output</option> must be used.
</para> </para>
</listitem> </listitem>
...@@ -321,12 +332,52 @@ ...@@ -321,12 +332,52 @@
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><option>--interface-info-header</option></term>
<listitem>
<para>
If this option is passed, it will generate the header code for the
<type>GDBusInterfaceInfo</type> structures only and will write it to
the disk by using the path and file name provided by
<option>--output</option>.
</para>
<para>
Using <option>--generate-c-code</option>, <option>--generate-docbook</option> or
<option>--output-directory</option> are not allowed to be used along with
the <option>--interface-info-header</option> and
<option>--interface-info-body</option> options, because these options
are used to generate only one file.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--interface-info-body</option></term>
<listitem>
<para>
If this option is passed, it will generate the source code for the
<type>GDBusInterfaceInfo</type> structures only and will write it to
the disk by using the path and file name provided by
<option>--output</option>.
</para>
<para>
Using <option>--generate-c-code</option>, <option>--generate-docbook</option> or
<option>--output-directory</option> are not allowed to be used along with
the <option>--interface-info-header</option> and
<option>--interface-info-body</option> options, because these options
are used to generate only one file.
</para>
</listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term><option>--output</option> <replaceable>OUTFILE</replaceable></term> <term><option>--output</option> <replaceable>OUTFILE</replaceable></term>
<listitem> <listitem>
<para> <para>
The full path where the header (<option>--header</option>) or the source code The full path where the header (<option>--header</option>,
(<option>--body</option>) will be written, using the path and filename provided by <option>--interface-info-header</option>) or the source code
(<option>--body</option>, <option>--interface-info-body</option>) will
be written, using the path and filename provided by
<option>--output</option>. The full path could be something like <option>--output</option>. The full path could be something like
<literal>$($OUTFILE).{c,h}</literal>. <literal>$($OUTFILE).{c,h}</literal>.
</para> </para>
......
...@@ -613,6 +613,286 @@ class HeaderCodeGenerator: ...@@ -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 <gio/gio.h>\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 <string.h>\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: class CodeGenerator:
def __init__(self, ifaces, namespace, generate_objmanager, header_name, def __init__(self, ifaces, namespace, generate_objmanager, header_name,
input_files_basenames, docbook_gen, outfile): input_files_basenames, docbook_gen, outfile):
......
...@@ -175,6 +175,10 @@ def codegen_main(): ...@@ -175,6 +175,10 @@ def codegen_main():
help='Generate C headers') help='Generate C headers')
group.add_argument('--body', action='store_true', group.add_argument('--body', action='store_true',
help='Generate C code') 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 = arg_parser.add_mutually_exclusive_group()
group.add_argument('--output', metavar='FILE', group.add_argument('--output', metavar='FILE',
...@@ -210,6 +214,24 @@ def codegen_main(): ...@@ -210,6 +214,24 @@ def codegen_main():
c_file = args.output c_file = args.output
header_name = os.path.splitext(os.path.basename(c_file))[0] + '.h' 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 = [] all_ifaces = []
input_files_basenames = [] input_files_basenames = []
...@@ -254,6 +276,23 @@ def codegen_main(): ...@@ -254,6 +276,23 @@ def codegen_main():
outfile) outfile)
gen.generate() 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) sys.exit(0)
if __name__ == "__main__": if __name__ == "__main__":
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment