JASP 0.98.8 requires the Qt NetworkAuth component, so the build installs qt6-networkauth and the package depends on it at runtime. Without it CMake stopped at configure and the run failed every evening. The scheduled run now starts at 22:00 Danish time instead of 18:00, so the hours-long build no longer competes with evening use of the runner host. Claude-Session: https://claude.ai/code/session_01JmhJG6JnhXfzNiKv8f2vHu
jasp-desktop-bin
Automated binary builds of JASP for Arch Linux, published
to the AUR as jasp-desktop-bin.
Why this exists
Upstream JASP ships installers for Windows and macOS only — there is no official
Linux binary. The official Linux channel is the Flathub build. The existing AUR
package jasp-desktop therefore builds everything from source, including
buildAllDefaultJaspModules.R, which compiles several hundred R packages. That is
the multi-hour build this repo exists to avoid: the compile happens once, here, and
users download the result.
The constraint that shapes everything
JASP pins its R package set with renv lockfiles against a frozen CRAN snapshot
— for 0.98.1, Modules/Rcpp_RInside.lock points at the Posit snapshot of
2026-03-31. That set targets R 4.5.
Building against Arch's current R (4.6.1) fails immediately: Rcpp 1.1.1 does not
compile there, because R_NamespaceRegistry is gone in R 4.6. The Rcpp release
that fixes this (1.1.2, 2026-07-05) is newer than the pinned snapshot, so the
lockfile cannot resolve it either. This is precisely the "old libraries with
issues" problem that makes the source AUR package painful.
So this build compiles and bundles its own R 4.5.3 into
/opt/jasp-desktop/R and points R_HOME at it — which is what upstream's own CI
does (.github/workflows/ubuntu.yml sets JASP_R_VERSION: 4.5.3 and an explicit
R_HOME). Tools/CMake/R.cmake reads R_HOME from the environment on Linux, so
this is a supported configuration rather than a hack.
What is bundled, and why nothing is version-pinned
After staging, build/bundle-libs.sh walks every ELF object in the tree, copies
each non-system shared library it depends on into /opt/jasp-desktop/lib, and
rewrites the rpath to look there. It works by denylist: glibc, libstdc++, Qt6,
the GL/X11/Wayland/dbus stack, fontconfig and freetype stay system-provided;
everything else — ICU, jsoncpp, glpk, JAGS, readstat, librdata, BLAS/LAPACK,
pcre2, readline — is bundled.
The point is that depends contains no version constraints at all. That is a
deliberate reversal of the obvious design. Pinning (icu>=78 <79) looks safer but
is actively harmful on a rolling distro: when Arch ships the next ICU, pacman
would refuse to upgrade it while JASP is installed, blocking the user's entire
system upgrade until a rebuild lands. Bundling means JASP never constrains the
system, and a pacman -Syu cannot break it.
Qt6 is the deliberate exception and stays a system dependency. Qt guarantees
binary compatibility across Qt 6 minor releases, and bundling QtWebEngine would
add ~500 MB for very little gain. A Qt 7 transition will need a rebuild — which is
why scripts/check-upstream.py watches the Qt major and nothing else besides
the JASP version. Rebuilding for anything else would cost every user a ~1.7 GB
re-download for no benefit.
jags and librdata-git are AUR-only, and JASP needs freexl >= 2.0.99 while Arch
ships 2.0.0 — so the build compiles those itself, matching what upstream CI does.
Layout
| Path | Purpose |
|---|---|
build/build-jasp.sh |
The actual build. Runs in an archlinux:base-devel container, emits the tarball plus build-info.json. |
build/bundle-libs.sh |
Copies non-system shared libraries into /opt/jasp-desktop/lib and rewrites rpaths, so the package needs no version-pinned deps. |
scripts/check-upstream.py |
Compares upstream JASP and the Arch Qt major against state.json; decides whether to build. |
scripts/gen-pkgbuild.py |
Fills packaging/PKGBUILD.in from build-info.json; derives pkgrel from what is on the AUR. |
scripts/publish-tarball.sh |
Uploads the tarball to asger.weirsoe.dk, verifies the checksum on arrival and prunes old releases. |
scripts/push-aur.sh |
Generates .SRCINFO and pushes to aur.archlinux.org. |
scripts/record-state.sh |
Commits state.json so the next nightly run knows what was built. |
packaging/PKGBUILD.in |
Template for the AUR package. |
state.json |
Last successfully built combination. |
Flow
daily (22:00 CPH) ─► check-upstream.py
│ new JASP tag, or a Qt major transition?
▼
build-jasp.sh (Arch container, hours)
│ builds R 4.5.3, JASP, all R modules, then bundles libs
│ jasp-desktop-<ver>-x86_64.tar.zst + build-info.json
▼
upload to asger.weirsoe.dk ──► verify public URL
│ ──► push to AUR
│
▼
state.json committed back
Required configuration
Set these in the Gitea repo before the first run.
Variables (Settings → Actions → Variables):
| Name | Example | Meaning |
|---|---|---|
AUR_MAINTAINER |
Asger Geel Weirsoe <asger at weircon dot dk> |
Written into the PKGBUILD header. The guidelines suggest obfuscating the address against spam harvesting. |
TARBALL_BASE_URL |
https://asger.weirsoe.dk/tarballz |
Public URL the PKGBUILD downloads from |
PUBLISH |
true |
Master switch. Unless it is true, a run only builds and verifies, leaving the tarball in the jasp-build volume under out/: no upload, no state.json commit, no AUR push. |
Secrets (Settings → Actions → Secrets):
| Name | Meaning |
|---|---|
GH_TOKEN |
Optional. A GitHub PAT, passed to the build as GITHUB_PAT. Not used for publishing — several JASP R modules install from GitHub and anonymous API rate limits can fail the build. |
TARBALL_HOST |
SSH host the tarballs are uploaded to. A secret rather than a variable: run logs of a public repo are public, and Gitea prints variables in them. |
TARBALL_PATH |
Directory on that host that the web server publishes. A secret for the same reason. |
TARBALL_SSH_KEY |
Private key authorised on TARBALL_HOST for uploading tarballs |
AUR_SSH_PRIVATE_KEY |
Private SSH key registered with your AUR account. Copy the value already set on agw/pi3-smart-workspace. |
STATE_PUSH_TOKEN |
Gitea token with write access to this repo, for committing state.json. (Gitea reserves the GITEA_ secret prefix.) |
Where the tarballs live
Tarballs are served from https://asger.weirsoe.dk/tarballz/. They are kept
outside the website's own web root: the site deploy replaces its web root
atomically, so anything stored inside it would vanish on the next deploy. The
web server exposes the tarball directory through a separate /tarballz/
location instead, with gzip off so makepkg gets a plain byte stream.
Note that asger.weirsoe.dk 301-redirects to the IDN form
asger.xn--weirse-fya.dk. makepkg follows redirects, so the readable hostname is
used in the PKGBUILD, but that redirect is load-bearing — if it is ever removed,
the source= URL must be repointed.
The publish step verifies the uploaded copy's sha256 on the host, and a separate step then fetches the public URL and checks it against the sha256 baked into the PKGBUILD. Both run before the AUR push, so the package can never be published pointing at a missing or truncated file.
Runner requirements
The build runs for hours and needs disk. On the Gitea runner host:
runner.timeoutin the runner'sconfig.yamlmust exceed the build time (set to16h).- Gitea's own job timeout (
[actions] ENDLESS_TASK_TIMEOUTinapp.ini, default 3h) must match, otherwise Gitea kills the job at 3h regardless of the runner. - Roughly 40–60 GB of free disk for the build tree, the bundled R and the staged install.
- The Docker volume
jasp-cache, mounted into the build as/cache. It holds ccache and the compiled R package cache (renv), and is what keeps a rebuild from recompiling 1000+ R packages from scratch. It is never wiped by the workflow; entries whose system libraries have gone missing after an Arch update are purged at the start of each build.docker volume rm jasp-cacheforces a clean build. A second volume,jasp-build, holds the work tree and output; the workflow empties it at the start of every run. - Swap.
rstanarm(required byjaspMixedModels) andOpenMxgenerate translation units where a singlecc1pluswants 4–8 GB. Without swap an over-commit is an instant OOM kill, which surfaces only as a module that never builds. 16 GB RAM plus 8 GB swap, with R packages compiled atR_JOBS=1, is what this build was verified against.
Running it by hand
# Build a specific tag without waiting for the schedule
Actions → Build JASP binary and publish to AUR → Run workflow
jasp_tag = v0.98.1 force = true
Where the binary comes from, and how that is disclosed
The AUR has no formal "unofficial binary" flag. What it has is the naming rule in AUR submission guidelines:
Packages that use prebuilt deliverables, when the sources are available, must use the
-binsuffix.
That rule is about the deliverable being prebuilt — it says nothing about who
built it, so -bin is the correct and sufficient signal here even though the
binary is ours rather than upstream's. The two rules that could bite do not:
JASP is not in any official repository, and the AUR repo carries only PKGBUILD
and .SRCINFO, never the makepkg tarball or a filelist.
Disclosure is therefore by convention rather than by mechanism, in the two places a user actually looks:
- the comment header at the top of the
PKGBUILD(the same header the guidelines require for maintainer info) states that upstream ships no Linux binary and links to the build pipeline that produced this one; - the
pkgdescends in(prebuilt binary).
Anyone who wants to verify can read build/build-jasp.sh in this repo and
reproduce the tarball.