emit an -rpath for tbb in LdFlags() on macos - #271
Conversation
The TBB libraries RcppParallel ships on macOS record an '@rpath'-relative install name, but LdFlags() emitted only a '-L' search path. That satisfies the linker, so downstream packages built fine, but the resulting binary carried no LC_RPATH at all: dyld could resolve '@rpath/libtbb.dylib' only when RcppParallel -- and hence TBB -- had already been loaded into the process. Loading such a binary on its own failed with "Library not loaded: @rpath/libtbb.dylib". Pair the '-L' with a matching '-rpath', as the TBB_LIB branch just above already does. A stale rpath (e.g. in a binary package built where RcppParallel lived at a different path) is harmless: dyld skips a path that does not resolve and falls back to matching the already-loaded image, which is the current behaviour. Fixes #209.
The flag-level check only asserts that LdFlags() looks right. Add a test that exercises the behaviour: build a package against those flags, then load its shared object from a fresh R process that never loads RcppParallel. That last part is the whole point. Loading such a package the usual way goes through its NAMESPACE, whose importFrom(RcppParallel, ...) pulls TBB into the process first and so succeeds either way -- as does the install, since the missing '-rpath' only affects load time, not linking. Only a direct dyn.load() in a clean process distinguishes the two, failing with "Library not loaded: @rpath/libtbb.dylib" without the fix. The test is CI-gated, as it needs a toolchain, and runs only where TBB is linked into the client explicitly (macOS, or a system TBB); elsewhere the symbols are deliberately resolved from the libraries RcppParallel loads. That gate mirrors the branches of tbbLdFlags() rather than inspecting its output, since gating on the '-rpath' under test would skip the very configuration that regressed.
|
Added That last part is what makes it a real test. Both the install and a normal The test is CI-gated (it needs a toolchain) and runs only where TBB is linked into the client explicitly -- macOS, or a system-TBB configuration. Its gate mirrors the branches of
|
Fixes #209.
The TBB libraries we ship on macOS record an
@rpath-relative install name(
@rpath/libtbb.dylib), butLdFlags()emitted only a-Lsearch path. Thatsatisfies the linker -- downstream packages build fine -- but the resulting
binary carries no
LC_RPATHat all, so dyld can resolve TBB only whenRcppParallel has already pulled it into the process. That is why the
importFrom(RcppParallel, RcppParallelLibs)requirement works in practice, andwhy anything loading its shared object without it fails:
This pairs the
-Lwith a matching-rpath, using the same format string asthe
TBB_LIBbranch just above, which already did this.Verification
Built a small dylib against an installed RcppParallel using the flags
RcppParallel::LdFlags()reports, thendyn.load()ed it:Library not loaded: @rpath/libtbb.dylibtests/test-ld-flags.Rasserts the invariant that every-Ldirectory is alsoemitted as an
-rpath; it fails against the unpatched package and passes here.The rest of the suite passes.
Note on stale rpaths
The rpath is an absolute path resolved at build time, so a binary package built
where RcppParallel lived elsewhere ends up with an rpath that does not resolve
on the target machine. That is harmless -- dyld skips it and falls back to
matching the already-loaded image, i.e. today's behaviour (checked by linking a
probe with only a bogus rpath). A
@loader_path-relative rpath would avoidstaleness but assumes both packages sit in the same library directory, which is
not guaranteed.