Skip to content

Commit

Permalink
Create "Writing a VAPI manually" Developer Guide (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
colinkiama authored May 4, 2024
1 parent d224ac1 commit 790c0aa
Show file tree
Hide file tree
Showing 65 changed files with 1,793 additions and 0 deletions.
8 changes: 8 additions & 0 deletions source/developer-guides/bindings.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Bindings
========

.. toctree::
:glob:
:maxdepth: 1

bindings/*
29 changes: 29 additions & 0 deletions source/developer-guides/bindings/writing-a-vapi-manually.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Writing a VAPI Manually
=======================

This document intends to be a tutorial and reference on how to write a Vala binding to an existing C library. If the library uses GLib, do not follow this document. Instead read :doc:`Generating a VAPI with GObject Introspection </developer-guides/bindings/generating-a-vapi-with-gobject-introspection>`. A library may not follow the GLib coding practices precisely, but it is better to fix the library to work with GObject Introspection than to write a manual binding.

C programmers are a rather liberal bunch; certain procedures are done in a multitude of ways depending on the mood of the programmer, whereas Vala is much more restricted. This guide cannot possibly cover all possible cases of different APIs written by C programmers. It is your job to understand the C API and present it with Vala-friendly semantics.

There is a lot of material in this document and that can make it hard to take in at first. A practical approach to working through the tutorial would be to:

1. | Bind an enum first because enums are easy to test.

| Once your test gives the expected result you know that the build process works. This means working through the "Getting Started" section and the "Enums and Flags" sub-section. Binding an enum also introduces the idea that there isn't a straight mapping from C to Vala
2. | Bind the creation and destruction of a compact class next.

| This means working through the "Using Vala's Automatic Memory Management" section and starting to understand that a struct in C can be bound as either a simple type, a struct or a compact class in Vala. The binding can be tested by looking at the C code produced from a single line in Vala like ``new MyBoundCompactClass ();``
3. | Bind methods of the compact class.

| This is when your binding starts to become useful and it will also give an overview of this document. Once you have an overview the document becomes more of a reference for solving tricky function bindings
The above assumes that the library is written in an object oriented style of C. A C binding, however, is only made up of structs and functions so understanding that in enough detail is the purpose of the approach.

.. toctree::
:glob:
:maxdepth: 1
:numbered:

*/*
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Prerequisites
=============

To write the binding collect the following:

* a functional copy of the library with headers
* the documentation for the library, if such a thing exists
* the source, if possible
* examples or tutorials that you can use as tests for your binding

If the library is written in C++, you cannot bind it to Vala unless there is a separate C binding of the C++ library (e.g., LLVM).

If you are using vim, you may wish to add the following to your .vimrc:

.. code-block:: vim
:noremap <F8> "gyiwO[CCode (cname = "<ESC>"gpa")]<ESC>
which allows you to insert an attribute to make it easier to rename a function by pressing F8 while your cursor is on the symbol.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Getting Started
===============

.. toctree::
:maxdepth: 2
:glob:

