feat(install): orchestrate install via single root-package LocalProvide call
This commit is contained in:
@@ -0,0 +1,152 @@
|
|||||||
|
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-216783/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-216783.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:12:13] "GET /recipes/hello/hello.recipe HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [08/Aug/2026 18:12:13] "GET /recipes/hello/package.lua HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [08/Aug/2026 18:12:13] "GET /recipes/hello/build.sh HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [08/Aug/2026 18:12:13] code 404, message File not found
|
||||||
|
127.0.0.1 - - [08/Aug/2026 18:12:13] "GET /recipes/hello/hello.recipe HTTP/1.1" 404 -
|
||||||
|
127.0.0.1 - - [08/Aug/2026 18:12:13] "GET /recipes/hello/hello.recipe HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [08/Aug/2026 18:12:13] code 404, message File not found
|
||||||
|
127.0.0.1 - - [08/Aug/2026 18:12:13] "GET /recipes/hello/package.lua HTTP/1.1" 404 -
|
||||||
|
127.0.0.1 - - [08/Aug/2026 18:12:13] code 404, message File not found
|
||||||
|
127.0.0.1 - - [08/Aug/2026 18:12:13] "GET /recipes/hello/build.sh HTTP/1.1" 404 -
|
||||||
|
127.0.0.1 - - [08/Aug/2026 18:12:13] "GET /recipes/hello/scripts/build.sh HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [08/Aug/2026 18:12:13] "GET /recipes/hello/hello.recipe HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [08/Aug/2026 18:12:13] code 404, message File not found
|
||||||
|
127.0.0.1 - - [08/Aug/2026 18:12:13] "GET /recipes/hello/package.lua HTTP/1.1" 404 -
|
||||||
|
127.0.0.1 - - [08/Aug/2026 18:12:13] code 404, message File not found
|
||||||
|
127.0.0.1 - - [08/Aug/2026 18:12:13] "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:50721//index.lua[0m
|
||||||
|
[32m ok index loaded: 3 packages[0m
|
||||||
|
[2m . fetching index http://127.0.0.1:47569//index.lua[0m
|
||||||
|
[2m . fetching index http://127.0.0.1:42797//index.lua[0m
|
||||||
|
[2m . fetching index http://127.0.0.1:42795//index.lua[0m
|
||||||
|
[2m . fetching index http://127.0.0.1:51365//index.lua[0m
|
||||||
|
[32m ok index loaded: 0 packages[0m
|
||||||
|
[2m . fetching index http://127.0.0.1:51389//index.lua[0m
|
||||||
|
[32m ok index loaded: 1 packages[0m
|
||||||
|
[2m . fetching index http://127.0.0.1:42841//index.lua[0m
|
||||||
|
[32m ok index loaded: 2 packages[0m
|
||||||
|
[2m . fetching index http://127.0.0.1:59485//index.lua[0m
|
||||||
|
[2m . fetching index http://127.0.0.1:35061//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 reinstall - already installed — skipping
|
||||||
|
[32m ok installed A with 2 dependencies[0m
|
||||||
|
- nothing to install
|
||||||
|
install failed: conflict detectedtestpkg-2.1 is already installed -- use -ReProvide to reinstall13 modules passed unittests
|
||||||
|
- already installed — skipping
|
||||||
|
[32m ok installed A with 0 dependencies[0m
|
||||||
|
- 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
|
||||||
@@ -684,3 +684,54 @@ The sandbox test (test 2) creates a sentinel file, serves an index containing `o
|
|||||||
- `dub test` passes — all 13 modules, including 6 new buildAll unittests.
|
- `dub test` passes — all 13 modules, including 6 new buildAll unittests.
|
||||||
- `dub build` passes with `warningsAsErrors`.
|
- `dub build` passes with `warningsAsErrors`.
|
||||||
- Evidence logged to `.omo/evidence/task-15-tofu-core.log`.
|
- Evidence logged to `.omo/evidence/task-15-tofu-core.log`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 17 — `tofu.install.installAll` (install orchestrator)
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
- `installAll(BuildPlan plan, Config cfg)` added to `tofu.install` — single root-package `-LocalProvide` call.
|
||||||
|
- Imports added: `tofu.types` (selective: `BuildPlan`, `BuildPlanEntry`, `Source`), `std.conv : to`, `std.file : exists`.
|
||||||
|
- Reuses `runLocalProvide` — no new process-spawning logic.
|
||||||
|
|
||||||
|
### Algorithm
|
||||||
|
1. **Empty plan** → `logInfo("nothing to install")`, return early.
|
||||||
|
2. **Verify built cache**: for each `Source.recipe` entry, check `cfg.builtPackagesDir()/name/package.lua` exists. Missing → `InstallException("built package missing from cache: <name> (was the build skipped?)")`.
|
||||||
|
3. **Root = last entry** in `plan.order()` — matches `deps.resolve`/`generateBuildPlan` convention (topological order, root last).
|
||||||
|
4. **Single call**: `runLocalProvide(rootName, cfg)` — ZETA's `deps.resolve` walks the full tree from `ZETA_LOCAL_PACKAGES` + `ZETA_REPO`.
|
||||||
|
5. **Success**: `logOk("installed <root> with N dependencies")` where N = `order.length - 1`.
|
||||||
|
6. **InstallException from `runLocalProvide`**: propagates to caller (no catch needed — install command records state).
|
||||||
|
|
||||||
|
### "already installed" handling
|
||||||
|
- Handled internally by `runLocalProvide` — exit 0 + "already installed" substring in output → `logInfo("already installed — skipping")`, no throw.
|
||||||
|
- `installAll` continues to `logOk` after.
|
||||||
|
|
||||||
|
### Dependencies counted
|
||||||
|
- All entries in `BuildPlan` are recipe-sourced (binary excluded by `generateBuildPlan`). Root is last entry. Dep count = `order.length - 1`.
|
||||||
|
|
||||||
|
### `@safe` / `@trusted` architecture
|
||||||
|
- `installAll` is `@safe` public.
|
||||||
|
- Only `std.file.exists` requires `@trusted` wrapper — inline lambda `() @trusted { pkgExists = exists(pkgPath); }()`.
|
||||||
|
|
||||||
|
### Imports strategy
|
||||||
|
- Top-level selective import: `import tofu.types : BuildPlan, BuildPlanEntry, Source;` — avoids pulling in the full types module.
|
||||||
|
- `import std.conv : to;` for `to!string(size_t)`.
|
||||||
|
- `import std.file : exists;` at module level for the built-cache verification check.
|
||||||
|
|
||||||
|
### Unittests — 5 new test blocks (test 1–5 for installAll)
|
||||||
|
- Reused existing test infrastructure: `makeTempDir`, `removeDir`, `writeFakeScript`, `testConfig`.
|
||||||
|
- Test (1): plan [B, C, A] with all package.lua + fake zeta captures `$2` → "A" only, no "B"/"C".
|
||||||
|
- Test (2): plan missing B's package.lua → `InstallException` "built package missing from cache: B".
|
||||||
|
- Test (3): empty plan → no-op, no throw.
|
||||||
|
- Test (4): fake zeta exits 1 → `InstallException` "install failed for A".
|
||||||
|
- Test (5): fake zeta prints "already installed" + exits 0 → no throw, `logOk` succeeds.
|
||||||
|
|
||||||
|
### Arg capture pattern
|
||||||
|
- Fake zeta script: `echo "$2" >> <path>` — `$2` = package name (args: `fake-zeta -LocalProvide <pkgName> --pass`).
|
||||||
|
- Previous learnings used `$3` incorrectly — in bash, `$0`=script name, `$1`=-LocalProvide, `$2`=pkgName, `$3`=--pass.
|
||||||
|
|
||||||
|
### Build verified
|
||||||
|
- `dub test` passes — all 13 modules, including 5 new installAll unittests (plus 5 pre-existing runLocalProvide tests = 10 total in install.d).
|
||||||
|
- `dub build` passes with `warningsAsErrors`.
|
||||||
|
- No D LSP server configured for `.d` — diagnostics verified via compiler.
|
||||||
|
- Evidence logged to `.omo/evidence/task-17-tofu-core.log`.
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ module tofu.install;
|
|||||||
|
|
||||||
import tofu.config;
|
import tofu.config;
|
||||||
import tofu.log;
|
import tofu.log;
|
||||||
|
import tofu.types : BuildPlan, BuildPlanEntry, Source;
|
||||||
|
|
||||||
// Selective imports to avoid `Config` name conflict between
|
// Selective imports to avoid `Config` name conflict between
|
||||||
// `std.process.Config` and `tofu.config.Config`.
|
// `std.process.Config` and `tofu.config.Config`.
|
||||||
@@ -21,6 +22,8 @@ import std.process : pipeProcess, ProcessPipes, Redirect, wait,
|
|||||||
ProcessException;
|
ProcessException;
|
||||||
import std.stdio : write, writeln, stdout;
|
import std.stdio : write, writeln, stdout;
|
||||||
import std.string : indexOf;
|
import std.string : indexOf;
|
||||||
|
import std.conv : to;
|
||||||
|
import std.file : exists;
|
||||||
|
|
||||||
// ─── Exception ───────────────────────────────────────────────────────────────
|
// ─── Exception ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -142,6 +145,77 @@ void runLocalProvide(string pkgName, Config cfg)
|
|||||||
"install failed for " ~ pkgName ~ ": " ~ lastLines);
|
"install failed for " ~ pkgName ~ ": " ~ lastLines);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Install orchestrator: verify all recipe-built packages exist in the
|
||||||
|
/// built cache, then invoke `zeta -LocalProvide` on the **root** package.
|
||||||
|
/// ZETA's `deps.resolve` walks the full dependency tree internally —
|
||||||
|
/// tofu passes only the root, not individual dependencies.
|
||||||
|
///
|
||||||
|
/// Root = last entry in plan.order() (topological order: deps first,
|
||||||
|
/// target last — same convention as `tofu.deps` and `tofu.resolve`).
|
||||||
|
///
|
||||||
|
/// Params:
|
||||||
|
/// plan = build plan from `generateBuildPlan` (only recipe entries).
|
||||||
|
/// cfg = loaded tofu configuration.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// InstallException if any recipe-built package is missing from the
|
||||||
|
/// cache, or if the zeta invocation fails.
|
||||||
|
///
|
||||||
|
/// Empty plan is a no-op (logInfo + return).
|
||||||
|
///
|
||||||
|
/// "already installed" is handled by `runLocalProvide` — exit 0 with
|
||||||
|
/// the expected message produces a logInfo note and no throw.
|
||||||
|
void installAll(BuildPlan plan, Config cfg)
|
||||||
|
{
|
||||||
|
// ── 1. Empty plan → no-op ────────────────────────────────────────────
|
||||||
|
|
||||||
|
if (plan.isEmpty())
|
||||||
|
{
|
||||||
|
logInfo("nothing to install");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto order = plan.order();
|
||||||
|
|
||||||
|
// ── 2. Verify all recipe-built packages exist in cache ───────────────
|
||||||
|
// ZETA's deps.resolve fetches package.lua from ZETA_LOCAL_PACKAGES.
|
||||||
|
// If a dependency is missing, deps.resolve fails with an unclear
|
||||||
|
// error — pre-checking gives a clear tofu-level message.
|
||||||
|
|
||||||
|
foreach (entry; order)
|
||||||
|
{
|
||||||
|
if (entry.source == Source.recipe)
|
||||||
|
{
|
||||||
|
auto pkgPath = cfg.builtPackagesDir() ~ "/" ~ entry.name
|
||||||
|
~ "/package.lua";
|
||||||
|
bool pkgExists;
|
||||||
|
() @trusted { pkgExists = exists(pkgPath); }();
|
||||||
|
if (!pkgExists)
|
||||||
|
{
|
||||||
|
throw new InstallException(
|
||||||
|
"built package missing from cache: " ~ entry.name
|
||||||
|
~ " (was the build skipped?)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 3. Determine root (last entry — topological order) ──────────────
|
||||||
|
|
||||||
|
string rootName = order[$ - 1].name;
|
||||||
|
|
||||||
|
// ── 4. Call runLocalProvide for the root ONLY ───────────────────────
|
||||||
|
// ZETA's deps.resolve handles the full tree: recipe-built deps via
|
||||||
|
// ZETA_LOCAL_PACKAGES, binary deps via ZETA_REPO. Tofu does NOT
|
||||||
|
// iterate per package. InstallException propagates to caller.
|
||||||
|
|
||||||
|
runLocalProvide(rootName, cfg);
|
||||||
|
|
||||||
|
// ── 5. Success ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
logOk("installed " ~ rootName ~ " with "
|
||||||
|
~ (order.length - 1).to!string ~ " dependencies");
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Unittests ───────────────────────────────────────────────────────────────
|
// ─── Unittests ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -348,3 +422,182 @@ SCRIPT");
|
|||||||
"expected ZETA_REPO=" ~ cfg.zuurUrl ~ "/binary"
|
"expected ZETA_REPO=" ~ cfg.zuurUrl ~ "/binary"
|
||||||
~ " in: " ~ captured);
|
~ " in: " ~ captured);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
// installAll unittests
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
// ── Test (1): plan [B, C, A(root)] with all package.lua
|
||||||
|
// → fake zeta invoked with ONLY root name "A" ──────────────────────────
|
||||||
|
|
||||||
|
@safe unittest
|
||||||
|
{
|
||||||
|
import std.file : mkdirRecurse, write, readText;
|
||||||
|
import std.string : strip, replace;
|
||||||
|
|
||||||
|
auto tmp = makeTempDir("installall-args");
|
||||||
|
scope (exit) removeDir(tmp);
|
||||||
|
|
||||||
|
// Create built package directories with package.lua files.
|
||||||
|
auto builtDir = tmp ~ "/built/packages";
|
||||||
|
foreach (name; ["A", "B", "C"])
|
||||||
|
{
|
||||||
|
auto pkgDir = builtDir ~ "/" ~ name;
|
||||||
|
() @trusted { mkdirRecurse(pkgDir); }();
|
||||||
|
() @trusted { write(pkgDir ~ "/package.lua", "-- fake package\n"); }();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fake zeta: capture package name arg ($2) to a file.
|
||||||
|
auto scriptPath = tmp ~ "/fake-zeta";
|
||||||
|
string scriptContent = replace(q"SCRIPT
|
||||||
|
#!/bin/bash
|
||||||
|
echo "$2" >> __ARGS_FILE__
|
||||||
|
exit 0
|
||||||
|
SCRIPT", "__ARGS_FILE__", tmp ~ "/args.txt");
|
||||||
|
writeFakeScript(scriptPath, scriptContent);
|
||||||
|
|
||||||
|
auto cfg = testConfig(scriptPath, tmp);
|
||||||
|
|
||||||
|
BuildPlan plan;
|
||||||
|
plan.add("B", "/fake/B.recipe", Source.recipe);
|
||||||
|
plan.add("C", "/fake/C.recipe", Source.recipe);
|
||||||
|
plan.add("A", "/fake/A.recipe", Source.recipe);
|
||||||
|
|
||||||
|
installAll(plan, cfg);
|
||||||
|
|
||||||
|
// Verify fake zeta received ONLY "A" as the package name.
|
||||||
|
string argsContent;
|
||||||
|
() @trusted { argsContent = readText(tmp ~ "/args.txt"); }();
|
||||||
|
auto capturedName = argsContent.strip;
|
||||||
|
assert(capturedName == "A",
|
||||||
|
"expected root name 'A', got '" ~ capturedName ~ "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Test (2): missing built package.lua → InstallException ──────────────
|
||||||
|
|
||||||
|
@safe unittest
|
||||||
|
{
|
||||||
|
import std.file : mkdirRecurse, write;
|
||||||
|
|
||||||
|
auto tmp = makeTempDir("installall-missing");
|
||||||
|
scope (exit) removeDir(tmp);
|
||||||
|
|
||||||
|
// Create package.lua for C and A only — B is missing.
|
||||||
|
auto builtDir = tmp ~ "/built/packages";
|
||||||
|
foreach (name; ["A", "C"])
|
||||||
|
{
|
||||||
|
auto pkgDir = builtDir ~ "/" ~ name;
|
||||||
|
() @trusted { mkdirRecurse(pkgDir); }();
|
||||||
|
() @trusted { write(pkgDir ~ "/package.lua", "-- fake package\n"); }();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto scriptPath = tmp ~ "/fake-zeta";
|
||||||
|
writeFakeScript(scriptPath, q"SCRIPT
|
||||||
|
#!/bin/bash
|
||||||
|
exit 0
|
||||||
|
SCRIPT");
|
||||||
|
|
||||||
|
auto cfg = testConfig(scriptPath, tmp);
|
||||||
|
|
||||||
|
BuildPlan plan;
|
||||||
|
plan.add("B", "/fake/B.recipe", Source.recipe);
|
||||||
|
plan.add("C", "/fake/C.recipe", Source.recipe);
|
||||||
|
plan.add("A", "/fake/A.recipe", Source.recipe);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
installAll(plan, cfg);
|
||||||
|
assert(false, "expected InstallException for missing built package");
|
||||||
|
}
|
||||||
|
catch (InstallException e)
|
||||||
|
{
|
||||||
|
assert(e.msg.indexOf("built package missing from cache: B") >= 0,
|
||||||
|
"expected 'built package missing from cache: B' in: " ~ e.msg);
|
||||||
|
assert(e.msg.indexOf("was the build skipped?") >= 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Test (3): empty plan → no-op (logInfo, return) ──────────────────────
|
||||||
|
|
||||||
|
@safe unittest
|
||||||
|
{
|
||||||
|
auto tmp = makeTempDir("installall-empty");
|
||||||
|
scope (exit) removeDir(tmp);
|
||||||
|
|
||||||
|
auto scriptPath = tmp ~ "/fake-zeta";
|
||||||
|
writeFakeScript(scriptPath, q"SCRIPT
|
||||||
|
#!/bin/bash
|
||||||
|
echo "SHOULD NOT BE INVOKED"
|
||||||
|
exit 1
|
||||||
|
SCRIPT");
|
||||||
|
|
||||||
|
auto cfg = testConfig(scriptPath, tmp);
|
||||||
|
BuildPlan plan; // empty
|
||||||
|
installAll(plan, cfg); // Should not throw and not invoke zeta.
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Test (4): fake zeta exits 1 → InstallException ──────────────────────
|
||||||
|
|
||||||
|
@safe unittest
|
||||||
|
{
|
||||||
|
import std.file : mkdirRecurse, write;
|
||||||
|
|
||||||
|
auto tmp = makeTempDir("installall-exit1");
|
||||||
|
scope (exit) removeDir(tmp);
|
||||||
|
|
||||||
|
auto pkgDir = tmp ~ "/built/packages/A";
|
||||||
|
() @trusted { mkdirRecurse(pkgDir); }();
|
||||||
|
() @trusted { write(pkgDir ~ "/package.lua", "-- fake package\n"); }();
|
||||||
|
|
||||||
|
auto scriptPath = tmp ~ "/fake-zeta";
|
||||||
|
writeFakeScript(scriptPath, q"SCRIPT
|
||||||
|
#!/bin/bash
|
||||||
|
echo "install failed: conflict detected"
|
||||||
|
exit 1
|
||||||
|
SCRIPT");
|
||||||
|
|
||||||
|
auto cfg = testConfig(scriptPath, tmp);
|
||||||
|
|
||||||
|
BuildPlan plan;
|
||||||
|
plan.add("A", "/fake/A.recipe", Source.recipe);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
installAll(plan, cfg);
|
||||||
|
assert(false, "expected InstallException");
|
||||||
|
}
|
||||||
|
catch (InstallException e)
|
||||||
|
{
|
||||||
|
assert(e.msg.indexOf("install failed for A") >= 0,
|
||||||
|
"expected 'install failed for A' in: " ~ e.msg);
|
||||||
|
assert(e.msg.indexOf("install failed: conflict detected") >= 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Test (5): "already installed" exit 0 → no throw ─────────────────────
|
||||||
|
|
||||||
|
@safe unittest
|
||||||
|
{
|
||||||
|
import std.file : mkdirRecurse, write;
|
||||||
|
|
||||||
|
auto tmp = makeTempDir("installall-already");
|
||||||
|
scope (exit) removeDir(tmp);
|
||||||
|
|
||||||
|
auto pkgDir = tmp ~ "/built/packages/A";
|
||||||
|
() @trusted { mkdirRecurse(pkgDir); }();
|
||||||
|
() @trusted { write(pkgDir ~ "/package.lua", "-- fake package\n"); }();
|
||||||
|
|
||||||
|
auto scriptPath = tmp ~ "/fake-zeta";
|
||||||
|
writeFakeScript(scriptPath, q"SCRIPT
|
||||||
|
#!/bin/bash
|
||||||
|
echo "testpkg-2.1 is already installed -- use -ReProvide to reinstall"
|
||||||
|
exit 0
|
||||||
|
SCRIPT");
|
||||||
|
|
||||||
|
auto cfg = testConfig(scriptPath, tmp);
|
||||||
|
|
||||||
|
BuildPlan plan;
|
||||||
|
plan.add("A", "/fake/A.recipe", Source.recipe);
|
||||||
|
|
||||||
|
installAll(plan, cfg); // Should not throw.
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user