Table of Contents:

This guide aims to explain how to use the apertis-abi-compare tool in order to detect API/ABI breakage between two versions of a library package.

Other Apertis documents are available:

How to use apertis-abi-compare tool

First, install the apertis-dev-tools package (>= 0.2021.11) providing the apertis-abi-compare tool.

1
sudo apt install apertis-dev-tools

Then, build you library package using gbp buildpackage (or dpkg-buildpackage) for example.

1
2
3
4
5
6
7
8
user@apertis:~$ cd dev/
user@apertis:~/dev$ git clone https://gitlab.apertis.org/pkg/zlib.git
user@apertis:~/dev$ cd zlib/
user@apertis:~/dev/zlib$ dch -i
...
# work on packaging update
...
user@apertis:~/dev/zlib$ dpkg-buildpackage

This has generated several files in the higher level folder:

user@apertis:~/dev/zlib$ cd ..
user@apertis:~/dev$ ls -l
total 1144
-rw-r--r--  1 user user  96856 Apr 15 09:33 lib32z1-dbgsym_1.2.11.dfsg-2+deb11u1+apertis0_amd64.deb
-rw-r--r--  1 user user  96620 Apr 15 09:33 lib32z1-dev_1.2.11.dfsg-2+deb11u1+apertis0_amd64.deb
-rw-r--r--  1 user user  94264 Apr 15 09:33 lib32z1_1.2.11.dfsg-2+deb11u1+apertis0_amd64.deb
drwxr-xr-x 15 user user   4096 Apr 15 09:33 zlib
-rw-r--r--  1 user user 102404 Apr 15 09:33 zlib1g-dbgsym_1.2.11.dfsg-2+deb11u1+apertis0_amd64.deb
-rw-r--r--  1 user user 191712 Apr 15 09:33 zlib1g-dev_1.2.11.dfsg-2+deb11u1+apertis0_amd64.deb
-rw-r--r--  1 user user  50368 Apr 15 09:33 zlib1g-udeb_1.2.11.dfsg-2+deb11u1+apertis0_amd64.udeb
-rw-r--r--  1 user user  92172 Apr 15 09:33 zlib1g_1.2.11.dfsg-2+deb11u1+apertis0_amd64.deb
-rw-r--r--  1 user user  23968 Apr 15 09:33 zlib_1.2.11.dfsg-2+deb11u1+apertis0.debian.tar.xz
-rw-r--r--  1 user user   1903 Apr 15 09:33 zlib_1.2.11.dfsg-2+deb11u1+apertis0.dsc
-rw-r--r--  1 user user  12293 Apr 15 09:33 zlib_1.2.11.dfsg-2+deb11u1+apertis0_amd64.buildinfo
-rw-r--r--  1 user user   4127 Apr 15 09:33 zlib_1.2.11.dfsg-2+deb11u1+apertis0_amd64.changes
-rw-r--r--  1 user user 370248 Apr 14 19:18 zlib_1.2.11.dfsg.orig.tar.gz

Only .deb and .dsc files are used to check API/ABI breakage, so let’s move them to a new folder for clarity:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
user@apertis:~/dev$ mkdir zlib_test_api
user@apertis:~/dev$ mv *.deb *.dsc zlib_test_api
user@apertis:~/dev$ ls -l zlib_test_api/
total 676
-rw-r--r-- 1 user user  96856 Apr 15 09:33 lib32z1-dbgsym_1.2.11.dfsg-2+deb11u1+apertis0_amd64.deb
-rw-r--r-- 1 user user  96620 Apr 15 09:33 lib32z1-dev_1.2.11.dfsg-2+deb11u1+apertis0_amd64.deb
-rw-r--r-- 1 user user  94264 Apr 15 09:33 lib32z1_1.2.11.dfsg-2+deb11u1+apertis0_amd64.deb
-rw-r--r-- 1 user user 102404 Apr 15 09:33 zlib1g-dbgsym_1.2.11.dfsg-2+deb11u1+apertis0_amd64.deb
-rw-r--r-- 1 user user 191712 Apr 15 09:33 zlib1g-dev_1.2.11.dfsg-2+deb11u1+apertis0_amd64.deb
-rw-r--r-- 1 user user  92172 Apr 15 09:33 zlib1g_1.2.11.dfsg-2+deb11u1+apertis0_amd64.deb
-rw-r--r-- 1 user user   1903 Apr 15 09:33 zlib_1.2.11.dfsg-2+deb11u1+apertis0.dsc

