Files
jasp-desktop-bin/build/build-jasp.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

243 lines
8.7 KiB
Bash
Executable File

#!/bin/bash
# Build JASP Desktop on Arch Linux and emit a relocatable binary tarball.
#
# Runs as root inside an `archlinux:base-devel` container. The resulting tarball
# is what the AUR `jasp-desktop-bin` package downloads and unpacks.
#
# JASP_TAG upstream tag to build, e.g. v0.98.1 (required)
# OUT_DIR where the tarball + metadata land (default /work/out)
# JOBS compile parallelism (default nproc)
# R_VERSION R to build and bundle (default 4.5.3)
# GITHUB_PAT token for R package downloads from GitHub (optional but
# strongly recommended — several JASP modules are installed from
# GitHub and anonymous API rate limits will fail the build)
#
# Why we build our own R instead of using Arch's:
#
# JASP pins its R package set with renv lockfiles against a frozen CRAN snapshot
# (Modules/Rcpp_RInside.lock points at the 2026-03-31 Posit snapshot for 0.98.1).
# That set targets R 4.5. Building against Arch's R 4.6 fails immediately —
# Rcpp 1.1.1 does not compile there (R_NamespaceRegistry is gone in R 4.6), and
# the pinned snapshot predates the Rcpp release that fixes it. Upstream's own CI
# installs a specific R and points R_HOME at it, which is what we do here.
#
# Bundling R also means the package does not depend on Arch's `r` at all, so an
# Arch R bump can no longer break an installed JASP.
set -euo pipefail
JASP_TAG="${JASP_TAG:?JASP_TAG is required, e.g. v0.98.1}"
OUT_DIR="${OUT_DIR:-/work/out}"
JOBS="${JOBS:-$(nproc)}"
R_VERSION="${R_VERSION:-4.5.3}"
SRC_DIR=/build/jasp-desktop
BUILD_DIR=/build/build
STAGE_DIR=/build/stage
PREFIX=/opt/jasp-desktop
R_HOME_DIR="${PREFIX}/R"
mkdir -p "$OUT_DIR" /build
log() { echo ":: $* "; }
# ---------------------------------------------------------------- system deps
log "installing Arch dependencies"
pacman -Syu --noconfirm --needed \
base-devel git cmake ninja gcc gcc-fortran make pkgconf patchelf \
autoconf automake libtool bison flex gettext blas lapack ccache \
qt6-base qt6-declarative qt6-svg qt6-positioning qt6-webchannel \
qt6-webengine qt6-5compat qt6-shadertools qt6-websockets qt6-tools \
qt6-httpserver vulkan-headers \
boost boost-libs glpk jsoncpp libarchive openssl curl sqlite zlib \
readstat netcdf mpfr cairo pango fontconfig harfbuzz fribidi icu pcre2 \
readline bzip2 xz libpng libtiff libjpeg-turbo minizip \
libxkbcommon libxkbcommon-x11 libxt wget unzip
# Stock makepkg.conf leaves MAKEFLAGS at -j1, which makes the AUR dependencies
# (JAGS especially) crawl.
sed -i "s|^#\?MAKEFLAGS=.*|MAKEFLAGS=\"-j${JOBS}\"|" /etc/makepkg.conf
export MAKEFLAGS="-j${JOBS}"
# makepkg refuses to run as root.
if ! id builder &>/dev/null; then
useradd -m builder
echo "builder ALL=(ALL) NOPASSWD: ALL" >/etc/sudoers.d/builder
fi
# jags and librdata are AUR-only; build them the same way a user would.
build_aur() {
local pkg="$1"
log "building AUR package $pkg"
sudo -u builder bash -euc "
cd /home/builder
rm -rf '$pkg'
git clone --depth 1 'https://aur.archlinux.org/$pkg.git'
cd '$pkg'
makepkg -si --noconfirm --needed
"
}
build_aur jags
build_aur librdata-git
# JASP requires freexl >= 2.0.99; Arch ships 2.0.0, so use upstream's own fork
# exactly as the JASP CI does.
log "building jasp-stats/freexl"
rm -rf /build/freexl
git clone --depth 1 https://github.com/jasp-stats/freexl.git /build/freexl
cd /build/freexl
./configure --prefix=/usr
make -j"$JOBS"
make install
# ------------------------------------------------------- R that we ship along
log "building R ${R_VERSION} into ${R_HOME_DIR}"
rm -rf "/build/R-${R_VERSION}"
cd /build
wget -q "https://cran.r-project.org/src/base/R-4/R-${R_VERSION}.tar.gz"
tar -xzf "R-${R_VERSION}.tar.gz"
cd "R-${R_VERSION}"
# --enable-R-shlib is required: JASP embeds R through RInside/libR.
# --libdir is pinned so R lands in lib/R rather than the lib64/R that Arch's
# config.site would otherwise pick, which keeps the rpath below predictable.
# The install prefix equals the final install path, so the rpath stays valid
# once the tarball is unpacked on the user's machine.
./configure \
--prefix="${R_HOME_DIR}" \
--libdir="${R_HOME_DIR}/lib" \
--enable-R-shlib \
--enable-memory-profiling \
--with-blas \
--with-lapack \
--with-x=no \
--with-tcltk=no \
LDFLAGS="-Wl,-rpath,${R_HOME_DIR}/lib/R/lib"
make -j"$JOBS"
make install
export PATH="${R_HOME_DIR}/bin:${PATH}"
# Ask R where it actually put itself rather than assuming lib vs lib64.
# Deliberately NOT exported: R.cmake notes that a set R_HOME makes R print
# "WARNING: ignoring environment value of R_HOME", which corrupts the output it
# parses. CUSTOM_R_PATH is the supported way to point JASP at a specific R.
JASP_R_HOME="$("${R_HOME_DIR}/bin/R" RHOME)"
log "bundled R_HOME is ${JASP_R_HOME}"
"${R_HOME_DIR}/bin/R" --version | sed -n 1p || true
# ------------------------------------------------------------------- checkout
log "cloning jasp-desktop $JASP_TAG"
rm -rf "$SRC_DIR"
git clone --depth 1 --branch "$JASP_TAG" --recurse-submodules --shallow-submodules \
https://github.com/jasp-stats/jasp-desktop.git "$SRC_DIR"
cd "$SRC_DIR"
JASP_COMMIT="$(git rev-parse HEAD)"
# ---------------------------------------------------------------------- build
log "configuring"
export CMAKE_C_COMPILER_LAUNCHER=ccache
export CMAKE_CXX_COMPILER_LAUNCHER=ccache
export CMAKE_Fortran_COMPILER_LAUNCHER=ccache
export CFLAGS="${CFLAGS:-} -fPIC"
export CXXFLAGS="${CXXFLAGS:-} -fPIC"
export FCFLAGS="${FCFLAGS:-} -fPIC"
export FFLAGS="${FFLAGS:-} -fPIC"
rm -rf "$BUILD_DIR"
cmake -S "$SRC_DIR" -B "$BUILD_DIR" -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="$PREFIX" \
-DLINUX_LOCAL_BUILD=OFF \
-DCUSTOM_R_PATH="${JASP_R_HOME}" \
-Wno-dev
log "compiling JASP (-j$JOBS)"
ninja -C "$BUILD_DIR" -j"$JOBS"
# The R modules are the long pole: several hundred R packages compiled from
# source, installed into the bundled R's library.
log "building R modules — this is the slow part"
mkdir -p "$BUILD_DIR"/Modules/{binary_pkgs,manifests,module_libs,Tools}
cd "$SRC_DIR/Tools"
if [[ -z "${GITHUB_PAT:-}" ]]; then
echo "warning: GITHUB_PAT is unset — R module downloads from GitHub may hit anonymous rate limits" >&2
fi
Rscript buildAllDefaultJaspModules.R
# -------------------------------------------------------------------- staging
log "staging install tree"
rm -rf "$STAGE_DIR"
mkdir -p "$STAGE_DIR"
DESTDIR="$STAGE_DIR" ninja -C "$BUILD_DIR" install
# The bundled R was installed straight to $PREFIX/R on the build host; copy it
# into the staged tree so it ends up in the tarball.
install -d "$STAGE_DIR$PREFIX"
cp -a "$R_HOME_DIR" "$STAGE_DIR$PREFIX/"
install -d "$STAGE_DIR$PREFIX/Modules"
if [[ -d "$SRC_DIR/Tools/ModuleBundleBuildDir/Modules" ]]; then
cp -a "$SRC_DIR/Tools/ModuleBundleBuildDir/Modules/." "$STAGE_DIR$PREFIX/Modules/"
fi
install -Dm644 /work/packaging/org.jaspstats.JASP.desktop \
"$STAGE_DIR/usr/share/applications/org.jaspstats.JASP.desktop"
if [[ -d "$STAGE_DIR$PREFIX/share/icons/hicolor" ]]; then
install -d "$STAGE_DIR/usr/share/icons/hicolor"
cp -a "$STAGE_DIR$PREFIX/share/icons/hicolor/." "$STAGE_DIR/usr/share/icons/hicolor/"
fi
install -d "$STAGE_DIR/usr/share/licenses/jasp-desktop-bin"
cd "$SRC_DIR"
find . -maxdepth 2 \( -name 'COPYING*' -o -name 'LICENSE*' \) -type f \
-exec install -m644 {} "$STAGE_DIR/usr/share/licenses/jasp-desktop-bin/" \;
find "$STAGE_DIR" -name '*.o' -delete 2>/dev/null || true
# Make the tree independent of Arch's rolling libraries (ICU, jsoncpp, glpk,
# JAGS, readstat, librdata, BLAS/LAPACK ...). Qt6 stays a system dependency.
log "bundling non-system shared libraries"
bash /work/build/bundle-libs.sh "$STAGE_DIR" "$PREFIX"
# ------------------------------------------------------------------- metadata
pkgver_of() { pacman -Q "$1" 2>/dev/null | awk '{print $2}'; }
QT_FULL="$(pkgver_of qt6-base)"
QT_MINOR="$(echo "$QT_FULL" | grep -oE '^[0-9]+\.[0-9]+')"
QT_MAJOR="$(echo "$QT_FULL" | grep -oE '^[0-9]+')"
ICU_FULL="$(pkgver_of icu)"
ICU_MAJOR="$(echo "$ICU_FULL" | grep -oE '^[0-9]+')"
BOOST_FULL="$(pkgver_of boost-libs)"
JASP_VER="${JASP_TAG#v}"
TARBALL="jasp-desktop-${JASP_VER}-x86_64.tar.zst"
log "creating $TARBALL"
cd "$STAGE_DIR"
tar --zstd -cf "$OUT_DIR/$TARBALL" .
SHA256="$(sha256sum "$OUT_DIR/$TARBALL" | awk '{print $1}')"
SIZE="$(stat -c%s "$OUT_DIR/$TARBALL")"
cat >"$OUT_DIR/build-info.json" <<EOF
{
"jasp_version": "${JASP_VER}",
"jasp_tag": "${JASP_TAG}",
"jasp_commit": "${JASP_COMMIT}",
"tarball": "${TARBALL}",
"sha256": "${SHA256}",
"size_bytes": ${SIZE},
"bundled_r_version": "${R_VERSION}",
"qt6_version": "${QT_FULL}",
"qt6_minor": "${QT_MINOR}",
"qt6_major": "${QT_MAJOR}",
"icu_version": "${ICU_FULL}",
"icu_major": "${ICU_MAJOR}",
"boost_version": "${BOOST_FULL}"
}
EOF
log "done"
cat "$OUT_DIR/build-info.json"