aboutsummaryrefslogtreecommitdiffstats
path: root/sb_builder/jobs.py
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-21 11:36:01 +0200
committerDanilo M. <danix@danix.xyz>2026-10-21 11:36:01 +0200
commit766ce6ff1ea584dfe980a5e0b29251768c4c0726 (patch)
treeeaa047f46fc9b59acae745406a116d2e479239ca /sb_builder/jobs.py
parentfb93767351dc0d7d4d4da8068b2f94df6ef44b6d (diff)
downloadsb-builder-766ce6ff1ea584dfe980a5e0b29251768c4c0726.tar.gz
sb-builder-766ce6ff1ea584dfe980a5e0b29251768c4c0726.zip
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 <noreply@anthropic.com>
Diffstat (limited to 'sb_builder/jobs.py')
-rw-r--r--sb_builder/jobs.py79
1 files changed, 75 insertions, 4 deletions
diff --git a/sb_builder/jobs.py b/sb_builder/jobs.py
index 9bfa2e3..a64bc58 100644
--- a/sb_builder/jobs.py
+++ b/sb_builder/jobs.py
@@ -17,15 +17,18 @@
"""Jobs tab: job list, live log through job-follow, start and kill; the next-step dialog."""
import codecs
+import os
import re
from PyQt6.QtCore import Qt, pyqtSignal
-from PyQt6.QtGui import QFontDatabase, QTextCursor
+from PyQt6.QtGui import QColor, QFontDatabase, QTextCursor
from PyQt6.QtWidgets import (QCheckBox, QDialog, QHBoxLayout, QInputDialog, QLineEdit,
QListWidget, QListWidgetItem, QMenu, QMessageBox, QPlainTextEdit,
- QPushButton, QSplitter, QVBoxLayout, QWidget)
+ QLabel, QPushButton, QSplitter, QVBoxLayout, QWidget)
-from .fmt import describe, job_title
+from .dashboard import STATUS
+from .fmt import describe, failed_items, job_title
+from .results import LogViewer
from .ui import SP, action, enable
ANSI = re.compile(rb"\x1b\[[0-9;?]*[A-Za-z]")
@@ -104,16 +107,80 @@ class NextStep(QDialog):
self.run.emit(["sbshadow", "--yes"])
else:
args = [a for a in self.job["args"] if a != "--preview"]
+ if args[0] in ("remove", "prune") and not self.confirm(
+ "Remove the packages listed in the preview from the repository?"):
+ return
if not self.publish.isChecked():
args.insert(1, "--no-publish")
self.run.emit(["slackrepo", *args])
self.accept()
+class BuildResultDialog(QDialog):
+ """After a slackrepo run that failed items: each item's result, its build log, Retry failed."""
+
+ retry = pyqtSignal(list) # package names
+
+ def __init__(self, remote, job, parent=None):
+ super().__init__(parent)
+ self.remote = remote
+ self.viewers = []
+ self.setWindowTitle("Build results: " + job_title(job))
+ self.resize(600, 400)
+ self.list = QListWidget()
+ for item, result in sorted(job["results"].items(), key=lambda r: (r[1] == "ok", r[0])):
+ row = QListWidgetItem(f"{item} {result}")
+ row.setData(Qt.ItemDataRole.UserRole, item)
+ if result in STATUS:
+ row.setForeground(QColor(STATUS[result]))
+ self.list.addItem(row)
+ self.list.itemDoubleClicked.connect(self.show_log)
+ self.status = QLabel()
+ # bad items (no such package) need a fix first: a retry would fail the same way
+ self.again = sorted({os.path.basename(i) for i, r in job["results"].items()
+ if r in ("failed", "aborted")})
+ log = QPushButton("Log")
+ log.clicked.connect(lambda: self.show_log(self.list.currentItem()))
+ self.retry_btn = QPushButton("Retry failed", enabled=bool(self.again))
+ self.retry_btn.setToolTip(" ".join(self.again) or "nothing failed or aborted")
+ self.retry_btn.clicked.connect(self._retry)
+ close = QPushButton("Close")
+ close.clicked.connect(self.accept)
+ bar = QHBoxLayout()
+ bar.addWidget(log)
+ bar.addWidget(self.status, 1)
+ bar.addWidget(self.retry_btn)
+ bar.addWidget(close)
+ layout = QVBoxLayout(self)
+ layout.addWidget(self.list)
+ layout.addLayout(bar)
+ self.list.setCurrentRow(0)
+
+ def show_log(self, row):
+ if not row:
+ return
+ item = row.data(Qt.ItemDataRole.UserRole)
+
+ def got(log, err):
+ if err:
+ self.status.setText(f"build log: {err}")
+ return
+ viewer = LogViewer(item, log, self)
+ viewer.finished.connect(lambda *_: self.viewers.remove(viewer))
+ self.viewers.append(viewer)
+ viewer.show()
+ self.remote.call(got, "build-log", item)
+
+ def _retry(self):
+ self.retry.emit(self.again)
+ self.accept()
+
+
class JobsTab(QWidget):
refresh_requested = pyqtSignal()
started = pyqtSignal(str) # id of a job this GUI started
preview_requested = pyqtSignal(dict) # a finished preview to take to its next step
+ results_requested = pyqtSignal(dict) # a slackrepo run that failed items, to show them
def __init__(self, remote, parent=None):
super().__init__(parent)
@@ -130,13 +197,16 @@ class JobsTab(QWidget):
self.act_pull.setMenu(self.pull_menu)
self.act_run = action(self, "Run it", "media-playback-start", SP.SP_MediaPlay,
lambda: self.preview_requested.emit(self.jobs[self.current]))
+ self.act_results = action(self, "Build results", "dialog-information",
+ SP.SP_FileDialogDetailedView,
+ lambda: self.results_requested.emit(self.jobs[self.current]))
self.act_kill = action(self, "Kill", "process-stop", SP.SP_BrowserStop, self.kill)
self.actions = [self.act_pull,
action(self, "Check shadowing", "edit-find", SP.SP_FileDialogContentsView,
lambda: self.start("sbshadow", "--no")),
action(self, "Rebuild tree", "edit-clear-all", SP.SP_DialogResetButton,
self.rebuild_tree),
- self.act_run, self.act_kill]
+ self.act_run, self.act_results, self.act_kill]
self.view_actions = []
self.list = QListWidget()
@@ -311,3 +381,4 @@ class JobsTab(QWidget):
j = self.jobs.get(self.current)
enable(self.act_kill, None if j and j["status"] == "running" else "select a running job")
enable(self.act_run, None if j and is_preview(j) else "select a finished preview")
+ enable(self.act_results, None if j and failed_items(j) else "select a build with failed packages")