jaspMixedModels stalled at 6/7 for three passes on rstanarm: g++: fatal error: Killed signal terminated program cc1plus ERROR: compilation failed for package 'rstanarm' Stan generates translation units where one cc1plus wants 4-8 GB. At -j2 on a 9 GB container that is an instant kill, and the symptom is simply a module that never appears - the same shape as the OpenMx failure earlier, and just as easy to mistake for something else. Default R_JOBS to 1, and give the runner swap so an over-commit degrades into a slow compile instead of a dead one. Documented in the runner requirements, since a fresh runner without swap would hit this too.
181 lines
9.1 KiB
Markdown
181 lines
9.1 KiB
Markdown
# jasp-desktop-bin
|
||
|
||
Automated binary builds of [JASP](https://jasp-stats.org/) 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
|
||
|
||
```
|
||
nightly (03:00) ─► 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_HOST` | `192.168.10.148` | CT148, where tarballs are uploaded |
|
||
| `TARBALL_PATH` | `/srv/tarballz` | Directory on that host |
|
||
| `TARBALL_BASE_URL` | `https://asger.weirsoe.dk/tarballz` | Public URL the PKGBUILD downloads from |
|
||
|
||
**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_SSH_KEY` | Private key authorised on CT148 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/`, backed by
|
||
`/srv/tarballz` on CT148.
|
||
|
||
They are **not** stored under `/var/www/asger`. The `asger-web` deploy replaces
|
||
that entire directory atomically — it unpacks into `.new`, swaps it in and
|
||
deletes the old one — so anything kept inside it would vanish on the next site
|
||
deploy. nginx exposes `/srv/tarballz` through a `location /tarballz/` alias
|
||
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.timeout` in `/var/lib/gitea-worker/config.yaml` must exceed the build
|
||
time (set to `16h`).
|
||
- Gitea's own job timeout (`[actions] ENDLESS_TASK_TIMEOUT` in `app.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.
|
||
- **Swap.** `rstanarm` (required by `jaspMixedModels`) and `OpenMx` generate
|
||
translation units where a single `cc1plus` wants 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 at `R_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](https://wiki.archlinux.org/title/AUR_submission_guidelines#Rules_of_submission):
|
||
|
||
> Packages that use **prebuilt** deliverables, when the sources are available,
|
||
> must use the `-bin` suffix.
|
||
|
||
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 **`pkgdesc`** ends in `(prebuilt binary)`.
|
||
|
||
Anyone who wants to verify can read `build/build-jasp.sh` in this repo and
|
||
reproduce the tarball.
|