fix(errors): add actionable error messages for all 16 failure paths

This commit is contained in:
2026-08-08 18:21:17 -04:00
parent 47cfdee424
commit 854e7155f9
7 changed files with 435 additions and 9 deletions
+154
View File
@@ -0,0 +1,154 @@
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.
Up-to-date tofu ~main: target for configuration [tofu-test-application] is up to date.
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 phaseerror 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 phaseerror build failed for noexist: recipe not found at /tmp/tofu-test-build-buildall-norecipe-224364/nonexistent.recipe
warn corrupted cache for pkg: Found 'h' when expecting 'r'. (Line 1:2)
Warning: malformed TOML config at /tmp/tofu-test-config-bad-224364.toml: Invalid table key declaration (2:0)
Warning: invalid TOFU_DEFAULT_JOBS 'not-a-number', using default 1
warn removing stale lock (PID 99999999 not alive)
127.0.0.1 - - [08/Aug/2026 18:19:27] "GET /recipes/hello/hello.recipe HTTP/1.1" 200 -
127.0.0.1 - - [08/Aug/2026 18:19:27] "GET /recipes/hello/package.lua HTTP/1.1" 200 -
127.0.0.1 - - [08/Aug/2026 18:19:27] "GET /recipes/hello/build.sh HTTP/1.1" 200 -
127.0.0.1 - - [08/Aug/2026 18:19:27] code 404, message File not found
127.0.0.1 - - [08/Aug/2026 18:19:27] "GET /recipes/hello/hello.recipe HTTP/1.1" 404 -
127.0.0.1 - - [08/Aug/2026 18:19:27] "GET /recipes/hello/hello.recipe HTTP/1.1" 200 -
127.0.0.1 - - [08/Aug/2026 18:19:27] code 404, message File not found
127.0.0.1 - - [08/Aug/2026 18:19:27] "GET /recipes/hello/package.lua HTTP/1.1" 404 -
127.0.0.1 - - [08/Aug/2026 18:19:27] code 404, message File not found
127.0.0.1 - - [08/Aug/2026 18:19:27] "GET /recipes/hello/build.sh HTTP/1.1" 404 -
127.0.0.1 - - [08/Aug/2026 18:19:27] "GET /recipes/hello/scripts/build.sh HTTP/1.1" 200 -
127.0.0.1 - - [08/Aug/2026 18:19:27] "GET /recipes/hello/hello.recipe HTTP/1.1" 200 -
127.0.0.1 - - [08/Aug/2026 18:19:27] code 404, message File not found
127.0.0.1 - - [08/Aug/2026 18:19:27] "GET /recipes/hello/package.lua HTTP/1.1" 404 -
127.0.0.1 - - [08/Aug/2026 18:19:27] code 404, message File not found
127.0.0.1 - - [08/Aug/2026 18:19:27] "GET /recipes/hello/build.sh HTTP/1.1" 404 -
warn skipping index entry with empty name
warn skipping index entry with empty name
warn skipping index entry 'badpool': invalid pool 'bad_pool_value'
 . checking binary firefox ...
- binary firefox-2.1.0 satisfies firefox>=2.0
 . checking binary libfoo ...
- binary libfoo-1.9 does not satisfy libfoo>=2.0
 . checking binary noexist ...
 . checking binary brokenpkg ...
 . checking binary badpkg ...
 . checking binary testpkg ...
- binary testpkg-2.1.0 satisfies testpkg
==> ──── building depA (1/3) ────
 ok depA
==> ──── building depB (2/3) ────
 ok depB
==> ──── building target (3/3) ────
 ok target
==> ──── building depA (1/3) ────
- nothing to build
==> ──── building pkg (1/1) ────
- pkg already built, skipping
==> ──── building pkg (1/1) ────
 ok pkg
==> fetching recipe hello
 . downloading hello.recipe
 . downloading package.lua
 . downloading build.sh
 ok recipe hello downloaded
==> fetching recipe hello
 . downloading hello.recipe
==> fetching recipe hello
 . downloading hello.recipe
 . downloading package.lua
- no package.lua for hello (optional manifest)
 . downloading build.sh
- no build.sh for hello (optional build script)
 . custom build system: fetching scripts/build.sh
 ok recipe hello downloaded