Now, we can call apertis-abi-compare to compare API/ABI between our freshly created package with the one available in the apt repository. We just need to give as an argument to apertis-abi-compare the folder containing our .deb and .dsc files:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
user@apertis:~/dev$ apertis-abi-compare zlib_test_api
Checking for ABI/API breakage!
INFO: abi-compliance-checker requires having Build-Deps installed
INFO: Trying to install them now:
['debhelper', 'gcc-multilib<!nobiarch>', 'dpkg-dev']
...
...
Comparing ABIs ...
Comparing APIs ...
Creating compatibility report ...
Binary compatibility: 100%
Source compatibility: 100%
Total binary compatibility problems: 0, warnings: 0
Total source compatibility problems: 0, warnings: 0
Report: compat_reports/z/1:1.2.11.dfsg-2+deb11u1+apertis0bv2023dev2b2_to_1:1.2.11.dfsg-2+deb11u1+apertis0/compat_report.html

apertis-abi-compare reads the .dsc file to determine and download the previous version of the library available in the apt repository. Then, it extracts the .deb files of both versions in order to compare headers and libraries. apertis-abi-compare uses abi-compliance-checker internally to compare versions. Thus, a library XML descriptor file can be provided with the library package. If the descriptor file is not provided, apertis-abi-compare tries to generate a basic one.

A summary of the analysis is displayed with the number of problems detected and a comprehensive HTML report has been generated at compat_reports/z/1:1.2.11.dfsg-2+deb11u1+apertis0bv2023dev2b2_to_1:1.2.11.dfsg-2+deb11u1+apertis0/compat_report.html.

This report contains a list of problems detected regarding the Binary Compatibility and the Source Compatibility, both in separate tabs.

The first paragraph Test Info contains information about the analyzed library including the library name, compared package versions and architecture.

A second paragraph Test Results reports how many header files, libraries and symbols/types have been analyzed with a percentage of compatibility between both versions. In our example, the compatibility is 100% because we compared the same version of the zlib.

Next, Problem Summary summarizes all issues detected. These issues are sorted by categories: Added Symbols, Removed Symbols, Problems with Data Types, Problems with Symbols and Problems with Constants. Then, by their severity: High, Medium or Low.

Finally, the last parts contain respectively the list of Header files and Libraries analyzed.

Now, let’s simulate an API breakage by downloading a very old zlib package (1:1.2.3-11) from snapshot.debian.org and compare it to the new version (1:1.2.11.dfsg-2). Because the old version has less functionalities compared to the new one, abi-compliance-checker will complain about API/ABI breakages.

The package automatically downloaded by apertis-abi-compare from the apt repository will always be considered as older than the local one.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
user@apertis:~/dev$ mkdir zlib_test_api_broken
user@apertis:~/dev$ wget https://snapshot.debian.org/archive/debian/20060308T000000Z/pool/main/z/zlib/zlib_1.2.3-11.dsc
user@apertis:~/dev$ wget https://snapshot.debian.org/archive/debian/20060318T000000Z/pool/main/z/zlib/zlib1g_1.2.3-11_amd64.deb
user@apertis:~/dev$ wget https://snapshot.debian.org/archive/debian/20060318T000000Z/pool/main/z/zlib/zlib1g-dev_1.2.3-11_amd64.deb
user@apertis:~/dev$ mv *.deb *.dsc zlib_test_api_broken
user@apertis:~/dev$ ls -l zlib_test_api_broken/
total 500
-rw-r--r-- 1 user user 430142 Oct  8  2009 zlib1g-dev_1.2.3-11_amd64.deb
-rw-r--r-- 1 user user  73222 Oct  8  2009 zlib1g_1.2.3-11_amd64.deb
-rw-r--r-- 1 user user    735 Oct  8  2009 zlib_1.2.3-11.dsc
user@apertis:~/dev$ apertis-abi-compare zlib_test_api_broken/
Checking for ABI/API breakage!
INFO: abi-compliance-checker requires having Build-Deps installed
INFO: Trying to install them now:
['debhelper', 'dbs', 'libc6-dev-powerpc', 'libc6-dev-i386', 'lib64c-dev']
...
...
Comparing ABIs ...
Comparing APIs ...
Creating compatibility report ...
Binary compatibility: 67.6%
Source compatibility: 68.2%
Total binary compatibility problems: 28, warnings: 2
Total source compatibility problems: 27, warnings: 12
Report: compat_reports/z/1:1.2.11.dfsg-2+deb11u1+apertis0bv2023dev2b2_to_1:1.2.3-11/compat_report.html

