Skip to content

release: preserve SONAME/install_name symlinks in liblbug tarballs#414

Merged
adsharma merged 1 commit into
LadybugDB:mainfrom
NathanHowell:fix-liblbug-soname-symlinks
Apr 22, 2026
Merged

release: preserve SONAME/install_name symlinks in liblbug tarballs#414
adsharma merged 1 commit into
LadybugDB:mainfrom
NathanHowell:fix-liblbug-soname-symlinks

Conversation

@NathanHowell
Copy link
Copy Markdown
Contributor

Summary

The liblbug shared-library release tarballs (liblbug-linux-{x86_64,aarch64}.tar.gz, liblbug-osx-{x86_64,arm64}.tar.gz) have inconsistent on-disk filenames vs the SONAME / install_name embedded in the library. A consumer that does the obvious thing — untar, link -llbug, run — succeeds at link time but fails at load time with

# macOS
dyld: Library not loaded: @rpath/liblbug.0.dylib

# Linux
error while loading shared libraries: liblbug.so.0: cannot open shared object file

Verified on v0.15.3; the same pattern is present on v0.15.2 and v0.15.1.

Repro (macOS)

curl -sSL -o liblbug-osx-arm64.tar.gz \
  https://github.com/LadybugDB/ladybug/releases/download/v0.15.3/liblbug-osx-arm64.tar.gz
tar -xzf liblbug-osx-arm64.tar.gz
otool -D liblbug.dylib
# → liblbug.dylib:
#     @rpath/liblbug.0.dylib        ← advertised name
ls liblbug*
# → liblbug.dylib                    ← but this is the only .dylib present

Repro (Linux)

readelf -d liblbug.so | grep SONAME
#  (SONAME) Library soname: [liblbug.so.0]     ← advertised name
ls liblbug*
# → liblbug.so                                   ← only entry present

Root cause

src/CMakeLists.txt:26-29 sets VERSION/SOVERSION on the lbug_shared target, which is semantically correct — CMake emits the standard three-name chain into the install tree:

liblbug.so     -> liblbug.so.0 -> liblbug.so.0.15.3    (SONAME=liblbug.so.0)
liblbug.dylib  -> liblbug.0.dylib -> liblbug.0.15.3.dylib   (install_name=@rpath/liblbug.0.dylib)

The bug lives in .github/workflows/precompiled-bin-workflow.yml: the "Collect artifacts" step uses cp -L install/lib/liblbug.dylib . (and the Linux analogue), which dereferences the symlink chain down to the real file — discarding the intermediate SONAME / install_name names. The archive ends up with only the unversioned name, but the library internally still refers to the versioned one.

Fix

This PR takes Option B from the bug report (matching Homebrew/distro convention and what make install into /usr/lib produces): copy and archive the full symlink chain, so the unversioned link-time name, the versioned SONAME / install_name, and the real file are all present in the tarball.

  • Replace cp -L with cp -P and enumerate both the unversioned name and the versioned glob.
  • Expand the tar arguments to include liblbug.so.* / liblbug.*.dylib so symlinks survive into the tarball (tar preserves symlinks as symlinks by default).
  • Add a new CI verification step that reads the SONAME (readelf) / install_name (otool -D) from the built library and asserts a tarball entry with that name exists, so any future regression is caught at build time rather than on a user's machine.

Scope

  • Shared builds only. Linux perf variant ships the static library only (unaffected).
  • Static libraries have no SONAME / install_name (unaffected).
  • Windows shared builds use PE with the DLL name directly, no SONAME indirection (unaffected).
  • The lbug CLI links the static library, not the shared one (unaffected).

After the fix, tarballs contain

macOS (liblbug-osx-<arch>.tar.gz):

lbug.h
lbug.hpp
liblbug.dylib           -> liblbug.0.dylib          (symlink, link-time name)
liblbug.0.dylib         -> liblbug.0.15.3.dylib     (symlink, matches install_name)
liblbug.0.15.3.dylib                                 (real file)

Linux (liblbug-linux-<arch>.tar.gz):

lbug.h
lbug.hpp
liblbug.so              -> liblbug.so.0             (symlink, link-time name)
liblbug.so.0            -> liblbug.so.0.15.3        (symlink, matches SONAME)
liblbug.so.0.15.3                                    (real file)

The new CI verification step reproduces the bug report's one-liner check and fails the workflow if the tarball doesn't contain an entry matching the library's advertised name.

Test plan

  • Build Precompiled Binaries workflow passes on this PR (Linux compat x86_64, Linux compat aarch64, macOS x86_64, macOS arm64).
  • New Verify tarball SONAME consistency (Linux compat) steps succeed and print the SONAME + tarball listing.
  • New Verify tarball install_name consistency (macOS) steps succeed and print the install_name + tarball listing.
  • After a release using these artifacts, the bug-report reproducer:
    tar -xzf liblbug-osx-arm64.tar.gz
    clang probe.c -L. -llbug -Wl,-rpath,"$PWD" -o probe && ./probe; echo "exit=$?"
    exits 0 on both platforms.

The liblbug shared-library tarballs (liblbug-linux-*.tar.gz,
liblbug-osx-*.tar.gz) extracted to an unversioned filename
(liblbug.so, liblbug.dylib) whose embedded SONAME / install_name
referred to a version-suffixed name (liblbug.so.0, @rpath/liblbug.0.dylib)
that was NOT present in the archive. A consumer linking -llbug would
succeed at link time but fail at load time with
"Library not loaded: @rpath/liblbug.0.dylib" / "cannot open shared
object file: liblbug.so.0".

Root cause: the precompiled-bin packaging step used cp -L, which
dereferenced the cmake-generated symlink chain
  liblbug.so -> liblbug.so.0 -> liblbug.so.0.15.3
down to the real file, discarding the intermediate names that
satisfy SONAME / install_name.

Fix (Option B from the bug report, matching Homebrew/distro
convention and what `make install` into /usr/lib produces): copy
and archive the full symlink chain, so the unversioned link-time
name, the versioned SONAME / install_name, and the real file are
all present in the tarball.

Also adds a CI verification step that reads the SONAME (via
readelf) / install_name (via otool -D) from the built library and
asserts a tarball entry matches, so any regression is caught at
build time rather than on a user's machine.

The Windows shared build is unaffected (PE uses the DLL name
directly, no SONAME indirection) and the static libraries are
unaffected (no SONAME / install_name).
@adsharma
Copy link
Copy Markdown
Contributor

Looks right. Testing it here:

https://github.com/LadybugDB/ladybug/actions/runs/24799643808

It could affect other language bindings such as:

LadybugDB/go-ladybug#14

@adsharma adsharma merged commit dce12ac into LadybugDB:main Apr 22, 2026
4 checks passed
@NathanHowell NathanHowell deleted the fix-liblbug-soname-symlinks branch April 23, 2026 00:37
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.

2 participants