02-00-getting-started/*
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
The VAPI File
=============

Check if the library's development package has installed a `pkg-config <http://www.freedesktop.org/wiki/Software/pkg-config/>`_ file (.pc file extension). If so, give your VAPI file the same name. For example libfoo.pc should have the VAPI called libfoo.vapi. This allows the details of the library files to be automatically picked up and passed through to the C compiler and linker.

When developing a VAPI a typical command to build the tests against the binding would be:

.. code-block:: shell
valac --vapidir . --pkg libfoo program_using_libfoo.vala
The dot after ``--vapidir`` tells ``valac`` to include the current directory when looking for VAPI files.
The ``--pkg libfoo`` switch tells ``valac`` to look for a VAPI called ``libfoo.vapi``. Note the the ``.vapi`` suffix is dropped. If the VAPI also has the same name as a ``.pc`` file then ``valac`` will find and use the ``.pc`` file to extract the relevant library details to pass to the C compiler and linker.

Example VAPI files can be found in the `of the Vala git repository <https://gitlab.gnome.org/GNOME/vala/tree/master/vapi>`_ of the Vala git repository. Files stating they have been generated by ``vapigen`` have been generated through GObject Introspection and are not examples of manually written bindings.

Once you have a working VAPI file, even if it is only a subset of the library's functionality, please consider sharing the file. See `Vala Extra VAPIs <https://gitlab.gnome.org/GNOME/vala-extra-vapis>`_ and :doc:`Why Distribute Bindings Upstream </developer-guides/bindings/upstream-guide>`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Attribution and License
=======================

To distribute the VAPI through one of the main repositories a copyright notice will be required. It may be easier to deal with this formality at the start of writing the binding.

The copyright notice should include an attribution and a copy of the license. The attribution is your name along with your email address. This identifies you as the author of the VAPI and a point of contact in the very unlikely event a third party is identified as using the binding in breach of the license. Free software and open source licenses allow the VAPI file to be copied as long as the terms of the license are met. The license should be the same as the library's license. This ensures compatibility between the binding and the library.

The copyright notice should be between multi-line comments, not documentation comments:

.. code-block:: vala
/*
* Copyright (c) 2016 My Name <my_email@my_address.com>
*
* This library is free software...[or whichever license is used by the library]
*
*/
Documentation comments have an additional asterisk at the beginning, ``/**``, and would be picked up by ``valadoc``. The use of ``valadoc`` is covered later.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
The CCode Attribute
===================

Vala generates C code in a certain style, examples are Vala following its own naming conventions and the ordering of automatically generated parameters. The ``CCode`` attribute provides fine control of the C code produced by Vala and will be used extensively when binding a C library that uses its own conventions.

The ``CCode`` attribute will be used for:

* including a C header file
* converting from Vala naming conventions to a library's naming conventions
* binding a library to Vala's assisted memory management
* controlling the position of function call arguments, especially Vala generated arguments
* overcoming various edge cases

These are introduced at the relevant points throughout the tutorial. For a single reference see the `Vala Manual Attributes Section <https://gnome.pages.gitlab.gnome.org/vala/manual/attributes.html>`_.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Create a Root Namespace
=======================

Normally all the bindings for a library are placed into a single root namespace. For example libfoo or foolib, would best be placed in a namespace called Foo. This follows the naming convention above. For example an initial VAPI would be:

.. code-block:: vala
namespace Foo {
// bindings
}
The binding can then either be used in a Vala program by prefixing the namespace, e.g.:

.. code-block:: vala
void main () {
Foo.library_function();
}
or bring the VAPI namespace into the scope of the file:

.. code-block:: vala
using Foo;
void main () {
library_function ();
}
Namespaces also provide a convenient way to group functions. Typically, for GLib-based libraries, the ``x_y_foo`` patterns can be translated directly into a namespace as ``x.y.foo``. Since most C libraries do not follow these conventions, things are slightly murkier. As general rules of thumb, try the following:

* Move global variables, functions, constants, enums, flags, and delegate definitions into the class and struct definitions if they are clearly related only to that type. That is, it might make sense to move the ``enum FooOptions`` into ``class Foo`` as simply ``Options``. Note that structs cannot contain enum, flag, or delegate definitions; only constants and static methods.
* Use header files and directories as a guide. If the headers are stored as ``foo-2.0/db/{handle,transaction,row}.h`` or ``foo-2.0/db_{handle,transaction,row}.h`` or if ``foo-2.0/db.h`` contains definitions for ``foo_handle``, ``foo_tx``, and ``foo_row``, there's a good chance that creating a namespace ``Db`` is a logical grouping.
* Create namespaces for large groups of related constants. Sometimes, constant collections cannot be converted to enums, in which case, grouping them into a namespace is much easier to manage.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Include the C Header Files
==========================

The ``CCode`` attribute ``cheader_filename`` defines the comma separated list of headers to include in the generated C. For example,

.. code-block:: vala
[CCode (cheader_filename = "libfoo/foo.h")]
namespace Foo {
// bindings
}
Try to apply headers to namespaces or containing types. Applying it to an outer context prevents having to repeat it in the inner context.

A library will often have a single header that includes a number of sub-headers. For an example see the `<https://gitlab.gnome.org/GNOME/glib/-/blob/main/glib/glib.h>`_ header. In these cases only the main header file needs to be included.

Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
Symbol Name Translations
========================

Vala has symbol name translation rules from Vala to C. The default rules follow the GLib naming conventions, but for a binding the name translations can be customised with the ``lower_case_cprefix``, ``cprefix`` and ``cname`` CCode details.

The following example illustrates the default symbol name translation rules. Vala's name translation rules apply to both Vala programs and bindings. Compile the following example program with ``valac --ccode name_conversion_example.vala`` then examine how the Vala symbol names have been translated:

.. code-block:: vala
void main () {
Foo.Bar a = new Foo.Bar ();
a.test ();
var b = Foo.Bar.UNCHANGING;
}
namespace Foo {
[Compact]
class Bar {
public const int UNCHANGING = 42;
public void test () {
}
}
}
The use of the ``[Compact]`` attribute makes the C code simpler and so easier to read, but the name translation rules apply to full Vala classes as well. Here is a table that summarizes the example's translations:

+------------------------+------------------------+------------------------------------------+
| *Vala Identifier* | *C Identifier* | *Notes* |
+========================+========================+==========================================+
| ``Foo.Bar`` | ``FooBar`` | This is the data type |
+------------------------+------------------------+------------------------------------------+
| ``new Foo.Bar ()`` | ``foo_bar_new ()`` | This is the constructor function |
+------------------------+------------------------+------------------------------------------+
| ``a.test ()`` | ``foo_bar_test (a)`` | This is a function acting on an instance |
+------------------------+------------------------+------------------------------------------+
| ``Foo.Bar.UNCHANGING`` | ``FOO_BAR_UNCHANGING`` | A constant defined with the type |
+------------------------+------------------------+------------------------------------------+

When binding the library the Vala symbol names should follow the following conventions and then ``lower_case_cprefix``, ``cprefix`` and ``cname`` can be used to ensure the C symbol name matches the library:

+---------------------------+---------------------------------------------------+----------------------------+----------------------------+
| *Vala Semantics* | *Vala Convention* | *Default Translation to C* | *Modify with CCode Detail* |
+===========================+===================================================+============================+============================+
| Classes | !TitleCase | | |
+---------------------------+---------------------------------------------------+----------------------------+----------------------------+
| Constants | UPPER_SNAKE_CASE | | |
+---------------------------+---------------------------------------------------+----------------------------+----------------------------+
| Delegates | !TitleCase | | |
+---------------------------+---------------------------------------------------+----------------------------+----------------------------+
| Enums and Flags | !TitleCase | | |
+---------------------------+---------------------------------------------------+----------------------------+----------------------------+
| Fields | lower_snake_case | | |
+---------------------------+---------------------------------------------------+----------------------------+----------------------------+
| Methods | lower_snake_case | | |
+---------------------------+---------------------------------------------------+----------------------------+----------------------------+
| Namespaces | !TitleCase | title_case\_ | ``lower_case_cprefix`` |
| | | | |
| | | TITLE_CASE\_ | ``lower_case_cprefix`` |
| | | | |
| | | !TitleCase | ``cprefix`` |
+---------------------------+---------------------------------------------------+----------------------------+----------------------------+
| Properties | lower_snake_case | | |
+---------------------------+---------------------------------------------------+----------------------------+----------------------------+
| Structs | !TitleCase | | |
+---------------------------+---------------------------------------------------+----------------------------+----------------------------+
| Type Variables (Generics) | T (A single uppercase letter). | | |
| | For maps, K, V are preferred for keys and values. | | |
+---------------------------+---------------------------------------------------+----------------------------+----------------------------+

Where appropriate, expand cryptic C names into more understandable Vala ones (e.g., ``Tx`` into ``Transaction``). Vala is usually much more compact than C, so we are willing to make different trade-offs and favor readability over being concise a bit more than C programmers generally do. In particular, ``var`` saves a lot of writing long type names and import helps make better use of prefixes.

Note the following:

* the use of ``cprefix`` and ``lower_case_cprefix`` with a namespace
* the priority of a class over a namespace when using ``cprefix`` and ``lower_case_cprefix``
* the use of ``cname`` with a function and constant

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Code Formatting Conventions
===========================

* Tabs for indentation
* A space before an opening parenthesis and no space afterwards
* A space either side of equals
* No space before a comma, but a space after
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
Documentation and Valadoc.org
=============================

`Valadoc.org <https://valadoc.org>`_ is often the first website a Vala developer visits when seeking how to use a binding. A new VAPI commited to `Vala Extra VAPIs <https://gitlab.gnome.org/GNOME/vala-extra-vapis>`_ can be added to Valadoc.org by adding the VAPI to the `list of downloaded packages <https://github.com/Valadoc/valadoc-org/blob/main/documentation/packages.xml>`_ at Valadoc.org and submitting a pull request to the `Valadoc.org repository <https://github.com/Valadoc/valadoc-org>`_. See the `libcolumbus pull request <https://github.com/Valadoc/valadoc-org/pull/39>`_ as an example.

Valadoc.org is frequently re-generated. When Valadoc.org is re-generated VAPIs are pulled in from ``vala-extra-vapis`` and documentation generated from them. If no documentation comments are associated with a VAPI then Valadoc.org will only show the symbols in the VAPI.

Add a documentation comment before a symbol in the VAPI. A documentation comment is a C multiline comment with an additional asterisk:

.. code-block:: vala
/**
* Brief description of class Foo
*
* Long description of class Foo, which can include an example
*/
[CCode (cname = "foo", ref_function = "foo_retain", unref_function = "foo_release")]
[Compact]
public class Foo {
// Details of binding
}
The comments can include additional markup. Details are at `Valadoc Comment Markup <https://valadoc.org/markup>`_.

