Files
jasp-desktop-bin/scripts/check-upstream.py
T
agw 0d4db32d5c
Build JASP binary and publish to AUR / check (push) Successful in 10s
Build JASP binary and publish to AUR / build (push) Failing after 12s
Automated Arch binary builds of JASP for the AUR
Upstream JASP ships no Linux binary, so the existing AUR package builds
everything from source including several hundred R packages. This builds
that once in CI and publishes the result as jasp-desktop-bin.

JASP links against the system R and Qt6 rather than bundling them, so a
rebuild is triggered by an Arch r/qt6-base bump as well as by a new JASP
release, and the generated PKGBUILD pins depends to the versions actually
built against.
2026-09-01 20:30:00 +02:00

83 lines
2.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""Decide whether a rebuild is needed.
A JASP binary is only valid for the R minor and Qt6 minor it was linked against,
so a rebuild is triggered by an upstream JASP release *or* by Arch moving R or
Qt6 — not just the former.
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"]
r_minor = minor(arch_version("r"))
qt_minor = minor(arch_version("qt6-base"))
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("r_minor") != r_minor:
reasons.append(f"R {state.get('r_minor', 'none')} -> {r_minor}")
if state.get("qt6_minor") != qt_minor:
reasons.append(f"Qt6 {state.get('qt6_minor', 'none')} -> {qt_minor}")
needs_build = bool(reasons)
print(f"jasp_tag = {jasp_tag}")
print(f"r_minor = {r_minor}")
print(f"qt6_minor = {qt_minor}")
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"r_minor={r_minor}\n")
fh.write(f"qt6_minor={qt_minor}\n")
fh.write(f"reasons={'; '.join(reasons)}\n")
return 0
if __name__ == "__main__":
sys.exit(main())