mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-01-26 22:16:16 +01:00
Add support for org.gtk.GDBus.Since annotation
And use this for a) documentation purposes; and b) to preserve C ABI when an interface is extended. See https://bugzilla.gnome.org/show_bug.cgi?id=647577#c5 for more details. Also add test cases for this. Signed-off-by: David Zeuthen <davidz@redhat.com>
This commit is contained in:
parent
76d3653721
commit
34a28f2f06
@ -215,6 +215,28 @@ gdbus-codegen --c-namespace MyApp \
|
||||
</listitem>
|
||||
</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>
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import distutils.version
|
||||
|
||||
import config
|
||||
import utils
|
||||
@ -201,27 +202,60 @@ class CodeGenerator:
|
||||
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))
|
||||
|
@ -163,6 +163,8 @@ class DocbookCodeGenerator:
|
||||
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):
|
||||
@ -180,6 +182,8 @@ class DocbookCodeGenerator:
|
||||
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):
|
||||
@ -190,6 +194,8 @@ class DocbookCodeGenerator:
|
||||
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):
|
||||
@ -255,6 +261,8 @@ class DocbookCodeGenerator:
|
||||
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:
|
||||
|
@ -14,10 +14,13 @@ class Arg:
|
||||
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
|
||||
@ -158,10 +161,13 @@ class Method:
|
||||
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')
|
||||
@ -186,10 +192,13 @@ class Signal:
|
||||
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')
|
||||
@ -224,10 +233,13 @@ class Property:
|
||||
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')
|
||||
@ -250,12 +262,15 @@ class Interface:
|
||||
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:
|
||||
|
@ -133,6 +133,8 @@ class DBusXMLParser:
|
||||
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:
|
||||
@ -161,6 +163,8 @@ class DBusXMLParser:
|
||||
# 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:
|
||||
@ -191,6 +195,8 @@ class DBusXMLParser:
|
||||
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:
|
||||
@ -215,6 +221,8 @@ class DBusXMLParser:
|
||||
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:
|
||||
|
@ -48,6 +48,13 @@ def lookup_docs(annotations):
|
||||
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:
|
||||
|
@ -1,11 +1,13 @@
|
||||
<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
|
||||
@ -23,6 +25,7 @@
|
||||
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.
|
||||
@ -35,6 +38,7 @@
|
||||
<!--
|
||||
Jumped:
|
||||
@height: Height, in meters, that the animal jumped.
|
||||
@since: 2.30
|
||||
|
||||
Emitted when the animal decides to jump.
|
||||
-->
|
||||
|
@ -2144,6 +2144,27 @@ gpointer name_forcing_4 = foo_rocket123_get_speed_xyz;
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------------- */
|
||||
|
||||
/* 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[])
|
||||
@ -2167,6 +2188,7 @@ main (int argc,
|
||||
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();
|
||||
|
@ -338,4 +338,64 @@
|
||||
<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>
|
||||
|
Loading…
Reference in New Issue
Block a user