feat(build): orchestrate sequential topological builds with fail-fast
Add buildAll() to src/tofu/build.d — sequential builder with: - Empty plan → 'nothing to build' no-op - Recipe existence pre-check → fail-fast on missing - Skip-if-exists: don't rebuild when output present (unless --force) - Separator line: U+2500 box chars '──── building <name> (i/n) ────' - Fail-fast: halt immediately on first BuildException 6 unittests: all succeed, first fails, empty plan, skip existing, force rebuild, missing recipe.
This commit is contained in:
@@ -641,3 +641,46 @@ The sandbox test (test 2) creates a sentinel file, serves an index containing `o
|
||||
- `dub build` passes with `warningsAsErrors`.
|
||||
- Pre-existing breakage in `build.d` (parallel task 15 artefact) excluded via `.skip` rename for testing — NOT caused by task 13.
|
||||
- Evidence logged to `.omo/evidence/task-13-tofu-core.log`.
|
||||
|
||||
---
|
||||
|
||||
## Task 15 — `tofu.build.buildAll` (sequential topological build orchestrator)
|
||||
|
||||
### Architecture
|
||||
- `buildAll(BuildPlan plan, Config cfg, bool force = false)` orchestrates sequential package builds with fail-fast semantics.
|
||||
- Added imports: `tofu.types : BuildPlan, BuildResult, BuildFailure, Source;`, `tofu.log : logStep, logOk, logError, logInfo;`.
|
||||
- No new exception classes — reuses existing `BuildException` from `runMakepkg`.
|
||||
|
||||
### Algorithm
|
||||
1. Empty plan → `logInfo("nothing to build")`, return empty `BuildResult`.
|
||||
2. Iterate `plan.order()` entries (caller guarantees deps-first topological order):
|
||||
a. **Pre-check**: `exists(entry.recipePath)` via `@trusted` wrapper. Missing → `BuildFailure` with "recipe not found at <path>", return immediately.
|
||||
b. **Separator**: `logStep("──── building %s (%d/%d) ────", name, idx, total)` — U+2500 box drawing chars.
|
||||
c. **Skip-if-exists**: If NOT force AND `cfg.builtDir()/packages/<name>/package.lua` exists → logInfo skip, add to succeeded, continue.
|
||||
d. **Execute**: `runMakepkg(entry.recipePath, cfg.builtDir(), cfg.defaultJobs, force, cfg)`.
|
||||
e. **Success**: logOk, add to succeeded.
|
||||
f. **BuildException**: logError, add to failed, return immediately (halt).
|
||||
3. Return populated `BuildResult`.
|
||||
|
||||
### `@safe` / `@trusted` architecture
|
||||
- `buildAll` is `@safe` public.
|
||||
- Only `std.file.exists` requires `@trusted` wrappers — same pattern as other modules.
|
||||
- `log*` functions are `@safe` — no `@trusted` needed for logging.
|
||||
|
||||
### Test strategy (6 new unittests, numbered 8–13)
|
||||
- Reused existing test helpers: `testTempDir`, `sWrite`, `sRmdirRecurse`, `makeFakeMakepkg`.
|
||||
- Test (8): plan [depA, depB, target] all valid → all 3 succeed in order, outputs created.
|
||||
- Test (9): fake always exits 1 → only depA fails, succeeded empty, depB/target skipped.
|
||||
- Test (10): empty plan → no-op, both lists empty.
|
||||
- Test (11): pre-existing package.lua + force=false → skipped, fake NOT invoked (args file absent).
|
||||
- Test (12): pre-existing package.lua + force=true → rebuilt, fake invoked (args file present).
|
||||
- Test (13): nonexistent recipe path → fail-fast with "recipe not found", remaining skipped.
|
||||
|
||||
### Skip-if-exists semantics
|
||||
- Path checked: `cfg.builtDir() ~ "/packages/" ~ name ~ "/package.lua"` — matches what `runMakepkg` produces (verified at line 190 in `runMakepkg`).
|
||||
- `force=true` bypasses skip — always invokes `runMakepkg`.
|
||||
|
||||
### Build verified
|
||||
- `dub test` passes — all 13 modules, including 6 new buildAll unittests.
|
||||
- `dub build` passes with `warningsAsErrors`.
|
||||
- Evidence logged to `.omo/evidence/task-15-tofu-core.log`.
|
||||
|
||||
Reference in New Issue
Block a user