[gnome-devel-docs] programming-guidelines: Add a page on parallel installability
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-devel-docs] programming-guidelines: Add a page on parallel installability
- Date: Tue, 10 Feb 2015 16:59:21 +0000 (UTC)
commit 1575da0c0de15d96652da1c5f44f497c73c0ae85
Author: Philip Withnall <philip withnall collabora co uk>
Date: Wed Feb 4 09:38:48 2015 +0000
programming-guidelines: Add a page on parallel installability
This is heavily based on Havoc’s original article on parallel
installability:
http://ometer.com/parallel.html
Confirmation has been obtained via e-mail that the original is licenced
under CC-BY-SA 3.0.
https://bugzilla.gnome.org/show_bug.cgi?id=376123
.../C/parallel-installability.page | 555 ++++++++++++++++++++
programming-guidelines/Makefile.am | 1 +
2 files changed, 556 insertions(+), 0 deletions(-)
---
diff --git a/programming-guidelines/C/parallel-installability.page
b/programming-guidelines/C/parallel-installability.page
new file mode 100644
index 0000000..3a010fe
--- /dev/null
+++ b/programming-guidelines/C/parallel-installability.page
@@ -0,0 +1,555 @@
+<page xmlns="http://projectmallard.org/1.0/"
+ xmlns:its="http://www.w3.org/2005/11/its"
+ type="topic"
+ id="parallel-installability">
+
+ <info>
+ <link type="guide" xref="index#coding-style"/>
+
+ <credit type="author copyright">
+ <name>Havoc Pennington</name>
+ <email its:translate="no">hp pobox com</email>
+ <years>2002</years>
+ <!-- Heavily based off Havoc’s original article about parallel
+ installability: http://ometer.com/parallel.html.
+ Licence CC-BY-SA 3.0 confirmed by e-mail with him. -->
+ </credit>
+ <credit type="author copyright">
+ <name>Philip Withnall</name>
+ <email its:translate="no">philip withnall collabora co uk</email>
+ <years>2015</years>
+ </credit>
+
+ <include href="cc-by-sa-3-0.xml" xmlns="http://www.w3.org/2001/XInclude"/>
+
+ <desc>
+ Writing libraries to be future proof through parallel installation
+ </desc>
+ </info>
+
+ <title>Parallel Installability</title>
+
+ <synopsis>
+ <title>Summary</title>
+
+ <p>
+ If two packages can be parallel installed, then they have no filenames in
+ common, and people developing against the package always compile against
+ the version they expected. This applies to daemons, utility programs and
+ configuration files as it does to header files and library binaries.
+ </p>
+
+ <list>
+ <item><p>
+ Ensure all versions of a library are parallel installable.
+ (<link xref="#justification"/>)
+ </p></item>
+ <item><p>
+ Version all files installed by a library.
+ (<link xref="#solution"/>)
+ </p></item>
+ <item><p>
+ Keep package version numbers separate from soname or libtool version
+ numbers. Be clear which part of the package version number changes with
+ the API.
+ (<link xref="#version-numbers"/>)
+ </p></item>
+ <item><p>
+ Install C header files to
+ <file><var>$(includedir)</var>/lib<var>library</var>-<var>version</var>/<var>library</var>/</file>.
+ (<link xref="#header-files"/>)
+ </p></item>
+ <item><p>
+ Install library binaries to
+ <file><var>$(libdir)</var>/lib<var>library</var>-<var>version</var>.so.<var>soname</var></file>.
+ (<link xref="#libraries"/>)
+ </p></item>
+ <item><p>
+ Install pkg-config files to
+ <file><var>$(libdir)</var>/pkgconfig/<var>library</var>-<var>version</var>.pc</file>.
+ (<link xref="#pkg-config"/>)
+ </p></item>
+ <item><p>
+ Make configuration files forwards and backwards compatible, or install
+ them to
+ <file><var>$(sysconfdir)</var>/<var>library</var>-<var>version</var>/</file>.
+ (<link xref="#configuration-files"/>)
+ </p></item>
+ <item><p>
+ Set <code>GETTEXT_PACKAGE</code> to
+ <code><var>library</var>-<var>version</var></code>.
+ (<link xref="#gettext"/>)
+ </p></item>
+ <item><p>
+ Include a version number in all D-Bus interface names, service names and
+ object paths. For example:
+ <code>org.domain.<var>Library</var><var>Version</var>.<var>Interface</var></code>,
+ <code>org.domain.<var>Library</var><var>Version</var></code> and
+ <code>/org/domain/<var>Library</var><var>Version</var>/</code>.
+ (<link xref="#dbus"/>)
+ </p></item>
+ <item><p>
+ Install daemon binaries to
+ <file><var>$(libexecdir)</var>/<var>library</var>-daemon-<var>version</var></file>.
+ (<link xref="#programs"/>)
+ </p></item>
+ <item><p>
+ Install utility binaries to
+ <file><var>$(bindir)</var>/<var>library</var>-utility-<var>version</var></file>
+ and install symbolic links to <file><var>$(bindir)</var>/<var>library</var>-utility</file>.
+ (<link xref="#programs"/>)
+ </p></item>
+ </list>
+ </synopsis>
+
+ <section id="justification">
+ <title>Justification</title>
+
+ <p>
+ All public libraries should be designed to be parallel installed to ease
+ API breaks later in the life of the library. If a library is used by
+ multiple projects, and wants to break API, either all of the projects must
+ be ported to the new API in parallel, or some of them will no longer be
+ installable at the same time as the others, due to depending on
+ conflicting versions of this library.
+ </p>
+
+ <p>
+ This is unmaintainable, and asking all the projects to port to a new API
+ at the same time is hard to organize and demoralizing, as most API breaks
+ do not bring large new features which would motivate porting.
+ </p>
+
+ <p>
+ The solution is to ensure that all libraries are parallel installable,
+ allowing the old and new versions of the API to be installed and compiled
+ against at the same time, without conflicts. Building in support for this
+ kind of parallel installation is much easier to do at the start of a
+ project than it is to do retroactively.
+ </p>
+
+ <p>
+ This eliminates the ‘chicken and egg’ problem of porting a collection of
+ applications from one version of a library to the next, and makes breaking
+ API a lot simpler for library maintainers, which can allow for more rapid
+ iteration and development of new features if they desire.
+ </p>
+
+ <p>
+ The alternative, and equally valid, solution is for the library to never
+ break API — the approach taken by <code>libc</code>.
+ </p>
+ </section>
+
+ <section id="solution">
+ <title>Solution</title>
+
+ <p>
+ The solution to the problem is essentially to rename the library, and in
+ most cases the nicest way to do so is to include the version number in the
+ path of every file it installs. This means multiple versions of the
+ library can be installed at the same time.
+ </p>
+
+ <p>
+ For example, say that library <code>Foo</code> traditionally installs
+ these files:
+ </p>
+ <list>
+ <item><p><file>/usr/include/foo.h</file></p></item>
+ <item><p><file>/usr/include/foo-utils.h</file></p></item>
+ <item><p><file>/usr/lib/libfoo.so</file></p></item>
+ <item><p><file>/usr/lib/pkgconfig/foo.pc</file></p></item>
+ <item><p><file>/usr/share/doc/foo/foo-manual.txt</file></p></item>
+ <item><p><file>/usr/bin/foo-utility</file></p></item>
+ </list>
+
+ <p>
+ You might modify <code>Foo</code> version 4 to install these files
+ instead:
+ </p>
+ <list>
+ <item><p><file>/usr/include/foo-4/foo/foo.h</file></p></item>
+ <item><p><file>/usr/include/foo-4/foo/utils.h</file></p></item>
+ <item><p><file>/usr/lib/libfoo-4.so</file></p></item>
+ <item><p><file>/usr/lib/pkgconfig/foo-4.pc</file></p></item>
+ <item><p><file>/usr/share/doc/foo-4/foo-manual.txt</file></p></item>
+ <item><p><file>/usr/bin/foo-utility-4</file></p></item>
+ </list>
+
+ <p>
+ It could then be parallel installed with version 5:
+ </p>
+ <list>
+ <item><p><file>/usr/include/foo-5/foo/foo.h</file></p></item>
+ <item><p><file>/usr/include/foo-5/foo/utils.h</file></p></item>
+ <item><p><file>/usr/lib/libfoo-5.so</file></p></item>
+ <item><p><file>/usr/lib/pkgconfig/foo-5.pc</file></p></item>
+ <item><p><file>/usr/share/doc/foo-5/foo-manual.txt</file></p></item>
+ <item><p><file>/usr/bin/foo-utility-5</file></p></item>
+ </list>
+
+ <p>
+ This is easily supported using
+ <link href="http://www.freedesktop.org/wiki/Software/pkg-config/">
+ <cmd>pkg-config</cmd></link>: <file>foo-4.pc</file> would add
+ <file>/usr/include/foo-4</file> to the include path and
+ <file>libfoo-4.so</file> to the list of libraries to link;
+ <file>foo-5.pc</file> would add <file>/usr/include/foo-5</file> and
+ <file>libfoo-5.so</file>.
+ </p>
+ </section>
+
+ <section id="version-numbers">
+ <title>Version Numbers</title>
+
+ <p>
+ The version number that goes in filenames is an <em>ABI/API</em> version.
+ It should not be the full version number of your package — just the part
+ which signifies an API break. If using the standard
+ <code><var>major</var>.<var>minor</var>.<var>micro</var></code> scheme for
+ project versioning, the API version is typically the major version number.
+ </p>
+
+ <p>
+ Minor releases (typically where API is added but <em>not</em> changed or
+ removed) and micro releases (typically bug fixes) do not affect API
+ backwards compatibility so do not require moving all the files.
+ </p>
+
+ <p>
+ The examples in the following sections assume that the API version and
+ soname are exported from <file>configure.ac</file> using the following
+ code:
+ </p>
+ <listing>
+ <title>API Versioning in Autoconf</title>
+ <desc>
+ Code to export the API version and soname from <file>configure.ac</file>
+ </desc>
+ <code># Before making a release, the <var>LIBRARY</var>_LT_VERSION string should be modified.
+# The string is of the form c:r:a. Follow these instructions sequentially:
+#
+# 1. If the library source code has changed at all since the last update,
+# then increment revision (‘c:r:a’ becomes ‘c:r+1:a’).
+# 2. If any interfaces have been added, removed, or changed since the last update,
+# increment current, and set revision to 0.
+# 3. If any interfaces have been added since the last public release,
+# then increment age.
+# 4. If any interfaces have been removed or changed since the last public release,
+# then set age to 0.
+AC_SUBST([<var>LIBRARY</var>_LT_VERSION],[1:0:0])
+
+AC_SUBST([<var>LIBRARY</var>_API_VERSION],[4])</code>
+ </listing>
+ </section>
+
+ <section id="header-files">
+ <title>C Header Files</title>
+
+ <p>
+ Header files should always be installed in a versioned subdirectory that
+ requires an <cmd>-I</cmd> flag to the C compiler. For example, if my
+ header is <file>foo.h</file>, and applications do this:
+ </p>
+ <code mime="text/x-csrc">#include <foo/foo.h></code>
+ <p>
+ then I should install these files:
+ </p>
+ <list>
+ <item><p><file>/usr/include/foo-4/foo/foo.h</file></p></item>
+ <item><p><file>/usr/include/foo-5/foo/foo.h</file></p></item>
+ </list>
+
+ <p>
+ Applications should pass the flag <cmd>-I/usr/include/foo-4</cmd> or
+ <cmd>-I/usr/include/foo-5</cmd> to the C compiler. Again, this is
+ facilitated by using <cmd>pkg-config</cmd>.
+ </p>
+
+ <p>
+ Note the extra <file>foo/</file> subdirectory. This namespaces the
+ <code mime="text/x-csrc">#include</code> to avoid file naming collisions
+ with other libraries. For example, if two different libraries install
+ headers called <file>utils.h</file>, which one gets included when you
+ use <code mime="text/x-csrc">#include <utils.h></code>?
+ </p>
+
+ <p>
+ There’s some temptation to keep one of the header files outside of any
+ subdirectory:
+ </p>
+ <list>
+ <item><p><file>/usr/include/foo.h</file></p></item>
+ <item><p><file>/usr/include/foo-5/foo.h</file></p></item>
+ </list>
+
+ <p>
+ The problem there is that users are always accidentally getting the wrong
+ header, since <cmd>-I/usr/include</cmd> seems to find its way onto compile
+ command lines with some regularity. If you must do this, at least add a
+ check to the library that detects applications using the wrong header file
+ when the library is initialized.
+ </p>
+
+ <p>
+ Versioned header files can be installed from automake using the following
+ code:
+ </p>
+ <listing>
+ <title>Header Files in Automake</title>
+ <desc>
+ Code to install versioned header files from <file>Makefile.am</file>
+ </desc>
+ <code><var>library</var>includedir =
$(includedir)/lib<var>library</var>-@<var>LIBRARY</var>_API_VERSION@/<var>library</var>
+<var>library</var>_headers = \
+ lib<var>library</var>/example1.h \
+ lib<var>library</var>/example2.h \
+ $(NULL)
+
+# The following headers are private, and shouldn't be installed:
+private_headers = \
+ lib<var>library</var>/example-private.h \
+ $(NULL)
+# The main header simply #includes all other public headers:
+main_header = lib<var>library</var>/<var>library</var>.h
+public_headers = \
+ $(main_header) \
+ $(<var>library</var>_headers) \
+ $(NULL)
+
+<var>library</var>include_HEADERS = $(public_headers)</code>
+ </listing>
+
+ <p>
+ As well as correct versioning, all APIs in installed headers should be
+ <link xref="namespacing">namespaced correctly</link>.
+ </p>
+ </section>
+
+ <section id="libraries">
+ <title>Libraries</title>
+
+ <p>
+ Library object files should have a versioned name. For example:
+ </p>
+ <list>
+ <item><p><file>/usr/lib/libfoo-4.so</file></p></item>
+ <item><p><file>/usr/lib/libfoo-5.so</file></p></item>
+ </list>
+
+ <p>
+ This allows applications to get exactly the one they want at compile time,
+ and ensures that versions 4 and 5 have no files in common.
+ </p>
+
+ <p>
+ Versioned libraries can be built and installed from automake using the
+ following code:
+ </p>
+ <listing>
+ <title>Libraries in Automake</title>
+ <desc>
+ Code to build a#nd install versioned libraries from
+ <file>Makefile.am</file>
+ </desc>
+ <code>lib_LTLIBRARIES = lib<var>library</var>/lib<var>library</var>-@<var>LIBRARY</var>_API_VERSION la
+
+lib<var>library</var>_lib<var>library</var>_@<var>LIBRARY</var>_API_VERSION _la_SOURCES = \
+ $(private_headers) \
+ $(<var>library</var>_sources) \
+ $(NULL)
+lib<var>library</var>_lib<var>library</var>_@<var>LIBRARY</var>_API_VERSION _la_CPPFLAGS = …
+lib<var>library</var>_lib<var>library</var>_@<var>LIBRARY</var>_API_VERSION _la_CFLAGS = …
+lib<var>library</var>_lib<var>library</var>_@<var>LIBRARY</var>_API_VERSION _la_LIBADD = …
+lib<var>library</var>_lib<var>library</var>_@<var>LIBRARY</var>_API_VERSION _la_LDFLAGS = \
+ -version-info $(<var>LIBRARY</var>_LT_VERSION) \
+ $(AM_LDFLAGS) \
+ $(NULL)</code>
+ </listing>
+
+ <section id="library-sonames">
+ <title>Library sonames</title>
+
+ <p>
+ Library sonames (also known as libtool version numbers) only address the
+ problem of runtime linking previously-compiled applications. They don’t
+ address the issue of compiling applications that require a previous
+ version, and they don’t address anything other than libraries.
+ </p>
+
+ <p>
+ For this reason, sonames should be used, but <em>in addition</em> to
+ versioned names for libraries. The two solutions address different
+ problems.
+ </p>
+ </section>
+ </section>
+
+ <section id="pkg-config">
+ <title>pkg-config Files</title>
+
+ <p>
+ pkg-config files should have a versioned name. For example:
+ </p>
+ <list>
+ <item><p><file>/usr/lib/pkgconfig/foo-4.pc</file></p></item>
+ <item><p><file>/usr/lib/pkgconfig/foo-5.pc</file></p></item>
+ </list>
+
+ <p>
+ Since each pkg-config file contains versioned information about the
+ library name and include paths, any project which depends on the library
+ should be able to switch from one version to another simply by changing
+ their pkg-config check from <file>foo-4</file> to <file>foo-5</file> (and
+ doing any necessary API porting).
+ </p>
+
+ <p>
+ Versioned pkg-config files can be installed from autoconf and automake
+ using the following code:
+ </p>
+ <listing>
+ <title>pkg-config Files in Autoconf and Automake</title>
+ <desc>
+ Code to install versioned pkg-config files from
+ <file>configure.ac</file> and <file>Makefile.am</file>
+ </desc>
+
+ <code>AC_CONFIG_FILES([
+lib<var>library</var>/<var>library</var>-$<var>LIBRARY</var>_API_VERSION.pc:lib<var>library</var>/<var>library</var>.pc.in
+],[],
+[<var>LIBRARY</var>_API_VERSION='$<var>LIBRARY</var>_API_VERSION'])</code>
+
+ <code># Note that the template file is called <var>library</var>.pc.in, but generates a
+# versioned .pc file using some magic in AC_CONFIG_FILES.
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = lib<var>library</var>/<var>library</var>-$(<var>LIBRARY</var>_API_VERSION).pc
+
+DISTCLEANFILES += $(pkgconfig_DATA)
+EXTRA_DIST += lib<var>library</var>/<var>library</var>.pc.in</code>
+ </listing>
+ </section>
+
+ <section id="configuration-files">
+ <title>Configuration Files</title>
+
+ <p>
+ From a user standpoint, the best approach to configuration files is to
+ keep the format both forward and backward compatible (both library
+ versions understand exactly the same configuration file syntax and
+ semantics). Then the same configuration file can be used for all versions
+ of the library, and no versioning is needed on the configuration file
+ itself.
+ </p>
+
+ <p>
+ If you can’t do that, the configuration files should simply be renamed,
+ and users will have to configure each version of the library separately.
+ </p>
+ </section>
+
+ <section id="gettext">
+ <title>Gettext Translations</title>
+
+ <p>
+ If you use gettext for translations in combination with autoconf and
+ automake, normally things are set up to install the translations to
+ <file>/usr/share/locale/<var>lang</var>/LC_MESSAGES/<var>package</var></file>.
+ You’ll need to change <var>package</var>. The convention used in GNOME is
+ to put this in <file>configure.ac</file>:
+ </p>
+
+ <code>GETTEXT_PACKAGE=foo-4
+AC_SUBST([GETTEXT_PACKAGE])
+AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE],["$GETTEXT_PACKAGE"])</code>
+
+ <p>
+ Then use <code>GETTEXT_PACKAGE</code> as the package name to pass to
+ <code mime="text/x-csrc">bindtextdomain()</code>,
+ <code mime="text/x-csrc">textdomain()</code>, and
+ <code mime="text/x-csrc">dgettext()</code>.
+ </p>
+
+ <comment>
+ <p>FIXME: Cross-reference to i18n pages.</p>
+ </comment>
+ </section>
+
+ <section id="dbus">
+ <title>D-Bus Interfaces</title>
+
+ <p>
+ A D-Bus interface is another form of API, similar to a C API except that
+ resolution of the version is done at runtime rather than compile time.
+ Versioning D-Bus interfaces is otherwise no different to C APIs: version
+ numbers must be included in interface names, service names and object
+ paths.
+ </p>
+
+ <p>
+ For example, for a service <code>org.example.Foo</code> exposing
+ interfaces <code>A</code> and <code>B</code> on objects
+ <code>Controller</code> and <code>Client</code>, versions 4 and 5 of the
+ D-Bus API would look like this:
+ </p>
+ <list>
+ <title>Service Names</title>
+ <item><p><code>org.example.Foo4</code></p></item>
+ <item><p><code>org.example.Foo5</code></p></item>
+ </list>
+ <list>
+ <title>Interface Names</title>
+ <item><p><code>org.example.Foo4.InterfaceA</code></p></item>
+ <item><p><code>org.example.Foo4.InterfaceB</code></p></item>
+ <item><p><code>org.example.Foo5.InterfaceA</code></p></item>
+ <item><p><code>org.example.Foo5.InterfaceB</code></p></item>
+ </list>
+ <list>
+ <title>Object Paths</title>
+ <item><p><code>/org/example/Foo4/Controller</code></p></item>
+ <item><p><code>/org/example/Foo4/Client</code></p></item>
+ <item><p><code>/org/example/Foo5/Controller</code></p></item>
+ <item><p><code>/org/example/Foo5/Client</code></p></item>
+ </list>
+
+ <comment>
+ <p>
+ FIXME: Link to the upstream D-Bus guide to writing a good API:
+ https://bugs.freedesktop.org/show_bug.cgi?id=88994
+ </p>
+ </comment>
+ </section>
+
+ <section id="programs">
+ <title>Programs, Daemons and Utilities</title>
+
+ <p>
+ Desktop applications generally do not need to be versioned, as they are
+ not depended on by any other modules. Daemons and utility programs,
+ however, interact with other parts of the system and hence need
+ versioning.
+ </p>
+
+ <p>
+ Given a daemon and utility program:
+ </p>
+ <list>
+ <item><p><file>/usr/libexec/foo-daemon</file></p></item>
+ <item><p><file>/usr/bin/foo-lookup-utility</file></p></item>
+ </list>
+ <p>
+ these should be versioned as:
+ </p>
+ <list>
+ <item><p><file>/usr/libexec/foo-daemon-4</file></p></item>
+ <item><p><file>/usr/bin/foo-lookup-utility-4</file></p></item>
+ </list>
+
+ <p>
+ You may want to install a symbolic link from
+ <file>/usr/bin/foo-lookup-utility</file> to the recommended versioned
+ copy of the utility, to make it more convenient for users to use.
+ </p>
+ </section>
+</page>
diff --git a/programming-guidelines/Makefile.am b/programming-guidelines/Makefile.am
index 85d9824..d171609 100644
--- a/programming-guidelines/Makefile.am
+++ b/programming-guidelines/Makefile.am
@@ -13,6 +13,7 @@ HELP_FILES = \
documentation.page \
index.page \
memory-management.page \
+ parallel-installability.page \
tooling.page \
writing-good-code.page \
$(NULL)
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]