Now, we see the binary and source compatibilities fell to 68%. Several problems and warnings are reported as well.

Opening the report will give use more information about the detected issues:

The Problem Summary informs us that 27 symbols have been removed with a high severity. Two problems have been detected regarding data types or constants, classified in low severity and one medium severity issue with symbols has been detected.

Then, we have the list of Removed Symbols

Followed by the list of detected Problems:

By clicking on the + button, the report displays the detected change and its implications.

The source compatibility tab reports similar information than the binary compatibility tab.

Packages providing multiple libraries

Support of packages providing multiple libraries has been added in apertis-dev-tools version 0.2022.1.

For packages providing multiple libraries, it is highly recommended to provide a library XML descriptor for each library provided in the package. abi-compliance-checker requires this file to analyze a library. In order to facilitate the analysis, apertis-abi-compare generates a simple XML descriptor file that should work in simple case. Unfortunately, this feature is not smart enough for packages containing multiple libraries. The tool cannot separate headers between libraries, thus it assigns all headers to each library.

The library descriptor file is handwritten as described in the following library descriptor paragraph.

Finally, the abi-checker job can be triggered as explained in the apertis-abi-compare job paragraph, it doesn’t make any difference whether it is a single library package or a multiple libraries package.

The zlib package, used in the above example, is a special case of a source package providing multiple libraries. From the source package, several binary packages can be generated zlib1g, lib64z1, lib32z1 and libn32z1. The main package zlib1g provides the library in a standard way whereas the other packages provide a different version of the library. In other words, they provide a 64 bit version of the library for 32 bits architectures or the other way around. Thus, all packages provide the same library, but for different architectures which is not possible to compare with abi-compliance-checker.

Therefore, the zlib package cannot be used by apertis-abi-compare to test the support of packages providing multiple libraries.

apertis-abi-compare will generate a report per analyzed library in the compat_reports folder. For instance, if a package provides libmyfirst and libmysecond, then the compat_reports folder will contain two folders. The first one, called myfirst, will contain report for libmyfirst library whereas the other folder mysecond will contain report for libmysecond library.

Example of library descriptor

To be detected by apertis-abi-compare, the descriptor file must be named MYLIB-abi-descriptor.xml (where MYLIB is the related library name) and must be installed by your lib***-dev package. The recommended installation path is /usr/share/doc/lib***-dev/ to be in compliance with the Debian Policy, but the path does not matter because apertis-abi-compare will scan all files installed by the lib***-dev package to find a file called *-abi-descriptor.xml.

In the source package, the recommendation is to place the descriptor file in the debian/apertis/abi/ folder in order to avoid mixing it with other packaging files.

Below is a basic example for zlib named zlib-abi-descriptor.xml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<ABI_Descriptor>
  <version>
    ${VER}
  </version>
  <headers>
    ${PATH}/usr/include/zconf.h
    ${PATH}/usr/include/zlib.h
  </headers>
  <libs>
    ${PATH}/usr/lib/libz.so
  </libs>
</ABI_Descriptor>

The library descriptor file is a simple XML file which contain several mandatory and optional sections. Each section is defined by its name and lists library information. A section contains one information per line.

The most important and mandatory sections are version, headers and libs. The first one describes the version of the analyzed library, the second one lists all library headers with their installed path and the last one defines the path to the installed shared library.

In order to prevent having to modify this file for each new version, we use ${VER} and ${PATH} variables which are filled by apertis-abi-compare. They refer to the package version and the path where the package was extracted. Although it is not necessary to use the ${VER} variable, not having the ${PATH} variable will result in a failure of abi-compliance-checker.

The list of all optional sections is available on the library descriptor documentation. These sections allow, for example, to define additional include paths in case your library need headers not in the default path to be built (add_include_paths). It is also possible to define additional GCC options if required (gcc_options). Other possibilities offered by optional sections are to exclude data types (skip_types), symbols (skip_symbols), namespaces (skip_namespaces), constants (skip_constants) and header (skip_headers) from the analyze.

