[glib/gdbus-codegen] Add support for org.gtk.GDBus.Since annotation



commit 34a28f2f062281d9fb75fcd02c4df238def6396e
Author: David Zeuthen <davidz redhat com>
Date:   Tue Apr 12 16:17:28 2011 -0400

    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>

 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                           \
     </varlistentry>
 
     <varlistentry>
+      <term><literal>org.gtk.GDBus.Since</literal></term>
+      <listitem>
+        <para>
+          Can be used on any <literal>&lt;interface&gt;</literal>,
+          <literal>&lt;method&gt;</literal>,
+          <literal>&lt;signal&gt;</literal> and
+          <literal>&lt;property&gt;</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>
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('  <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:
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 @@
 <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.
       -->
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 @@
     <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>



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]