openSUSE:Shared library packaging policy
Build Service Tutorial · Tips & Tricks · Cross Distribution Howto · Packaging checks
Desktop menu categories · RPM Macros · Scriptlets · Init scripts · How to write good changes
Rationale
This scheme makes it possible to install with the RPM package manager and use multiple shared libraries that share the same name stem but having different SO version (shared object version). Traditionally (pre-10.3), only one particular version of a library was built and shipped, and when the SO version changed, all packages depending on it also needed updating. This becomes a problem especially with packages coming from sources other than the distribution itself, since updated versions may not have been made available yet.
With the following naming and packaging guidelines, each library version becomes a separate installable entity, which allows for their coexistence.
From that follows that only files should be included therein which are not generating file conflicts later if installed together with another libfoo*.rpm. Hence only lib*.so.* files are allowed in them. To ensure that no shared libraries creep in which are not handled, we disallow them to be in any package not named via these rules.
Effectively, this creates a partition of all files into shared libs and others, and makes sure that no rpm package contains files from both partitions.
Eligibility
- If the shared library in question is a plugin (“module” in libtool nomenclature), the shlib policy does not apply — but do see the hints in the “Plugins” section below.
- If (and only if) there are no corresponding development files (.so link, .h files, .pc files) and there is therefore no -devel package for the shared library, the library can be considered private and the shlib policy does not apply. (Example: libencfs inside the encfs package as of openSUSE Leap 42.1.) The placement of the library file is up to the maintainer.
Underlying principles
The principle rule of versioning is that removing or changing the ABI in an incompatible way (forwards as well as backwards) requires a new, different SONAME. (With a technique called symbol versioning, the SONAME may be kept on forward-compatible changes, but that is for some other documentation to address.) This task of managing the SONAMEs generally falls to the upstream project, but a openSUSE package maintainer should be aware of it nevertheless, in case upstream does not do its job properly.
There is a utility called abidiff (libabigail-tools package) which can tell whether the ABI changed, and in what ways. It supports reading split-debug files, but only if they are present in /usr/lib/debug.
There are no restrictions for choosing SONAMEs. You will often see names followed by single or triple integers, as in libfoo.so.1 and libfoo.so.1.0.0, but this is not mandatory. It is technically valid to use arbitrary characters and notations. It all comes down to the uniqueness of a SONAME in its surrounding environment. The extent of the uniqueness requirement usually (1.) stretches beyond Linux distributions but (2.) does not cross kernel families (Linux↔*BSD), (3.) does not cross architecture boundaries (e.g. i586↔x86_64), (4.) does not cross object formats (PE↔ELF).
$ readelf -a /usr/lib64/*.so | grep SONAME 0x000000000000000e (SONAME) Library soname: [libstdc++.so.6] [...] 0x000000000000000e (SONAME) Library soname: [libbfd-2.25.0.so] [...]
When there is no SONAME entry, the filename itself is taken as the SONAME.
Versioning schemes
The two most common schemes:
libstdc++.so.6: Numbers after the .so part are abstract identifiers for the ABI that are decoupled from, and may increment separately from, the package version.
libbfd-2.25.0.so: Numbers before the .so part are often the package version (or part of it) and are used when the authors expect ABI changes every release and as such do not want to deal with interface identifiers.
Other combinations:
libmwaw-0.3.so.3.0.11: The LibreOffice project(s) decided to add the version/generation number (0.3) so that they can have the development files and symlinks of multiple generations installed at the same time.
libVkLayer_utils.so: This name suggests either a plugin, or an unfortunate complete lack of versioning.
When there is no versioning
When the installation procedure (e.g. `make install`) of a source package…
- installs a static library but no corresponding shared library,
- installs a shared library with no versioning,
- installs a shared library that, in contrast to a prior version of the software, breaks the ABI but re-uses the same SONAME,
then the build procedure should be modified to remedy the situation and produce a shared library that is unambiguous to RPM. A few possible solutions are:
- Integration of the package version string into the filename before (and only before) .so, i.e. turning libutf8_validity.so into libutf8_validity-28.2.so, or
- Manually assign numbers (“whatever-works”) under a "SUSE" namespace, e.g. libutf8_validity.so.suse0 / libutf8_validity-suse0.so / libutf8_validity-suse.so.0 (example openSUSE packages: wxWidgets, lzma-sdk), or
- Addition of forced symbol versioning where the linker is called with -Wl,--version-script= at all times (example openSUSE package: dwarves). Note that this method can impede or nullify the co-installability feature of SLPP because the filename does not change. Assess and use with care.
The exact steps needed to effect the desired change depends on the particular software being packaged.
Package naming
A library's name, specifically its SONAME, which is used as a base to construct the versioned package name, can be composed of multiple parts (though not all are required). Looking at a full-featured example, a filename like libfoo-bar-2.5.so.8.2.1 contains:
- general system prefix lib
- name of the library: foo-bar
- package version (optional): -2.5
- general system suffix for shared libraries: .so
- SO version (optional): 8.2.1
The distropackage's name is derived from the filename, and is constructed by concatenating:
- the lib prefix and the name, dots are replaced by underscores
- if an upstream version is specified, dots are replaced by underscores
- to avoid ambiguities, a dash is inserted between two numbers
- the SO version from the SONAME (which may be shorter than the filename), dots are replaced by underscores
All possibilities, with examples mostly taken from openSUSE:
Description | SONAME | Versioned package name in distro |
---|---|---|
Nothing | libdsocks.so | (shlib packaging not applicable) |
With package version | libdb-4.8.so | libdb-4_8 |
With SO version | libblkid.so.1 | libblkid1 |
Name that has a number, and SO version | libbz2.so.1 | libbz2-1 |
With version and SO version | libzziplib-0.so.13 | libzziplib-0-13 |
All together and long SO number | libgame2-1.9.so.10.0.0 | libgame2-1_9-10_0_0 |
N.B.: The astute reader might have noticed that this mapping is not bijective (1:1). The hypothetical filenames libgame3.so.1 and libgame3-1.so would indeed lead to the same shlib package name (libgame3-1). This is not catered for by this policy, but such a situation has yet to occur in practice. Were such to occur, we would probably choose to patch the second package to produce libgame3-1.so.0 in place of libgame3-1.so to resolve the disambiguity.
Unversioned packages
Versioned packages are so called because they include all or part of a version in their name. The antipole to these are unversioned shlib packages for which most of this document does not apply. Examples of such packages are: samba-libs, xen-libs. Ever since the shlib policy was formalized, we seek to avoid or eliminate unversioned shlib packages within the boundaries of common sense — and gut feeling of when to better keep unversioned packages.
Layout of the spec file
The shared library installables are usually a subpackage of a greater .spec file. The Build Service package name and .spec file name generally follow the name of the tarball. For the example of SDL2_image and libHX, it looks like:
Name: SDL2_image ... %package -n libSDL2_image-2_0-0 ... %package devel ... |
Name: libHX ... %package -n libHX28 ... %package devel ... |
Package Contents
- In general, versioned packages shall not contain anything but the shared library/libraries. No headers, no devel .so symlink, no config files, no documentation, etc.
- It may contain the license file if so required, and it must be in a versioned directory. (Using "%doc LICENSE" in a specfile's files section usually suffices.)
- If a versioned shared library package must contain other files than shared libraries for whatever (approved!) reason, their names must be made unambiguous by putting them in a versioned directory or by versioning their names to not conflict with the rationale of this policy. (Example: the libnl3-200 package has a /usr/lib64/libnl3-200 directory.)
- The (simpler) alternative: have the shlib package require another package (can be unversioned) for such files, if so supported.
- A versioned package is allowed to ship multiple library files, provided they share the same numbers and these numbers always change in lockstep throughout. (Example: libqt4 package shipping libQtCore.so.4, libQtNetwork.so.4 etc.)
- If one of the contained libraries does not create a dependency on all or most other contained libraries, then it is preferred to not merge those libraries into one rpm, but leave them in their own rpm. Consider installation size.
- All unsuffixed packages named lib* end with $NUM
- Common suffixes include e.g. -devel or -debuginfo
- Packages with suffix -devel should in general omit $NUM as -devel packages for different library versions usually conflict due to common header file names. See (4a) and (4b) for how it works otherwise.
- Files needed to develop programs using shared libraries contained in lib$NAME$NUM.rpm are packaged in a -devel package (see (4a) and (4b) for cases that need to version this package). Those files include lib*.so, lib*.la and all headers. Optionally those files can also be placed in $NAME.rpm, in the case that it also comes with other tools or documentation. But if there is a *-devel.rpm package, then it contains all lib*.so, lib*.la and headers. The -devel package is also an appropriate place to put license files.
- lib$NAME$NUM.rpm should not be needed to be manually installed by any user. The correct rpm group for such packages i System/Libraries
Best Practices
The following are guidelines for library packaging in general:
- Avoid packaging static libraries. You should use --disable-static configure option or, as a last resort, remove static libraries after make install. If in doubt, ask.
- If you package a static library in addition to a shared library, the static library should normally not be built with -fPIC if possible (remove the --with-pic configure option).
- If you package a static library without a corresponding shared library, the static library must be built with -fPIC (add --with-pic.
- Avoid packaging libtool config files (.la files). If you do not package a static library, and if you are also placing the shared library in a standard search directory (i.e. %_lib, /usr/%_lib), they will not be needed. If your shared library resides — for whatever reason — in, for example, /opt/package/lib/libfoo.so.7, you do need the libfoo.la file, or further programs using libtool to link against libfoo.so.7 will be lacking the correct path.
- If in doubt, ask.
- Shared libraries are normally not standalone runnable (exception: libc.so.6 and ld-linux.so.2). But they often have the +x bit set!
Exceptions
- (2) A list of packages that are exempt from this policy is: (to be extended with explicit approval only)
- glibc
- pam
- (4a) If more than one version of a library is available from a single source repository, even conflicting -devel packages need to be suffixed to avoid multiple packages with the same package name. Proper conflicts need to be added in this case as well.
- (4b) If more than one version of a -devel package can be installed at the same time (for example because includes are packaged in a versioned directory and shared libraries have a versioned name like libgtk1.so.1), the -devel packages should be suffixed with a number that allows identifying the version of the library (usually this is the same number as the shared library package suffix $NUM). So such a -devel package would be named lib$NAME$NUM-devel.
Plugins
Plugins in the form of a shared library should be in a subdirectory in %_libdir appropriate for the program. (Example: %_libdir/apache2/mod_alias.so).
In automake (since 0.25), the ${pkglibdir} variable is available for this purpose. It defaults to ${libdir}/${PACKAGE} and therefore may need to be overriden if the plugin is shipped in a tarball separate from the program's main source.
Plugins in the form of executables should be in a subdirectory in %_libexecdir appropriate for the program. (Example: %_libexecdir/cups/filter/pdftops).
In automake (since 1.10b), the ${pkglibexecdir} variable is available for this purpose.
Hints
You should remember the following while trying to conform to this policy:
- There is no reason why a -devel package cannot provide development files for multiple shared library packages.
- There is no need to have a binary package that matches the source package name. In fact, you should avoid renaming the source package for different versions as we prefer to only have a single package version in the source repository.
- Dependencies on the shared library package result either from automatic dependencies created by rpm on the shared libraries if they are used, or from the single other case, the library -devel package, which should require the shared library package with a properly versioned requires.
- This policy and its enforcement only is about shared library packages and shared libraries placed in /%_lib or /usr/%_lib. Rules that apply to other parts are only best practices and policies for them may exist elsewhere.
For libraries that change the SONAME frequently, updates will cause multiple versions of the library to be installed in parallel, even if no other packages depend on older versions. This is no problem, because zypper dup cleans out unneeded shared library packages on a regular basis. This works through "Provides: weakremover(...)" dependencies in the package openSUSE-release. These dependencies are created automatically by a bot on the build service. When zypper dup encounters a weakremover dependency for some shared library, and no other installed package depends on that shared library, it will remove the package.
Examples
The simplest example is a source package which builds a single shared library and development files (the zlib package matches this example). The source package should be names according upstream tarball (zlib, see Naming guidelines and Layout of the specfile) and resulting binary package has to be named libz1 and zlib-devel (libz-devel would be acceptable). You can omit the %files section for main package, therefor no zlib.rpm will be generated.
zlib.spec:
Name: zlib ... %package -n libz1 %package devel
with the following contents (file list shortened),
libz1:
/lib/libz.so.1 /lib/libz.so.1.2.3
zlib-devel:
/usr/lib/libz.so /usr/lib/libz.a /usr/include/zlib.h /usr/share/man/man3/zlib.3.gz /usr/share/doc/packages/zlib/algorithm.txt
A more complicated example is if apart from the library and the development files there are also executables and documentation (the bzip2 package matches this example). There should be four packages (file list shortened),
libbz2-1:
/lib/libbz2.so.1 /lib/libbz2.so.1.0.0
libbz2-devel:
/usr/include/bzlib.h /usr/lib/libbz2.so /usr/lib/libbz2.a
bzip2:
/usr/bin/bzip2 /usr/share/man/man1/bzip2.1.gz
bzip2-doc:
/usr/share/doc/packages/bzip2-doc/manual.ps.gz
where documentation may be either merged into the binary package bzip2 or the development package libbz2-devel, if it is sufficiently small and related.
Another example is a library which depends on configuration or data files that can be shared with other versions of the library (the curl package matches this example). In this case, the config or data files should be split into a separate package and the shared library package should require that. For example, curl has the four packages libcurl4, libcurl-devel, curl and curl-ca-bundle (CA certificates required by libcurl4).
Windows/MinGW packages
Everything what has been mentioned above can also be readily applied to packages containing Dynamic Link Libraries, Windows's implementation of shared libraries. This is mostly relevant for the projects rooted in the "windows" project on build.opensuse.org. The highlights:
- DLLs are placed in /usr/platform/sys-root/mingw/bin.
- The SONAME for a library is usually in the form of libfoo-2.dll. The composition is:
- general system prefix lib
- name of the library foo-bar
- package version (optional): -2.5
- SO version (optional): -8
- system suffix for shared libraries (required): .dll
- Like on the BSD platform(s), the libtool naming for Windows DLLs only contains the major number. (see table above)
Name transformation matrix:
Description | SONAME | Versioned package name in distro |
---|---|---|
Nothing | libdsocks.dll | (shlib packaging not applicable) |
With package version | libdb-4.8.dll | libdb-4_8 |
With SO version | libblkid-1.dll | libblkid1 |
Name that has a number, and SO version | libbz2-1.dll | libbz2-1 |
With version and SO version | libzziplib-0-13.dll | libzziplib-0-13 |
All together and long SO number | libgame2-1.9-10.0.0.dll | libgame2-1_9-10_0_0 |
Projects may decide to prepend the mingw{32,64} identifier into the package name to avoid conflicts with native packages. You will therefore see names like mingw64-libHX28, for example.
Some libraries are built without the use of libtool, which may make it ambiguous at times as to which part constitutes the -release number and the -version-info. Examples and their resolutions:
- libmwaw-0.1.dll (libmwaw-0.1.so.1 on Linux); given the corresponding Linux name, "libmwaw-0.1" is the clear basename, and the DLL is unversioned. The package name is therefore libmwaw-0_1.
- libnettle-4-6.dll (libnettle.so.4 on Linux); while dashes are technically possible in SONAMEs, they are practically non-existant. Chose /-(\d+).dll/ for SO number, making the base name "libnettle-4". The package name is therefore libnettle-4-6, rather than libnettle4-6.