Skip to content

fix(release,#339): bundle deps before erasing the RUNPATH that finds them - #340

Merged
CrispStrobe merged 8 commits into
mainfrom
fix-339-hip-bundler-ordering
Aug 10, 2026
Merged

fix(release,#339): bundle deps before erasing the RUNPATH that finds them#340
CrispStrobe merged 8 commits into
mainfrom
fix-339-hip-bundler-ordering

Conversation

@CrispStrobe

Copy link
Copy Markdown
Owner

Closes the remaining half of #339: crispasr-linux-x86_64-hip.tar.gz still failed to package in v0.8.27, for a reason the earlier fix could not have caught.

The build succeeds; check-bundled-deps.py then refuses the staged directory with crispasr needs libomp.so.

bundle-linux-runtime.sh rewrote RUNPATH to $ORIGIN before asking ldd what the binaries needed. ldd resolves through the binary's own RUNPATH, so erasing it first turns exactly those dependencies into => not found, and the copy loop's grep '^/' dropped them with the blank lines. ROCm's clang links OpenMP against LLVM's libomp.so under /opt/rocm/lib/llvm/lib — reachable only that way. gcc's libgomp.so.1 is in the default loader path, which is why six legs were unaffected.

  • Resolve and copy first, rewrite second; scan a copied library at its original path, since a $ORIGIN-relative RUNPATH means something else once the file has moved.
  • An unresolved dependency is now fatal in the bundler, by name. It used to print rpaths normalised, 0 librar(ies) bundled — a success line with a count, over a dropped dependency. The check consults the same exclusion list the copy loop uses, so libcuda.so.1 (legitimately absent from a driverless runner) still does not fail the CUDA legs.
  • tests/test-bundle-linux-runtime.sh reproduces all of it with cc and -Wl,-rpath — no ROCm, no GPU, no release — in the unit tier. These packaging scripts previously ran only inside a release job, which is why two defects in them shipped.
  • patchelf added to the CI unit job: without it the test SKIPs, and a skip reads like a pass.
  • validate-version no longer fails on dry runs (empty tag), the mode that input's own documentation recommends.

Red-verified on Linux against the script as shipped in v0.8.27:

CONTROL (v0.8.27 script): "rpaths normalised, 0 librar(ies) bundled"
                          FAIL — RUNPATH-only dependency dropped
FIXED:                    ok: bundled, and the relocated binary runs with its
                              build-time libdir deleted
                          ok: an unresolvable dependency fails the bundler, by name
                          ok: an absent host-provided runtime is tolerated
                          PASS

A dry run of the HIP leg alone is in flight; this is not merged on the unit test alone.

🤖 Generated with Claude Code

CrispStrobe and others added 8 commits August 10, 2026 13:01
…them

The v0.8.27 HIP tarball never packaged. The build succeeded;
check-bundled-deps.py refused the staged directory:

    crispasr            needs  libomp.so
    crispasr-quantize   needs  libomp.so

bundle-linux-runtime.sh did its two jobs in the wrong order. It rewrote
RUNPATH to $ORIGIN first, and only then asked ldd what the binaries
needed — but ldd resolves through the binary's own RUNPATH, so erasing it
first turns exactly those dependencies into `=> not found`, and the copy
loop's `grep '^/'` discarded them along with the blank lines. ROCm's
clang links OpenMP against LLVM's libomp.so in /opt/rocm/lib/llvm/lib,
reachable only via that RUNPATH. gcc's libgomp.so.1 sits in the default
loader path, which is why every non-HIP leg was unaffected and this
stayed hidden.

Resolve first, rewrite second, and scan a copied library at its original
path rather than at the staged copy — a $ORIGIN-relative RUNPATH means
something different once the file has moved.

An unresolved dependency is now fatal in the bundler instead of silent.
It previously printed "rpaths normalised, 0 librar(ies) bundled" — a
green line over a dropped dependency — and left it to a later gate to
notice. The fatal check consults the same exclusion list the copy loop
uses, so a host-provided runtime that is legitimately absent from a CI
runner (libcuda.so.1 on the CUDA legs) still does not fail the release.

tests/test-bundle-linux-runtime.sh reproduces all of this with cc and a
private directory — no ROCm, no GPU, no release. Registered as a unit
test because these packaging scripts previously ran only inside a
release job, so a defect in them could not be observed without
publishing one, and two shipped that way.

Red-verified on Linux against the shipped bundler:
  control (v0.8.27 script): FAIL — "rpaths normalised, 0 librar(ies)
                            bundled", dependency dropped
  fixed:                    PASS — bundled, and the relocated binary runs
                            with its build-time libdir deleted

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
test-bundle-linux-runtime SKIPs without it, and a skip reads exactly like
a pass in the ctest summary — which is the same blindness that let the
packaging scripts ship broken twice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The tag input documents "LEAVE EMPTY for a dry run", and then this job
compared VERSION against the branch name and failed — so every dry run
was red before the job you dispatched it for had finished, exactly when
you are trying to read one job's result. Nothing to verify when nothing
is published.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every Windows, Android and libcrispasr artifact already carried both.
No CLI tarball did — not the seven Linux legs, not macOS — while the
Linux ones bundle third-party shared libraries (libgomp today, libomp on
HIP once #339's bundler fix lands) whose terms require the notice to
travel with the binary. Added after the dependency gate and before the
tar, so it cannot perturb the checks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
bundle-c2pa.sh drops libc2pa_c.so into the directory before the bundler
runs, and crispasr-quantize used to carry no $ORIGIN in its RUNPATH at
all — so `ldd` reports a library that is sitting right there as
`not found`. Making an unresolved dependency fatal without this
exemption would have failed every Linux leg on the next release.

Fourth arm added: the file is staged but unreachable, the bundler must
not fail, and the binary must run once step 2 grants it $ORIGIN. All
four arms green on Linux; the control still red at arm 1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…eps too

Same defect as #339, two more artifact classes, neither reported.

libcrispasr-linux-x86_64-hip as published in v0.8.27:
    libcrispasr.so.0.8.27   needs  libomp.so
    libggml-base.so         needs  libomp.so
    libggml-cpu.so          needs  libomp.so
    libggml-hip.so          needs  libhipblas.so.2
crispasr-python-linux-x86_64:
    libwhisper.so           needs  libgomp.so.1, libblas.so.3
    libggml-base.so         needs  libgomp.so.1

Both verified against the downloaded release assets, not inferred.

WHY NOTHING CAUGHT IT. The libs bundles never ran check-bundled-deps.py;
verify-lib-bundle.sh gates INTRA-bundle resolvability, which is a
different question. Worse, its dlopen DID fail on libomp.so — and
libomp is on its EXTERNAL tolerance list, so it printed "dlopen
deferred: external driver absent in CI" and exited 0, skipping the rest
of the check as well. That tolerance is sound for gcc's versioned
libgomp.so.1 on the default loader path; ROCm clang's unversioned
libomp.so lives only under /opt/rocm/lib/llvm/lib. Comment corrected in
place rather than left to mislead the next reader.

The python legs did the RUNPATH half inline and had no closure step and
no gate at all, so `import crispasr` died in the loader on any host
without OpenBLAS and gcc's OpenMP.

- package-lib-bundle.sh copies the closure before rewriting rpaths,
  operating on the flattened lib/ so bundle-provided sonames are skipped
  by name and the SOVERSION symlinks are not overwritten. Linux branch
  only; macOS is untouched.
- The five Linux libs legs and both python legs now run
  check-bundled-deps.py, with the same per-leg --allow contracts the CLI
  legs use.
- tests/test-package-lib-bundle.sh builds a bundle shaped like a real
  one — SOVERSION symlinks, src/ + ggml/src/, one dependency reachable
  only via RUNPATH — packages it, deletes the toolchain dir, and dlopens
  the relocated result. Red-verified against the v0.8.27 script.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Linux archives bundle libgomp.so.1 and, on HIP, libomp.so, and the
notices file mentioned neither. It also still claimed OpenBLAS was "not
bundled … users must have libopenblas installed", which #296 changed two
releases ago.

CrispASR's own source stays MIT. The GCC Runtime Library Exception is
what makes an MIT binary linking libgomp unproblematic, but shipping the
libgomp.so.1 FILE is separately a conveyance of a GPLv3 work and carries
GPLv3 §6's corresponding-source obligation — so there is now a source
pointer and a written offer. LLVM's libomp is Apache-2.0 WITH
LLVM-exception: permissive, notice only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@CrispStrobe
CrispStrobe merged commit 208166e into main Aug 10, 2026
23 checks passed
@CrispStrobe
CrispStrobe deleted the fix-339-hip-bundler-ordering branch August 10, 2026 13:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant