Skip to content

Commit

Permalink
Add library writing info from GNOME Wiki to Main Tutorial
Browse files Browse the repository at this point in the history
  • Loading branch information
colinkiama committed Aug 27, 2024
1 parent 4e6b1bf commit e37efdf
Show file tree
Hide file tree
Showing 4 changed files with 334 additions and 140 deletions.
Original file line number Diff line number Diff line change
@@ -1,145 +1,6 @@
Creating a Library
==================

Using Autotools
---------------

It is possible to use Autotools to create a library written in Vala. A library is created by using C code generated by Vala compiler, linked and installed as any other library. Then you need tell which C files must be used to create the library and which of them must be distributable, allowing others to compile a tarball without Vala using standard Autotools commands: *configure*, *make* and *make install*.

Example
~~~~~~~

This example was taken from GXml recent additions. GXmlDom is a library aimed to have a GObject based libxml2 replacement; is written in Vala and originally used to use WAF to build.

* **valac** can be used to generate C code and headers from Vala sources. At this time is possible to generate a GObjectIntrospection and the VAPI file from the vala sources too.
* **gxml.vala.stamp** is used as the code sources for our library.

It's important to add --pkg switches in order to valac to success and set all CFLAGS and LIBS required by the C library to compile and link against.

.. code-block:: shell
NULL =
AM_CPPFLAGS = \
-DPACKAGE_LOCALE_DIR=\""$(prefix)/$(DATADIRNAME)/locale"\" \
-DPACKAGE_SRC_DIR=\""$(srcdir)"\" \
-DPACKAGE_DATA_DIR=\""$(datadir)"\"
BUILT_SOURCES = gxml.vala.stamp
CLEANFILES = gxml.vala.stamp
AM_CFLAGS =\
-Wall\
-g \
$(GLIB_CFLAGS) \
$(LIBXML_CFLAGS) \
$(GIO_CFLAGS) \
$(GEE_CFLAGS) \
$(VALA_CFLAGS) \
$(NULL)
lib_LTLIBRARIES = libgxml.la
VALAFLAGS = \
$(top_srcdir)/vapi/config.vapi \
--vapidir=$(top_srcdir)/vapi \
--pkg libxml-2.0 \
--pkg gee-1.0 \
--pkg gobject-2.0 \
--pkg gio-2.0 \
$(NULL)
libgxml_la_VALASOURCES = \
Attr.vala \
BackedNode.vala \
CDATASection.vala \
CharacterData.vala \
Comment.vala \
Document.vala \
DocumentFragment.vala \
DocumentType.vala \
DomError.vala \
Element.vala \
Entity.vala \
EntityReference.vala \
Implementation.vala \
NamespaceAttr.vala \
NodeList.vala \
NodeType.vala \
Notation.vala \
ProcessingInstruction.vala \
Text.vala \
XNode.vala \
$(NULL)
libgxml_la_SOURCES = \
gxml.vala.stamp \
$(libgxml_la_VALASOURCES:.vala=.c) \
$(NULL)
# Generate C code and headers, including GObject Introspection GIR files and VAPI file
gxml-1.0.vapi gxml.vala.stamp GXml-1.0.gir: $(libgxml_la_VALASOURCES)
$(VALA_COMPILER) $(VALAFLAGS) -C -H $(top_builddir)/gxml/gxml-dom.h --gir=GXmlDom-1.0.gir --library gxmldom-1.0 $^
@touch $@
# Library configuration
libgxml_la_LDFLAGS =
libgxml_la_LIBADD = \
$(GLIB_LIBS) \
$(LIBXML_LIBS) \
$(GIO_LIBS) \
$(GEE_LIBS) \
$(VALA_LIBS) \
$(NULL)
include_HEADERS = \
gxml.h \
$(NULL)
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = libgxml-1.0.pc
gxmlincludedir=$(includedir)/libgxml-1.0/gxml
gxmlinclude_HEADERS= gxml-dom.h
# GObject Introspection
if ENABLE_GI_SYSTEM_INSTALL
girdir = $(INTROSPECTION_GIRDIR)
typelibsdir = $(INTROSPECTION_TYPELIBDIR)
else
girdir = $(datadir)/gir-1.0
typelibsdir = $(libdir)/girepository-1.0
endif
# GIR files are generated automatically by Valac so is not necessary to scan source code to generate it
INTROSPECTION_GIRS =
INTROSPECTION_GIRS += GXmlDom-1.0.gir
INTROSPECTION_COMPILER_ARGS = \
--includedir=. \
--includedir=$(top_builddir)/gxml
GXmlDom-1.0.typelib: $(INTROSPECTION_GIRS)
$(INTROSPECTION_COMPILER) $(INTROSPECTION_COMPILER_ARGS) $< -o $@
gir_DATA = $(INTROSPECTION_GIRS)
typelibs_DATA = GXmlDom-1.0.typelib
vapidir = $(VALA_VAPIDIR)
vapi_DATA=gxmldom-1.0.vapi
CLEANFILES += $(INTROSPECTION_GIRS) $(typelibs_DATA) gxml-1.0.vapi
EXTRA_DIST = \
libgxml-1.0.pc.in \
$(libgxml_la_VALASOURCES) \
$(typelibs_DATA) \
$(INTROSPECTION_GIRS) \
gxml.vala.stamp
Compilation and linking using Command Line
------------------------------------------

