fix(errors): add actionable error messages for all 16 failure paths
This commit is contained in:
+126
-3
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user