Files
jasp-desktop-bin/scripts/check-upstream.py
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

91 lines
3.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""Decide whether a rebuild is needed.
The build bundles its own R plus every non-system library it links against, so
Arch moving `r`, `icu`, `jsoncpp` and friends cannot affect an installed JASP and
must not trigger a rebuild — republishing costs every user a ~1.7 GB download.
What is left is Qt6, which stays a system dependency. Qt guarantees binary
compatibility across Qt 6 minor releases, so only a *major* Qt transition
warrants a rebuild.
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"
# No repo filter: icu lives in core while qt6-base lives in extra, and pinning
# the wrong repo silently returns zero results and disables the trigger.
ARCH_PKG = "https://archlinux.org/packages/search/json/?name={}"
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", [])
# Ignore staging/testing/kde-unstable: users are on the stable repos.
stable = [r for r in results if r["repo"] in ("core", "extra")]
if not stable:
raise RuntimeError(f"package {pkg} not found in Arch core/extra")
return stable[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_version = arch_version("qt6-base")
qt_major = qt_version.split(".")[0]
qt_minor = minor(qt_version)
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_major") != qt_major:
reasons.append(f"Qt major {state.get('qt6_major', 'none')} -> {qt_major}")
needs_build = bool(reasons)
print(f"jasp_tag = {jasp_tag}")
print(f"qt = {qt_version} (major {qt_major})")
print(f"decision = {'BUILD' if needs_build else 'up to date'}")
for reason in reasons:
print(f" reason: {reason}")
if not needs_build:
print(" (Qt minor bumps are binary-compatible and everything else is bundled)")
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_major={qt_major}\n")
fh.write(f"qt6_minor={qt_minor}\n")
fh.write(f"reasons={'; '.join(reasons)}\n")
return 0
if __name__ == "__main__":
sys.exit(main())