Expand All @@ -150,7 +11,7 @@ Vala is not yet capable of directly creating dynamic or static libraries. To cre
$ valac -c ...(source files)
$ ar cx ...(object files)
or by compiling the intermediate C code with *gcc*
or by compiling the intermediate C code in the compiler of you choice. We'll be using *gcc* in these examples.

.. code-block:: console
Expand Down Expand Up @@ -228,3 +89,16 @@ You can also create a GObjectIntrospection GIR file for your library with the ``
GIR files are XML descriptions of the API.

This will generate a GIR file named ``Test-1.0.gir``. The name of the GIR file should follow the GObject Introspection
naming conventions and include an API version number.

A typelib file can then be generated from the GIR using g-ir-compiler:

.. code-block:: console
g-ir-compiler --output MyLibrary-1.0.typelib MyLibrary-1.0.gir
GIR files are typically used to generate compile time bindings.
Typelib files are used to create runtime bindings and a binding generator
will read them using ``libgirepository``.

Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
ABI and API Design Choices
==========================

ABI
---

``abit-compliance-checker`` is a cross-platform tool for checcking the stability
of an ABI. Supported platforms are GNU/Linux, FreeBSD, Mac OS X and MS Windows.
See `ABI Compliance Checker <https://lvc.github.io/abi-compliance-checker/>`_ for
more details.

The tool uses debug symbols to generate an ABI profile of a shared library:

.. code-block:: console
abi-dumper my_library.so -o ABI-0.dump -lver 0
This is then repeated for the new version of the library:

.. code-block:: console
abi-dumper my_library.so -o ABI-1.dump -lver 1
A report is then generated with:

.. code-block:: console
abi-compliance-checker -l my_library -old ABI-0.dump -new ABI-1.dump
The report is an HTML file showing changes in the ABI.
An `example report for Vala <https://abi-laboratory.pro/?view=timeline&l=vala>`_ is available online.

API Design
----------

Avoid Custom Constructors
~~~~~~~~~~~~~~~~~~~~~~~~~

The following example contains a custom constructors:

.. code-block:: vala
public class MyClass : Object {
private string value;
public MyClass (string contents) {
value = contents;
}
}
This will be translated to C as the function name ``my_class_new`` and can be called with a string argument to create the object.

The problem is GObject has an alternative way of creating an object. In Vala this is of the form:

.. code-block:: vala
void main () {
Object.new (typeof(MyClass));
}
The Vala ``Object.new`` method is bound to GObject `g_object_new <https://docs.gtk.org/gobject/ctor.Object.new.html>`_ function in C and is often used to instantiate GObjects.
This can be from C, but also from languages using GObject introspection bindings. The problem is the construction defined Vala is not called.

Vala does have a way to run a function at instantiation time that is the use of the construct {} in the class.
See :doc:`GObject Style Construction page </tutorials/programming-language/main/03-00-object-oriented-programming/03-14-gobject-style-construction>`
in the :doc:`Vala Main Tutorial </tutorials/programming-language/main>` in the Vala tutorial.

Avoid Using varargs
~~~~~~~~~~~~~~~~~~~

A function with a variable number of arguments is not introspectable.
Although the GObject Introspection Repository will contain a method or function that can be called with a variable
number of arguments, the method or function will be marked as ``introspectable="0"``. This causes binding generators to
ignore the method or function. In Vala this can be overridden using ``skip = false`` in the metadata, but such
techniques are not available in all bindings.

Avoid Using Generics
~~~~~~~~~~~~~~~~~~~~

Since GObject Introspection does not handle generics, using them in APIs is harmful, since GI will generate 3 new
parameters in the constructors of each generic class: one for the GType function, one for the duplication function and
another for the destruction. These parameters are quite complicated to handle in languages like Python or Javascript.

In addition to this, the properties that expose the generic type parameter will be exposed as ``gpointer``s, which makes it
even more complicated. Even generic methods like Gee's ``add ()`` will expect a ``gpointer`` in GI, so doing something like
this in Python will result in an error, contrary to what you expect.

.. code-block:: python
list = get_a_list_of_strings ()
list.add ('Hi')
Further Reading
---------------

- `APIs, like diamonds, are forever <http://essentials.xebia.com/apis-are-forever/>`_ - some criteria for good API design
- `Libraries in Vala - ABI compatibility - part I <https://blog.piechotka.com.pl/2013/07/30/libraries-in-vala-abi-compatibility-part-i/>`_
- `Libraries in Vala - ABI compatibility - part II <https://blog.piechotka.com.pl/2013/12/20/libraries-in-vala-abi-compatibility-part-ii/>`_
- `Writing Bindable APIs (GObject Intropsection) <https://gi.readthedocs.io/en/latest/writingbindableapis.html>`_
- `Minimalistic example of the GLib's GBoxedType usage <https://storageapis.wordpress.com/2014/07/25/minimalistic-example-of-the-glibs-gboxedtype-usage/>`_ - explanation of basic types when used with GObject Introspection
and how to bind structs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
Binding to Vala Libraries from Other Languages
==============================================

