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).
91 lines
3.1 KiB
Python
Executable File
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())
|