From 34a28f2f062281d9fb75fcd02c4df238def6396e Mon Sep 17 00:00:00 2001 From: David Zeuthen Date: Tue, 12 Apr 2011 16:17:28 -0400 Subject: [PATCH] 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 --- docs/reference/gio/gdbus-codegen.xml | 22 ++++++++++++ gio/gdbus-codegen/codegen.py | 60 ++++++++++++++++++++++++------- gio/gdbus-codegen/codegen_docbook.py | 8 +++++ gio/gdbus-codegen/dbustypes.py | 15 ++++++++ gio/gdbus-codegen/parser.py | 8 +++++ gio/gdbus-codegen/utils.py | 7 ++++ gio/tests/gdbus-example-objectmanager.xml | 4 +++ gio/tests/gdbus-test-codegen.c | 22 ++++++++++++ gio/tests/test-codegen.xml | 60 +++++++++++++++++++++++++++++++ 9 files changed, 193 insertions(+), 13 deletions(-) diff --git a/docs/reference/gio/gdbus-codegen.xml b/docs/reference/gio/gdbus-codegen.xml index 50cf264..d41d6f0 100644 --- a/docs/reference/gio/gdbus-codegen.xml +++ b/docs/reference/gio/gdbus-codegen.xml @@ -216,6 +216,28 @@ gdbus-codegen --c-namespace MyApp \ + org.gtk.GDBus.Since + + + Can be used on any <interface>, + <method>, + <signal> and + <property> element to specify the + version (any free-form string but compared using a + version-aware sort function) the element appeared in. + + + When generating C code, this field is used to ensure + function pointer order for preserving ABI/API. + + + When generating Docbook XML, the value of this tag appears + in the documentation. + + + + + org.gtk.GDBus.C.ForceGVariant diff --git a/gio/gdbus-codegen/codegen.py b/gio/gdbus-codegen/codegen.py index fbe2521..9ad1293 100644 --- a/gio/gdbus-codegen/codegen.py +++ b/gio/gdbus-codegen/codegen.py @@ -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)) diff --git a/gio/gdbus-codegen/codegen_docbook.py b/gio/gdbus-codegen/codegen_docbook.py index 889c965..0babb20 100644 --- a/gio/gdbus-codegen/codegen_docbook.py +++ b/gio/gdbus-codegen/codegen_docbook.py @@ -163,6 +163,8 @@ class DocbookCodeGenerator: self.out.write(' %s\n'%(self.expand(a.doc_string))) self.out.write('\n'%()) self.out.write('\n') + if len(m.since) > 0: + self.out.write('Since %s\n'%(m.since)) self.out.write('\n') def print_signal(self, i, s): @@ -180,6 +182,8 @@ class DocbookCodeGenerator: self.out.write(' %s\n'%(self.expand(a.doc_string))) self.out.write('\n'%()) self.out.write('\n') + if len(s.since) > 0: + self.out.write('Since %s\n'%(s.since)) self.out.write('\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('\n') self.out.write('%s\n'%(self.expand(p.doc_string))) + if len(p.since) > 0: + self.out.write('Since %s\n'%(p.since)) self.out.write('\n') def expand(self, s): @@ -255,6 +261,8 @@ class DocbookCodeGenerator: self.out.write('\n'%(utils.dots_to_hyphens(i.name))) self.out.write(' Description\n'%()) self.out.write(' %s\n'%(self.expand(i.doc_string))) + if len(i.since) > 0: + self.out.write(' Since %s\n'%(i.since)) self.out.write('\n'%()) if len(i.methods) > 0: diff --git a/gio/gdbus-codegen/dbustypes.py b/gio/gdbus-codegen/dbustypes.py index a0cecbb..5241371 100644 --- a/gio/gdbus-codegen/dbustypes.py +++ b/gio/gdbus-codegen/dbustypes.py @@ -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: diff --git a/gio/gdbus-codegen/parser.py b/gio/gdbus-codegen/parser.py index e5d93bc..a566c5c 100644 --- a/gio/gdbus-codegen/parser.py +++ b/gio/gdbus-codegen/parser.py @@ -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: diff --git a/gio/gdbus-codegen/utils.py b/gio/gdbus-codegen/utils.py index 4966077..0cb8d61 100644 --- a/gio/gdbus-codegen/utils.py +++ b/gio/gdbus-codegen/utils.py @@ -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: diff --git a/gio/tests/gdbus-example-objectmanager.xml b/gio/tests/gdbus-example-objectmanager.xml index 1ce7a11..1036b7a 100644 --- a/gio/tests/gdbus-example-objectmanager.xml +++ b/gio/tests/gdbus-example-objectmanager.xml @@ -1,11 +1,13 @@ diff --git a/gio/tests/gdbus-test-codegen.c b/gio/tests/gdbus-test-codegen.c index b70bd42..ea85a4b 100644 --- a/gio/tests/gdbus-test-codegen.c +++ b/gio/tests/gdbus-test-codegen.c @@ -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(); diff --git a/gio/tests/test-codegen.xml b/gio/tests/test-codegen.xml index 328d888..236d5fd 100644 --- a/gio/tests/test-codegen.xml +++ b/gio/tests/test-codegen.xml @@ -338,4 +338,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- 2.7.4