Vala produces C code and also produces C headers. Binding from C is relatively easy, although the library may make use
of a lot of GObject boiler plate code.

For Vala based projects, using either the Vala or Genie syntax, a VAPI can be produced that makes bindings easy.

The Vala compiler can also produce a GObject Introspection Repository (GIR) file. This makes bindings from languages
that support GObject Introspection very easy. Often the binding is at runtime so a typelib file
also needs to be produced and libgirepository and libffi are used at runtime for the binding.

Haskell
-------

As of January 2017 the Haskell generator from GObject introspection repositories, haskell-gi, is described as complete.
The Haskell wiki page on GObject Introspection advises "The Haskell code generator at haskell-gi is now essentially
complete: all the information exposed in the bindings should now be available from the autogenerated bindings.
This includes: ordinary functions, signals, virtual functions, structure fields, object properties, etc."

`haskell-gi <https://github.com/haskell-gi/haskell-gi>`_ - Generate Haskell bindings for GObject-Introspection capable libraries

JavaScript
----------

`node-gtk <https://github.com/romgrk/node-gtk>`_ - "uses the GObject Introspection library (as PyGObject, for example), so any gobject-introspectable library is supported"

`node-gir <https://github.com/creationix/node-gir>`_ - "Node-gir is Node.js bindings to GObject Introspection making it
possible to make automatic and dynamic calls to any library that has GI annotations installed...With it you can also
write the performance-intensive parts of your applications in Vala and call them from Node.js and other languages."

Lua
---

`LGI <https://github.com/pavouk/lgi>`_ provides runtime bindings for Lua 5.1+ and LuaJIT2. It uses libgirepository to read typelib files.

There are alternative binding generators: lgob and LuiGI. `lgob <https://bitbucket.org/lucashnegri/lgob/src/master/>`_ parses
GIR files to generate Lua modules. LuiGI was an experimental dynamic binding generator. LGI should be used instead of LuiGI.

- `LGI <https://github.com/pavouk/lgi>`_ - "LGI is gobject-introspection based dynamic Lua binding to GObject based libraries...LGI is tested and
compatible with standard Lua 5.1, Lua 5.2, Lua 5.3 and LuaJIT2."
- `lgob <https://bitbucket.org/lucashnegri/lgob/src/master/>`_ - "lgob provides bindings of GObject-based libraries (like GTK+ and WebKitGTK+), for Lua 5.1 / 5.2 / LuaJIT.
It consists of a compiler that parses GObject-Instrospection gir files and generates Lua modules. lgob ships with
bindings for GTK+, pango, cairo, vte, WebKitGtk, GtkTextView, and others"
- `Some thoughts (and code) around GObject-Introspection <https://perezdecastro.org/2010/some-thoughts-and-code-around-gobject-introspection.html>`_- blog post
from 2010 about the origins of LuiGI, "the kind people there made me note about LGI, which is also a dynamic GI binding
for Lua...but looking at its code I can tell that it is more complete than my own, so I will be probably contributing
to it instead of duplicating efforts"

Perl
----

`perl-Glib-Object-Introspection <https://git.gnome.org/browse/perl-Glib-Object-Introspection/>`_ creates Perl bindings at runtime from a typelib file.

- `Glib::Object::Introspection <http://search.cpan.org/~xaoc/Glib-Object-Introspection/lib/Glib/Object/Introspection.pm>`_ - CPAN module
of `perl-Glib-Object-Introspection <https://git.gnome.org/browse/perl-Glib-Object-Introspection/>`_. The CPAN page includes examples.

Python
------

`PyGObject <https://pygobject.gnome.org/>`_ is a Python package providing bindings using GObject introspection.

- `PyGObject Documentation <https://pygobject.gnome.org/>`_ - "PyGObject provides full support of GObject Introspection and all of its features (callbacks, GVariant support, closures, sub-classing, etc.)"
- `PyGObject source repository at GNOME GitLab <https://gitlab.gnome.org/GNOME/pygobject/>`_
- `pgi-docgen <https://github.com/pygobject/pgi-docgen>`_ - GitHub repository of the API Documentation Generator for PyGObject
- `PyGObject API Reference <https://lazka.github.io/pgi-docs/>`_ - pre-built API documentation for numerous libraries available through PyGObject

Rust
----

The `gtk-rs <https://gtk-rs.org/>`_ project has developed the ``gir`` tool to generate Rust bindings from a GIR file.

- `gtk-rs/gir <https://github.com/gtk-rs/gir>`_ - GitHub repository for the ``gir`` tool. A GIR file is needed and an additional TOML file is used to pass binding metadata to the tool
Loading

0 comments on commit e37efdf

Please sign in to comment.