Files
jasp-desktop-bin/build/bundle-libs.sh
T
agw 56434ba7ed Bundle non-system libraries instead of version-pinning depends
Bundling R alone was not enough: R links libicuuc, and Arch moves ICU
regularly. The obvious fix - pinning 'icu>=78 <79' in depends - is worse
than the disease on a rolling distro, because pacman would then refuse to
upgrade ICU while JASP is installed, blocking the user's whole system
upgrade until a rebuild lands.

So bundle-libs.sh copies every non-system shared library into
/opt/jasp-desktop/lib and rewrites rpaths, by denylist rather than
allowlist. depends now carries no version constraints at all.

Qt6 stays system-provided on purpose - Qt is binary compatible across Qt 6
minors, and bundling QtWebEngine would add ~500 MB. That also means the
only rebuild triggers left are a new JASP release and a Qt major bump;
rebuilding for anything else would cost users a 1.7 GB re-download.

Also fixes check-upstream.py querying only repo=Extra, which silently
returned nothing for icu (it lives in core).
2026-09-01 22:05:00 +02:00

105 lines
4.0 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
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.
echo ":: setting rpath on ELF objects"
patched=0
for obj in "${elf_files[@]}" "$LIBDIR"/*.so*; do
[[ -f "$obj" ]] || continue
if patchelf --set-rpath "${PREFIX}/lib" "$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"