GitHub Action¶
abicheck ships as a reusable GitHub Action that you can add to any CI pipeline with a few lines of YAML. It installs Python, system dependencies, and abicheck automatically, then runs ABI comparison and reports results.
Picking a mode or failure policy? See Choose Your Workflow for the decision matrix — which artifacts map to which
mode, and which severity inputs gate the build.
Quick start¶
- uses: abicheck/abicheck@v0.3.0
with:
old-library: abi-baseline.json
new-library: build/libfoo.so
new-header: include/foo.h
Inputs¶
Library inputs¶
| Input | Required | Description |
|---|---|---|
mode |
no | compare (default), compare-release, dump, scan, merge, appcompat, deps, or stack-check |
old-library |
yes (compare, compare-release) | Path to old library, JSON snapshot, ABICC dump, directory, or package |
new-library |
yes (compare, dump, scan, …) | Path to new library, binary, directory, or package. In scan mode this is the scanned binary or .abi.json snapshot. |
Header inputs¶
| Input | Required | Description |
|---|---|---|
header |
no | Public header file(s) or directory(ies) for both sides (space-separated) |
old-header |
no | Header file(s) or directory(ies) for old side only |
new-header |
no | Header file(s) or directory(ies) for new side only |
include |
no | Extra include dirs for castxml (both sides) |
old-include |
no | Include dirs for old side only |
new-include |
no | Include dirs for new side only |
Evidence layers in the Action
The Action drives the same five-layer evidence
model as the CLI. The inputs
above cover L0 (old-library/new-library), L1 (debug info —
embedded, or debug-info1/debug-info2 packages in compare-release
mode), and L2 (header/include).
The deeper layers — L3 build context, L4 source-ABI replay, and
L5 source graphs — are now first-class Action inputs. Use the
sources/build-info/compile-db inputs in scan or dump mode and
abicheck collects them inline; no separate CLI steps are required. See
Source scans below and the
Build Info & Sources concept guide.
Application compatibility inputs (appcompat mode)¶
| Input | Required | Description |
|---|---|---|
app-binary |
yes (appcompat) | Path to application binary (ELF, PE, or Mach-O) |
check-against |
no | Library for weak-mode symbol availability check (no old library needed) |
show-irrelevant |
no | Include library changes not affecting the application (default false) |
list-required-symbols |
no | List symbols the app requires and exit (default false) |
Version labels¶
| Input | Default | Description |
|---|---|---|
old-version |
old |
Version label for old library |
new-version |
new |
Version label for new library |
Language and compiler¶
| Input | Default | Description |
|---|---|---|
lang |
c++ |
Language mode for the header backend: c++ or c |
ast-frontend |
castxml (auto) |
L2 header-AST frontend (compare/dump modes): auto, castxml, or clang (clang -ast-dump=json, for clang-only hosts). auto falls back to clang on a castxml toolchain error. Same as ABICHECK_AST_FRONTEND. |
gcc-path |
— | Path to cross-compiler binary (dump mode only) |
gcc-prefix |
— | Cross-toolchain prefix, e.g. aarch64-linux-gnu- (dump mode only) |
gcc-options |
— | Extra flags for castxml (dump mode only) |
sysroot |
— | Alternative system root (dump and deps modes) |
nostdinc |
false |
Skip standard include paths (dump mode only) |
Full-stack dependency validation (Linux ELF)¶
| Input | Default | Description |
|---|---|---|
follow-deps |
false |
Include transitive dependency graph and symbol bindings in dump/compare output |
baseline |
— | Sysroot for baseline environment (required for stack-check mode) |
candidate |
— | Sysroot for candidate environment (required for stack-check mode) |
search-path |
— | Additional library search directories (space-separated) |
ld-library-path |
— | Simulated LD_LIBRARY_PATH (colon-separated) |
Source-scan and build-source evidence (scan / dump / merge modes)¶
These inputs drive source intelligence —
L3 build context, L4 source-ABI replay, and L5 source graphs — through the
scan orchestrator, or fold the same evidence into a dump snapshot. L4/L5
need clang (installed automatically by install-deps: true); without it the
scan degrades gracefully and L0–L2 stay authoritative.
| Input | Modes | Description |
|---|---|---|
sources |
scan, dump | Source checkout/tree; drives L4 replay and graph collection. With a source-level depth and no compile DB, abicheck auto-detects the build system (CMake/Bazel) and runs the query itself to emit one — no flag, no manual build. |
build-info |
scan, dump | Out-of-tree L3 context: a build dir, a compile_commands.json, or a collected evidence pack. |
compile-db |
scan (dump folds into build-info) |
Explicit compile_commands.json path. |
build-config |
scan, dump | Trusted .abicheck.yml; its build.query runs automatically (operator-supplied = trusted). |
allow-build-query |
scan, dump | Deprecated, ignored. Build queries now run automatically when sources is given; kept as a no-op for backward compatibility. |
depth |
scan, dump | Evidence-depth dial: binary, headers, build, source, or full. Maps to --depth. (scan can also derive this from the level inputs below.) |
baseline |
scan | Previous build's dump/library to compare against (or use abi-baseline to auto-fetch one). |
scan-mode |
scan | Fixed (L,S) preset: pr (default), pr-deep, baseline, audit. |
source-method |
scan | Precise S-axis: s0…s6, or auto (risk-driven, opt-in). |
since |
scan | Focus the scan on files changed vs a git ref (e.g. origin/main). |
changed-path |
scan | Changed path(s) to focus on (space-separated; alternative to since). |
budget |
scan | Time guard (e.g. 15m). The step fails on overflow (verdict: BUDGET_OVERFLOW) — a budget never silently shrinks scope. |
audit |
scan | Single-build hygiene lint, no baseline (intra-version cross-source checks). |
estimate |
scan | Dry-run: print projected per-layer cost and scan nothing (always exits 0). |
crosscheck |
scan | Per-check severity overrides KEY=LEVEL (off/info/warning/error), space-separated. Promoting a check to =error makes a finding for it exit 2 (the API_BREAK tier); pair with fail-on-api-break: true to gate the step. |
risk-rules |
scan | Path to a YAML file overriding the risk_rules profile. |
merge-inputs |
merge | Space-separated .abi.json dumps and/or a Flow-2 abicheck_inputs/ pack directory to combine. |
on-conflict |
merge | warn (default, first-wins + diagnostic) or error (exit non-zero) when two inputs supply the same layer with differing facts. |
format in scan mode
scan supports format: text (default) or json; other values fall back
to text with a warning. merge writes a .abi.json baseline to
output-file (default merged-baseline.json).
Output and policy¶
| Input | Default | Description |
|---|---|---|
format |
markdown (text for scan) |
Output format: markdown, json, sarif, html. sarif/html are compare-only; compare-release/appcompat/deps/stack-check fall back to markdown; scan supports only text/json and falls back to text. |
output-file |
— | Path to write report (auto-set for SARIF) |
policy |
strict_abi |
Built-in policy: strict_abi, sdk_vendor, plugin_abi |
policy-file |
— | Custom YAML policy file |
suppress |
— | YAML suppression file (supports label, source_location, expires) |
verbose |
false |
Enable debug output |
To enable suppression lifecycle enforcement, pass the flags via extra-args:
Action behavior¶
| Input | Default | Description |
|---|---|---|
python-version |
3.13 |
Python version for setup-python |
install-deps |
true |
Install castxml + gcc automatically |
upload-sarif |
false |
Upload SARIF to GitHub Code Scanning |
fail-on-breaking |
true |
Fail step on binary ABI break |
fail-on-api-break |
false |
Fail step on source-level API break |
severity-preset |
— | Severity preset: default, strict, or info-only (compare mode only) |
severity-addition |
— | Severity for additions: error, warning, or info (compare mode only) |
extra-args |
'' |
Additional CLI arguments passed to abicheck |
add-job-summary |
true |
Write summary to Job Summary panel (ignored for dump mode) |
pr-comment |
true |
Post a sticky ABI report comment on the PR (compare/compare-release/appcompat). No-op outside pull_request events. |
pr-comment-mode |
update |
update keeps one comment and edits it in place; new posts a fresh comment each run |
pr-comment-on |
changes |
When to comment: changes, always, or never |
pr-comment-detail |
standard |
Comment detail: summary, standard, or full |
github-token |
${{ github.token }} |
Token for the PR comment and baseline auto-fetch (needs pull-requests: write) |
Package comparison inputs (compare-release mode)¶
| Input | Default | Description |
|---|---|---|
debug-info1 |
— | Debug info package for old side (RPM/Deb/tar) |
debug-info2 |
— | Debug info package for new side (RPM/Deb/tar) |
devel-pkg1 |
— | Development package with headers for old side |
devel-pkg2 |
— | Development package with headers for new side |
dso-only |
false |
Only compare shared objects, skip executables |
include-private-dso |
false |
Include private (non-public) shared objects |
keep-extracted |
false |
Keep extracted temp files for debugging |
fail-on-removed-library |
false |
Exit 8 when a library present in old is absent in new |
Outputs¶
| Output | Description |
|---|---|
verdict |
compare: COMPATIBLE, SEVERITY_ERROR, API_BREAK, BREAKING, or ERROR. compare-release: COMPATIBLE, API_BREAK, BREAKING, REMOVED_LIBRARY, or ERROR. appcompat: COMPATIBLE, API_BREAK, BREAKING, or ERROR. dump: COMPATIBLE or ERROR. scan: COMPATIBLE, API_BREAK, BREAKING, BUDGET_OVERFLOW, or ERROR. merge: COMPATIBLE or ERROR. stack-check: PASS, WARN, FAIL, or ERROR. deps: PASS, FAIL, or ERROR. |
exit-code |
compare: 0 (compatible), 1 (severity error), 2 (API break), 4 (ABI break). compare-release: 0 (compatible), 2 (API break), 4 (ABI break), 8 (library removed). appcompat: 0 (compatible), 2 (API break), 4 (ABI break). scan: 0 (compatible/advisory), 2 (API break), 4 (ABI break), 5 (budget overflow). merge: 0 (ok). stack-check: 0 (pass), 1 (warn), 4 (fail). deps: 0 (ok), 1 (missing). |
report-path |
Path to the generated report file (empty when no output file was produced) |
Usage examples¶
Compare two libraries on a PR¶
name: ABI Check
on: [pull_request]
jobs:
abi-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build library
run: mkdir build && cd build && cmake .. && make
- name: Check ABI compatibility
uses: abicheck/abicheck@v0.3.0
with:
old-library: abi-baseline.json # committed to repo
new-library: build/libfoo.so
new-header: include/foo.h
new-version: pr-${{ github.event.pull_request.number }}
Save a baseline on release¶
The baseline is a JSON snapshot of the library's ABI surface. Generate it when you release a version, then compare against it on every PR.
name: ABI Baseline
on:
release:
types: [published]
jobs:
save-baseline:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build library
run: mkdir build && cd build && cmake .. && make
- name: Dump ABI baseline
uses: abicheck/abicheck@v0.3.0
with:
mode: dump
new-library: build/libfoo.so
header: include/foo.h
new-version: ${{ github.ref_name }}
output-file: abi-baseline.json
- name: Upload baseline as release asset
uses: softprops/action-gh-release@v2
with:
files: abi-baseline.json
Download baseline and compare on PR¶
- name: Download baseline from latest release
run: gh release download --pattern 'abi-baseline.json' --dir .
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check ABI
uses: abicheck/abicheck@v0.3.0
with:
old-library: abi-baseline.json
new-library: build/libfoo.so
new-header: include/foo.h
Source scans (build & source evidence)¶
mode: scan is the one-step entry point for source intelligence. It
classifies the PR's changed paths, always runs the compiler-free pattern and
intra-version cross-source checks, then runs the pinned evidence level
(L3 build context / L4 source-ABI replay / L5 source graph) and — when a
baseline is given — compares against it. It emits a single
coverage-annotated report saying, per layer, what ran versus what was skipped.
The common case needs four inputs — the built binary, its public headers, the source tree, and a baseline:
permissions:
contents: read
jobs:
abi-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # needed for `since: origin/...` change focusing
- name: Build
run: cmake -B build -S . && cmake --build build
- name: Source-intelligence scan
uses: abicheck/abicheck@v0.3.0
with:
mode: scan
new-library: build/libfoo.so
new-header: include/
sources: .
baseline: abi-baseline.json # committed, or use abi-baseline: latest-release
since: origin/${{ github.base_ref }} # focus on changed files
fail-on-api-break: true # gate on source/API breaks too
clang is installed automatically (for L4/L5). On a pull_request run,
since: origin/${{ github.base_ref }} focuses the (expensive) source replay on
the files the PR touched — pair it with fetch-depth: 0 in checkout so the
base ref is available.
Pin the scan depth¶
By default scan runs the pr preset. Pin a precise level for reproducible CI
with source-method (S-axis) or the coarser depth (L-axis):
- uses: abicheck/abicheck@v0.3.0
with:
mode: scan
new-library: build/libfoo.so
new-header: include/
sources: .
baseline: abi-baseline.json
source-method: s5 # full source-ABI replay (deterministic)
budget: 15m # fail (BUDGET_OVERFLOW) rather than overrun
| Want… | Set |
|---|---|
| Cheap build-flag drift only (L3) | depth: build |
| Source semantics on changed TUs | source-method: s4 + since: |
| Full source-ABI replay | source-method: s5 |
| Source graph / localization (L5) | source-method: s4 or scan-mode: pr-deep |
| Risk-driven (dev/local, opt-in) | source-method: auto + since: |
Single-release audit (no baseline)¶
Run the intra-version hygiene checks against one build — no old version needed. Useful as a standing lint on the default branch:
- uses: abicheck/abicheck@v0.3.0
with:
mode: scan
new-library: build/libfoo.so
new-header: include/
sources: .
audit: 'true'
Estimate cost before committing to a depth¶
estimate: 'true' is a dry run — it prints the projected per-layer cost (TU
count, seconds) and scans nothing, always exiting 0. Handy when sizing a budget
for a large repo:
- uses: abicheck/abicheck@v0.3.0
with:
mode: scan
new-library: build/libfoo.so
new-header: include/
sources: .
source-method: s5
estimate: 'true'
Gate CI on a specific cross-source check¶
Cross-source findings are advisory by default. Promoting one to error makes a
finding for it exit 2 (the API_BREAK tier); add fail-on-api-break: true so
that exit turns the step red:
- uses: abicheck/abicheck@v0.3.0
with:
mode: scan
new-library: build/libfoo.so
new-header: include/
sources: .
baseline: abi-baseline.json
crosscheck: 'private_header_leak=error odr_type_variant=error'
fail-on-api-break: true # gate on the exit-2 (API_BREAK) tier
fail-on-api-break gates the whole API_BREAK tier (baseline/source breaks and
promoted cross-checks alike); the Action can't tell from the exit code which one
fired, so leave it false if you only want binary ABI breaks (exit 4) to gate.
Passing sources into a baseline (build/source evidence)¶
There are three ways to feed L3/L4/L5 evidence into the comparison. Pick by where your build produces facts.
A. Inline at dump time (simplest)¶
dump with sources/build-info embeds the build/source facts inline in
the snapshot, so any later compare (including one run from this Action on two
such snapshots) carries the L3/L4/L5 findings — no out-of-band directories:
- name: Dump baseline with build + source evidence
uses: abicheck/abicheck@v0.3.0
with:
mode: dump
new-library: build/libfoo.so
header: include/
sources: .
depth: source # full L3+L4+L5 for a baseline
output-file: abi-baseline.json
Compare two such snapshots later with the default compare mode — the embedded
evidence diffs automatically.
B. Combine independently-produced dumps with merge¶
When the binary side and source side are produced in parallel (e.g. on
different runners), mode: merge folds them into one self-contained baseline:
# one job produces the artifact-side dump (L0/L1/L2)…
- uses: abicheck/abicheck@v0.3.0
with:
mode: dump
new-library: build/libfoo.so
header: include/
output-file: libfoo.bin.json
# …another produces the source-side dump (L3/L4/L5)…
- uses: abicheck/abicheck@v0.3.0
with:
mode: dump
sources: ./libfoo-src/
output-file: libfoo.src.json
# …then merge them into one baseline:
- uses: abicheck/abicheck@v0.3.0
with:
mode: merge
merge-inputs: 'libfoo.bin.json libfoo.src.json'
on-conflict: error # good for baseline generation
output-file: libfoo.baseline.json
C. Build-emitted facts (Flow-2 abicheck_inputs/ pack)¶
A product build that emits a self-describing abicheck_inputs/ pack (via
abicheck-cc — see Build Info & Sources)
needs no source replay in CI. mode: merge ingests the pack directly:
- uses: abicheck/abicheck@v0.3.0
with:
mode: merge
merge-inputs: 'libfoo.bin.json ./abicheck_inputs/'
output-file: libfoo.baseline.json
The resulting libfoo.baseline.json is a normal snapshot — pass it as
old-library/baseline to any later compare or scan.
Use GitHub Actions cache for baseline¶
- name: Restore cached baseline
uses: actions/cache@v4
with:
path: abi-baseline.json
key: abi-baseline-${{ github.event.repository.default_branch }}-${{ github.sha }}
restore-keys: |
abi-baseline-${{ github.event.repository.default_branch }}-
- name: Check ABI
uses: abicheck/abicheck@v0.3.0
with:
old-library: abi-baseline.json
new-library: build/libfoo.so
new-header: include/foo.h
SARIF with GitHub Code Scanning¶
Upload results to the Security tab so ABI breaks appear as code scanning alerts.
Note
Requires security-events: write permission. On PRs, GitHub only shows
new alerts introduced by the PR — existing alerts stay on the default
branch and don't clutter the review.
jobs:
abi-check:
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
steps:
- uses: actions/checkout@v4
- run: mkdir build && cd build && cmake .. && make
- uses: abicheck/abicheck@v0.3.0
with:
old-library: abi-baseline.json
new-library: build/libfoo.so
new-header: include/foo.h
format: sarif
upload-sarif: true
Cross-compilation check (dump mode)¶
Cross-compilation flags (gcc-prefix, sysroot, gcc-options) are only supported
in dump mode. Use mode: dump to generate a baseline from a cross-compiled binary,
then compare with a separate step.
# Step 1: dump ABI snapshot from cross-compiled binary
- uses: abicheck/abicheck@v0.3.0
with:
mode: dump
new-library: build-arm64/libfoo.so
header: include/foo.h
gcc-prefix: aarch64-linux-gnu-
sysroot: /usr/aarch64-linux-gnu
lang: c
output-file: baseline-arm64.json
Matrix: multiple libraries¶
strategy:
matrix:
lib:
- { name: libfoo, so: build/libfoo.so, header: include/foo.h }
- { name: libbar, so: build/libbar.so, header: include/bar.h }
steps:
- uses: abicheck/abicheck@v0.3.0
with:
old-library: baselines/${{ matrix.lib.name }}.json
new-library: ${{ matrix.lib.so }}
new-header: ${{ matrix.lib.header }}
Matrix: multiple platforms (native scan per OS)¶
Use native runners to get the best platform-specific signal (Linux/ELF, macOS/Mach-O, Windows/PE):
jobs:
abi-scan:
strategy:
matrix:
include:
- os: ubuntu-latest
ext: so
- os: macos-latest
ext: dylib
- os: windows-latest
ext: dll
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
# Build your platform artifact here (example command only)
- name: Build
run: |
echo "build on ${{ matrix.os }}"
- name: ABI compare (native)
uses: abicheck/abicheck@v0.3.0
with:
old-library: baselines/${{ runner.os }}/abi-old.json
new-library: build/${{ runner.os }}/libfoo.${{ matrix.ext }}
new-header: include/foo.h
format: json
output-file: abi-report-${{ runner.os }}.json
- name: Upload platform ABI report
uses: actions/upload-artifact@v4
with:
name: abi-report-${{ runner.os }}
path: abi-report-${{ runner.os }}.json
Post-matrix ABI gate (unified verdict)¶
After per-platform matrix runs, a gate job downloads all JSON reports and produces one aggregated exit code for the entire workflow:
jobs:
abi-scan:
strategy:
matrix:
include:
- os: ubuntu-latest
ext: so
- os: macos-latest
ext: dylib
- os: windows-latest
ext: dll
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Build
run: cmake -B build && cmake --build build
- name: ABI compare (native)
uses: abicheck/abicheck@v0.3.0
with:
old-library: baselines/${{ runner.os }}/abi-old.json
new-library: build/libfoo.${{ matrix.ext }}
new-header: include/foo.h
format: json
output-file: abi-report-${{ runner.os }}.json
fail-on-breaking: false # let gate job decide
- name: Upload platform ABI report
uses: actions/upload-artifact@v4
with:
name: abi-report-${{ runner.os }}
path: abi-report-${{ runner.os }}.json
abi-gate:
needs: abi-scan
runs-on: ubuntu-latest
steps:
- name: Download all ABI reports
uses: actions/download-artifact@v4
with:
pattern: abi-report-*
merge-multiple: true
path: abi-reports/
- name: Aggregate verdicts and gate
run: |
pip install abicheck --quiet
python3 - <<'PYEOF'
import json, sys, os, glob
SEVERITY = {"NO_CHANGE": 0, "COMPATIBLE": 0, "COMPATIBLE_WITH_RISK": 0,
"API_BREAK": 2, "BREAKING": 4, "ERROR": 4}
worst = 0
rows = []
for path in sorted(glob.glob("abi-reports/*.json")):
with open(path) as f:
data = json.load(f)
verdict = data.get("verdict", "ERROR")
platform = os.path.basename(path).replace("abi-report-", "").replace(".json", "")
rows.append(f"| {platform} | {verdict} |")
worst = max(worst, SEVERITY.get(verdict, 4))
table = "\n".join(rows)
print(f"## ABI Gate\n\n| Platform | Verdict |\n|---|---|\n{table}")
if worst >= 4:
print("BREAKING ABI change detected on at least one platform.", file=sys.stderr)
sys.exit(4)
elif worst >= 2:
print("API break detected on at least one platform.", file=sys.stderr)
sys.exit(2)
print("All platforms: compatible.")
PYEOF
Tip
Set fail-on-breaking: false in each matrix job so runners don't fail
early. The gate job reads all JSON reports and exits 4 (breaking),
2 (API break), or 0 (compatible).
Skip system dependency installation¶
If castxml + compiler are already available (custom image, pre-provisioned VM,
or conda-forge environment), set install-deps: false:
- uses: abicheck/abicheck@v0.3.0
with:
old-library: old.json
new-library: new.json
install-deps: false
Example (conda-forge pre-step):
- name: Install abicheck from conda-forge
run: |
conda install -y -c conda-forge abicheck
- uses: abicheck/abicheck@v0.3.0
with:
old-library: old.json
new-library: new.json
install-deps: false
When comparing two JSON snapshots, no header-analysis toolchain is needed.
Full-stack dependency check on container image update¶
Validate that updating a base image doesn't break your application's dependency
stack. This runs stack-check to compare the binary's full transitive
dependency tree across old and new container root filesystems:
jobs:
stack-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Extract old rootfs
run: |
mkdir -p /tmp/old-root
docker export $(docker create old-image:latest) | tar -xf - -C /tmp/old-root
- name: Extract new rootfs
run: |
mkdir -p /tmp/new-root
docker export $(docker create new-image:latest) | tar -xf - -C /tmp/new-root
- name: Full-stack ABI check
uses: abicheck/abicheck@v0.3.0
with:
mode: stack-check
new-library: usr/bin/myapp
baseline: /tmp/old-root
candidate: /tmp/new-root
format: json
output-file: stack-report.json
Exit codes for stack-check: 0 = PASS, 1 = WARN (ABI risk), 4 = FAIL (load failure or ABI break).
Dependency tree audit¶
Show the resolved dependency tree and symbol binding status for a binary. Useful for auditing which libraries a binary actually loads and detecting missing dependencies before deployment:
- name: Audit dependencies
uses: abicheck/abicheck@v0.3.0
with:
mode: deps
new-library: build/myapp
sysroot: /path/to/target/rootfs
Include dependency info in compare¶
Add follow-deps: true to include the transitive dependency graph and symbol
binding information alongside the regular ABI diff:
- name: Compare with dependency context
uses: abicheck/abicheck@v0.3.0
with:
old-library: baseline.json
new-library: build/libfoo.so
new-header: include/foo.h
follow-deps: true
Inline PR annotations¶
Add --annotate to get ABI breaking changes as inline comments on the PR diff.
See GitHub PR Annotations for full details.
- uses: abicheck/abicheck@v0.3.0
with:
old-library: baseline.json
new-library: build/libfoo.so
new-header: include/foo.h
extra-args: --annotate
Sticky PR comment¶
On pull_request runs the action posts a single, self-updating comment that
groups every finding into Breaking, Needs review, and Safe sections
and shows the scanned head SHA. It is a content channel only — it never
changes the check's red/green state, which is still driven by fail-on-breaking
/ fail-on-api-break / severity-*. This means review-needed items (source
breaks, risk, additions) surface as a green check with a ⚠️ Review recommended
comment, while real ABI breaks turn the check red and post a ❌ comment.
permissions:
contents: read
pull-requests: write # required for the comment
jobs:
abi:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: abicheck/abicheck@v0.3.0
with:
old-library: baseline.json
new-library: build/libfoo.so
new-header: include/foo.h
# all optional — these are the defaults:
pr-comment: true
pr-comment-mode: update # one sticky comment, edited each run
pr-comment-on: changes # skip the comment when nothing changed
pr-comment-detail: standard # per-symbol tables for breaking/review
Behavior knobs:
pr-comment-mode: newposts a fresh comment per run instead of editing the previous one (use when you want a per-commit history in the thread).pr-comment-on: alwayscomments every run, including a clean No ABI changes result;neverdisables it.pr-comment-detail: fulllists every change with source locations and expands all sections;summaryreduces the comment to the verdict and counts.
On large diffs the standard view stays readable by rolling related changes up
to their enclosing API — overloads, template instantiations and members of the
same type/namespace collapse into one row showing the family and a member count
(distinct symbols keep their own row; full keeps every change separate). The
body is always kept under GitHub's 65,536-character comment limit: if it would
overflow, the detail level is automatically reduced (and, as a last resort, the
body is truncated), with a link back to the full report uploaded as the
workflow-run artifact so nothing is lost.
"Safe" mirrors whatever the checker already classified as compatible — so
public-header surface scoping (--scope-public-headers) and policy profiles
(e.g. sdk_vendor demoting a removal) flow through automatically; the comment
never re-classifies anything.
The comment also tracks the gate: with fail-on-api-break: true (which turns
the check red on source/API breaks), those findings are filed under Breaking
in the comment to match, rather than Needs review.
Conditional failure¶
Allow API breaks but block binary ABI breaks:
- uses: abicheck/abicheck@v0.3.0
with:
old-library: baseline.json
new-library: build/libfoo.so
new-header: include/foo.h
fail-on-breaking: true
fail-on-api-break: false
Detect unintentional API expansion¶
Block PRs that accidentally add new public symbols or types:
- uses: abicheck/abicheck@v0.3.0
with:
old-library: baseline.json
new-library: build/libfoo.so
new-header: include/foo.h
fail-on-breaking: true
severity-addition: error # exit code 1 if any new public API appears
When severity-addition: error:
- Exit code 1 → new public symbol/type added (verdict: SEVERITY_ERROR)
- Exit code 0 → no additions, no breaks (verdict: COMPATIBLE)
- Exit code 4 → binary ABI break (verdict: BREAKING)
This is useful when your library has a stable frozen API and any expansion must be a deliberate, reviewed decision rather than an accidental side effect.
Compare RPM packages¶
Use mode: compare-release to compare all shared libraries inside two packages
without manual extraction. Supported formats: RPM, Deb, tar (.tar.gz,
.tar.xz, .tar.bz2, .tgz), conda (.conda, .tar.bz2), wheel (.whl),
and plain directories.
- name: Compare RPM packages
uses: abicheck/abicheck@v0.3.0
with:
mode: compare-release
old-library: libfoo-1.0-1.el9.x86_64.rpm
new-library: libfoo-1.1-1.el9.x86_64.rpm
Compare packages with debug info¶
Provide separate debug info packages for full type-level analysis via build-id resolution:
- name: Compare with debug info
uses: abicheck/abicheck@v0.3.0
with:
mode: compare-release
old-library: libfoo-1.0.rpm
new-library: libfoo-1.1.rpm
debug-info1: libfoo-debuginfo-1.0.rpm
debug-info2: libfoo-debuginfo-1.1.rpm
Compare Deb packages with development headers¶
- name: Compare Deb packages
uses: abicheck/abicheck@v0.3.0
with:
mode: compare-release
old-library: libfoo1_1.0-1_amd64.deb
new-library: libfoo1_1.1-1_amd64.deb
devel-pkg1: libfoo-dev_1.0-1_amd64.deb
devel-pkg2: libfoo-dev_1.1-1_amd64.deb
Compare tar archives (DSOs only)¶
- name: Compare SDK tarballs
uses: abicheck/abicheck@v0.3.0
with:
mode: compare-release
old-library: sdk-2.0.tar.gz
new-library: sdk-2.1.tar.gz
dso-only: true
Compare conda packages¶
- name: Compare conda packages
uses: abicheck/abicheck@v0.3.0
with:
mode: compare-release
old-library: pkg-v1.conda
new-library: pkg-v2.conda
Application compatibility check¶
Check whether your application binary is affected by a library update:
- uses: abicheck/abicheck@v0.3.0
with:
mode: appcompat
app-binary: build/myapp
old-library: libfoo.so.1
new-library: build/libfoo.so.2
header: include/foo.h
Quick symbol availability check (weak mode)¶
Verify a library provides all symbols an application needs — no old library required:
- uses: abicheck/abicheck@v0.3.0
with:
mode: appcompat
app-binary: build/myapp
check-against: build/libfoo.so
install-deps: false
Versioning¶
The action follows semantic versioning. While abicheck
is pre-1.0, pin an exact release tag (the examples in this guide use the latest,
v0.3.0); a floating major tag is not published yet:
uses: abicheck/abicheck@v0.3.0 # exact release tag (recommended, reproducible)
uses: abicheck/abicheck@abc123def # exact commit SHA (most secure)
Released tags are listed on the
Releases page. Once abicheck
reaches a stable 1.0, a floating v1 major tag updated on each patch/minor
release will become the recommended pin.