Files
agw 2d5b6a8a0d Keep the bundled R on the rpath, and stop makepkg tidying the tree
First run of the actual binary: ldd reported 'libR.so => not found'.

patchelf --set-rpath replaces the existing rpath rather than appending, so
setting it to $PREFIX/lib discarded the -Wl,-rpath the build had given the
JASP binary for the bundled R. Every other bundled library resolved; only R
went missing, because libR.so/libRblas.so/libRlapack.so live under
R/lib/R/lib rather than in the bundled lib directory. The rpath now lists
both.

Two smaller things the same run surfaced:

- makepkg ran its tidying steps on a tree that was already built and
  verified - removing libtool and static library files, purging, compressing
  man pages. For a -bin package that is a difference between what was tested
  and what gets installed. All of them are now disabled.
- /opt/jasp-desktop/bin/JASP was not on PATH; the package now symlinks
  /usr/bin/jasp.
2026-09-03 21:20:00 +02:00

117 lines
4.7 KiB
Bash
Executable File

#!/bin/bash
# Make the staged JASP tree independent of Arch's rolling libraries.
#
# Copies every shared library JASP and its bundled R depend on into
# $PREFIX/lib, except a denylist of genuinely stable system components, then
# rewrites the rpath of every ELF object in the tree to look there first.
#
# The alternative — declaring versioned `depends` on icu, jsoncpp and friends —
# is worse on a rolling distro: when Arch ships the next ICU, pacman would
# refuse to upgrade it while JASP is installed, blocking the user's whole
# system upgrade. Bundling means we never constrain the user's system.
#
# $1 staged install tree (contains .$PREFIX)
# $2 install prefix, e.g. /opt/jasp-desktop
set -euo pipefail
STAGE_DIR="${1:?staged tree is required}"
PREFIX="${2:-/opt/jasp-desktop}"
LIBDIR="${STAGE_DIR}${PREFIX}/lib"
# Libraries that must keep coming from the system. Bundling any of these either
# breaks (glibc must match the running kernel/loader) or causes symbol clashes
# with the system Qt6 that JASP still links against.
#
# Qt6 itself is deliberately here: variant B keeps Qt as a system dependency and
# relies on Qt's binary-compatibility guarantee within Qt 6.
is_system_lib() {
case "$1" in
ld-linux*|libc.so*|libm.so*|libdl.so*|libpthread.so*|librt.so*|\
libresolv.so*|libnsl.so*|libutil.so*|libcrypt.so*|libanl.so*|\
libstdc++.so*|libgcc_s.so*|libatomic.so*) return 0 ;;
libQt6*) return 0 ;;
libGL*.so*|libEGL*.so*|libGLX*.so*|libOpenGL.so*|libGLdispatch.so*|\
libgbm.so*|libdrm*.so*|libvulkan.so*) return 0 ;;
libX11*.so*|libXext.so*|libXrender.so*|libXrandr.so*|libXi.so*|\
libXfixes.so*|libXcursor.so*|libXinerama.so*|libXcomposite.so*|\
libXdamage.so*|libXtst.so*|libXau.so*|libXdmcp.so*|libXss.so*|\
libxcb*.so*|libwayland*.so*|libxkbcommon*.so*) return 0 ;;
libdbus-1.so*|libsystemd.so*|libudev.so*) return 0 ;;
libasound.so*|libpulse*.so*) return 0 ;;
libfontconfig.so*|libfreetype.so*) return 0 ;;
libz.so*) return 0 ;;
*) return 1 ;;
esac
}
mkdir -p "$LIBDIR"
# Every ELF object in the tree: the JASP binary, libR, and the compiled .so of
# each of the several hundred R module packages.
mapfile -t elf_files < <(
find "$STAGE_DIR" -type f \( -name '*.so' -o -name '*.so.*' -o -perm -u+x \) \
-exec sh -c 'file -b "$1" | grep -q "^ELF" && echo "$1"' _ {} \;
)
echo ":: found ${#elf_files[@]} ELF objects in the staged tree"
# Resolving deps is iterative: a bundled library pulls in its own dependencies.
copied_any=1
round=0
while [[ "$copied_any" -eq 1 ]]; do
copied_any=0
round=$((round + 1))
mapfile -t scan < <(
printf '%s\n' "${elf_files[@]}"
find "$LIBDIR" -type f -name '*.so*' 2>/dev/null || true
)
for obj in "${scan[@]}"; do
[[ -f "$obj" ]] || continue
# ldd resolves against the build container, which is exactly the Arch
# userland we want to capture.
while read -r soname path; do
[[ -n "$path" && -f "$path" ]] || continue
# ldd reports some entries with an absolute path in the first column —
# the dynamic loader shows up as "/lib64/ld-linux-x86-64.so.2 => ...".
# Without this the denylist misses it and the copy below tries to create
# $LIBDIR/lib64/ld-linux-x86-64.so.2 in a directory that does not exist.
soname="${soname##*/}"
is_system_lib "$soname" && continue
[[ -f "$LIBDIR/$soname" ]] && continue
# Never bundle something that already lives inside our own tree.
case "$path" in "$STAGE_DIR"*|"$PREFIX"*) continue ;; esac
cp -L "$path" "$LIBDIR/$soname"
echo ":: bundled $soname"
copied_any=1
done < <(ldd "$obj" 2>/dev/null | awk '$2=="=>" && $3 ~ /^\// {print $1, $3}')
done
done
echo ":: bundling converged after $round round(s)"
chmod u+w "$LIBDIR"/*.so* 2>/dev/null || true
# An absolute rpath is safe here because the tarball always unpacks to $PREFIX;
# it avoids $ORIGIN arithmetic across bin/, R/lib/R/lib/ and the per-package
# libs/ directories, which all sit at different depths.
#
# Both directories must be listed. patchelf --set-rpath *replaces* the existing
# rpath rather than appending to it, so setting only $PREFIX/lib discards the
# -Wl,-rpath the build gave the JASP binary for the bundled R and leaves
# "libR.so => not found". libR.so, libRblas.so and libRlapack.so live under
# R/lib/R/lib, not in the bundled lib directory.
RPATH="${PREFIX}/lib:${PREFIX}/R/lib/R/lib"
echo ":: setting rpath to ${RPATH}"
patched=0
for obj in "${elf_files[@]}" "$LIBDIR"/*.so*; do
[[ -f "$obj" ]] || continue
if patchelf --set-rpath "$RPATH" "$obj" 2>/dev/null; then
patched=$((patched + 1))
fi
done
echo ":: rpath set on $patched objects"
echo ":: bundled library count: $(find "$LIBDIR" -name '*.so*' | wc -l)"
du -sh "$LIBDIR"