aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-19 21:36:15 +0200
committerDanilo M. <danix@danix.xyz>2026-10-19 21:36:15 +0200
commitfc02ae2b562a3c608b41302d84d69b29415b927c (patch)
tree9fe27aad03c05eea293352dbffa4a77b8a784efa
parent993950b7733ba9300915955ec6e51e1f21dad806 (diff)
downloadsb-builder-fc02ae2b562a3c608b41302d84d69b29415b927c.tar.gz
sb-builder-fc02ae2b562a3c608b41302d84d69b29415b927c.zip
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 <noreply@anthropic.com>
-rw-r--r--README.md7
-rwxr-xr-xremote/sbb-remote14
-rw-r--r--sb_builder/__init__.py2
-rw-r--r--sb_builder/results.py7
-rw-r--r--tests/test_gui.py4
-rw-r--r--tests/test_remote.py12
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):