Migrating from libabigail¶
This guide maps a libabigail workflow — abidiff, abidw, abipkgdiff —
onto the abicheck equivalents. Unlike the ABICC migration,
there is no flag-compatible wrapper mode: abidiff and abicheck compare
share the same shape (tool old.so new.so + header/suppression/debug-info
options), so you migrate by swapping the command and translating a handful of
flags, not by keeping the old ones.
Step 1: Swap the command¶
# Before (libabigail):
abidiff libfoo.so.1 libfoo.so.2
# After (abicheck):
abicheck compare libfoo.so.1 libfoo.so.2
Both tools read DWARF automatically when the binaries carry it. As with
abidiff, results are much stronger with public headers (see the flag map
below) — headers also let abicheck scope out internal types, the equivalent of
abidiff --drop-private-types.
# Before (libabigail):
abidiff --headers-dir1 include-v1/ --headers-dir2 include-v2/ \
--drop-private-types libfoo.so.1 libfoo.so.2
# After (abicheck — public-surface scoping is automatic with headers):
abicheck compare libfoo.so.1 libfoo.so.2 \
--header old=include-v1/ --header new=include-v2/
Step 2: Update CI exit-code checks¶
abidiff returns a bitmask; abicheck returns a scalar verdict code.
Translate your gates:
| Condition | abidiff exit |
abicheck compare exit |
|---|---|---|
| No differences | 0 |
0 (NO_CHANGE) |
| Compatible changes only | bit 2 set (4) |
0 (COMPATIBLE / COMPATIBLE_WITH_RISK) |
| Source-level (recompile-needed) break | — (folded into bit 3) | 2 (API_BREAK) |
| Incompatible ABI change | bit 3 set (8, or 12 with bit 2) |
4 (BREAKING) |
| Tool/runtime error (e.g. malformed input that exists) | bit 0 set (1) |
1 |
| Usage error (bad arguments/options, unreadable input) | bit 1 set (2) |
64 |
With abidiff, failing a pipeline on incompatible changes means testing the
bitmask (rc=$?; if [ $((rc & 8)) -ne 0 ]; then exit 1; fi). With abicheck
the exit code is already CI-shaped: a plain abicheck compare … step fails
on any break (2 or 4). To fail only on binary ABI breaks and tolerate
source-level ones:
# capture the status first — under `set -e` (GitHub Actions' default shell
# options) a bare compare exiting 2 would kill the step before the test runs
rc=0
abicheck compare libfoo.so.1 libfoo.so.2 -H include/ || rc=$?
# succeeds only for compatible/API_BREAK verdicts; fails for tool errors (1)
# and BREAKING (4)
test "$rc" -eq 0 || test "$rc" -eq 2
See Exit Codes for the full matrix (including the severity-aware scheme).
Flag-by-flag map¶
libabigail (abidiff) |
abicheck equivalent | Notes |
|---|---|---|
lib1.so lib2.so positional args |
compare OLD NEW positional args |
abicheck also accepts JSON snapshots and directories/packages |
--headers-dir1 DIR / --hd1 |
--header old=DIR |
Directories are scanned recursively; needs castxml or clang |
--headers-dir2 DIR / --hd2 |
--header new=DIR |
Use -H DIR once when the same headers apply to both sides |
--header-file1 / --header-file2 |
--header old=FILE / --header new=FILE |
Same flags accept files or directories |
--drop-private-types |
(automatic) | With headers, abicheck scopes findings to the public surface by default; opt out with --no-scope-public-headers |
--suppressions FILE / --suppr |
--suppress FILE |
Different file format: YAML instead of libabigail's INI sections — see Suppressions and the translation section below |
--no-default-suppression |
(not needed) | abicheck applies no default suppression specs |
--debug-info-dir1 DIR / --d1 |
--debug-root old=DIR |
Sidecar/split debug trees |
--debug-info-dir2 DIR / --d2 |
--debug-root new=DIR |
--debug-root DIR applies to both sides |
| (no equivalent) | --debuginfod |
Fetch debug info from a debuginfod server |
--stat |
--stat |
One-line summary instead of the full report |
--leaf-changes-only / -l |
--report-mode leaf |
Root-type-grouped leaf view |
--impacted-interfaces |
--show-impact |
Impact summary appended to the report |
--no-added-syms |
--show-only removed,changed |
Display-only filter; verdict and exit code unchanged |
--harmless |
(default) | Compatible changes are already reported; isolate them with --show-only compatible |
--exported-interfaces-only |
(default) | abicheck always analyses the exported ABI surface |
--fail-no-debug-info |
(no direct flag) | abicheck degrades gracefully and reports the evidence it had — check abicheck dump LIB --dry-run or the report's evidence tier |
--verbose |
-v / --verbose |
Output formats: where abidiff emits its text report, abicheck compare
defaults to Markdown and adds --format json|sarif|html|junit — see
Output Formats.
Snapshot workflow: abidw → abicheck dump¶
If you store abidw ABIXML baselines, the equivalent is a JSON snapshot:
# Before (libabigail):
abidw --out-file libfoo.abi libfoo.so
abidiff libfoo.abi build/libfoo.so
# After (abicheck):
abicheck dump libfoo.so -H include/ --version 1.0 -o libfoo.abi.json
abicheck compare libfoo.abi.json build/libfoo.so --header new=include/
Snapshots and binaries mix freely on either side of compare; the input
format is auto-detected. ABIXML files are not readable by abicheck —
re-dump each stored baseline once from the original binary (see
Storing Baselines for storage recipes).
Package comparison: abipkgdiff → compare on directories/packages¶
# Before (libabigail):
abipkgdiff --d1 foo-debuginfo-1.rpm --d2 foo-debuginfo-2.rpm foo-1.rpm foo-2.rpm
# After (abicheck — directory, archive, or package inputs):
abicheck compare foo-1.rpm foo-2.rpm \
--debug-info old=foo-debuginfo-1.rpm --debug-info new=foo-debuginfo-2.rpm
Multi-library inputs are compared as a co-versioned bundle, with per-library
verdicts and a bundle-level worst-wins verdict; add --fail-on-removed-library
to exit 8 when a library disappeared — see
Multi-Binary Releases.
Translating suppression files¶
libabigail INI suppressions translate mechanically to abicheck's YAML schema:
# Before (libabigail INI):
[suppress_function]
name_regexp = ^internal_.*
[suppress_type]
name = FooPrivate
# After (abicheck YAML — patterns are fullmatch regexes):
version: 1
suppressions:
- symbol_pattern: "internal_.*"
reason: "internal namespace, not public API"
- type_pattern: "FooPrivate"
reason: "private type"
The YAML schema also supports change-kind filters, expiry dates, and required justifications — see Suppressions for the full schema.
Semantics to be aware of¶
- Enum values. abicheck intentionally classifies enum member value
changes as
BREAKING(they break switch statements and serialized data);abidiffreports them as compatible. - A separate source-level verdict. Changes that require recompilation but
don't break existing binaries get their own
API_BREAKverdict and exit code2, instead of being folded into a single incompatible bit. - Evidence layers. Like
abidiff, abicheck works binary-only, but its verdict strengthens with each added source — debug info, headers, build data, sources. See Evidence & Detectability.
The per-case verdict agreement between the two tools is tracked in the
libabigail parity matrix, and the
benchmark comparison (including abidiff accuracy on the example catalog) in
Tool Comparison & Benchmarks.
Validate side-by-side¶
Keep both tools installed during the transition and compare verdicts on a few historical releases:
for ver in 1.0 1.1 1.2; do
abidiff libfoo.so.${ver} libfoo.so.current; echo "abidiff: $?"
abicheck compare libfoo.so.${ver} libfoo.so.current; echo "abicheck: $?"
done
Where the tools disagree, the parity matrix documents the known, intentional differences.