Posts

Lilv 0.24.22

Lilv 0.24.22 has been released. Lilv is a C library to make the use of LV2 plugins as simple as possible for applications. For more information, see http://drobilla.net/software/lilv.

Changes:

  • Allow LILV_API to be defined by the user
  • Clean up code
  • Clean up inconsistent tool command line interfaces
  • Convert man pages to mdoc
  • Fix crash when plugins pass NULL to the LV2_State_Retrieve_Function
  • Fix dependencies in pkg-config file
  • Fix potential crash when writing state files fails
  • Order plugin classes by URI
  • Override pkg-config dependency within meson
  • Remove junk files from documentation install
  • Replace duplicated dox_to_sphinx script with sphinxygen dependency
  • Switch to external zix dependency

Suil 0.10.20

Suil 0.10.20 has been released. Suil is a library for loading and wrapping LV2 plugin UIs. For more information, see http://drobilla.net/software/suil.

Changes:

  • Allow SUIL_API to be defined by the user
  • Fix dependencies in pkg-config file
  • Only check for Gtk Quartz support on MacOS
  • Override pkg-config dependency within meson
  • Remove Gtk in Qt and Qt in Gtk wrappers
  • Remove junk files from documentation install
  • Replace duplicated dox_to_sphinx script with sphinxygen dependency

Sratom 0.6.16

Sratom 0.6.16 has been released. Sratom is a small library for serializing LV2 atoms. Sratom reads/writes atoms from/to RDF, allowing them to be converted between binary and text or stored in a model. For more information, see http://drobilla.net/software/sratom.

Changes:

  • Clean up code
  • Constrain relative URI references to the base URI
  • Fix dependencies in pkg-config file
  • Override pkg-config dependency within meson
  • Remove junk files from documentation install
  • Replace duplicated dox_to_sphinx script with sphinxygen dependency

Sord 0.16.16

Sord 0.16.16 has been released. Sord is a lightweight C library for storing RDF statements in memory. For more information, see http://drobilla.net/software/sord.

Changes:

  • Allow SORD_API to be defined by the user
  • Fix command line help interface of sord_validate
  • Fix dependencies in pkg-config file
  • Override pkg-config dependency within meson
  • Port sord_validate to pcre2
  • Switch to external zix dependency

Serd 0.32.0

Serd 0.32.0 has been released. Serd is a lightweight C library for working with RDF data.

Changes:

  • Add Windows path separator support to serd_node_new_file_uri()
  • Add long "help" and "version" options to serdi
  • Add options to disable html or singlehtml documentation
  • Add serd_reader_skip_until_byte() to public API
  • Allow SERD_API to be defined by the user
  • Avoid creating test files in the current directory
  • Avoid using ASCII grave as a quote
  • Check for POSIX features with the build system
  • Clean up and improve test suite
  • Clean up code
  • Fix crash when trying to read chunks without starting
  • Fix hang when skipping an error at EOF when lax parsing
  • Fix incorrect parsing of strange quote escape patterns
  • Fix possible hang when writing nested Turtle lists
  • Fix potential memory leaks when a write is aborted
  • Fix relative URI creation
  • Gracefully handle bad characters in Turtle blank node syntax
  • Gracefully handle bad characters in Turtle datatype syntax
  • Improve TriG pretty-printing and remove trailing newlines
  • Improve pretty-printing of lists and inline subjects
  • Improve serdi man page
  • Improve writer error handling
  • Make URI writing stricter by default
  • Make serd_reader_read_chunk() work with NQuads
  • Override pkg-config dependency within meson
  • Remove junk files from documentation install
  • Remove support for writing Turtle named inline nodes extension
  • Replace duplicated dox_to_sphinx script with sphinxygen dependency
  • Test header for warnings more strictly
  • Update standard test suites

Zix 0.4.2

Zix 0.4.2 has been released. Zix is a lightweight C library of portability wrappers and data structures.

Changes:

  • Clean up documentation build
  • Fix documentation build in a virtualenv
  • Improve test suite code coverage

Zix 0.4.0

Zix 0.4.0 has been released. Zix is a lightweight C library of portability wrappers and data structures.

Sphinxygen 1.0.4

Sphinxygen 1.0.4 has been released. Sphinxygen is a Python module/script that generates Sphinx markup to describe a C API, from an XML description extracted by Doxygen.

Changes:

  • Handle anonymous structs without the "@" placeholder

Sphinxygen 1.0.2

Sphinxygen 1.0.2 has been released. Sphinxygen is a Python module/script that generates Sphinx markup to describe a C API, from an XML description extracted by Doxygen.

Changes:

  • Add command line flags to print version
  • Fix links to documentation groups

In Search of the Ultimate Compile-Time Configuration System

One of the many programming side quests I embark on from time to time is finding the best way to do compile-time configuration in C and C++ code. This is one of those characteristically C things that most projects need to do, but that has no well-established best practice. What you can find is all over the place, and often pretty half-baked just to suit the particularities of the "official" build. Let's try to come up with something better.

Ideal requirements:

  • Ability to enable or disable any features from the command line by defining symbols, including the ability to override or completely disable any automatic checks implemented in the code.

  • Good integration with, but no hard dependency on, any build system.

  • The code should build with reasonable defaults when simply thrown at a compiler "as-is".

  • Mistakes, such as forgetting to include the configuration header or using misspelled symbols, are caught by tooling (preferably compiler warnings).

  • It's never necessary to modify the code to achieve a particular build.