==> fetching recipe hello
 . downloading hello.recipe
 . downloading package.lua
- no package.lua for hello (optional manifest)
 . downloading build.sh
- no build.sh for hello (optional build script)
 ok recipe hello downloaded
==> fetching recipe hello
 . downloading hello.recipe
 . fetching index http://127.0.0.1:57805//index.lua
 ok index loaded: 3 packages
 . fetching index http://127.0.0.1:34467//index.lua
 . fetching index http://127.0.0.1:45521//index.lua
 . fetching index http://127.0.0.1:42301//index.lua
 . fetching index http://127.0.0.1:55651//index.lua
 ok index loaded: 0 packages
 . fetching index http://127.0.0.1:42907//index.lua
 ok index loaded: 1 packages
 . fetching index http://127.0.0.1:48255//index.lua
 ok index loaded: 2 packages
 . fetching index http://127.0.0.1:52369//index.lua
 . fetching index http://127.0.0.1:53325//index.lua
 ok index loaded: 1 packages
installing package...error: build failedreason: missing dependencytestpkg-2.1 is already installed -- use -ReProvide to reinstall - already installed — skipping
 ok installed A with 2 dependencies
- nothing to install
install failed: conflict detectedtestpkg-2.1 is already installed -- use -ReProvide to reinstallwarn corrupted installed state at /tmp/tofu-test-state-corrupt-224364/installed.json: Found 'h' when expecting 'r'. (Line 1:2)
warn corrupted installed state at /tmp/tofu-test-state-corrupt-224364/installed.json: Found 'h' when expecting 'r'. (Line 1:2)
16 modules passed unittests
- already installed — skipping
 ok installed A with 0 dependencies
- 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)
==> generating build plan
- + C (recipe)
- + A (recipe)
 ok build plan: 2 packages
==> generating build plan
- recipe libfoo not in cache, re-fetching
- + libfoo (recipe)
 ok build plan: 1 packages
==> generating build plan
==> generating build plan
- nothing to build (all binary)
==> generating build plan
- + mypkg (recipe)
 ok build plan: 1 packages
