GObject-Introspection Build System Example


“…why should we not calmly and patiently review our own thoughts, and thoroughly examine and see what these appearances in us really are?” — Plato

GObject Introspection is a pretty neat tool from the Gnome project that allows you to expose a common API from a C library to various scripting languages. This is accomplished by “introspection” (my favorite computer metaphor I’ve come across so far) where by a coder may follow conventions that enable the program to take an inventory of its internal state after compiling and describe itself in a structured, language-agnostic kind of way.

The downside of this approach is the complexity it imposes on the library’s initial creators. The most challenging aspect for me was actually getting the thing to build. The best supported build system is the venerable autotools aka the GNU build system, with autoconf, automake, libtool, et al.

This guide is meant to be a gentle introduction to setting up a build system for a GI library. You should have a basic understanding of the GObject class before you begin. All the files discussed here are available on the Github project page.

Introducing libmaman

The name of this project is called Maman (“Maman” is French for “mother”). Maman has one object called Bar, which is the main thing we are trying to build. Let’s have a look at the structure of the project.

1
2
3
4
5
6
7
8
9
10
11
12
.
├── autogen.sh
├── configure.ac
├── COPYING
├── Makefile.am
├── maman
│   ├── Makefile.am
│   ├── maman-bar.c
│   └── maman-bar.h
└── README.md

1 directory, 8 files

The source files maman-bar.c and maman-bar.h define the Bar object. They are discussed in detail in the GObject reference manual under the tutorial on how to define and implement a new GObject. Most of the code in these files are boilerplate required to define a GObject. You can extend these files to implement your own methods, properties, and event signals.

The build system source code consists of the files autogen.sh, configure.ac, Makefile.am, and maman/Makefile.am.

Using the build system

The executable shell file autogen.sh (short for “automatically generate” I guess) should make the GNU Build System files. It usually consists of just a few lines of portable shell to prepare the project.

1
2
3
4
autoreconf --force --install --verbose || exit 1
# You can optionally perform other tasks here such as cleaning up generated
# files that are no longer needed, or running `./configure && make` if that is
# convenient for your users.

This step of the process requires autotools. If you want to distribute a buildable package without requiring autotools, the generated files are designed to be included in your project repository. Do not edit these files by hand because they will be overwritten the next time autogen.sh is run.

After autogen.sh is run, you can generate a Makefile for your system with the included ./configure script, and use make to build the project. The GNU Build System takes care of all the platform-specific details of this process, so your library will now build on a toaster.

Examining the build system source

Now that you know how it is supposed to work, let’s have a look at the build system source files configure.ac, Makefile.am, and maman/Makefile.am. Be warned that one of the most common criticisms of autotools is that it tends to be less accessible to non-experts. There are indeed plenty of magic words to be spoken here. Like, you are seriously going to feel like Harry Potter after you write these files. The advantage of the GNU system however is that once written, they tend to be low maintenance.

Let’s look at each file individually and examine its purpose and implementation so you know what to do if you ever need to make any changes to your build.

Writing configure.ac

The configure.ac file is used to make the ./configure script. Each line in this script is actually being expanded to shell code that will become ./configure. Use square brackets ([ and ]) to quote argument parameters.

The configuration script defined by this file has two important purposes. The first is to test that the system has everything that is required to build the project in a way that clearly lets people know if something is missing. The second is to define important variables that will be used in other parts of the build system representing the best choice for the target environment, including compiler tools, the path to library headers, environment flags, and the location of resources within the project.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
AC_PREREQ(2.69)

AC_INIT([Maman], [1.0.0], [SendBugsHere@example.com])

AC_CONFIG_AUX_DIR([build])
AC_CONFIG_MACRO_DIR([build/autotools])
AC_CONFIG_SRCDIR([maman])
AC_CONFIG_HEADER([config.h])

##
# Checks for programs
##
AC_PROG_CC
AC_PROG_INSTALL

##
# Checks for libraries
##

# This defines the GOBJECT_CFLAGS and GOBJECT_LIBS variables which are useful
# in the Makefile.am files
PKG_CHECK_MODULES([GOBJECT], [gobject-2.0 >= 2.38])

##
# Checks for typedefs, structures, and compiler characteristics
##

# Adds the latest standard flag (e.g. std=gnu99) to the compiler flags
AC_PROG_CC_STDC

# Compiler flags (you might need to do some checks and set these conditionally)
MAMAN_CFLAGS="-Wall"

AM_INIT_AUTOMAKE([-Wall -Werror foreign])

# library-specific macros
AM_PROG_AR
LT_INIT

##
# Introspection
##
GOBJECT_INTROSPECTION_CHECK([1.38.0])

##
# Output
##

