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:
@@ -0,0 +1,148 @@
|
||||
Warning
|
||||
Warning ## Warning for package tofu ##
|
||||
Warning
|
||||
Warning The following compiler flags have been specified in the package description
|
||||
Warning file. They are handled by DUB and direct use in packages is discouraged.
|
||||
Warning Alternatively, you can set the DFLAGS environment variable to pass custom flags
|
||||
Warning to the compiler, or use one of the suggestions below:
|
||||
Warning
|
||||
Warning warningsAsErrors: Use "buildRequirements" to control the warning level
|
||||
Warning
|
||||
Generating test runner configuration 'tofu-test-application' for 'application' (executable).
|
||||
Warning Excluding main source file src/main.d from test.
|
||||
Starting Performing "unittest" build using /usr/bin/dmd for x86_64.
|
||||
Up-to-date toml 1.0.0: target for configuration [library] is up to date.
|
||||
Building tofu ~main: building configuration [tofu-test-application]
|
||||
Linking tofu-test-application
|
||||
Finished To force a rebuild of up-to-date targets, run again with --force
|
||||
Running tofu-test-application
|
||||
zeta-makepkg: building hello...zeta-makepkg: configure phasezeta-makepkg: build phasezeta-makepkg: install phasezeta-makepkg: building hello...zeta-makepkg: configure phasezeta-makepkg: build phasezeta-makepkg: install phasezeta-makepkg: building hello...zeta-makepkg: configure phasezeta-makepkg: build phasezeta-makepkg: install phasezeta-makepkg: building hello...zeta-makepkg: configure phasezeta-makepkg: build phasezeta-makepkg: install phasedonezeta-makepkg: building depA...zeta-makepkg: configure phasezeta-makepkg: build phasezeta-makepkg: install phasezeta-makepkg: building depB...zeta-makepkg: configure phasezeta-makepkg: build phasezeta-makepkg: install phasezeta-makepkg: building target...zeta-makepkg: configure phasezeta-makepkg: build phasezeta-makepkg: install phasezeta-makepkg: building depA...zeta-makepkg: configure phasezeta-makepkg: build phasezeta-makepkg: install phase[31merror[0m build failed for depA: build failed for depA:
|
||||
zeta-makepkg: building depA...
|
||||
zeta-makepkg: configure phase
|
||||
zeta-makepkg: build phase
|
||||
zeta-makepkg: install phase
|
||||
zeta-makepkg: building pkg...zeta-makepkg: configure phasezeta-makepkg: build phasezeta-makepkg: install phase[31merror[0m build failed for noexist: recipe not found at /tmp/tofu-test-build-buildall-norecipe-214411/nonexistent.recipe
|
||||
[33mwarn corrupted cache for pkg: Found 'h' when expecting 'r'. (Line 1:2)[0m
|
||||
Warning: malformed TOML config at /tmp/tofu-test-config-bad-214411.toml: Invalid table key declaration (2:0)
|
||||
Warning: invalid TOFU_DEFAULT_JOBS 'not-a-number', using default 1
|
||||
127.0.0.1 - - [08/Aug/2026 18:08:36] "GET /recipes/hello/hello.recipe HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [08/Aug/2026 18:08:36] "GET /recipes/hello/package.lua HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [08/Aug/2026 18:08:36] "GET /recipes/hello/build.sh HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [08/Aug/2026 18:08:36] code 404, message File not found
|
||||
127.0.0.1 - - [08/Aug/2026 18:08:36] "GET /recipes/hello/hello.recipe HTTP/1.1" 404 -
|
||||
127.0.0.1 - - [08/Aug/2026 18:08:36] "GET /recipes/hello/hello.recipe HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [08/Aug/2026 18:08:36] code 404, message File not found
|
||||
127.0.0.1 - - [08/Aug/2026 18:08:36] "GET /recipes/hello/package.lua HTTP/1.1" 404 -
|
||||
127.0.0.1 - - [08/Aug/2026 18:08:36] code 404, message File not found
|
||||
127.0.0.1 - - [08/Aug/2026 18:08:36] "GET /recipes/hello/build.sh HTTP/1.1" 404 -
|
||||
127.0.0.1 - - [08/Aug/2026 18:08:36] "GET /recipes/hello/scripts/build.sh HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [08/Aug/2026 18:08:36] "GET /recipes/hello/hello.recipe HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [08/Aug/2026 18:08:36] code 404, message File not found
|
||||
127.0.0.1 - - [08/Aug/2026 18:08:36] "GET /recipes/hello/package.lua HTTP/1.1" 404 -
|
||||
127.0.0.1 - - [08/Aug/2026 18:08:36] code 404, message File not found
|
||||
127.0.0.1 - - [08/Aug/2026 18:08:36] "GET /recipes/hello/build.sh HTTP/1.1" 404 -
|
||||
[33mwarn skipping index entry with empty name[0m
|
||||
[33mwarn skipping index entry with empty name[0m
|
||||
[33mwarn skipping index entry 'badpool': invalid pool 'bad_pool_value'[0m
|
||||
[2m . checking binary firefox ...[0m
|
||||
- binary firefox-2.1.0 satisfies firefox>=2.0
|
||||
[2m . checking binary libfoo ...[0m
|
||||
- binary libfoo-1.9 does not satisfy libfoo>=2.0
|
||||
[2m . checking binary noexist ...[0m
|
||||
[2m . checking binary brokenpkg ...[0m
|
||||
[2m . checking binary badpkg ...[0m
|
||||
[2m . checking binary testpkg ...[0m
|
||||
- binary testpkg-2.1.0 satisfies testpkg
|
||||
[36m==> ──── building depA (1/3) ────[0m
|
||||
[32m ok depA[0m
|
||||
[36m==> ──── building depB (2/3) ────[0m
|
||||
[32m ok depB[0m
|
||||
[36m==> ──── building target (3/3) ────[0m
|
||||
[32m ok target[0m
|
||||
[36m==> ──── building depA (1/3) ────[0m
|
||||
- nothing to build
|
||||
[36m==> ──── building pkg (1/1) ────[0m
|
||||
- pkg already built, skipping
|
||||
[36m==> ──── building pkg (1/1) ────[0m
|
||||
[32m ok pkg[0m
|
||||
[36m==> fetching recipe hello[0m
|
||||
[2m . downloading hello.recipe[0m
|
||||
[2m . downloading package.lua[0m
|
||||
[2m . downloading build.sh[0m
|
||||
[32m ok recipe hello downloaded[0m
|
||||
[36m==> fetching recipe hello[0m
|
||||
[2m . downloading hello.recipe[0m
|
||||
[36m==> fetching recipe hello[0m
|
||||
[2m . downloading hello.recipe[0m
|
||||
[2m . downloading package.lua[0m
|
||||
- no package.lua for hello (optional manifest)
|
||||
[2m . downloading build.sh[0m
|
||||
- no build.sh for hello (optional build script)
|
||||
[2m . custom build system: fetching scripts/build.sh[0m
|
||||
[32m ok recipe hello downloaded[0m
|
||||
[36m==> fetching recipe hello[0m
|
||||
[2m . downloading hello.recipe[0m
|
||||
[2m . downloading package.lua[0m
|
||||
- no package.lua for hello (optional manifest)
|
||||
[2m . downloading build.sh[0m
|
||||
- no build.sh for hello (optional build script)
|
||||
[32m ok recipe hello downloaded[0m
|
||||
[36m==> fetching recipe hello[0m
|
||||
[2m . downloading hello.recipe[0m
|
||||
[2m . fetching index http://127.0.0.1:49413//index.lua[0m
|
||||
[32m ok index loaded: 3 packages[0m
|
||||
[2m . fetching index http://127.0.0.1:38411//index.lua[0m
|
||||
[2m . fetching index http://127.0.0.1:43727//index.lua[0m
|
||||
[2m . fetching index http://127.0.0.1:33325//index.lua[0m
|
||||
[2m . fetching index http://127.0.0.1:45567//index.lua[0m
|
||||
[32m ok index loaded: 0 packages[0m
|
||||
[2m . fetching index http://127.0.0.1:43115//index.lua[0m
|
||||
[32m ok index loaded: 1 packages[0m
|
||||
[2m . fetching index http://127.0.0.1:36939//index.lua[0m
|
||||
[32m ok index loaded: 2 packages[0m
|
||||
[2m . fetching index http://127.0.0.1:47607//index.lua[0m
|
||||
[2m . fetching index http://127.0.0.1:48661//index.lua[0m
|
||||
[32m ok index loaded: 1 packages[0m
|
||||
installing package...error: build failedreason: missing dependencytestpkg-2.1 is already installed -- use -ReProvide to reinstall13 modules passed unittests
|
||||
- already installed — skipping
|
||||
- binary libfoo-2.1 satisfies libfoo>=2.0
|
||||
- libfoo: binary 1.9 too old, building from recipe
|
||||
- libfoo: binary 1.9 too old, building from recipe
|
||||
- binary libbar-3.0 satisfies libbar (unconstrained)
|
||||
- libbaz: no binary available, building from recipe
|
||||
- binary libfoo-1.5 satisfies libfoo>=1.0
|
||||
- libfoo: binary 1.5 too old, building from recipe
|
||||
- binary parentA-1.5 satisfies parentA (unconstrained)
|
||||
- binary parentB-1.5 satisfies parentB (unconstrained)
|
||||
[36m==> generating build plan[0m
|
||||
- + C (recipe)
|
||||
- + A (recipe)
|
||||
[32m ok build plan: 2 packages[0m
|
||||
[36m==> generating build plan[0m
|
||||
- recipe libfoo not in cache, re-fetching
|
||||
- + libfoo (recipe)
|
||||
[32m ok build plan: 1 packages[0m
|
||||
[36m==> generating build plan[0m
|
||||
[36m==> generating build plan[0m
|
||||
- nothing to build (all binary)
|
||||
[36m==> generating build plan[0m
|
||||
- + mypkg (recipe)
|
||||
[32m ok build plan: 1 packages[0m
|
||||
[36m==> generating build plan[0m
|
||||
- + libfoo (recipe)
|
||||
- + mypkg (recipe)
|
||||
[32m ok build plan: 2 packages[0m
|
||||
Warning
|
||||
Warning ## Warning for package tofu ##
|
||||
Warning
|
||||
Warning The following compiler flags have been specified in the package description
|
||||
Warning file. They are handled by DUB and direct use in packages is discouraged.
|
||||
Warning Alternatively, you can set the DFLAGS environment variable to pass custom flags
|
||||
Warning to the compiler, or use one of the suggestions below:
|
||||
Warning
|
||||
Warning warningsAsErrors: Use "buildRequirements" to control the warning level
|
||||
Warning
|
||||
Starting Performing "debug" build using /usr/bin/dmd for x86_64.
|
||||
Up-to-date toml 1.0.0: target for configuration [library] is up to date.
|
||||
Up-to-date tofu ~main: target for configuration [application] is up to date.
|
||||
Finished To force a rebuild of up-to-date targets, run again with --force
|
||||
@@ -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