The documentation can be generated locally to test how it will appear. First, download and build ``valadoc``:

.. code-block:: shell
git clone git://git.gnome.org/valadoc
cd valadoc
./autogen.sh
make
make install
Second, generate the documentation:

.. code-block:: shell
cd my_binding_directory
valadoc --directory docs --force --package-name mybinding mybinding.vapi
This will generate the HTML documentation in the ``docs`` directory. ``valadoc`` expects the ``docs`` directory to not exist, but ``--force`` overrides this. ``--package-name mybinding`` will create a sub-directory called ``mybinding`` in ``docs`` that contains the generated documentation for ``mybinding.vapi``.

The locally generated documentation will have the same structure as ``valadoc.org``, although the visual styling may differ.

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
The Version Attribute
=====================

Vala symbol's can be annotated with the ``[Version]`` attribute. This allows a symbol to be marked as
experimental, deprecated and to indicate version information. For example:

.. code-block:: vala
namespace Test {
[Version (experimental = true)]
public void test_function_1 ();
[Version (deprecated = true)]
public void test_function_2 ();
[Version (deprecated_since = "2.0")]
public void test_function_3 ();
[Version (deprecated = true, deprecated_since = "2.0", replacement = "test_function_5", since = "1.0")]
public void test_function_4 ();
[Version (since = "1.0")]
public void test_function_5 ();
}
Loading

0 comments on commit 790c0aa

Please sign in to comment.