</varlistentry>
<varlistentry>
+ <term><literal>org.gtk.GDBus.Since</literal></term>
+ <listitem>
+ <para>
+ Can be used on any <literal><interface></literal>,
+ <literal><method></literal>,
+ <literal><signal></literal> and
+ <literal><property></literal> element to specify the
+ version (any free-form string but compared using a
+ version-aware sort function) the element appeared in.
+ </para>
+ <para>
+ When generating C code, this field is used to ensure
+ function pointer order for preserving ABI/API.
+ </para>
+ <para>
+ When generating Docbook XML, the value of this tag appears
+ in the documentation.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>org.gtk.GDBus.C.ForceGVariant</literal></term>
<listitem>
<para>
import sys
import argparse
+import distutils.version
import config
import utils
self.h.write('struct _%sIface\n'%(i.camel_name))
self.h.write('{\n')
self.h.write(' GTypeInterface parent_iface;\n')
+
+ function_pointers = {}
+
if len(i.methods) > 0:
self.h.write('\n')
- self.h.write(' /* GObject signal class handlers for incoming D-Bus method calls: */\n')
for m in i.methods:
- self.h.write(' gboolean (*handle_%s) (\n'
- ' %s *object,\n'
- ' GDBusMethodInvocation *invocation'%(m.name_lower, i.camel_name))
+ key = (m.since, '_method_%s'%m.name_lower)
+ value = ' gboolean (*handle_%s) (\n'%(m.name_lower)
+ value += ' %s *object,\n'%(i.camel_name)
+ value += ' GDBusMethodInvocation *invocation'%()
for a in m.in_args:
- self.h.write(',\n %s%s'%(a.ctype_in, a.name))
- self.h.write(');\n')
- self.h.write('\n')
+ value += ',\n %s%s'%(a.ctype_in, a.name)
+ value += ');\n\n'
+ function_pointers[key] = value
+
if len(i.signals) > 0:
self.h.write('\n')
- self.h.write(' /* GObject signal class handlers for received D-Bus signals: */\n')
for s in i.signals:
- self.h.write(' void (*%s) (\n'
- ' %s *object'%(s.name_lower, i.camel_name))
+ key = (s.since, '_signal_%s'%s.name_lower)
+ value = ' void (*%s) (\n'%(s.name_lower)
+ value += ' %s *object'%(i.camel_name)
for a in s.args:
- self.h.write(',\n %s%s'%(a.ctype_in, a.name))
- self.h.write(');\n')
- self.h.write('\n')
+ value += ',\n %s%s'%(a.ctype_in, a.name)
+ value += ');\n\n'
+ function_pointers[key] = value
+
+ # Sort according to @since tag, then name.. this ensures
+ # that the function pointers don't change order assuming
+ # judicious use of @since
+ #
+ # Also use a proper version comparison function so e.g.
+ # 10.0 comes after 2.0.
+ #
+ # See https://bugzilla.gnome.org/show_bug.cgi?id=647577#c5
+ # for discussion
+
+ # I'm sure this could be a lot more elegant if I was
+ # more fluent in python...
+ def my_version_cmp(a, b):
+ if len(a[0]) > 0 and len(b[0]) > 0:
+ va = distutils.version.LooseVersion(a[0])
+ vb = distutils.version.LooseVersion(b[0])
+ ret = va.__cmp__(vb)
+ else:
+ ret = cmp(a[0], b[0])
+ if ret != 0:
+ return ret
+ return cmp(a[1], b[1])
+ keys = function_pointers.keys()
+ if len(keys) > 0:
+ keys.sort(cmp=my_version_cmp)
+ for key in keys:
+ self.h.write('%s'%function_pointers[key])
+
self.h.write('};\n')
self.h.write('\n')
self.h.write('GType %s_get_gtype (void) G_GNUC_CONST;\n'%(i.name_lower))
self.out.write(' <listitem><para>%s</para></listitem>\n'%(self.expand(a.doc_string)))
self.out.write('</varlistentry>\n'%())
self.out.write('</variablelist>\n')
+ if len(m.since) > 0:
+ self.out.write('<para role="since">Since %s</para>\n'%(m.since))
self.out.write('</refsect2>\n')
def print_signal(self, i, s):
self.out.write(' <listitem><para>%s</para></listitem>\n'%(self.expand(a.doc_string)))
self.out.write('</varlistentry>\n'%())
self.out.write('</variablelist>\n')
+ if len(s.since) > 0:
+ self.out.write('<para role="since">Since %s</para>\n'%(s.since))
self.out.write('</refsect2>\n')
def print_property(self, i, p):
self.print_property_prototype(i, p, in_synopsis=False)
self.out.write('</programlisting>\n')
self.out.write('<para>%s</para>\n'%(self.expand(p.doc_string)))
+ if len(p.since) > 0:
+ self.out.write('<para role="since">Since %s</para>\n'%(p.since))
self.out.write('</refsect2>\n')
def expand(self, s):
self.out.write('<refsect1 role="desc" id="gdbus-interface-%s">\n'%(utils.dots_to_hyphens(i.name)))
self.out.write(' <title role="desc.title">Description</title>\n'%())
self.out.write(' <para>%s</para>\n'%(self.expand(i.doc_string)))
+ if len(i.since) > 0:
+ self.out.write(' <para role="since">Since %s</para>\n'%(i.since))
self.out.write('</refsect1>\n'%())
if len(i.methods) > 0:
self.signature = signature
self.annotations = []
self.doc_string = ''
+ self.since = ''
def post_process(self, interface_prefix, c_namespace, arg_number):
if len(self.doc_string) == 0:
self.doc_string = utils.lookup_docs(self.annotations)
+ if len(self.since) == 0:
+ self.since = utils.lookup_since(self.annotations)
if self.name == None:
self.name = 'unnamed_arg%d'%arg_number
self.out_args = []
self.annotations = []
self.doc_string = ''
+ self.since = ''
def post_process(self, interface_prefix, c_namespace):
if len(self.doc_string) == 0:
self.doc_string = utils.lookup_docs(self.annotations)
+ if len(self.since) == 0:
+ self.since = utils.lookup_since(self.annotations)
name = self.name
overridden_name = utils.lookup_annotation(self.annotations, 'org.gtk.GDBus.Name')
self.args = []
self.annotations = []
self.doc_string = ''
+ self.since = ''
def post_process(self, interface_prefix, c_namespace):
if len(self.doc_string) == 0:
self.doc_string = utils.lookup_docs(self.annotations)
+ if len(self.since) == 0:
+ self.since = utils.lookup_since(self.annotations)
name = self.name
overridden_name = utils.lookup_annotation(self.annotations, 'org.gtk.GDBus.Name')
else:
raise RuntimeError('Invalid access type %s'%self.access)
self.doc_string = ''
+ self.since = ''
def post_process(self, interface_prefix, c_namespace):
if len(self.doc_string) == 0:
self.doc_string = utils.lookup_docs(self.annotations)
+ if len(self.since) == 0:
+ self.since = utils.lookup_since(self.annotations)
name = self.name
overridden_name = utils.lookup_annotation(self.annotations, 'org.gtk.GDBus.Name')
self.annotations = []
self.doc_string = ''
self.doc_string_brief = ''
+ self.since = ''
def post_process(self, interface_prefix, c_namespace):
if len(self.doc_string) == 0:
self.doc_string = utils.lookup_docs(self.annotations)
if len(self.doc_string_brief) == 0:
self.doc_string_brief = utils.lookup_brief_docs(self.annotations)
+ if len(self.since) == 0:
+ self.since = utils.lookup_since(self.annotations)
overridden_name = utils.lookup_annotation(self.annotations, 'org.gtk.GDBus.Name')
if overridden_name:
if self.doc_comment_params.has_key('short_description'):
short_description = self.doc_comment_params['short_description']
self._cur_object.doc_string_brief = short_description
+ if self.doc_comment_params.has_key('since'):
+ self._cur_object.since = self.doc_comment_params['since']
elif self.state == DBusXMLParser.STATE_INTERFACE:
if name == DBusXMLParser.STATE_METHOD:
# assign docs, if any
if attrs.has_key('name') and self.doc_comment_last_symbol == attrs['name']:
self._cur_object.doc_string = self.doc_comment_body
+ if self.doc_comment_params.has_key('since'):
+ self._cur_object.since = self.doc_comment_params['since']
elif self.state == DBusXMLParser.STATE_METHOD:
if name == DBusXMLParser.STATE_ARG:
doc_string = self.doc_comment_params[attrs['name']]
if doc_string != None:
self._cur_object.doc_string = doc_string
+ if self.doc_comment_params.has_key('since'):
+ self._cur_object.since = self.doc_comment_params['since']
elif self.state == DBusXMLParser.STATE_SIGNAL:
if name == DBusXMLParser.STATE_ARG:
doc_string = self.doc_comment_params[attrs['name']]
if doc_string != None:
self._cur_object.doc_string = doc_string
+ if self.doc_comment_params.has_key('since'):
+ self._cur_object.since = self.doc_comment_params['since']
elif self.state == DBusXMLParser.STATE_PROPERTY:
if name == DBusXMLParser.STATE_ANNOTATION:
else:
return s
+def lookup_since(annotations):
+ s = lookup_annotation(annotations, 'org.gtk.GDBus.Since')
+ if s == None:
+ return ''
+ else:
+ return s
+
def lookup_brief_docs(annotations):
s = lookup_annotation(annotations, 'org.gtk.GDBus.DocString.Short')
if s == None:
<node>
<!-- org.gtk.GDBus.Example.ObjectManager.Animal:
@short_description: Example docs generated by gdbus-codegen
+ @since: 2.30
This D-Bus interface is used to describe a simple animal.
-->
<interface name="org.gtk.GDBus.Example.ObjectManager.Animal">
<!-- Mood: The mood of the animal.
+ @since: 2.30
Known values for this property include
<literal>Happy</literal> and <literal>Sad</literal>. Use the
Poke:
@make_sad: Whether to make the animal sad.
@make_happy: Whether to make the animal happy.
+ @since: 2.30
Method used to changing the mood of the animal. See also the
#org.gtk.GDBus.Example.ObjectManager.Animal:Mood property.
<!--
Jumped:
@height: Height, in meters, that the animal jumped.
+ @since: 2.30
Emitted when the animal decides to jump.
-->
/* ---------------------------------------------------------------------------------------------------- */
+/* See https://bugzilla.gnome.org/show_bug.cgi?id=647577#c5 for details */
+
+#define CHECK_FIELD(name, v1, v2) g_assert_cmpint (G_STRUCT_OFFSET (FooChangingInterface##v1##Iface, name), ==, G_STRUCT_OFFSET (FooChangingInterface##v2##Iface, name));
+
+static void
+test_interface_stability (void)
+{
+ CHECK_FIELD(handle_foo_method, V1, V2);
+ CHECK_FIELD(handle_bar_method, V1, V2);
+ CHECK_FIELD(handle_baz_method, V1, V2);
+ CHECK_FIELD(foo_signal, V1, V2);
+ CHECK_FIELD(bar_signal, V1, V2);
+ CHECK_FIELD(baz_signal, V1, V2);
+ CHECK_FIELD(handle_new_method_in2, V2, V10);
+ CHECK_FIELD(new_signal_in2, V2, V10);
+}
+
+#undef CHECK_FIELD
+
+/* ---------------------------------------------------------------------------------------------------- */
+
int
main (int argc,
char *argv[])
usleep (500 * 1000);
g_test_add_func ("/gdbus/codegen/annotations", test_annotations);
+ g_test_add_func ("/gdbus/codegen/interface_stability", test_interface_stability);
g_test_add_func ("/gdbus/codegen/object-manager", test_object_manager);
ret = g_test_run();
<property name="FancyProperty" type="s" access="read"/>
</interface>
+ <interface name="ChangingInterfaceV1">
+ <method name="FooMethod"/>
+ <method name="BarMethod"/>
+ <method name="BazMethod"/>
+ <signal name="FooSignal"/>
+ <signal name="BarSignal"/>
+ <signal name="BazSignal"/>
+ </interface>
+
+ <interface name="ChangingInterfaceV2">
+ <!--
+ NewSignalIn2:
+ @since: 2.0
+ -->
+ <signal name="NewSignalIn2"/>
+ <!--
+ NewMethodIn2:
+ @since: 2.0
+ -->
+ <method name="NewMethodIn2"/>
+
+ <!-- reverse order -->
+ <signal name="BazSignal"/>
+ <signal name="BarSignal"/>
+ <signal name="FooSignal"/>
+ <method name="BazMethod"/>
+ <method name="BarMethod"/>
+ <method name="FooMethod"/>
+ </interface>
+
+ <interface name="ChangingInterfaceV10">
+ <!--
+ AddedSignalIn10:
+ @since: 10.0
+ -->
+ <signal name="AddedSignalIn10"/>
+ <method name="AddedMethodIn10">
+ <annotation name="org.gtk.GDBus.Since" value="10.0"/>
+ </method>
+
+ <!--
+ NewSignalIn2:
+ @since: 2.0
+ -->
+ <signal name="NewSignalIn2"/>
+ <!--
+ NewMethodIn2:
+ @since: 2.0
+ -->
+ <method name="NewMethodIn2"/>
+
+ <!-- reverse order -->
+ <signal name="BazSignal"/>
+ <signal name="BarSignal"/>
+ <signal name="FooSignal"/>
+ <method name="BazMethod"/>
+ <method name="BarMethod"/>
+ <method name="FooMethod"/>
+ </interface>
+
</node>