Files
jasp-desktop-bin/scripts/check-upstream.py
T
agw d880e6748c Bundle our own R 4.5.3 instead of building against Arch's R
Arch ships R 4.6.1, but JASP pins its R packages against a frozen CRAN
snapshot (2026-03-31 for 0.98.1) that targets R 4.5. Configuring against
Arch's R fails at Rcpp 1.1.1, which cannot compile on R 4.6 since
R_NamespaceRegistry was removed; the Rcpp release that fixes it is newer
than the pinned snapshot, so the lockfile cannot reach it either.

Build R 4.5.3 into /opt/jasp-desktop/R and point R_HOME at it, matching
what upstream CI does. This drops 'r' from depends entirely, so an Arch R
upgrade can no longer break an installed JASP, and the rebuild triggers
become Qt6 and ICU - the libraries actually still linked against.
2026-09-01 21:40:00 +02:00

84 lines
2.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""Decide whether a rebuild is needed.
The build bundles its own R, so Arch's `r` is irrelevant here. What the binary
*is* still linked against is Qt6 and ICU, both of which break ABI across
releases — so a rebuild is triggered by an upstream JASP release or by Arch
moving either of those.
Writes GITHUB_OUTPUT-style key=value pairs to stdout (and to $GITHUB_OUTPUT).
"""
import json
import os
import sys
import urllib.request
STATE_FILE = "state.json"
GH_LATEST = "https://api.github.com/repos/jasp-stats/jasp-desktop/releases/latest"
ARCH_PKG = "https://archlinux.org/packages/search/json/?name={}&repo=Extra"
def fetch_json(url: str) -> dict:
req = urllib.request.Request(url, headers={"User-Agent": "jasp-desktop-bin-ci"})
with urllib.request.urlopen(req, timeout=30) as r:
return json.load(r)
def arch_version(pkg: str) -> str:
results = fetch_json(ARCH_PKG.format(pkg)).get("results", [])
if not results:
raise RuntimeError(f"package {pkg} not found in Arch Extra")
return results[0]["pkgver"]
def minor(version: str) -> str:
return ".".join(version.split(".")[:2])
def main() -> int:
forced = os.environ.get("FORCE_BUILD", "").lower() == "true"
pinned_tag = os.environ.get("JASP_TAG", "").strip()
jasp_tag = pinned_tag or fetch_json(GH_LATEST)["tag_name"]
qt_minor = minor(arch_version("qt6-base"))
icu_major = arch_version("icu").split(".")[0]
try:
state = json.loads(open(STATE_FILE).read())
except FileNotFoundError:
state = {}
reasons = []
if forced:
reasons.append("manually forced")
if state.get("jasp_tag") != jasp_tag:
reasons.append(f"JASP {state.get('jasp_tag', 'none')} -> {jasp_tag}")
if state.get("qt6_minor") != qt_minor:
reasons.append(f"Qt6 {state.get('qt6_minor', 'none')} -> {qt_minor}")
if state.get("icu_major") != icu_major:
reasons.append(f"ICU {state.get('icu_major', 'none')} -> {icu_major}")
needs_build = bool(reasons)
print(f"jasp_tag = {jasp_tag}")
print(f"qt6_minor = {qt_minor}")
print(f"icu_major = {icu_major}")
print(f"decision = {'BUILD' if needs_build else 'up to date'}")
for reason in reasons:
print(f" reason: {reason}")
out = os.environ.get("GITHUB_OUTPUT")
if out:
with open(out, "a") as fh:
fh.write(f"needs_build={'true' if needs_build else 'false'}\n")
fh.write(f"jasp_tag={jasp_tag}\n")
fh.write(f"qt6_minor={qt_minor}\n")
fh.write(f"icu_major={icu_major}\n")
fh.write(f"reasons={'; '.join(reasons)}\n")
return 0
if __name__ == "__main__":
sys.exit(main())