Checking a Conda-Forge Package¶
This runbook covers the specific friction of pointing abicheck at a library
you got from conda-forge (or any binary-distribution channel): finding the
pieces, mapping versions, and the packaging shapes that need special handling.
For the general flow and report walkthrough, see
Worked Example: Scanning a Library.
1. The pieces live in different packages¶
A conda package is usually split, and abicheck needs parts from more than one:
| You need | Typically in | Example |
|---|---|---|
the runtime .so (the binary to check) |
the runtime package | tbb, onednn, dal |
| the public headers | the *-devel package |
tbb-devel |
| headers in a third package (some recipes) | a dedicated *-include |
dal-include (oneDAL) |
*-devel may be CMake/pkgconfig only
For some recipes (e.g. oneDAL) the -devel package ships only
lib/cmake + lib/pkgconfig and the headers live in a separate
*-include package. If your -H directory looks empty of .h/.hpp,
you're pointed at the wrong package.
You don't need the conda CLI to get these — a .conda file is a zip of
zstd-compressed tarballs; unzip pkg.conda, then extract the pkg-* member
(zstd -d < pkg-*.tar.zst | tar -x) to recover its lib/ and include/ trees.
2. Map the conda version to an upstream tag¶
Conda's version is the product version, which often differs from the upstream git tag. Read the version header to pin the matching source:
| Library | Conda version | Version header to read | Upstream tag |
|---|---|---|---|
| oneTBB | 2023.0.0 |
oneapi/tbb/version.h (TBB_VERSION_*, TBB_INTERFACE_VERSION) |
v2023.0.0 |
| oneDNN | 3.12 |
oneapi/dnnl/dnnl_version.h (DNNL_VERSION_*) |
v3.12 |
| oneDAL | 2026.1.0 |
services/library_version_info.h |
2026.1.0 |
When in doubt, the SONAME (libtbb.so.12.18 → interface 12, patch 18) and the
version header together identify the release.
3. Pass the public surface with the umbrella header¶
Point -H at the library's umbrella header — the single header a consumer
includes (oneapi/tbb.h, dnnl.hpp, oneapi/dal.hpp). abicheck now adds the
header's include root (the umbrella's directory and any ancestor named
include/inc) to the compiler search path automatically, so for a standard
layout no separate -I is needed. A non-standard layout still needs -I, and
any -I/--include you pass takes precedence over the auto-added roots:
The everyday case is comparing two conda versions of the same library, which
is a plain compare:
export ABICHECK_AST_FRONTEND=clang # on a clang-only host (no castxml)
abicheck compare old/lib/libtbb.so.12.17 new/lib/libtbb.so.12.18 \
--header old=old/include/oneapi/tbb.h \
--header new=new/include/oneapi/tbb.h \
--depth headers
The umbrella header shown above already establishes the public/internal
boundary — a lone -H file enters the same public-header set as a
directory would, so the cross-source hygiene checks (which compare runs
automatically on every invocation) already have a "public" side to
reconcile against with no extra flag. A project can declare the same
boundary once in .abicheck.yml instead:
The single-release audit
Auditing one conda build with no previous version — an accidental export, a private-header leak, an unversioned symbol — is Scenario S5. Its spelling is:
That gap is closed — the audit now reproduces the candidate-side
findings the retired scan reported. Note it is advisory by
default: add --severity-preset default to gate on a breaking
finding. See Understand your first
report.
Prefer the umbrella over the include directory
Passing -H <include-dir> makes abicheck parse every header in the
tree as one translation unit — which pulls in optional backend headers
(OpenCL/SYCL: dnnl_ocl.h → CL/cl.h) and preview headers gated by
#error macros (oneTBB's blocked_rangeNd.h). Those need their SDK or a
a compile.options: [-DXXX_PREVIEW] entry to parse. The umbrella header
includes only the library's curated, default-public surface, so it
sidesteps both. Use the umbrella unless you specifically want a
preview/backend header analysed — a -H header file establishes the
public-header boundary just as a directory does (both are split into
the same public-header set that feeds provenance classification), so
the umbrella alone is enough for the cross-source checks to have a
public side.
4. Going deeper needs a build¶
--depth headers (L2) works from the binary + headers alone. The deeper levels
(build/source → L3/L4/L5) read a compile_commands.json:
- abicheck auto-discovers one under the source tree (
.,build/,builddir/,out/,_build/,cmake-build-debug/, or any immediate subdirectory); - a fresh checkout has none — generate it (CMake: configure with
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON; Meson: emitted bymeson setup; Make/Autotools:bear -- make), or pass--build-info <dir|compile_commands.json>.
Without one, L3/L4/L5 report not_collected and only the compiler-free pattern
pre-scan runs. On a large tree, scope that pre-scan with --since <ref> /
--changed-path <file> (or set ABICHECK_PATTERN_SCAN_JOBS to fan it out).
5. Packaging shapes that need a workaround¶
| Shape | Symptom | What to do |
|---|---|---|
Static-only (e.g. oneCCL on conda-forge ships libccl.a, no .so) |
abicheck rejects the .a: "static/import library archive … not analysed" |
extract members (ar x lib.a) and compare the resulting objects, or compare a shared library built from them |
| Headers in a third package | -H dir has no headers |
fetch the *-include package (see §1) |
Stripped release .so, no DWARF |
header-aware L2 still works; DWARF cross-checks skip | pass -H headers (recommended anyway) |
See also¶
- Worked Example: Scanning a Library — the full flow and reports
- Evidence Depth — what L0–L5 collect and cost
- CLI Usage — every flag