1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
|
# Docker test-build script - design
Date: 2026-07-13
## Purpose
A single bash script that verifies an already-published SBo package still
builds cleanly on a target Slackware version, inside a throwaway docker
container. This is the "test-build" step of this repo's maintenance loop
(track upstream -> bump -> **test-build** -> make tarball).
The container is disposable, so unlike a real `slackrepo build` this runs
directly (no host install), and Claude may run it (per the repo's
`Who builds SlackBuilds` exception).
## Scope and non-goals
- One target package per run: `test-build <pkg>`.
- Two Slackware targets: `-current` (default) and `15.0` (`--stable`).
- Resolves the target's SBo dependency tree from the LOCAL SBo tree, builds
deps then target in the container, reports, discards everything.
- NOT a redistributable-package builder. The container's built `.txz` is
throwaway; the SBo submission tarball is made separately (post-commit hook)
only after a green run.
- Does NOT wrap slackrepo or sbo-batch-test. It REUSES sbo-batch-test's
resolver + build logic, adapted, but is an independent script.
- Shares the host kernel (docker). No kernel-module build claims.
## Why not the overlay approach (sbo-batch-test)
`sbo-batch-test` already resolves deps + builds + reports, but against a
LOCAL overlayfs over a 15.0 base. In docker the container IS the disposable
base, and overlayfs-over-docker-storage (itself overlayfs) is fragile. So we
drop the overlay layer and build directly in the container. We port
sbo-batch-test's dep resolver and its `build_one` chroot heredoc
(download -> md5 -> build -> installpkg -> status token) into a `docker run`.
## Architecture
Single self-contained bash script (`test-build`, location TBD in
implementation - likely `.extras/`). Host does resolution + orchestration;
the container does the actual building.
```
host: parse args, load config
host: pick version -> image tag + SBo tree
host: resolve dep tree (local tree, topo sort) [ported from sbo-batch-test]
host: apply current-vs-stable overrides
host: print final build order, Y/n confirm (--yes: still prints first)
docker run --rm -v <tree>:ro -v <pkg>:ro -v <cache/digest>:ro <image>:
for each dep in order:
cache hit (version match) -> installpkg from cache (CACHED)
else -> download/md5/build/installpkg + cache
target: download/md5/build/installpkg (always fresh)
target: sbopkglint on the built .txz
host: collect per-package results, print color summary
container discarded (--rm)
```
### Images (external input, not built by this script)
The script does NOT build images. It consumes a ready image per version,
tagged locally:
- current: `sbo-testbuild:current`
- 15.0: `sbo-testbuild:15.0`
Each image is a FULL, patched Slackware install (SBo tests against a full
install; a minimal base causes false "missing dependency" results, a lesson
from sbo-batch-tester) plus `sbo-maintainer-tools` (for `sbopkglint`) and
`sbopkg` configured to that version's SBo repo (`:current` -> -current repo,
`:15.0` -> 15.0 repo).
**How the images are produced is out of scope for this script.** A separate
nightly job (a LAN machine, images stored on the NAS, later served from a
local registry) builds and refreshes them. That builder is its own tool,
specced separately.
For now the script assumes the tagged image is present locally: it runs
`docker run` against the tag and errors with a clear message if the image is
missing (pointing at the image-builder job). When the local registry exists,
a `docker pull` of the tag slots in ahead of the run via the same config,
no other change.
Per-run cost is just the SlackBuild's own build time + ~1s container start.
### Config (external file)
`~/.config/sbo-testbuild/config`, empty defaults in-script, sourced if
present (same pattern as sbo-batch-test). Keys:
```sh
SBO_TREE_CURRENT=/path/to/SBo-current # local SBo tree, -current
SBO_TREE_STABLE=/path/to/SBo-15.0 # local SBo tree, 15.0
IMAGE_CURRENT=sbo-testbuild:current # ready image tag (built elsewhere)
IMAGE_STABLE=sbo-testbuild:15.0
LOG_ROOT=/path/to/logs
PKG_CACHE=/path/to/cache # dep cache; empty = disabled
```
The version flag selects BOTH the image and the tree together (default
current; `--stable` / `15.0` switches both), so tree and image never mismatch.
### current-vs-stable overrides
A data file next to the config, `~/.config/sbo-testbuild/overrides`. It
encodes the known deltas between building on 15.0 and on -current. Applied
ONLY when the target version is `-current` (15.0 is the SBo baseline). Three
rule kinds:
```
# dep present in 15.0 but already in -current base -> drop from order
drop: rust
# dep renamed on -current -> map old -> new before lookup
rename: python3-foo -> foo
# dep removed from the -current tree -> fetch from SBo instead of local tree
fetch: somelib
```
Format: one rule per line, `kind: value` (rename uses `old -> new`), `#`
comments and blank lines ignored.
### Dependency resolution + unknown deps
Resolver is ported from sbo-batch-test (`resolve_target` / `_resolve_visit`,
DFS topo sort + cycle detection, `installed_in_base` check). Runs on the host
against the selected tree.
After resolving and applying overrides, every dep must be accounted for:
in base, in the tree, or covered by a `fetch:` rule. If a dep is none of
those (and no rule covers it), the script **stops before building**, reports
it as UNMET, and asks the user to add an override rule and rerun, or abort.
Never silently skip or guess.
### Confirmation
Always print the final build order before building, with overrides marked
(dropped / renamed / fetch). Then `Y/n` to proceed. `--yes` skips the prompt
for non-interactive/agent runs but STILL prints the order first.
`--dry-run` resolves, applies overrides, prints the order, and exits without
building (no confirm).
### Build in container (ported from build_one)
Deps then target, in order. For each package, inside the container:
1. download sources (arch-specific `DOWNLOAD_x86_64`/`MD5SUM_x86_64` when
present, else `DOWNLOAD`/`MD5SUM`)
2. verify md5
3. `bash <pkg>.SlackBuild` with `OUTPUT` forced to a known dir
4. `installpkg` the resulting `.txz`
5. write a status token read back by the host
`fetch:` deps (removed from the -current tree) are pulled via the baked-in
`sbopkg`, which points at the image's matching SBo repo; everything else
builds from the mounted local tree. Start light: handle only the few known
`fetch:` cases, add rules as real packages need them.
### Dependency cache
Ported from sbo-batch-tester (`cache_decision`/`cache_path`/`cache_store`/
`version_of`). A host dir caches built dep packages so consecutive runs reuse
them instead of rebuilding the whole tree.
- Config `PKG_CACHE=/path` enables it; empty (default) disables. `--no-cache`
forces a full rebuild for one run.
- Bind-mounted read-only into the container; a cached dep is `installpkg`ed
instead of built (status `CACHED`). Storing is host-side: a freshly built
package is written to `OUTPUT` (a mounted dir the host reads back), and the
host copies it into the cache after the build (`cache_store`), so the cache
mount stays read-only. The **target always builds fresh** and refreshes its
own cache entry; it is never `CACHED`.
- Key = prog + version (build/arch/tag ignored). Layout mirrors the SBo tree:
`$PKG_CACHE/<image-digest>/<category>/<prog>/<prog>-<ver>-...txz`, one .txz
per prog.
- **Invalidation by image digest.** The cache is namespaced by the running
image's digest. When the digest changes (image updated by the nightly job),
the old namespace is stale: its deps were built against the previous image,
so they are ignored and rebuilt once under the new digest. This also keeps
the `:current` and `:15.0` caches separate (different images, different
digests). Old digest namespaces can be pruned.
- A per-dep version bump (`bump: OLD -> NEW`) rebuilds and re-caches that dep.
- Build-order line shows the outcome per dep: `cached (1.1)`,
`rebuild: 1.0 -> 1.1`, or `build (new)`; `--dry-run` shows the same without
building.
### Target lint
After the target builds, run `sbopkglint` on its `.txz` (tool baked into the
image). Fail-soft: findings reported but do not change SUCCESS; missing tool
prints a skip note. Target only (deps are vetted).
### Report
Per-package status + a color summary (screen) and a plain-text log under
`LOG_ROOT/<timestamp>/`:
```
<timestamp>/
<prog>.log per-package full build/install output
summary.log plain recap
build-order.txt the resolved+overridden order actually used
```
Status values (from sbo-batch-test):
`SUCCESS CACHED DOWNLOAD-FAILED MD5-MISMATCH BUILD-FAILED INSTALL-FAILED
BLOCKED-BY-DEP UNMET-DEP`. `CACHED` = a dep installed from the cache instead
of rebuilt (target is never CACHED). `%README%` deps flagged as a reminder,
not built.
A fully green run tells the user it is safe to make the SBo submission tarball
on the host (the container is no longer needed).
## Options (planned)
| Option | Effect |
|--------|--------|
| `--stable` / `15.0` | Target 15.0 (image + tree). Default is -current. |
| `--dry-run` | Resolve + apply overrides + print order, no build. |
| `--yes` | Skip the Y/n confirm (still prints the order). |
| `--no-cache` | Rebuild all deps this run, ignore/refresh the cache. |
| `--no-color` | Disable ANSI (auto-off when not a TTY). |
| `-h`, `--help` | Usage. |
## Self-check
A `test-logic.sh` (host, no docker) covering the pure logic:
- dep topo order + cycle detection (ported tests from sbo-batch-test)
- override application: drop removes, rename maps, fetch marks source
- unknown-dep -> UNMET stop
- BLOCKED-BY-DEP propagation
- cache decision: `cached`/`rebuild: OLD -> NEW`/`build (new)`, digest
namespacing, version-bump eviction (ported from sbo-batch-tester's tests)
Docker/build/installpkg paths are out of the self-check's reach (verified by
running a real target), same boundary as sbo-batch-test.
## Resolved decisions
- **Image**: a ready FULL-install image per version (full pkg set +
`sbo-maintainer-tools` + `sbopkg` on that version's repo), built by a
separate nightly job, NOT by this script. Script consumes it by tag.
- **Missing image**: error clearly, point at the image-builder job. A local
registry `docker pull` slots in later via the same tag, no script change.
- **`fetch:`**: via the image's `sbopkg` (per-version repo). Start with only
the few known cases; grow the override rules as needed.
- **Script location**: `.extras/` (a tool, not a package).
## Out of scope / later
- The nightly image-builder (full-install + tools + sbopkg per version, NAS
storage, local registry). Its own tool, specced when the LAN machine is set
up.
- Local registry pull step in this script (slots into the same config tag).
```
|