From fc02ae2b562a3c608b41302d84d69b29415b927c Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Mon, 19 Oct 2026 21:36:15 +0200 Subject: build-log: show the last attempt's log, not always build.log slackrepo writes one log per command (build, rebuild, update, remove). A failed update left build.log from an older successful build, so the viewer showed a success next to a failed result. The helper (0.4.1) now returns the newest command log and its name, which the viewer shows in the title and header. Co-Authored-By: Claude Opus 5.5 --- README.md | 7 ++++--- remote/sbb-remote | 14 +++++++++----- sb_builder/__init__.py | 2 +- sb_builder/results.py | 7 ++++--- tests/test_gui.py | 4 +++- tests/test_remote.py | 12 ++++++++++++ 6 files changed, 33 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index cf84e0d..bd5ee1c 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,10 @@ Every key is optional. `File > Reload config` rereads the GUI config. **Publish** later to push them. - **Results** (ribbon, or click a result in the Dashboard's slackrepo bar or legend): slackrepo's last build attempt per package, newest first, filterable - by result. **Show build log** (or double-click) opens the end of the - package's `build.log`, where slackrepo reports the failure, with its - `resource.log` (load, memory, tmp over the build). + by result. **Show build log** (or double-click) opens the end of the log of + the package's last attempt (slackrepo keeps one per command: `build.log`, + `update.log`, `rebuild.log`, `remove.log`), where it reports the failure, + with its `resource.log` (load, memory, tmp over the build). - Real runs sign the repository. When the package signing key is locked (status bar 🔒), the GUI asks for its passphrase first and hands it to the VM's gpg-agent; it is never stored. diff --git a/remote/sbb-remote b/remote/sbb-remote index a3b83a6..25427c0 100755 --- a/remote/sbb-remote +++ b/remote/sbb-remote @@ -38,7 +38,7 @@ import tempfile import time import tomllib -VERSION = "0.4.0" +VERSION = "0.4.1" DEFAULTS = { "state_dir": "/var/lib/sb-builder", "repo_dir": "/var/lib/sbopkg/SBo-danix", @@ -59,7 +59,8 @@ PKGNAME = re.compile(r"^[A-Za-z0-9._+-]+$") ACTIONS = ("build", "rebuild", "update", "remove") PRESET = "/usr/libexec/gpg-preset-passphrase" ITEM = re.compile(r"^[A-Za-z0-9._+-]+/[A-Za-z0-9._+-]+$") # category/pkg -LOG_TAIL = 512 * 1024 # bytes of build.log returned; the failure is at the end +LOG_TAIL = 512 * 1024 # bytes of the log returned; the failure is at the end +COMMAND_LOGS = ("build.log", "rebuild.log", "update.log", "remove.log") # one per slackrepo command ACTIVE = ("starting", "running") START_GRACE = 10 # seconds a job may sit in "starting" before it counts as lost @@ -510,12 +511,15 @@ def cmd_build_log(cfg, item=None): if not ITEM.match(item or "") or ".." in item: raise Error(f"bad package path: {item}") d = os.path.join(cfg["slackrepo_logdir"], item) - build = os.path.join(d, "build.log") - if not os.path.isfile(build): + # the last attempt's log is the newest command log, not necessarily build.log + logs = [os.path.join(d, n) for n in COMMAND_LOGS if os.path.isfile(os.path.join(d, n))] + if not logs: raise Error(f"no build log for {item}") + build = max(logs, key=os.path.getmtime) text, truncated = _tail(build, LOG_TAIL) resource = os.path.join(d, "resource.log") - return {"build": text, "truncated": truncated, "mtime": int(os.path.getmtime(build)), + return {"name": os.path.basename(build), "build": text, "truncated": truncated, + "mtime": int(os.path.getmtime(build)), "resource": _tail(resource, LOG_TAIL)[0] if os.path.isfile(resource) else None} diff --git a/sb_builder/__init__.py b/sb_builder/__init__.py index 539b874..e0f577e 100644 --- a/sb_builder/__init__.py +++ b/sb_builder/__init__.py @@ -16,4 +16,4 @@ """sb-builder: Qt6 GUI driving the SlackBuild tooling on a build VM.""" -VERSION = "0.4.0" # must match remote/sbb-remote VERSION +VERSION = "0.4.1" # must match remote/sbb-remote VERSION diff --git a/sb_builder/results.py b/sb_builder/results.py index 30a3adc..1fc6b9a 100644 --- a/sb_builder/results.py +++ b/sb_builder/results.py @@ -44,16 +44,17 @@ class LogViewer(QDialog): def __init__(self, path, log, parent=None): super().__init__(parent) - self.setWindowTitle(f"{path}: build log") + name = log.get("name", "build.log") + self.setWindowTitle(f"{path}: {name}") self.resize(1000, 700) self.text = QPlainTextEdit(readOnly=True) self.text.setFont(QFontDatabase.systemFont(QFontDatabase.SystemFont.FixedFont)) parts = [] # resource.log first: the end of build.log, where it failed, stays last if log["resource"]: parts.append("──── resource.log ────\n" + ANSI.sub("", log["resource"]) + "\n") - parts.append("──── build.log ────\n") + parts.append(f"──── {name} ────\n") if log["truncated"]: - parts.append("[... start of build.log truncated, showing its end ...]\n") + parts.append(f"[... start of {name} truncated, showing its end ...]\n") parts.append(ANSI.sub("", log["build"])) self.text.setPlainText("".join(parts)) self.text.moveCursor(QTextCursor.MoveOperation.End) diff --git a/tests/test_gui.py b/tests/test_gui.py index d77df20..4eb4211 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -291,7 +291,8 @@ class SmokeTest(unittest.TestCase): "stats": (STATS, None), "jobs": ({"jobs": []}, None), "gpg-status": ({"unlocked": True, "key": "K"}, None), "inventory": (INVENTORY, None), "results": (RESULTS, None), - "build-log": ({"build": "\x1b[1mERROR\x1b[0m: make failed\n", + "build-log": ({"name": "update.log", + "build": "\x1b[1mERROR\x1b[0m: make failed\n", "truncated": True, "mtime": 0, "resource": "peak memory 3308M\n"}, None), **answers}) @@ -483,6 +484,7 @@ class SmokeTest(unittest.TestCase): self.assertIn("peak memory", viewer.text.toPlainText()) self.assertIn("truncated", viewer.text.toPlainText()) self.assertTrue(viewer.text.toPlainText().endswith("make failed\n")) # failure stays last + self.assertIn("update.log", viewer.windowTitle()) viewer.close() def test_dashboard_results_clicks(self): diff --git a/tests/test_remote.py b/tests/test_remote.py index 85e642b..db3d0a1 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -489,8 +489,20 @@ class ResultsTest(Base): self.assertEqual(log["build"], "\nERROR: make failed\n"[-20:]) self.assertTrue(log["truncated"]) self.assertEqual(log["resource"], "peak memory 3308M\n") + self.assertEqual(log["name"], "build.log") self.assertIsInstance(log["mtime"], int) + def test_newest_command_log_wins(self): + # slackrepo keeps one log per command: the last attempt may be an update + d = os.path.join(self.cfg["slackrepo_logdir"], "cat", "c") + os.utime(os.path.join(d, "build.log"), (1000, 1000)) + with open(os.path.join(d, "update.log"), "w") as f: + f.write("ERROR: Download failed\n") + with open(os.path.join(d, "config.log"), "w") as f: + f.write("autotools noise\n") + log = sbb.cmd_build_log(self.cfg, "cat/c") + self.assertEqual((log["name"], log["build"]), ("update.log", "ERROR: Download failed\n")) + def test_build_log_rejects(self): for bad in ("../etc/passwd", "cat/../../x", "cat", "cat/a", "/etc/x"): with self.subTest(bad=bad), self.assertRaises(sbb.Error): -- cgit v1.2.3