Skip to content

koga: give installed binaries an rpath pointing at the install prefix - #1832

Open
dg1sbg wants to merge 1 commit into
clasp-developers:mainfrom
dg1sbg:fix/install-rpath
Open

koga: give installed binaries an rpath pointing at the install prefix#1832
dg1sbg wants to merge 1 commit into
clasp-developers:mainfrom
dg1sbg:fix/install-rpath

Conversation

@dg1sbg

@dg1sbg dg1sbg commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #1831 — installed binaries keep loading libclasp out of the build tree.

The bug

The link step emits one rpath: the absolute build lib directory. install copies the binary but cannot rewrite an rpath baked in at link time, so an installed clasp resolves libclasp through the build tree.

$ otool -l <prefix>/bin/iclasp | grep -A2 LC_RPATH
    path /path/to/clasp/build/boehmprecise/lib
$ DYLD_PRINT_LIBRARIES=1 <prefix>/bin/clasp --version
dyld[...] /path/to/clasp/build/boehmprecise/lib/libclasp.dylib

Even with a single install this means removing the build tree breaks the install, rebuilding it silently changes which clasp the installed binary runs, and the libclasp copied into the prefix is never loaded. With two installs neither works: both resolve to whatever the build tree currently holds, so each reports the other's version. I found it exactly that way.

The change

Emit a loader-relative rpath ahead of the absolute one:

#+darwin "-Wl,-rpath,@loader_path/../lib"
#-darwin "-Wl,-rpath,'$$ORIGIN/../lib'"
layout binary library ../lib resolves to
install <prefix>/bin/iclasp <prefix>/lib <prefix>/lib
build <build>/<variant>/iclasp <build>/<variant>/lib <build>/lib — absent, skipped

So one link serves both: installed binaries prefer their own prefix, and the in-tree binary falls through to the absolute entry exactly as before. No install-time rewriting, and no dependency on install_name_tool or patchelf (the latter is often not installed).

$ORIGIN must survive two layers to reach the linker — ninja ($$) and /bin/sh (single quotes). Confirmed via ninja -t commands:

-Wl,-rpath,'$ORIGIN/../lib'

Verification

macOS (arm64, LLVM 22):

$ otool -l build/boehmprecise/iclasp | grep -A2 LC_RPATH
    path @loader_path/../lib
    path /path/to/clasp/build/boehmprecise/lib
in-tree binary:            ok 42, loads .../build/boehmprecise/lib/libclasp.dylib
copy in install layout:    loads /tmp/rpx/lib/libclasp.dylib

x86-64 Linux (LLVM 18, bytecode):

$ readelf -d build/boehmprecise/iclasp | grep RUNPATH
  Library runpath: [$ORIGIN/../lib:/mnt/.../build/boehmprecise/lib]
in-tree binary:            intree-ok 42
copy in install layout:    /tmp/rpx/bin/../lib/libclasp.so   (build tree still present)

In both cases the install-layout copy prefers its own prefix while the build tree exists, and the in-tree binary is unchanged.

One thing worth your eye

The relative entry is inert in the build tree only because <build>/lib does not exist. That holds for every variant today, but it is a load-bearing assumption: a future layout that creates build/lib would make the in-tree binary prefer it. If you would rather not depend on that, the alternative is rewriting the rpath during install (what CMake does), at the cost of requiring install_name_tool/patchelf — happy to do it that way instead.

The reproducible-build branch of that cond is untouched; it already remaps paths for its own reasons and I did not want to disturb it without understanding the intent.

The link step emits a single rpath: the absolute path of the build tree's
lib directory.  `install' copies the binary but cannot rewrite an rpath baked
in at link time, so an INSTALLED clasp keeps resolving libclasp through the
build tree.  Consequences, none of them signalled:

  - removing the build directory breaks the installed clasp
  - rebuilding it silently changes which clasp the installed binary runs
  - the libclasp copied into the install prefix is never loaded
  - two installs cannot coexist: both resolve to whatever the build tree
    currently holds, so each reports the other's version

Observed by installing two versions into separate prefixes and asking each
for its version: both answered with the build tree's, and
DYLD_PRINT_LIBRARIES confirmed both loaded the same library.

Emit a loader-relative rpath ahead of the absolute one.  In the install
layout the binary is <prefix>/bin/iclasp and the library <prefix>/lib, so
../lib resolves.  In the build tree the binary is <build>/<variant>/iclasp
with its library in <build>/<variant>/lib, so ../lib names <build>/lib,
which does not exist and is skipped -- the in-tree binary falls through to
the absolute entry exactly as before.  One link therefore serves both, with
no install-time rewriting and no dependency on install_name_tool or patchelf.

$ORIGIN has to survive ninja and /bin/sh to reach the linker, hence the
doubled dollar and the single quotes.

Verified on both platforms.  macOS: rpaths are
(@loader_path/../lib, <build>/boehmprecise/lib); the in-tree binary still
loads from the build tree, and a copy in an install layout loads
/tmp/rpx/lib/libclasp.dylib.  Linux: RUNPATH is
[$ORIGIN/../lib:<build>/boehmprecise/lib], the in-tree binary runs, and a
copy in an install layout resolves through its own prefix while the build
tree is still present.

Fixes clasp-developers#1831.
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.

Installed binaries keep loading libclasp from the build tree (rpath baked at link time)

1 participant