diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-21 11:36:01 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-21 11:36:01 +0200 |
| commit | 766ce6ff1ea584dfe980a5e0b29251768c4c0726 (patch) | |
| tree | eaa047f46fc9b59acae745406a116d2e479239ca /sb_builder/jobs.py | |
| parent | fb93767351dc0d7d4d4da8068b2f94df6ef44b6d (diff) | |
| download | sb-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.py | 79 |
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") |
