Skip to content

GHC/Build/Link: relativize absolute rpaths + self-$ORIGIN (forward-port onto master)#383

Closed
angerman wants to merge 3 commits into
stable-haskell/masterfrom
feat/rpath-relativize-and-origin-master
Closed

GHC/Build/Link: relativize absolute rpaths + self-$ORIGIN (forward-port onto master)#383
angerman wants to merge 3 commits into
stable-haskell/masterfrom
feat/rpath-relativize-and-origin-master

Conversation

@angerman

Copy link
Copy Markdown

Draft / forward-port for review. Brings the absolute-rpath relativization
to post-split stable-haskell/master and adds a self-$ORIGIN rpath entry.

What

Three commits onto stable-haskell/master:

  1. (Andrea) GHC/Build/Link: relativize absolute rpaths when --enable-relocatable
  2. (Andrea) GHC/Build/Link: drop the relocatable gate, always relativize abs rpaths
  3. (new) GHC/Build/Link: include $ORIGIN/@loader_path in rpaths

(1)+(2) are #368 (feat/rpath-relativize-absolute), which currently targets the
pre-split feature/rebase-CI; this is the same change rebased onto
post-split master, where getRPaths still bakes absolute build-host paths
into LC_RPATH/DT_RUNPATH.

(3) is new: getRPaths never adds the artifact's own directory to the
runpath. For a dependency that ends up in the same directory (a Backpack
signature impl libHSp next to the instantiated libHSindef),
depLibraryPaths yields the parent and it relativizes to $ORIGIN/.. — so the
dep isn't found. glibc papers over it via the runtime LD_LIBRARY_PATH GHC
sets before dlopen; musl reads LD_LIBRARY_PATH only at startup, so it's
fatal there (GHC testsuite backpack/cabal/T14304 on Alpine/musl; passes on
every glibc platform). Prepending the loader-relative self dir is relative, so
it does not reintroduce the absolute path (1)+(2) remove (no macOS regression).

Why here

This is part of reconciling the cross/dual-compiler stack onto master. The
fixes the stable-haskell GHC build needs (#361 tool-guess, #368 rpath, this
$ORIGIN follow-up, and the stage-qualified/host-only work in #378) are spread
across pre- and post-split feature branches; master has none of the rpath
work. stable-haskell/ghc currently has to pin the pre-split
feature/wasm-cross-ghcup-stack to get these, which forces it to also revert
GHC's Setup.hs to the pre-VerbosityHandles API and drop hooks-exe. Landing
the rpath stack on master (alongside #361 and #378) removes that need.

Caveat

Cherry-picked cleanly (no conflicts) but not built locally — please let CI
validate, particularly that the relativization still typechecks against the
post-split LocalBuildInfo/InstallDirs API (absoluteComponentInstallDirs,
NoCopyDest, bindir/libdir). Filing as draft for @andreabedini's review;
could alternatively stack on wip/andrea/stage-qualified-options.

angerman added 3 commits June 15, 2026 15:02
depLibraryPaths returns absolute library paths whenever the dep's
libdir is not under the package's own install prefix (i.e. almost
always, in a cabal-store layout where each package gets its own
hash-suffixed subdir). The previous getRPaths only prefixed
`@loader_path` / `$ORIGIN` to already-relative paths, so those
absolute store paths were baked directly into LC_RPATH (Mach-O)
and DT_RUNPATH (ELF).

On macOS up to and including Sonoma (macOS 14) dyld silently falls
through to subsequent rpath entries when an absolute rpath does
not exist. Sequoia (macOS 15) dyld treats the same condition as
fatal and aborts the binary on launch. This affected the
stable-haskell GHC bindist: a `/Volumes/WorkSpace/_work/ghc/ghc/
_build/stage2/store/host/aarch64-apple-darwin/lib` rpath baked in
by the build runner caused `ghc --numeric-version` to SIGABRT
when the bindist was installed on any macos-15 host.

When the user has opted into relocatable mode
(`--enable-relocatable`, or `relocatable: True` in a cabal
project), this commit replaces such absolute rpaths with a
`shortRelativePath`-computed relative form against the artifact's
install directory. The relative form is well-formed (no `/Volumes`
prefix to abort dyld on), and harmless when the bindist layout no
longer matches the build store (dyld treats it as a normal missing
rpath and falls through to subsequent entries).

Non-relocatable builds (the default) are unchanged: absolute paths
still pass through to preserve the existing semantics for
non-relocated installs.

The PackageDescription, InstallDirs, and shortRelativePath utility
this needs are all already in scope or come from already-imported
modules; only `bindir` and `libdir` field accessors are pulled in
freshly from `Distribution.Simple.InstallDirs`.

Companion to commit 010b365582c in stable-haskell/ghc, which strips
the same leaked rpaths post-build with `install_name_tool` while
this Cabal-side fix propagates through bindist rebuilds.
Initial version of this fix gated the new relativization on
`relocatable lbi` to keep non-relocatable builds byte-identical. The
gate doesn't help in practice:

  * the stable-haskell GHC bindist build requires the new behavior
    (else the darwin host binaries abort-trap on macOS 15);

  * setting `relocatable: True` to flip the gate also triggers
    `checkRelocatable` (which refuses cabal-store layouts whose deps
    live in sibling prefixes) and changes how `library-dirs` are
    written into .conf files — neither change is desirable for the
    rpath fix, and the latter actively breaks the bindist
    post-stage2 .conf rewriting in our Makefile pipeline.

The new always-on behavior is a strict improvement for every
relocatable scenario (cabal-store relocation, bindist relocation,
nix-style closure moves) and only a theoretical regression for the
"copy a single executable to an unrelated host and expect the same
absolute path to still resolve" case, which has never been part of
cabal's documented contract.

Companion to stable-haskell/ghc commit that drops the matching
`relocatable: True` flag from configure.ac.
…deps)

The rpath relativization (added for the darwin LC_RPATH-leak fix) never adds the
artifact's own directory to the runpath. depLibraryPaths can return the parent
directory for a dependency installed in the same directory as the artifact, so
that entry relativizes to "$ORIGIN/.." and the dependency is left unfound.

glibc papers over this because GHC sets LD_LIBRARY_PATH at runtime before
dlopen and glibc re-reads it per dlopen; musl reads LD_LIBRARY_PATH only at
process startup and ignores the runtime change, so the missing self-rpath is
fatal there. Concretely, a Backpack signature implementation (libHSp)
installed next to the instantiated unit (libHSindef) fails to load under musl
(GHC testsuite backpack/cabal/T14304 on Alpine), though it passes on every
glibc platform.

Always prepend the loader-relative self directory ($ORIGIN, or @loader_path on
macOS) to the rpaths so a same-directory sibling is found. This is a relative
entry, so it does not reintroduce the absolute build-host path the
relativization removed (no macOS regression).
@angerman

Copy link
Copy Markdown
Author

Part of the reconciliation tracked in #384.

@angerman

Copy link
Copy Markdown
Author

Superseded by #385, which stacks the same rpath + $ORIGIN forward-port on #378 (wip/andrea/stage-qualified-options) instead of targeting master directly. See #384.

@angerman angerman closed this Jun 15, 2026
@angerman angerman deleted the feat/rpath-relativize-and-origin-master branch June 15, 2026 06:08
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