Files
jasp-desktop-bin/scripts/gen-pkgbuild.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

101 lines
3.3 KiB
Python
Executable File

#!/usr/bin/env python3
"""Turn build-info.json into a ready-to-publish PKGBUILD.
pkgrel is derived from what is currently on the AUR: same pkgver means this is a
rebuild (against a new R/Qt6), so pkgrel increments; a new pkgver resets it to 1.
"""
import argparse
import json
import re
import sys
import urllib.request
AUR_RPC = "https://aur.archlinux.org/rpc/v5/info?arg[]={}"
def next_minor(version: str) -> str:
"""4.6 -> 4.7, 6.11 -> 6.12 — the upper bound of the compatible range."""
major, minor = version.split(".")[:2]
return f"{major}.{int(minor) + 1}"
def current_aur_version(pkgname: str) -> tuple[str, int] | None:
"""(pkgver, pkgrel) currently published on the AUR, or None if absent."""
try:
with urllib.request.urlopen(AUR_RPC.format(pkgname), timeout=30) as r:
results = json.load(r).get("results", [])
except Exception as exc: # network trouble must not silently reset pkgrel
print(f"warning: could not query AUR: {exc}", file=sys.stderr)
return None
if not results:
return None
full = results[0]["Version"] # e.g. "0.98.1-2"
ver, _, rel = full.rpartition("-")
return ver, int(rel) if rel.isdigit() else 1
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--build-info", required=True)
ap.add_argument("--template", required=True)
ap.add_argument("--out", required=True)
ap.add_argument("--gh-owner", required=True)
ap.add_argument("--gh-repo", default="jasp-desktop-bin")
ap.add_argument("--maintainer", required=True)
ap.add_argument("--pkgname", default="jasp-desktop-bin")
args = ap.parse_args()
info = json.loads(open(args.build_info).read())
jasp_version = info["jasp_version"]
published = current_aur_version(args.pkgname)
if published and published[0] == jasp_version:
pkgrel = published[1] + 1
print(f"rebuild of {jasp_version}: pkgrel {published[1]} -> {pkgrel}")
else:
pkgrel = 1
print(f"new version {jasp_version}: pkgrel 1")
tag = f"v{jasp_version}-{pkgrel}"
download_url = (
f"https://github.com/{args.gh_owner}/{args.gh_repo}"
f"/releases/download/{tag}/{info['tarball']}"
)
qt_minor = info["qt6_minor"]
substitutions = {
"@MAINTAINER@": args.maintainer,
"@JASP_VERSION@": jasp_version,
"@PKGREL@": str(pkgrel),
"@BUNDLED_R@": info["bundled_r_version"],
"@DOWNLOAD_URL@": download_url,
"@SHA256@": info["sha256"],
}
text = open(args.template).read()
for key, value in substitutions.items():
text = text.replace(key, value)
leftover = re.findall(r"@[A-Z0-9_]+@", text)
if leftover:
print(f"error: unsubstituted placeholders: {sorted(set(leftover))}", file=sys.stderr)
return 1
with open(args.out, "w") as fh:
fh.write(text)
# Consumed by the release + AUR-push steps of the workflow.
with open("release-meta.json", "w") as fh:
json.dump({"tag": tag, "pkgrel": pkgrel, "download_url": download_url, **info}, fh, indent=2)
print(f"wrote {args.out} ({args.pkgname} {jasp_version}-{pkgrel})")
print(f" R bundled at {info['bundled_r_version']}; built against Qt {qt_minor}")
print(" depends are unpinned - everything volatile is bundled")
return 0
if __name__ == "__main__":
sys.exit(main())