#!/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"