2

First, some context: I'm a Python developer who has written a medium-sized application using PyGObject, taking advantage of GObject Introspection to access things like GSettings, etc. Some of my Python objects actually subclass GObject.GObject, so I'm using GObject quite extensively.

Recently, a certain library has come to my attention that wraps a C library in GObject (gexiv2, used by Shotwell/Vala), however it doesn't currently support introspection. I'm interested in adding introspection support to gexiv2 so that I can access it from Python, but I don't even know where to begin on this topic.

When I research introspection and VAPI, I see lots of documentation referencing the fact that the VAPI can be automatically generated from the introspection annotations... but what about a project that already has a VAPI, but no introspection? Is it possible to automatically generate the introspection annotations given the VAPI?

Thanks.

Jens Mühlenhoff
  • 14,565
  • 6
  • 56
  • 113
robru
  • 2,313
  • 2
  • 29
  • 38

2 Answers2

2

VAPI bindings are not necessarily related to GObject introspection. For instance, there are VAPI bindings for POSIX, Linux, libudev, and other things that are definitely not GObject-based. There isn't a direct way to convert a VAPI to a GObject binding.

However, if you have C header files and a working library, then you can generally build a GObject introspection file from the library. For gexiv2, download and build the source, then execute:

g-ir-scanner -I gexiv2 gexiv2/gexiv2-{metadata,managed-stream,preview-properties,preview-image,log,startup}.h -n GExiv2 --library libgexiv2.la --pkg gobject-2.0

And this will produce a GIR binding (XML) that you can use in Python.

apmasell
  • 7,033
  • 19
  • 28
  • 1
    I agree that this the right way forward, but AFAICT there are no GTK-Doc/GObject Introspection comments in that project so you'll likely end up with a pretty broken GIR until you add them. See https://live.gnome.org/GObjectIntrospection/Annotations. Also, you'll need to compile to a typelib (using g-ir-compiler) then install both to the correct location (`pkg-config --variable=typelibdir gobject-introspection-1.0` for the typelib, `pkg-config --variable=girdir gobject-introspection-1.0` for the GIR) – nemequ Jun 26 '12 at 00:40
  • 1
    I forgot about the GTK-Doc. Though, the VAPI might be helpful in inferring them. I guess the short answer is: there's no completely mechanical way to make use of this library in Python. – apmasell Jun 26 '12 at 02:46
  • Yes I've spent the better part of today reviewing the source code of some projects that I already access from Python, in order to learn what real-world annotations look like. I've only just now compiled gexiv2 and I'm about to run g-ir-scanner on it just to see what happens. If I understand correctly, there's no automatic way to generate annotations from the VAPI, but referring to the VAPI will likely be a huge help in writing the annotations. – robru Jun 26 '12 at 06:04
  • Hey guys, I posted an answer with a script that parses VAPI and outputs crude annotations if you want to check it out. – robru Jun 30 '12 at 07:38
1

Well, after getting sick of the tediousness of hand-copying VAPI definitions into introspection annotations, I wrote this (crude) script to do it for me:

#!/bin/env python

import sys

from collections import defaultdict

ANNOTATION = """/**
 * %s:
%s *
 * Returns:%s
 */
"""

PARAMETER = """ * @%s:%s
"""

methods = defaultdict(set)

attrs = defaultdict(dict)

with open(sys.argv[1]) as vapi:
    for line in vapi:
        tokens = line.split()
        try:
            names = tuple(tokens[0].split('.'))
        except IndexError:
            continue

        attrs[names] = {}
        for attribute in tokens[1:]:
            key, val = attribute.split('=')
            if val == '"1"': val = True
            if val == '"0"': val = False
            attrs[names][key] = val

        methods[names[0]]
        if len(names) > 1:
            methods[names[0]].add(names[-1])

for method in methods:
    params = ''
    for param in methods[method]:
        param_attributes = ''
        param_attrs = attrs[(method, param)]
        if param_attrs.get('hidden'):
            param_attributes += ' (skip)'
        if param_attrs.get('is_out'):
            param_attributes += ' (out)'
        if param_attrs.get('transfer_ownership'):
            param_attributes += ' (transfer full)'
        elif 'transfer_ownership' in param_attrs:
            param_attributes += ' (transfer none)'
        if param_attrs.get('array_null_terminated'):
            param_attributes += ' (array zero-terminated=1)'
        if param_attrs.get('array_length_pos'):
            param_attributes += ' (array length=FIXME)'
        if param_attributes:
            param_attributes += ':'
        params += PARAMETER % (param, param_attributes)

    attributes = ''
    method_attrs = attrs[(method,)]
    if method_attrs.get('transfer_ownership'):
        attributes += ' (transfer full)'
    elif 'transfer_ownership' in method_attrs:
        attributes += ' (transfer none)'
    if method_attrs.get('nullable'):
        attributes += ' (allow-none)'
    if method_attrs.get('array_null_terminated'):
        attributes += ' (array zero-terminated=1)'
    if attributes:
        attributes += ':'

    print ANNOTATION % (method, params, attributes)

This obviously has some disadvantages: It doesn't insert the annotations into the code, it simply prints them, so you have to do quite a bit of copy & pasting to get everything into the right place. It also doesn't handle arrays very well, but it at least lets you know when there's an array you need to fix manually. All in all, it was significantly less work to run this script and then massage the results than it was to parse by hand. I'm posting it here in the hopes that it gets picked up by google and somebody else may benefit one day (although I dearly hope that all GObject-based projects from here on out simply start with annotations and then use vapigen).

robru
  • 2,313
  • 2
  • 29
  • 38