From 766ce6ff1ea584dfe980a5e0b29251768c4c0726 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Wed, 21 Oct 2026 11:36:01 +0200 Subject: sbb-remote 0.12.0: named builds, per-package job results, prune - Packages: named packages run slackrepo build, which also updates outdated ones; update on a named item finds only already-built packages. The per-row Update action is gone, Update all stays. - A real slackrepo job reads its items' buildresults rows into the job's results and is failed when any item failed, aborted or was bad: slackrepo itself exits 0. The notification names the failed items; a Build results dialog lists every item with its log and Retry failed, reopenable from the Jobs ribbon. - slackrepo prune: the helper lists built items whose SlackBuild is gone and runs slackrepo remove on them, without an Update all. stats gives the count; Packages gets a Prune action, the Built tile a 'to prune' note. Remove and prune ask for confirmation at Run it. Co-Authored-By: Claude Opus 5.5 --- sb_builder/fmt.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'sb_builder/fmt.py') diff --git a/sb_builder/fmt.py b/sb_builder/fmt.py index 5c50628..3676fd0 100644 --- a/sb_builder/fmt.py +++ b/sb_builder/fmt.py @@ -17,10 +17,12 @@ """Text formatting shared by the tabs.""" import time +from collections import Counter NA = "n/a" ICON = {"starting": "…", "running": "▶", "ok": "✔", "failed": "✘", "killed": "■", "lost": "?"} +BAD_RESULTS = ("failed", "aborted", "bad") # slackrepo item results that fail a job def fmt_bytes(n): @@ -54,10 +56,22 @@ def job_title(j): return " ".join([j["kind"], *j["args"]]) +def counts(results): + """'3 ok, 1 failed' from a slackrepo job's {item: result}.""" + return ", ".join(f"{n} {r}" for r, n in Counter(results.values()).most_common()) + + +def failed_items(j): + """The items of a slackrepo job whose result failed it, sorted.""" + return sorted(i for i, r in (j.get("results") or {}).items() if r in BAD_RESULTS) + + def outcome(j): """How an ended job went, as a sentence for its notification.""" took = fmt_duration(j["end"] - j["start"]) if "end" in j and "start" in j else None after = f" after {took}" if took else "" + if failed_items(j): + return f"Failed: {', '.join(failed_items(j))} ({counts(j['results'])}){after}" return {"ok": f"Succeeded in {took}" if took else "Succeeded", "failed": f"Failed with exit code {j.get('exit', '?')}{after}", "killed": f"Killed{after}", @@ -68,4 +82,5 @@ def describe(j): """One-line job summary: icon, kind and args, start time, duration.""" dur = None if "start" not in j else j.get("end", time.time()) - j["start"] return (f"{ICON.get(j['status'], '?')} {job_title(j)} " - f"{fmt_time(j.get('start', j['created']))} {fmt_duration(dur)}") + f"{fmt_time(j.get('start', j['created']))} {fmt_duration(dur)}" + + (f" {counts(j['results'])}" if j.get("results") else "")) -- cgit v1.2.3