Skip to content

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:

# .abicheck.yml
scope:
  public_header_dirs: [include]
compile:
  lang: c++

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:

abicheck compare --no-baseline lib/libtbb.so.12.18 -H include --depth headers

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 by meson 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