feat(resolve): generate build plan from constrained dep tree
Add generateBuildPlan() to tofu.resolve — filters constrained dep tree to recipe-only packages, verifies cached recipes exist, and produces an ordered BuildPlan ready for the build orchestrator (task 15). - Skips binary-satisfied deps (Zeta handles those) - Preserves topological order from constrained nodes (deps-first) - Missing recipe files: re-fetch via injectable delegate seam (production wires tofu.fetch.fetchRecipe; tests inject mocks) - Empty plan (all binary) → returns empty BuildPlan + log info - Root always recipe — always included in plan - 6 unittests: filter binary, fetch seam, missing no-seam, all-binary empty, root with binary dep, cache hit no-fetch - dub test + dub build pass with warningsAsErrors
This commit is contained in:
@@ -516,3 +516,57 @@ The sandbox test (test 2) creates a sentinel file, serves an index containing `o
|
||||
- `dub test` passes — all 11 modules (config, log, types, vercmp, http, index, fetch, cache, binary, deps, resolve) pass unittests.
|
||||
- No D LSP server configured for `.d` files — diagnostics verified via compiler.
|
||||
- Evidence logged to `.omo/evidence/task-12-tofu-core.log`.
|
||||
|
||||
---
|
||||
|
||||
## Task 13 — `tofu.resolve.generateBuildPlan` (build plan from constrained tree)
|
||||
|
||||
### Architecture
|
||||
- New function `generateBuildPlan(constrained, tree, cfg, fetchRecipe = null)` in `tofu.resolve`.
|
||||
- Input: `ConstrainedNode[]` from `constrainDepTree` (already in topological order, deps-first, root last). The `DepTree` parameter is preserved for future context but not used for logic — ordering comes from the constrained array.
|
||||
- Output: `BuildPlan` with `Source.recipe` entries for every recipe-sourced node (binary nodes excluded).
|
||||
- The `fetchRecipe` delegate (`scope string delegate(string) @safe`) is the testability seam. Default `null` means "no fetcher available" — missing recipes throw `FetchException`.
|
||||
- Production wires: `delegate (string name) @safe { return tofu.fetch.fetchRecipe(name, cfg); }`.
|
||||
|
||||
### Algorithm
|
||||
1. Iterate constrained nodes in order (preserves topological ordering).
|
||||
2. Skip nodes with `source == DepSource.binary` — Zeta handles binary deps.
|
||||
3. For recipe nodes: compute cache path via `cfg.recipesCacheDir(name) ~ "/" ~ name ~ ".recipe"`.
|
||||
4. Check `std.file.exists(path)` via `@trusted` wrapper.
|
||||
5. Missing → if `fetchRecipe` delegate provided, call it; else throw `FetchException("recipe not cached and no fetcher provided")`.
|
||||
6. Add to `BuildPlan` via `plan.add(name, recipePath, Source.recipe)`.
|
||||
7. Empty plan → log `"nothing to build (all binary)"`.
|
||||
8. Non-empty → log `"build plan: N packages"` via `logOk`.
|
||||
|
||||
### `@safe` / `@trusted` architecture
|
||||
- `generateBuildPlan` is `@safe`.
|
||||
- Only `std.file.exists` requires `@trusted` wrapper — inline lambda `() @trusted { return exists(path); }()`.
|
||||
- Follows existing patterns from `fetch.d`, `cache.d`, `index.d`.
|
||||
|
||||
### FetchException reuse
|
||||
- Uses `tofu.fetch.FetchException` (imported via `import tofu.fetch;`). No new exception class needed — the message `"recipe not cached and no fetcher provided"` is distinct and searchable.
|
||||
|
||||
### New imports in resolve.d
|
||||
- `import tofu.config;` — for `Config` type (parameter in function signature).
|
||||
- `import tofu.fetch;` — for `FetchException`.
|
||||
- `import std.file : exists;` — for recipe cache existence check.
|
||||
- `import tofu.log;` already had `logInfo`; now also uses `logStep`, `logOk`.
|
||||
|
||||
### Test strategy (6 new unittests, numbered 9–14)
|
||||
- Reused the temp-dir + `scope(exit)` cleanup pattern from other modules.
|
||||
- `version(unittest)` block with `@trusted` helpers: `createCachedRecipe(dir, name)`, `testTempDir(suffix)`, `testRmdir(path)`, `testConfig(cacheDir)`.
|
||||
- Test (9): [C(recipe), B(binary), A(recipe)] → plan [C, A] — B excluded, order preserved.
|
||||
- Test (10): missing cache + fetch delegate → fetch called, returned path used in plan.
|
||||
- Test (11): missing cache + no fetch delegate → FetchException thrown.
|
||||
- Test (12): all binary → empty plan.
|
||||
- Test (13): root included even when dep is binary.
|
||||
- Test (14): recipe files exist in cache → fetch delegate NOT called (delegate throws assert on invocation).
|
||||
|
||||
### `scope` on constrained parameter
|
||||
- `scope const ConstrainedNode[] constrained` — DMD 2.112 requires `scope` on array/class reference parameters for `@safe` inference when the function does not escape them. Same pattern as `constrainDepTree` (which uses `scope const PackageIndex[] index`).
|
||||
|
||||
### Build verified
|
||||
- `dub test` passes — all 11 modules, including 6 new generateBuildPlan unittests.
|
||||
- `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`.
|
||||
|
||||
Reference in New Issue
Block a user