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).
105 lines
4.0 KiB
Bash
Executable File
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"
|