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

105 lines
3.4 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']}"
)
r_minor = info["r_minor"]
qt_minor = info["qt6_minor"]
substitutions = {
"@MAINTAINER@": args.maintainer,
"@JASP_VERSION@": jasp_version,
"@PKGREL@": str(pkgrel),
"@R_MINOR@": r_minor,
"@R_NEXT_MINOR@": next_minor(r_minor),
"@QT_MINOR@": qt_minor,
"@QT_NEXT_MINOR@": next_minor(qt_minor),
"@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 pinned to >={r_minor} <{next_minor(r_minor)}")
print(f" Qt pinned to >={qt_minor} <{next_minor(qt_minor)}")
return 0
if __name__ == "__main__":
sys.exit(main())