Here's a skeleton of the best I've managed to come up with so far, for a made-up "mylib" project and a few POSIX functions. It has a bit of boilerplate, but there's good reasons for everything that I'll get to. This configuration header is written manually (not generated) and included (privately) in the source code:

#ifndef MYLIB_CONFIG_H
#define MYLIB_CONFIG_H

#if !defined(MYLIB_NO_DEFAULT_CONFIG)

// Derive default configuration from the build environment

// We need unistd.h to check _POSIX_VERSION
#  ifdef __has_include
#    if __has_include(<unistd.h>)
#      include <unistd.h>
#    endif
#  elif defined(__unix__)
#    include <unistd.h>
#  endif

// Define MYLIB_POSIX_VERSION unconditionally for convenience below
#  if defined(_POSIX_VERSION)
#    define MYLIB_POSIX_VERSION _POSIX_VERSION
#  else
#    define MYLIB_POSIX_VERSION 0
#  endif

// POSIX.1-2001: fileno()
#  if !defined(HAVE_FILENO)
#    if MYLIB_POSIX_VERSION >= 200112L || defined(_WIN32)
#      define HAVE_FILENO 1
#    endif
#  endif

// POSIX.1-2001: posix_fadvise()
#  if !defined(HAVE_POSIX_FADVISE)
#    if MYLIB__POSIX_VERSION >= 200112L && !defined(__APPLE__)
#      define HAVE_POSIX_FADVISE 1
#    endif
#  endif

#endif // !defined(MYLIB_NO_DEFAULT_CONFIG)

// Define USE variables for use in the code

#if defined(HAVE_FILENO) && HAVE_FILENO
#  define USE_FILENO 1
#else
#  define USE_FILENO 0
#endif

#if defined(HAVE_POSIX_FADVISE) && HAVE_POSIX_FADVISE
#  define USE_POSIX_FADVISE 1
#else
#  define USE_POSIX_FADVISE 0
#endif

User interface:

  • By default, features are enabled if they can be detected or assumed to be available from the build environment, unless MYLIB_NO_DEFAULT_CONFIG is defined, which disables everything by default to allow complete control.

  • If a symbol like HAVE_SOMETHING is defined to non-zero, then the "something" feature is assumed to be available. If it is zero, then the feature is disabled.

Usage in code:

  • To check for a feature, the configuration header must be included, and the symbol like USE_SOMETHING (not HAVE_SOMETHING) used as a boolean in an #if expression, like:

    #include "mylib_config.h"
    
    // [snip]
    
    #if USE_FILENO
        int fd = fileno(file);
    #endif
    
  • None of the other configuration symbols described here may be used directly. In particular, the configuration header should be the only place in the code that touches HAVE symbols.

The main "trick" here which allows for all of the different configuration "modes" is the use of two "kinds" of symbol: HAVE symbols and USE symbols. HAVE symbols are exclusively the interface for the user or build system, and USE symbols are the opposite: exclusively for use in the code and never by the user or build system. This way, use of the configuration header is mandatory for any code that needs configuration.

The USE symbols are defined to 0 or 1 unconditionally, and code must check them with #if, not with #ifdef. This prevents mistakes, since both forgetting to include the configuration header, and misspelling a symbol, will be caught by compiler warnings. Tools like include-what-you-use can also enforce direct inclusion more strictly.

From the command line, basic usage is typical: define symbols like HAVE_SOMETHING to enable features. For complete control over the configuration, define MYLIB_NO_DEFAULT_CONFIG, in which case all features must be explicitly enabled. This is mainly useful for build systems, so that all features can be checked for and only those that are found used in the code. It's also useful for avoiding issues with strange compilers or platforms that aren't supported by the checks.

I think this design covers all of the above requirements, and while the header itself can get a bit verbose, it's relatively straightforward and, more importantly, usage of it is simple and resilient to mistakes.

There is one thing here that isn't caught by tooling though: misspelling a HAVE variable will silently not work. This is a concession to the simple case of just defining a few relevant HAVE symbols on the command line, and to keep command lines from the build system as terse as possible. It is however possible to modify this pattern a bit to catch this potential mistake as well: require all known HAVE variables to be defined to 1 or 0, and check those with #if as well in the configuration header itself. This adds a couple of lines per check to the boilerplate, for example:

// POSIX.1-2001, Windows: fileno()
#  ifndef HAVE_FILENO
#    if defined(_WIN32) || defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L
#      define HAVE_FILENO 1
#    else
#      define HAVE_FILENO 0
#    endif
#  endif

// [snip]

#if HAVE_FILENO
#  define USE_FILENO 1
#else
#  define USE_FILENO 0
#endif

This way, compiler warnings will catch any mistakes in the build system (because, for example, HAVE_FILENO isn't defined), ensuring that everything is explicitly either enabled or disabled. I'm not sure which style to use. Potential silent errors in the build system are pretty bad, but at the same time, I don't want to sacrifice the ability of the code to be easily compiled "manually". It's probably possible to have both, but I'm not sure how painful the boilerplate cost would be. I did have the stricter version for a while, but the extremely verbose compiler command lines were pretty annoying, so I removed it. Now, as I write this, I'm second guessing myself, but so it goes.

Questions for another day, I suppose. One of the things about programming side quests is that they usually never end.

Page 1 / 20 »