# Exposes the MAMAN_CFLAGS variable to the Makefile.am files.
AC_SUBST(MAMAN_CFLAGS)

# You should generally have one entry here for each Makefile.am
AC_CONFIG_FILES([Makefile maman/Makefile])

AC_OUTPUT

Once you have this written, you can use autoscan to get suggestions for additional macros to improve portability based on your source code.

Writing the Makefile.am files

There are two Makefile.am files, one at the project root and one in the source directory. These files describe your project in such a way that autotools can write Makefiles to build the project for the environment it was configured for.

First lets look at the top-level Makefile.am.

1
2
3
ACLOCAL_AMFLAGS = -I build/autotools
DISTCHECK_CONFIGURE_FLAGS = --enable-introspection
SUBDIRS=maman

The first line points to the location we defined for the AC_CONFIG_MACRO_DIR and the second is required for GObject-introspection.

The SUBDIRS definition tells where to look for other Makefiles, such as the one that will be made in maman, our source code directory.

The last file in our build system is maman/Makefile.am. This is where the project is really defined.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# preamble
# - This is so we can call += freely on these variables.
NULL =
AM_CPPFLAGS =
AM_CFLAGS =
BUILT_SOURCES =
CLEANFILES =
EXTRA_DIST =
DISTCLEANFILES =
lib_LTLIBRARIES =
bin_PROGRAMS =

# C Preprocessor flags - GOBJECT_CFLAGS was defined by PKG_CHECK_MODULES
AM_CPPFLAGS += \
  $(GOBJECT_CFLAGS) \
  $(NULL)

# CFlags - we defined MAMAN_CFLAGS with AC_SUBST
AM_CFLAGS += \
  $(MAMAN_CFLAGS) \
  $(NULL)

# Define source C and header files. Note that a few variables have been defined
# for you such as `top_srcdir` and `srcdir` which are convenient to use.
source_h = \
  $(top_srcdir)/maman/maman-bar.h \
  $(NULL)

source_c = \
  maman-bar.c \
  $(NULL)

# This is defines the actual library to build.
lib_LTLIBRARIES += libmaman-1.0.la

# There are now several libmaman_1_0_la_* variables you can define to control
# additional library objects to include, source files, linker objects, etc.
# There is also a similar variable, bin_PROGRAMS, you can define here to define
# any programs you want to build.
libmaman_1_0_la_LIBADD = $(GOBJECT_LIBS)
libmaman_1_0_la_SOURCES = $(source_c) $(source_h)

# Now define the GIR file to make. This is an XML file that shows how the
# introspection utilities interpreted your source code. You should read this file
# to make sure everything was interpreted as you expected.
-include $(INTROSPECTION_MAKEFILE)

# Make sure the name of your library matches the name you defined in
# lib_LTLIBRARIES. Capitalization can be important in these steps.
INTROSPECTION_GIRS = Maman_1_0_gir
# These are flags to pass to g-ir-scanner and g-ir-compiler.
INTROSPECTION_SCANNER_ARGS = --add-include-path=$(srcdir) --warn-all
INTROSPECTION_COMPILER_ARGS =

if HAVE_INTROSPECTION
introspection_sources = $(libmaman_1_0_la_SOURCES)

# This step is fairly well documented in the Gnome wiki here:
# https://wiki.gnome.org/Projects/GObjectIntrospection/AutotoolsIntegration
Maman-1.0.gir: libmaman-1.0.la
Maman_1_0_gir_INCLUDES = GObject-2.0
Maman_1_0_gir_CFLAGS = $(AM_CPPFLAGS)
Maman_1_0_gir_PACKAGES =
Maman_1_0_gir_LIBS = libmaman-1.0.la
Maman_1_0_gir_FILES = $(introspection_sources)
Maman_1_0_gir_NAMESPACE = Maman
INTROSPECTION_GIRS += Maman-1.0.gir

girdir = $(datadir)/gir-1.0
gir_DATA = $(INTROSPECTION_GIRS)

typelibdir = $(libdir)/girepository-1.0
typelib_DATA = $(INTROSPECTION_GIRS:.gir=.typelib)

CLEANFILES += $(gir_DATA) $(typelib_DATA)
endif

Using the bindings

Once your library is built, set up the development environment for the bindings by defining some variables in your shell.

1
2
export GI_TYPELIB_PATH=/path/to/library/maman:$GI_TYPELIB_PATH
export LD_LIBRARY_PATH=/path/to/library/maman/.libs:$LD_LIBRARY_PATH

When the PyGObject module available, the following python script should now work as you expect it to.

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env python3

from gi.repository import Maman

# Create a new object
bar = Maman.Bar()

# Say hello to bar
words = bar.speak('Hello, world!')

print(words)

Further reading


Comments