When changes in the library need to be reflected on the descriptor file, then it is enough just to manually update this file in git for the corresponding new library version.

Please refer to the documentation of abi-compliance-checker to know more about the XML library descriptor file: library descriptor documentation.

How to enable the apertis-abi-compare job in the build pipeline

The apertis-abi-compare tool is only useful for comparing ABI of different versions of a library. Thus, it is useless to run the tool on packages not providing a library, and consequently the associated pipeline job is not enabled by default. To enable the apertis-abi-compare job in the build pipeline, you just need to create an empty debian/apertis/abi-compare in your package.

1
2
3
4
5
6
cd pkg/my_lib
git checkout apertis/v2023
touch debian/apertis/abi-compare
git add debian/apertis/abi-compare
git commit -m"Add debian/apertis/abi-compare to enable the apertis-abi-compare job"
git push origin

Pushing the commit adding the debian/apertis/abi-compare file will trigger the pipeline with a new job called abi-checker.

Let’s use the tests/zlib repository as an example. The last two commits added a debian/apertis/abi-compare file and created a new debian/changelog entry. Then, go to the CI/CD > Pipelines page. As shown in the screenshot below, a new abi-checker job is available.

By clicking on it, the log is displayed. In case no ABI breaks are detected, the job will succeed and the report generated by abi-compliance-checker can be downloaded by clicking on Download.

The generated report is also directly displayable by using the Browse button.

Now, let’s take a look at the tests/zlib_broken repository. Like in the previous example for apertis-abi-compare, this repository contains an old version of zlib in order to simulate an ABI breakage. The pipeline will try to upload the old version 1.2.3 over the current version 1.2.11, thus some functionalities will be removed and apertis-abi-compare will detect a breakage. By looking at its CI/CD > Pipelines page, we see the abi-checker job fails.

The job log contains some information regarding the ABI breaks detected, but a comprehensive report can be downloaded.

This report contains all information useful to understand the ABI breaks detected as explained in the previous part above.

Limitations

  • The abi-compliance-checker tool which is used by apertis-abi-compare is only able to compare libraries for the current architecture. Indeed, it compiles headers in order to create an ABI dump. Since GitLab runners run on Intel machines, this means only x86_64 can be checked and we can’t check the ABI of ARM packages.

  • To analyze packages containing multiple libraries it is highly recommended to provide XML descriptors. See the Packages providing multiple libraries paragraph for a comprehensive explanation.

FAQ

abi-compliance-checker fails to build headers

abi-compliance-checker can fails to build headers of your library. In this case, you need to provide and customize a library XML descriptor. Please refer to the documentation of abi-compliance-checker to know how to write it: library descriptor documentation.

Troubleshooting

In some cases, abi-compliance-checker fails to compile headers and displays the following error messages:

1
2
ERROR: some errors occurred when compiling headers
ERROR: see log for details:

The given log will provide useful information regarding what was wrong.

The first step is to check if there is no missing dependency. The library development package (libXXX-dev) should contain all dependencies required to compile it. If missing dependencies are found, they should be installed and added to the Depends field of the -dev package.

If all dependencies are defined and installed, then it is most likely an issue in the header file. The most common reason is this header does not compile on its own. Compilation of this header can be simply tested with:

1
2
3
4
# For a C header:
gcc myheader.h
# Or for a C++ header:
g++ myheader.h

For example, trying to compile a header gives this error message:

error: ‘queue’ in namespace ‘std’ does not name a template type
note: ‘std::queue’ is defined in header ‘<queue>’; did you forget to ‘#include <queue>’?

In this example, the std::queue class is used in the header without including . Adding #include <queue> in the header makes it compilable. This was an easy case because the solution was given as a note in the log, but the solution is not always given and could require some investigations.

The failing header was not self-contained. In other words, it refers to a symbol defined in another header file without directly including this header file. The good practice is to always include headers defining or declaring symbols used (See Google C++ Style Guide#Include What You Use, Google C++ Style Guide#Self contained Headers or C++ Core Guidelines#Header files should be self-contained).

For more general good practices regarding header files, see Google C++ Style Guide#Header Files.

References