Write a DEBIAN/symbols File That Keeps ABI History
Get one minimum version wrong in DEBIAN/symbols and a package installs happily, then crashes when a program looks up a symbol that is not there. This guide builds a small, reviewable file for a shared library: SONAME, dependency template, exported symbols and minimum package versions. The examples follow the installed deb-symbols(5) manual from the Ubuntu build of dpkg 1.22.6, package version 1.22.6ubuntu6.6. Allow about twenty minutes, plus time to get the real symbol list from your library.
The route
Jump straight to the step you need, or tick off Done means at the end.
This is packaging work, normally done in a source tree or a temporary staging directory. The examples do not install a package, alter the system database or need elevated privileges. You need:
- Tooling.
dpkg-dev. - A library. A shared library to inspect.
- Package knowledge. Enough to know which package provides its runtime and development interfaces.
1. Confirm the format on this machine
Check the package version and read the installed manual before editing a file. That ties the syntax to the toolchain you will actually use:
$ dpkg-query -W -f='${Package} ${Version}\n' dpkg-dev dpkg
dpkg-dev 1.22.6ubuntu6.6
dpkg 1.22.6ubuntu6.6
$ man 5 deb-symbols
The file is installed inside a binary package as DEBIAN/symbols. Debian tooling reads its entries when it calculates shared-library dependencies. It is not a linker script, and it does not tell the dynamic loader where to find a library.
Checkpoint
You know which package you are building and the library's exact SONAME. Do not copy the filename from a development symlink and assume it is the SONAME.
2. Record the library SONAME and dependency
Use objdump to inspect the library you will ship. It is read-only:
$ objdump -p /path/to/libexample.so.1 | grep SONAME
SONAME libexample.so.1
The first line of a symbols entry starts with that exact SONAME, followed by the main dependency template. The #MINVER# marker is replaced when a dependency is generated. If a symbol needs version 1.4-2, the generated dependency can become libexample1 (>= 1.4-2). If an unversioned dependency is enough, the marker adds no version constraint.
libexample.so.1 libexample1 #MINVER#
Use the package that actually contains the runtime library. A -dev package normally supplies headers and the unversioned linker name, but it is not automatically the runtime dependency.
3. Add one line per public symbol
Each symbol line has three columns, separated by one whitespace character:
- Symbol and ABI version. The exported symbol with its version label.
- Minimum package version. The first one where the symbol is available.
- Alternative-template identifier. Optional.
libexample.so.1 libexample1 #MINVER#
public_api@Base 1.0-1
public_api_v2@Base 1.4-2
Base is the version label used when the library does not use symbol versioning. If the ELF symbol has another version label, keep the name your symbol inspection tooling reports. Do not dump every private implementation detail into the file by habit. The useful set is the ABI that consumers may link against.
The version is a package version, not necessarily the upstream library version. It must name the first Debian package version that shipped the symbol. Too old a value can let an installation resolve cleanly and fail later, when a program looks up the missing symbol.
Checkpoint
Compare your list with the library's exported dynamic symbols and with the previous package revision. Removing a symbol from the file is an ABI statement, so review it as carefully as a source-code interface change.
4. Add an alternative dependency for other providers
A library can have a main dependency and one or more alternatives. Add an alternative line beginning with |, then give symbols a numeric identifier based on its order:
libexample.so.1 libexample1 #MINVER#
| libexample-compat #MINVER#
* Build-Depends-Package: libexample-dev
public_api@Base 1.0-1
compatibility_api@Base 1.2-1 1
The first alternative template is 1, the second is 2, and so on. The main template is always part of the dependency. A symbol's identifier selects the additional template combined with it. In the example, compatibility_api requires both the main dependency and the first alternative.
Warning
Only use this form when the alternative package genuinely provides the required ABI. It is easy to write a dependency that is satisfiable on paper but cannot run the consumer. Test the resulting dependency with the real packages in every distribution you support.
5. Add metadata that affects dependency generation
Metadata lines begin with an asterisk and belong to the library entry. The common field names the development package that must be reflected in generated dependencies:
* Build-Depends-Package: libexample-dev
Build-Depends-Packages accepts a comma-separated list and overrides Build-Depends-Package if both are present:
* Build-Depends-Packages: libexample-dev, libexample-compat-dev
Use the plural field for a deliberate transition or metapackage arrangement, not to silence a dependency mismatch.
On dpkg 1.22.6 the supported internal-symbol field is Allow-Internal-Symbol-Groups. Its value is a whitespace-separated list such as aeabi or gomp on ELF and GNU systems. The older Ignore-Blacklist-Groups name is deprecated.
6. Review the file before packaging
Read the finished file as a dependency map, not just as text. A complete small example:
libexample.so.1 libexample1 #MINVER#
| libexample-compat #MINVER#
* Build-Depends-Package: libexample-dev
public_api@Base 1.0-1
compatibility_api@Base 1.2-1 1
Check the SONAME, runtime package, first-seen package versions, alternative numbering and metadata spelling. Keep the leading spaces shown. The manual specifies the entry columns and metadata layout, and a malformed line can fail the package build or produce the wrong dependency.
Then build the package in your normal unprivileged packaging workflow and inspect the generated control metadata. For a staged package directory, a safe first check is:
$ dpkg-deb --build /path/to/package-tree /tmp/example.deb
$ dpkg-deb -I /tmp/example.deb | sed -n '/^ Depends:/p'
Warning
Do not use sudo for this build. Test-installing the resulting package is a separate, state-changing operation: take a backup or use a disposable environment, and follow your normal package rollback procedure. Removing a package to undo a test can also remove dependants, so do not make that your first validation step.
Common traps
- SONAME versus linker name.
libexample.so.1is not interchangeable with the development namelibexample.so. - Upstream versus package version. They can differ. Record the first package version that shipped each symbol.
- Positional identifiers. Inserting a new alternative above an existing one changes the meaning of every later identifier.
- Plural override.
Build-Depends-Packagesoverrides the singular field, so do not leave both with conflicting intent. - Green build, broken consumer. A successful package build does not prove a consumer can run. Test representative consumers against the generated dependency.
Done means
- Header is exact. The file uses the library's exact SONAME and the correct runtime package.
- Versions are first-seen. Every listed public symbol carries the first Debian package version that supplied it.
- Alternatives are ordered. Templates are numbered in order and used only for symbols they can provide.
- Metadata matches. Field names follow the installed
deb-symbols(5)format, including plural-field precedence. - Dependency inspected. You checked the built package's generated dependency before any test installation.