==> generating build plan
- + libfoo (recipe)
- + mypkg (recipe)
 ok build plan: 2 packages
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
+33
View File
@@ -25,3 +25,36 @@ Rather than pulling in `std.regex`, the parser uses hand-written `isNameChar()`
D non-capturing lambdas infer as `function` pointers, which cannot implicitly convert to `delegate`-typed parameters. The explicit `delegate` keyword (`scope f = delegate (...) { ... };`) forces the correct type. This pattern is needed for resolution seam delegates that production code will instantiate with captures (e.g. closing over `Config cfg`).
---
### Task 26: Error-to-exit-code mapping via marker fields, not string inspection
**Decision: bool marker fields on exception classes instead of string-content inspection in `exitCodeFor`.**
The plan initially suggested checking `e.msg.indexOf("not found")` to distinguish exit code 2 (package not found) from exit code 6 (network error) for `FetchException`. Instead, each exception class carries a boolean marker:
- `BuildException.toolMissing` — distinguishes tool-not-found (exit 7) from build failure (exit 4).
- `InstallException.toolMissing` — distinguishes tool-not-found (exit 7) from install failure (exit 5).
- `FetchException.notFound` — distinguishes package-not-found (exit 2) from network error (exit 6).
**Rationale**: String inspection is fragile — error messages may change, use different formatting, or get truncated. A dedicated field communicates intent unambiguously and survives message refactoring.
### Task 26: `TofuError` base class for explicit-exit exceptions
Rather than a separate `throwExit(int, string)` helper, `TofuError : Exception` with an `int exitCode` field lets callers throw the exception and have `exitCodeFor` read the code directly. This keeps the exit-mapping logic centralized (always go through `exitCodeFor`) while allowing callers to set explicit codes when they know them (e.g. in command stubs).
### Task 26: Lock file at `<cacheDir>/.lock` with PID-liveness check
The lock uses `kill(pid, 0)` (POSIX signal 0) to test whether the locking PID is still alive. A dead PID means the lock is stale and can be safely removed. This avoids the need for a separate lock-daemon or file-lock (flock/fcntl).
**Conservative assumption**: If `kill(pid, 0)` fails with anything other than ESRCH (e.g. EPERM), the PID is assumed alive. This errs on the side of safety — false-positive "another process" is better than concurrent writes.
### Task 26: `not implemented yet` stubs in dispatch for commands 20–24
Commands 20–24 (search, install, upgrade, remove, info) are separate tasks running in parallel. Since D cannot conditionally import modules at compile time, main.d dispatches via `final switch` on the `Command` enum with all cases present. For commands whose modules don't exist yet, a `logError("command '<x>' not implemented yet")` + `return 1` stub is used. These stubs are documented and will be replaced when tasks 20–24 land.
### Task 26: `write(2, ...)` POSIX syscall for signal handler, not `stderr.rawWrite`
DMD 2.112's `core.sys.posix.signal.signal` requires the handler to be `@nogc`. `std.stdio.File.rawWrite` is NOT `@nogc` (File is a GC-managed class). Instead, the handler uses the raw POSIX `write(2, msg.ptr, msg.length)` syscall from `core.sys.posix.unistd`, which is a direct C call and fully `@nogc`.
Similarly, `_exit(130)` from `core.sys.posix.unistd` is used instead of `core.stdc.stdlib.exit` — `_exit` does NOT run atexit handlers or flush stdio buffers, making it safe in a signal-handler context.
---
+96 -1
View File
@@ -731,13 +731,108 @@ The sandbox test (test 2) creates a sentinel file, serves an index containing `o
- 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 test` passes — all 16 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`.
---
## Task 26 — Cross-cutting error-path audit and main.d entry point
### Architecture
- New module: `src/tofu/errors.d` — shared error helpers (TofuError, exitCodeFor, lock-file management).
- Rewritten `src/main.d` — real entry point with argument parsing, dispatch, catch-all handler, SIGINT, and lock.
- Minimal edits to `build.d`, `install.d`, `fetch.d` — added marker bool fields to their exception classes.
### `TofuError` base class
- Extends `Exception` with `int exitCode` field.
- Direct callers throw this when they already know the exit code (bypasses `exitCodeFor` mapping).
- `exitCodeFor` checks `cast(TofuError)` first — if found, uses the embedded exit code directly.
### `exitCodeFor(Exception)` mapping
- Maps every known exception type to the plan's exit-code table:
- `HttpException`, `IndexException` → 6 (network)
- `BuildException` → 4 (build failure), unless `toolMissing` flag is set → 7 (config/tool)
- `InstallException` → 5 (install failure), unless `toolMissing` flag → 7
- `FetchException` → 6 (network), unless `notFound` flag → 2 (package not found)
- `DepException`, `ResolveException` → 3 (dependency resolution)
- `ConfigException` → 7 (config error)
- `CliException` → 1 (generic/usage)
- `TofuError` → uses its `exitCode` field directly
- Any other `Exception` → 1 (generic fallback)
- Uses `cast`-based type checking — no typeid/RTTI overhead beyond what D already provides.
### Marker fields on exceptions
- **BuildException.toolMissing** (bool, default false): Set to `true` at the two "zeta-makepkg not found" throw sites in build.d. Maps to exit code 7 instead of 4.
- **InstallException.toolMissing** (bool, default false): Set to `true` at the "zeta not found" throw site in install.d. Maps to exit code 7 instead of 5.
- **FetchException.notFound** (bool, default false): Set to `true` at the 404-on-.recipe throw site in fetch.d. Maps to exit code 2 instead of 6.
- Minimal edits — only 3 files touched, only the exception class definition + the throw sites.
### Lock-file management (`~/.cache/tofu/.lock`)
- **PID-based**: Lock file contains `thisProcessID.to!string`. On acquire, check if existing lock's PID is alive via `kill(pid, 0)` on POSIX.
- **Stale lock detection**: If PID is dead (ESRCH), `logWarn("removing stale lock (PID %d not alive)")`, remove the file, and proceed.
- **Live lock → LockException**: Message includes lock path and PID: `"another tofu process is running (lock: <path>, PID <pid>)"`.
- **acquireLock / releaseLock / isLocked**: All `@safe` public API. Filesystem operations isolated in `@trusted` wrappers.
- **Acquire in main, release on scope(exit) + scope(failure)**: Lock is released even on exception/early return.
### SIGINT handler
- Uses `core.sys.posix.signal` — handler must be `nothrow @nogc` per DMD 2.112's `signal` wrapper.
- Handler: sets `__gshared bool g_interrupted = true`, writes `"error interrupted\n"` to stderr via `write(2, ...)` POSIX syscall (async-signal-safe, no allocation, no GC), then calls `_exit(130)`.
- `_exit` (not `exit`) — no atexit handlers, no stdio flush. Immediate termination with code 130.
- `g_interrupted` flag is checked in main body after lock acquisition — would allow graceful shutdown if we ever switch to deferred-exit model.
### main.d dispatch pattern
- **Import**: `import tofu.cli;` — uses `parseArgs(args[1..$])` returning `ParsedArgs`.
- **`final switch` on `Command`**: All 6 enum members covered:
- `help` → prints `tofu.cli.helpText`, exits 0.
- `install`, `search`, `upgrade`, `remove_`, `info` → `logError("command '<x>' not implemented yet")`, exits 1.
- These "not implemented yet" stubs are replaced when tasks 20–24 land.
- **Catch-all**: try/catch around the dispatch block → `exitCodeFor(e)` + `logError("%s", e.msg)` + return exit code.
- **LockException** is caught separately (before dispatch) — has a fixed actionable message, no need for `exitCodeFor` remapping.
### D language gotchas for this task
- **`core.sys.posix.signal.signal` requires `@nogc`** on the handler function in DMD 2.112. The POSIX `write(2)` syscall from `core.sys.posix.unistd` is `@nogc` (raw C call). `stderr.rawWrite` from `std.stdio.File` is NOT `@nogc` (File is a GC-managed class).
- **`_exit` vs `exit`**: Use `core.sys.posix.unistd._exit` for the signal handler (immediate, no cleanup). `core.stdc.stdlib.exit` runs atexit handlers which is unsafe in signal context.
- **`write(2, ptr, len)` from POSIX**: First arg is file descriptor — `2` is `STDERR_FILENO`. The `msg.ptr` of a string literal is `const(char)*`, which converts to `const(void)*` for the syscall.
- **`helpText` not `usage`**: The cli.d module exports `helpText`, not `usage`. Named to be self-documenting.
- **`final switch` on enum**: D's `-w` (warningsAsErrors) requires either `final switch` (compile-time exhaustive) or a `default` case for `switch` on enum types. Using `final switch` ensures the compiler catches new enum members.
- **`Command.remove_`**: cli.d uses trailing underscore to avoid D keyword collision. Main dispatch must use `Command.remove_`.
- **`parseArgs` takes argv sans program name**: cli.d's `parseArgs(string[] args)` expects `args` to be the argument vector WITHOUT `argv[0]`. main.d passes `args[1..$]`.
### Test coverage (errors.d unittests)
- **20 unittests** in errors.d:
- 13 tests for `exitCodeFor` mapping (every exception type + TofuError bypass + generic fallback)
- 4 tests for lock management (acquire, stale lock, live lock → LockException, corrupted lock)
- 2 tests for `isLocked` (no lock → false, dead PID → false, live PID → true)
- 1 test for BuildException with disk-full stderr → still exit 4
- **Important**: Lock tests create temp directories in `/tmp`, clean up with `scope(exit)`. For live-lock test, the current PID is written to simulate a live lock — the test must clean up manually since the lock file uses its own PID.
### Build verified
- `dub build` passes — produces `./tofu` binary.
- `dub test` passes — all 16 modules (config, log, types, vercmp, http, index, fetch, cache, binary, deps, resolve, build, install, state, cli, errors).
- `dub test` output logged to `.omo/evidence/task-26-tofu-core.log`.
### Exit-code verification (all 16 failure paths mapped)
- [x] (1) zeta-makepkg not found → BuildException.toolMissing=true → exit 7
- [x] (2) zeta not found → InstallException.toolMissing=true → exit 7
- [x] (3) network timeout → HttpException → exit 6
- [x] (4) 404 on index.lua → IndexException (wraps HttpException) → exit 6
- [x] (5) 404 on recipe → FetchException.notFound=true → exit 2
- [x] (6) 404 on binary manifest → treated as recipe-only (no change — binary.d returns exists=false)
- [x] (7) disk full during build → BuildException (no marker) → exit 4 (stderr tail included)
- [x] (8) recipe parse error → ResolveException (future task) → exit 3
- [x] (9) dep cycle → DepException → exit 3
- [x] (10) missing dep → ResolveException → exit 3
- [x] (11) constraint unsatisfied → ResolveException → exit 3
- [x] (12) build failure → BuildException → exit 4
- [x] (13) permission denied on install → InstallException → exit 5 (stderr tail passes through)
- [x] (14) SIGINT → handler → exit 130
- [x] (15) corrupted cache → cache.d logWarn + re-fetch (no change needed)
- [x] (16) concurrent tofu → LockException in acquireLock → exit 1
---
## Task 18 — `tofu.state` (post-install state tracking for -Syu upgrades)
### Architecture
+126 -3
View File
@@ -1,7 +1,130 @@
import std.stdio;
/// tofu — package manager for the ZereneOS Unofficial User Repository.
///
/// Entry point: parses args, acquires the global lock, dispatches to command
/// implementations, and catches ALL exceptions with actionable messages and
/// correct exit codes.
///
/// Exit codes (per plan):
/// 0 success | 1 generic | 2 pkg not found | 3 dep resolution | 4 build
/// failure | 5 install failure | 6 network | 7 config | 130 SIGINT
///
/// Implementation note:
/// Tasks 20–24 (command modules) may not exist yet in parallel execution.
/// This module dispatches via a `switch` on `Command`. Commands whose
/// modules don't exist yet print "command '<x>' not implemented yet" and
/// exit 1 — these stubs are replaced when tasks 20–24 land. Only `help`
/// is fully implemented here.
module main;
int main()
import tofu.errors; // exitCodeFor, acquireLock, releaseLock, LockException
import tofu.config; // Config, load
import tofu.cli; // Command, ParsedArgs, parseArgs, helpText, CliException
import tofu.log; // logError, logInfo
import std.stdio; // writeln, stderr
// ────────────────────────────────────────────────────────────
// SIGINT handler
// ────────────────────────────────────────────────────────────
version (Posix) {
import core.sys.posix.signal : signal, SIGINT, SIG_ERR;
import core.sys.posix.unistd : write, _exit;
__gshared bool g_interrupted;
/// Signal handler for SIGINT. Sets the global flag, writes a bare message
/// to stderr via the `write(2)` syscall (fully async-signal-safe), then calls
/// `_exit(130)` immediately (no atexit / stdio flush).
/// The @nogc attribute is required by DMD's `core.sys.posix.signal` wrapper.
extern (C) void _onSigint(int) nothrow @nogc
{
g_interrupted = true;
// write(2, ...) — POSIX syscall, no allocation, no GC, async-signal-safe.
const char[] msg = "error interrupted\n";
write(2 /* STDERR_FILENO */, msg.ptr, msg.length);
_exit(130);
}
}
// ────────────────────────────────────────────────────────────
// Main
// ────────────────────────────────────────────────────────────
int main(string[] args)
{
writeln("tofu: ZUUR package manager");
// ── 1. Register SIGINT handler (must happen early) ─────────────
version (Posix) {
if (signal(SIGINT, &_onSigint) == SIG_ERR) {
stderr.writeln("error failed to register SIGINT handler");
}
}
// ── 2. Load configuration ─────────────────────────────────────
// Config::load() never throws — falls back to defaults on errors.
auto cfg = load(null);
// ── 3. Acquire global lock (prevents concurrent tofu runs) ────
// Wrapped in try/catch: LockException has a fixed, actionable
// message and should not be remapped through exitCodeFor.
try {
acquireLock(cfg.cacheDir);
} catch (LockException e) {
logError("%s", e.msg);
return 1;
}
scope (exit) releaseLock(cfg.cacheDir);
scope (failure) releaseLock(cfg.cacheDir);
// ── 4. Check for premature SIGINT flag ────────────────────────
version (Posix) {
if (g_interrupted)
return 130;
}
// ── 5. Parse command-line args ─────────────────────────────────
// parseArgs expects argv[1..$] (no program name).
ParsedArgs pa;
try {
pa = parseArgs(args[1 .. $]);
} catch (CliException e) {
logError("%s", e.msg);
return 1;
}
// ── 6. Dispatch ────────────────────────────────────────────────
try {
final switch (pa.cmd) {
case Command.help:
writeln(tofu.cli.helpText);
return 0;
case Command.install:
logError("command 'install' not implemented yet");
return 1;
case Command.search:
logError("command 'search' not implemented yet");
return 1;
case Command.upgrade:
logError("command 'upgrade' not implemented yet");
return 1;
case Command.remove_:
logError("command 'remove' not implemented yet");
return 1;
case Command.info:
logError("command 'info' not implemented yet");
return 1;
}
} catch (Exception e) {
// Any uncaught exception during dispatch — map to exit code
int ec = exitCodeFor(e);
logError("%s", e.msg);
return ec;
}
return 0;
}
+12 -3
View File
@@ -29,6 +29,10 @@ version (unittest) {
class BuildException : Exception {
mixin basicExceptionCtors;
/// True when the error is "zeta-makepkg not found" (→ exit code 7,
/// not 4). Set by the two throw sites below.
bool toolMissing = false;
this(string message, string file = __FILE__, size_t line = __LINE__)
@safe pure nothrow {
super(message, file, line);
@@ -132,10 +136,13 @@ string runMakepkg(string recipePath, string outputDir, int jobs, bool force,
string zetaMakepkg;
if (cfg.zetaToolchainPath.length > 0) {
zetaMakepkg = cfg.zetaToolchainPath;
if (!() @trusted { return exists(zetaMakepkg); }())
throw new BuildException(
if (!() @trusted { return exists(zetaMakepkg); }()) {
auto be = new BuildException(
"zeta-makepkg not found. Install zeta-toolchain or set "
~ "TOFU_ZETA_TOOLCHAIN_PATH in ~/.config/tofu/config.toml");
be.toolMissing = true;
throw be;
}
} else {
zetaMakepkg = "zeta-makepkg";
}
@@ -169,9 +176,11 @@ string runMakepkg(string recipePath, string outputDir, int jobs, bool force,
// Binary not found on PATH (or exec failed)
if (cfg.zetaToolchainPath.length == 0
&& e.msg.indexOf("not found") >= 0) {
throw new BuildException(
auto be = new BuildException(
"zeta-makepkg not found. Install zeta-toolchain or set "
~ "TOFU_ZETA_TOOLCHAIN_PATH in ~/.config/tofu/config.toml");
be.toolMissing = true;
throw be;
}
throw new BuildException(
"failed to execute zeta-makepkg: " ~ e.msg);
+7 -1
View File
@@ -32,6 +32,10 @@ import std.string;
/// errors are propagated with their original message.
class FetchException : Exception
{
/// True when the error is "package not found in ZUUR recipes"
/// (→ exit code 2, not 6). Set by the 404-on-.recipe throw site.
bool notFound = false;
@safe this(string msg)
{
super(msg);
@@ -191,8 +195,10 @@ private void cleanupFiles(string[] paths) @trusted
{
if (e.msg.indexOf("HTTP 404") >= 0)
{
throw new FetchException(
auto fe = new FetchException(
format("package '%s' not found in ZUUR recipes", name));
fe.notFound = true;
throw fe;
}
throw new FetchException(e.msg);
}
+7 -1
View File
@@ -30,6 +30,10 @@ import std.file : exists;
/// Thrown when a package install fails.
class InstallException : Exception
{
/// True when the error is "zeta not found" (→ exit code 7,
/// not 5). Set by the throw site below.
bool toolMissing = false;
this(string message, string file = __FILE__, size_t line = __LINE__)
@safe pure nothrow
{
@@ -87,8 +91,10 @@ void runLocalProvide(string pkgName, Config cfg)
}
catch (ProcessException e)
{
throw new InstallException(
auto ie = new InstallException(
"zeta not found. Install Zeta to use package management.");
ie.toolMissing = true;
throw ie;
}
}();