diff --git a/src/build-remote/build-remote.cc b/src/build-remote/build-remote.cc index d2ea6c956..49b35a4e9 100644 --- a/src/build-remote/build-remote.cc +++ b/src/build-remote/build-remote.cc @@ -200,9 +200,12 @@ static int _main(int argc, char * * argv) } catch (std::exception & e) { auto msg = chomp(drainFD(5, false)); - printError("cannot build on '%s': %s%s", - bestMachine->storeUri, e.what(), - (msg.empty() ? "" : ": " + msg)); + logError({ + .name = "Remote build", + .hint = hintfmt("cannot build on '%s': %s%s", + bestMachine->storeUri, e.what(), + (msg.empty() ? "" : ": " + msg)) + }); bestMachine->enabled = false; continue; } diff --git a/src/error-demo/error-demo.cc b/src/error-demo/error-demo.cc deleted file mode 100644 index a9ff6057c..000000000 --- a/src/error-demo/error-demo.cc +++ /dev/null @@ -1,66 +0,0 @@ -#include "error.hh" -#include "nixexpr.hh" - -#include -#include - -int main() -{ - using namespace nix; - - // In each program where errors occur, this has to be set. - ErrorInfo::programName = std::optional("error-demo"); - - // Error in a program; no hint and no nix code. - printErrorInfo( - ErrorInfo { .level = elError, - .name = "name", - .description = "error description", - }); - - // Warning with name, description, and hint. - // The hintfmt function makes all the substituted text yellow. - printErrorInfo( - ErrorInfo { .level = elWarning, - .name = "name", - .description = "error description", - .hint = std::optional( - hintfmt("there was a %1%", "warning")), - }); - - - // Warning with nix file, line number, column, and the lines of - // code where a warning occurred. - SymbolTable testTable; - auto problem_file = testTable.create("myfile.nix"); - - printErrorInfo( - ErrorInfo{ - .level = elWarning, - .name = "warning name", - .description = "warning description", - .hint = hintfmt("this hint has %1% templated %2%!!", "yellow", "values"), - .nixCode = NixCode { - .errPos = Pos(problem_file, 40, 13), - .prevLineOfCode = std::nullopt, - .errLineOfCode = "this is the problem line of code", - .nextLineOfCode = std::nullopt - }}); - - // Error with previous and next lines of code. - printErrorInfo( - ErrorInfo{ - .level = elError, - .name = "error name", - .description = "error description", - .hint = hintfmt("this hint has %1% templated %2%!!", "yellow", "values"), - .nixCode = NixCode { - .errPos = Pos(problem_file, 40, 13), - .prevLineOfCode = std::optional("previous line of code"), - .errLineOfCode = "this is the problem line of code", - .nextLineOfCode = std::optional("next line of code"), - }}); - - - return 0; -} diff --git a/src/error-demo/local.mk b/src/error-demo/local.mk deleted file mode 100644 index 2c528490a..000000000 --- a/src/error-demo/local.mk +++ /dev/null @@ -1,12 +0,0 @@ -programs += error-demo - -error-demo_DIR := $(d) - -error-demo_SOURCES := \ - $(wildcard $(d)/*.cc) \ - -error-demo_CXXFLAGS += -I src/libutil -I src/libexpr - -error-demo_LIBS = libutil libexpr - -error-demo_LDFLAGS = -pthread $(SODIUM_LIBS) $(EDITLINE_LIBS) $(BOOST_LDFLAGS) -lboost_context -lboost_thread -lboost_system diff --git a/src/libexpr/attr-path.cc b/src/libexpr/attr-path.cc index 76d101b98..9a9531a3f 100644 --- a/src/libexpr/attr-path.cc +++ b/src/libexpr/attr-path.cc @@ -19,7 +19,7 @@ static Strings parseAttrPath(const string & s) ++i; while (1) { if (i == s.end()) - throw Error(format("missing closing quote in selection path '%1%'") % s); + throw Error("missing closing quote in selection path '%1%'", s); if (*i == '"') break; cur.push_back(*i++); } @@ -60,11 +60,11 @@ std::pair findAlongAttrPath(EvalState & state, const string & attr if (v->type != tAttrs) throw TypeError( - format("the expression selected by the selection path '%1%' should be a set but is %2%") - % attrPath % showType(*v)); - + "the expression selected by the selection path '%1%' should be a set but is %2%", + attrPath, + showType(*v)); if (attr.empty()) - throw Error(format("empty attribute name in selection path '%1%'") % attrPath); + throw Error("empty attribute name in selection path '%1%'", attrPath); Bindings::iterator a = v->attrs->find(state.symbols.create(attr)); if (a == v->attrs->end()) @@ -77,9 +77,9 @@ std::pair findAlongAttrPath(EvalState & state, const string & attr if (!v->isList()) throw TypeError( - format("the expression selected by the selection path '%1%' should be a list but is %2%") - % attrPath % showType(*v)); - + "the expression selected by the selection path '%1%' should be a list but is %2%", + attrPath, + showType(*v)); if (attrIndex >= v->listSize()) throw AttrPathNotFound("list index %1% in selection path '%2%' is out of range", attrIndex, attrPath); diff --git a/src/libexpr/attr-set.hh b/src/libexpr/attr-set.hh index 118c7bd5d..f5651891f 100644 --- a/src/libexpr/attr-set.hh +++ b/src/libexpr/attr-set.hh @@ -76,7 +76,12 @@ public: { auto a = get(name); if (!a) - throw Error("attribute '%s' missing, at %s", name, pos); + throw Error( + ErrorInfo { + .hint = hintfmt("attribute '%s' missing", name), + .nixCode = NixCode { .errPos = pos } + }); + return *a; } diff --git a/src/libexpr/eval-inline.hh b/src/libexpr/eval-inline.hh index 942cda1ea..eee49e02e 100644 --- a/src/libexpr/eval-inline.hh +++ b/src/libexpr/eval-inline.hh @@ -7,20 +7,28 @@ namespace nix { -LocalNoInlineNoReturn(void throwEvalError(const char * s, const Pos & pos)) +LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s)) { - throw EvalError(format(s) % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt(s), + .nixCode = NixCode { .errPos = pos } + }); } LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v)) { - throw TypeError(format(s) % showType(v)); + throw TypeError(s, showType(v)); } -LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v, const Pos & pos)) +LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const Value & v)) { - throw TypeError(format(s) % showType(v) % pos); + throw TypeError( + ErrorInfo { + .hint = hintfmt(s, showType(v)), + .nixCode = NixCode { .errPos = pos } + }); } @@ -43,7 +51,7 @@ void EvalState::forceValue(Value & v, const Pos & pos) else if (v.type == tApp) callFunction(*v.app.left, *v.app.right, v, noPos); else if (v.type == tBlackhole) - throwEvalError("infinite recursion encountered, at %1%", pos); + throwEvalError(pos, "infinite recursion encountered"); } @@ -59,7 +67,7 @@ inline void EvalState::forceAttrs(Value & v, const Pos & pos) { forceValue(v, pos); if (v.type != tAttrs) - throwTypeError("value is %1% while a set was expected, at %2%", v, pos); + throwTypeError(pos, "value is %1% while a set was expected", v); } @@ -75,7 +83,7 @@ inline void EvalState::forceList(Value & v, const Pos & pos) { forceValue(v, pos); if (!v.isList()) - throwTypeError("value is %1% while a list was expected, at %2%", v, pos); + throwTypeError(pos, "value is %1% while a list was expected", v); } /* Note: Various places expect the allocated memory to be zeroed. */ diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 82eb1582e..7bf25ea17 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -501,52 +501,81 @@ Value & EvalState::getBuiltin(const string & name) LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2)) { - throw EvalError(format(s) % s2); + throw EvalError(s, s2); } -LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, const Pos & pos)) +LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const string & s2)) { - throw EvalError(format(s) % s2 % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt(s, s2), + .nixCode = NixCode { .errPos = pos } + }); } LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, const string & s3)) { - throw EvalError(format(s) % s2 % s3); + throw EvalError(s, s2, s3); } -LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, const string & s3, const Pos & pos)) +LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const string & s2, const string & s3)) { - throw EvalError(format(s) % s2 % s3 % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt(s, s2, s3), + .nixCode = NixCode { .errPos = pos } + }); } -LocalNoInlineNoReturn(void throwEvalError(const char * s, const Symbol & sym, const Pos & p1, const Pos & p2)) +LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const Symbol & sym, const Pos & p2)) { - throw EvalError(format(s) % sym % p1 % p2); + // p1 is where the error occurred; p2 is a position mentioned in the message. + throw EvalError( + ErrorInfo { + .hint = hintfmt(s, sym, p2), + .nixCode = NixCode { .errPos = p1 } + }); } -LocalNoInlineNoReturn(void throwTypeError(const char * s, const Pos & pos)) +LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s)) { - throw TypeError(format(s) % pos); + throw TypeError( + ErrorInfo { + .hint = hintfmt(s), + .nixCode = NixCode { .errPos = pos } + }); } LocalNoInlineNoReturn(void throwTypeError(const char * s, const string & s1)) { - throw TypeError(format(s) % s1); + throw TypeError(s, s1); } -LocalNoInlineNoReturn(void throwTypeError(const char * s, const ExprLambda & fun, const Symbol & s2, const Pos & pos)) +LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, const Symbol & s2)) { - throw TypeError(format(s) % fun.showNamePos() % s2 % pos); + throw TypeError( + ErrorInfo { + .hint = hintfmt(s, fun.showNamePos(), s2), + .nixCode = NixCode { .errPos = pos } + }); } -LocalNoInlineNoReturn(void throwAssertionError(const char * s, const string & s1, const Pos & pos)) +LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, const string & s1)) { - throw AssertionError(format(s) % s1 % pos); + throw AssertionError( + ErrorInfo { + .hint = hintfmt(s, s1), + .nixCode = NixCode { .errPos = pos } + }); } -LocalNoInlineNoReturn(void throwUndefinedVarError(const char * s, const string & s1, const Pos & pos)) +LocalNoInlineNoReturn(void throwUndefinedVarError(const Pos & pos, const char * s, const string & s1)) { - throw UndefinedVarError(format(s) % s1 % pos); + throw UndefinedVarError( + ErrorInfo { + .hint = hintfmt(s, s1), + .nixCode = NixCode { .errPos = pos } + }); } LocalNoInline(void addErrorPrefix(Error & e, const char * s, const string & s2)) @@ -614,7 +643,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval) return j->value; } if (!env->prevWith) - throwUndefinedVarError("undefined variable '%1%' at %2%", var.name, var.pos); + throwUndefinedVarError(var.pos, "undefined variable '%1%'", var.name); for (size_t l = env->prevWith; l; --l, env = env->up) ; } } @@ -812,7 +841,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos) Value v; e->eval(*this, env, v); if (v.type != tBool) - throwTypeError("value is %1% while a Boolean was expected, at %2%", v, pos); + throwTypeError(pos, "value is %1% while a Boolean was expected", v); return v.boolean; } @@ -926,7 +955,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) Symbol nameSym = state.symbols.create(nameVal.string.s); Bindings::iterator j = v.attrs->find(nameSym); if (j != v.attrs->end()) - throwEvalError("dynamic attribute '%1%' at %2% already defined at %3%", nameSym, i.pos, *j->pos); + throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, *j->pos); i.valueExpr->setName(nameSym); /* Keep sorted order so find can catch duplicates */ @@ -1014,7 +1043,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) } else { state.forceAttrs(*vAttrs, pos); if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) - throwEvalError("attribute '%1%' missing, at %2%", name, pos); + throwEvalError(pos, "attribute '%1%' missing", name); } vAttrs = j->value; pos2 = j->pos; @@ -1140,7 +1169,7 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & po } if (fun.type != tLambda) - throwTypeError("attempt to call something which is not a function but %1%, at %2%", fun, pos); + throwTypeError(pos, "attempt to call something which is not a function but %1%", fun); ExprLambda & lambda(*fun.lambda.fun); @@ -1168,8 +1197,8 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & po for (auto & i : lambda.formals->formals) { Bindings::iterator j = arg.attrs->find(i.name); if (j == arg.attrs->end()) { - if (!i.def) throwTypeError("%1% called without required argument '%2%', at %3%", - lambda, i.name, pos); + if (!i.def) throwTypeError(pos, "%1% called without required argument '%2%'", + lambda, i.name); env2.values[displ++] = i.def->maybeThunk(*this, env2); } else { attrsUsed++; @@ -1184,7 +1213,7 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & po user. */ for (auto & i : *arg.attrs) if (lambda.formals->argNames.find(i.name) == lambda.formals->argNames.end()) - throwTypeError("%1% called with unexpected argument '%2%', at %3%", lambda, i.name, pos); + throwTypeError(pos, "%1% called with unexpected argument '%2%'", lambda, i.name); abort(); // can't happen } } @@ -1273,7 +1302,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v) if (!state.evalBool(env, cond, pos)) { std::ostringstream out; cond->show(out); - throwAssertionError("assertion '%1%' failed at %2%", out.str(), pos); + throwAssertionError(pos, "assertion '%1%' failed at %2%", out.str()); } body->eval(state, env, v); } @@ -1425,14 +1454,14 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) nf = n; nf += vTmp.fpoint; } else - throwEvalError("cannot add %1% to an integer, at %2%", showType(vTmp), pos); + throwEvalError(pos, "cannot add %1% to an integer", showType(vTmp)); } else if (firstType == tFloat) { if (vTmp.type == tInt) { nf += vTmp.integer; } else if (vTmp.type == tFloat) { nf += vTmp.fpoint; } else - throwEvalError("cannot add %1% to a float, at %2%", showType(vTmp), pos); + throwEvalError(pos, "cannot add %1% to a float", showType(vTmp)); } else s << state.coerceToString(pos, vTmp, context, false, firstType == tString); } @@ -1443,7 +1472,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) mkFloat(v, nf); else if (firstType == tPath) { if (!context.empty()) - throwEvalError("a string that refers to a store path cannot be appended to a path, at %1%", pos); + throwEvalError(pos, "a string that refers to a store path cannot be appended to a path"); auto path = canonPath(s.str()); mkPath(v, path.c_str()); } else @@ -1492,7 +1521,7 @@ NixInt EvalState::forceInt(Value & v, const Pos & pos) { forceValue(v, pos); if (v.type != tInt) - throwTypeError("value is %1% while an integer was expected, at %2%", v, pos); + throwTypeError(pos, "value is %1% while an integer was expected", v); return v.integer; } @@ -1503,7 +1532,7 @@ NixFloat EvalState::forceFloat(Value & v, const Pos & pos) if (v.type == tInt) return v.integer; else if (v.type != tFloat) - throwTypeError("value is %1% while a float was expected, at %2%", v, pos); + throwTypeError(pos, "value is %1% while a float was expected", v); return v.fpoint; } @@ -1512,7 +1541,7 @@ bool EvalState::forceBool(Value & v, const Pos & pos) { forceValue(v, pos); if (v.type != tBool) - throwTypeError("value is %1% while a Boolean was expected, at %2%", v, pos); + throwTypeError(pos, "value is %1% while a Boolean was expected", v); return v.boolean; } @@ -1527,7 +1556,7 @@ void EvalState::forceFunction(Value & v, const Pos & pos) { forceValue(v, pos); if (v.type != tLambda && v.type != tPrimOp && v.type != tPrimOpApp && !isFunctor(v)) - throwTypeError("value is %1% while a function was expected, at %2%", v, pos); + throwTypeError(pos, "value is %1% while a function was expected", v); } @@ -1536,7 +1565,7 @@ string EvalState::forceString(Value & v, const Pos & pos) forceValue(v, pos); if (v.type != tString) { if (pos) - throwTypeError("value is %1% while a string was expected, at %2%", v, pos); + throwTypeError(pos, "value is %1% while a string was expected", v); else throwTypeError("value is %1% while a string was expected", v); } @@ -1565,8 +1594,8 @@ string EvalState::forceStringNoCtx(Value & v, const Pos & pos) string s = forceString(v, pos); if (v.string.context) { if (pos) - throwEvalError("the string '%1%' is not allowed to refer to a store path (such as '%2%'), at %3%", - v.string.s, v.string.context[0], pos); + throwEvalError(pos, "the string '%1%' is not allowed to refer to a store path (such as '%2%')", + v.string.s, v.string.context[0]); else throwEvalError("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0]); @@ -1622,7 +1651,7 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context, return *maybeString; } auto i = v.attrs->find(sOutPath); - if (i == v.attrs->end()) throwTypeError("cannot coerce a set to a string, at %1%", pos); + if (i == v.attrs->end()) throwTypeError(pos, "cannot coerce a set to a string"); return coerceToString(pos, *i->value, context, coerceMore, copyToStore); } @@ -1653,7 +1682,7 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context, } } - throwTypeError("cannot coerce %1% to a string, at %2%", v, pos); + throwTypeError(pos, "cannot coerce %1% to a string", v); } @@ -1684,7 +1713,7 @@ Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context) { string path = coerceToString(pos, v, context, false, false); if (path == "" || path[0] != '/') - throwEvalError("string '%1%' doesn't represent an absolute path, at %2%", path, pos); + throwEvalError(pos, "string '%1%' doesn't represent an absolute path", path); return path; } @@ -1891,8 +1920,11 @@ void EvalState::printStats() string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const { - throw TypeError(format("cannot coerce %1% to a string, at %2%") % - showType() % pos); + throw TypeError( + ErrorInfo { + .hint = hintfmt("cannot coerce %1% to a string", showType()), + .nixCode = NixCode { .errPos = pos } + }); } diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l index c34e5c383..85376a08f 100644 --- a/src/libexpr/lexer.l +++ b/src/libexpr/lexer.l @@ -127,14 +127,14 @@ or { return OR_KW; } try { yylval->n = boost::lexical_cast(yytext); } catch (const boost::bad_lexical_cast &) { - throw ParseError(format("invalid integer '%1%'") % yytext); + throw ParseError("invalid integer '%1%'", yytext); } return INT; } {FLOAT} { errno = 0; yylval->nf = strtod(yytext, 0); if (errno != 0) - throw ParseError(format("invalid float '%1%'") % yytext); + throw ParseError("invalid float '%1%'", yytext); return FLOAT; } diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index 63cbef1dd..91a508305 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -267,8 +267,12 @@ void ExprVar::bindVars(const StaticEnv & env) /* Otherwise, the variable must be obtained from the nearest enclosing `with'. If there is no `with', then we can issue an "undefined variable" error now. */ - if (withLevel == -1) throw UndefinedVarError(format("undefined variable '%1%' at %2%") % name % pos); - + if (withLevel == -1) + throw UndefinedVarError( + ErrorInfo { + .hint = hintfmt("undefined variable '%1%'", name), + .nixCode = NixCode { .errPos = pos } + }); fromWith = true; this->level = withLevel; } diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 25798cac6..47d0e85ec 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -2,6 +2,7 @@ #include "value.hh" #include "symbol-table.hh" +#include "error.hh" #include @@ -235,8 +236,11 @@ struct ExprLambda : Expr : pos(pos), arg(arg), matchAttrs(matchAttrs), formals(formals), body(body) { if (!arg.empty() && formals && formals->argNames.find(arg) != formals->argNames.end()) - throw ParseError(format("duplicate formal function argument '%1%' at %2%") - % arg % pos); + throw ParseError( + ErrorInfo { + .hint = hintfmt("duplicate formal function argument '%1%'", arg), + .nixCode = NixCode { .errPos = pos } + }); }; void setName(Symbol & name); string showNamePos() const; diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 1993fa6c1..0417a3c21 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -31,7 +31,7 @@ namespace nix { Expr * result; Path basePath; Symbol path; - string error; + ErrorInfo error; Symbol sLetBody; ParseData(EvalState & state) : state(state) @@ -64,15 +64,23 @@ namespace nix { static void dupAttr(const AttrPath & attrPath, const Pos & pos, const Pos & prevPos) { - throw ParseError(format("attribute '%1%' at %2% already defined at %3%") - % showAttrPath(attrPath) % pos % prevPos); + throw ParseError( + ErrorInfo { + .hint = hintfmt("attribute '%1%' already defined at %2%", + showAttrPath(attrPath), prevPos), + .nixCode = NixCode { .errPos = pos }, + }); } static void dupAttr(Symbol attr, const Pos & pos, const Pos & prevPos) { - throw ParseError(format("attribute '%1%' at %2% already defined at %3%") - % attr % pos % prevPos); + throw ParseError( + ErrorInfo { + .hint = hintfmt("attribute '%1%' already defined at %2%", + attr, prevPos), + .nixCode = NixCode { .errPos = pos }, + }); } @@ -140,8 +148,12 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath, static void addFormal(const Pos & pos, Formals * formals, const Formal & formal) { if (!formals->argNames.insert(formal.name).second) - throw ParseError(format("duplicate formal function argument '%1%' at %2%") - % formal.name % pos); + throw ParseError( + ErrorInfo { + .hint = hintfmt("duplicate formal function argument '%1%'", + formal.name), + .nixCode = NixCode { .errPos = pos }, + }); formals->formals.push_front(formal); } @@ -249,8 +261,10 @@ static inline Pos makeCurPos(const YYLTYPE & loc, ParseData * data) void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * error) { - data->error = (format("%1%, at %2%") - % error % makeCurPos(*loc, data)).str(); + data->error = ErrorInfo { + .hint = hintfmt(error), + .nixCode = NixCode { .errPos = makeCurPos(*loc, data) } + }; } @@ -327,8 +341,11 @@ expr_function { $$ = new ExprWith(CUR_POS, $2, $4); } | LET binds IN expr_function { if (!$2->dynamicAttrs.empty()) - throw ParseError(format("dynamic attributes not allowed in let at %1%") - % CUR_POS); + throw ParseError( + ErrorInfo { + .hint = hintfmt("dynamic attributes not allowed in let"), + .nixCode = NixCode { .errPos = CUR_POS }, + }); $$ = new ExprLet($2, $4); } | expr_if @@ -405,7 +422,11 @@ expr_simple | URI { static bool noURLLiterals = settings.isExperimentalFeatureEnabled("no-url-literals"); if (noURLLiterals) - throw ParseError("URL literals are disabled, at %s", CUR_POS); + throw ParseError( + ErrorInfo { + .hint = hintfmt("URL literals are disabled"), + .nixCode = NixCode { .errPos = CUR_POS } + }); $$ = new ExprString(data->symbols.create($1)); } | '(' expr ')' { $$ = $2; } @@ -475,8 +496,11 @@ attrs $$->push_back(AttrName(str->s)); delete str; } else - throw ParseError(format("dynamic attributes not allowed in inherit at %1%") - % makeCurPos(@2, data)); + throw ParseError( + ErrorInfo { + .hint = hintfmt("dynamic attributes not allowed in inherit"), + .nixCode = NixCode { .errPos = makeCurPos(@2, data) }, + }); } | { $$ = new AttrPath; } ; @@ -671,11 +695,11 @@ Path EvalState::findFile(SearchPath & searchPath, const string & path, const Pos Path res = r.second + suffix; if (pathExists(res)) return canonPath(res); } - format f = format( - "file '%1%' was not found in the Nix search path (add it using $NIX_PATH or -I)" - + string(pos ? ", at %2%" : "")); - f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit); - throw ThrownError(f % path % pos); + throw ThrownError( + ErrorInfo { + .hint = hintfmt("file '%1%' was not found in the Nix search path (add it using $NIX_PATH or -I)", path), + .nixCode = NixCode { .errPos = pos } + }); } @@ -691,7 +715,11 @@ std::pair EvalState::resolveSearchPathElem(const SearchPathEl res = { true, store->toRealPath(fetchers::downloadTarball( store, resolveUri(elem.second), "source", false).storePath) }; } catch (FileTransferError & e) { - printError(format("warning: Nix search path entry '%1%' cannot be downloaded, ignoring") % elem.second); + logWarning( + ErrorInfo { + .name = "Entry download", + .hint = hintfmt("Nix search path entry '%1%' cannot be downloaded, ignoring", elem.second) + }); res = { false, "" }; } } else { @@ -699,7 +727,11 @@ std::pair EvalState::resolveSearchPathElem(const SearchPathEl if (pathExists(path)) res = { true, path }; else { - printError(format("warning: Nix search path entry '%1%' does not exist, ignoring") % elem.second); + logWarning( + ErrorInfo { + .name = "Entry not found", + .hint = hintfmt("warning: Nix search path entry '%1%' does not exist, ignoring", elem.second) + }); res = { false, "" }; } } diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index eb3116d81..23ab7dec6 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -93,8 +93,12 @@ static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args try { state.realiseContext(context); } catch (InvalidPathError & e) { - throw EvalError(format("cannot import '%1%', since path '%2%' is not valid, at %3%") - % path % e.path % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("cannot import '%1%', since path '%2%' is not valid", + path, e.path), + .nixCode = NixCode { .errPos = pos } + }); } Path realPath = state.checkSourcePath(state.toRealPath(path, context)); @@ -170,8 +174,13 @@ void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value try { state.realiseContext(context); } catch (InvalidPathError & e) { - throw EvalError(format("cannot import '%1%', since path '%2%' is not valid, at %3%") - % path % e.path % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt( + "cannot import '%1%', since path '%2%' is not valid", + path, e.path), + .nixCode = NixCode { .errPos = pos } + }); } path = state.checkSourcePath(path); @@ -180,17 +189,17 @@ void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL); if (!handle) - throw EvalError(format("could not open '%1%': %2%") % path % dlerror()); + throw EvalError("could not open '%1%': %2%", path, dlerror()); dlerror(); ValueInitializer func = (ValueInitializer) dlsym(handle, sym.c_str()); if(!func) { char *message = dlerror(); if (message) - throw EvalError(format("could not load symbol '%1%' from '%2%': %3%") % sym % path % message); + throw EvalError("could not load symbol '%1%' from '%2%': %3%", sym, path, message); else - throw EvalError(format("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected") - % sym % path); + throw EvalError("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected", + sym, path); } (func)(state, v); @@ -206,7 +215,11 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v) auto elems = args[0]->listElems(); auto count = args[0]->listSize(); if (count == 0) { - throw EvalError(format("at least one argument to 'exec' required, at %1%") % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("at least one argument to 'exec' required"), + .nixCode = NixCode { .errPos = pos } + }); } PathSet context; auto program = state.coerceToString(pos, *elems[0], context, false, false); @@ -217,22 +230,25 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v) try { state.realiseContext(context); } catch (InvalidPathError & e) { - throw EvalError(format("cannot execute '%1%', since path '%2%' is not valid, at %3%") - % program % e.path % pos); - } + throw EvalError( + ErrorInfo { + .hint = hintfmt("cannot execute '%1%', since path '%2%' is not valid", + program, e.path), + .nixCode = NixCode { .errPos = pos } + });} auto output = runProgram(program, true, commandArgs); Expr * parsed; try { parsed = state.parseExprFromString(output, pos.file); } catch (Error & e) { - e.addPrefix(format("While parsing the output from '%1%', at %2%\n") % program % pos); + e.addPrefix(fmt("While parsing the output from '%1%', at %2%\n", program, pos)); throw; } try { state.eval(parsed, v); } catch (Error & e) { - e.addPrefix(format("While evaluating the output from '%1%', at %2%\n") % program % pos); + e.addPrefix(fmt("While evaluating the output from '%1%', at %2%\n", program, pos)); throw; } } @@ -338,7 +354,7 @@ struct CompareValues if (v1->type == tInt && v2->type == tFloat) return v1->integer < v2->fpoint; if (v1->type != v2->type) - throw EvalError(format("cannot compare %1% with %2%") % showType(*v1) % showType(*v2)); + throw EvalError("cannot compare %1% with %2%", showType(*v1), showType(*v2)); switch (v1->type) { case tInt: return v1->integer < v2->integer; @@ -349,7 +365,7 @@ struct CompareValues case tPath: return strcmp(v1->path, v2->path) < 0; default: - throw EvalError(format("cannot compare %1% with %2%") % showType(*v1) % showType(*v2)); + throw EvalError("cannot compare %1% with %2%", showType(*v1), showType(*v2)); } } }; @@ -370,7 +386,11 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar Bindings::iterator startSet = args[0]->attrs->find(state.symbols.create("startSet")); if (startSet == args[0]->attrs->end()) - throw EvalError(format("attribute 'startSet' required, at %1%") % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("attribute 'startSet' required"), + .nixCode = NixCode { .errPos = pos } + }); state.forceList(*startSet->value, pos); ValueList workSet; @@ -381,7 +401,11 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar Bindings::iterator op = args[0]->attrs->find(state.symbols.create("operator")); if (op == args[0]->attrs->end()) - throw EvalError(format("attribute 'operator' required, at %1%") % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("attribute 'operator' required"), + .nixCode = NixCode { .errPos = pos } + }); state.forceValue(*op->value, pos); /* Construct the closure by applying the operator to element of @@ -400,7 +424,11 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar Bindings::iterator key = e->attrs->find(state.symbols.create("key")); if (key == e->attrs->end()) - throw EvalError(format("attribute 'key' required, at %1%") % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("attribute 'key' required"), + .nixCode = NixCode { .errPos = pos } + }); state.forceValue(*key->value, pos); if (!doneKeys.insert(key->value).second) continue; @@ -430,7 +458,7 @@ static void prim_abort(EvalState & state, const Pos & pos, Value * * args, Value { PathSet context; string s = state.coerceToString(pos, *args[0], context); - throw Abort(format("evaluation aborted with the following error message: '%1%'") % s); + throw Abort("evaluation aborted with the following error message: '%1%'", s); } @@ -505,9 +533,9 @@ static void prim_trace(EvalState & state, const Pos & pos, Value * * args, Value { state.forceValue(*args[0], pos); if (args[0]->type == tString) - printError(format("trace: %1%") % args[0]->string.s); + printError("trace: %1%", args[0]->string.s); else - printError(format("trace: %1%") % *args[0]); + printError("trace: %1%", *args[0]); state.forceValue(*args[1], pos); v = *args[1]; } @@ -532,13 +560,17 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * /* Figure out the name first (for stack backtraces). */ Bindings::iterator attr = args[0]->attrs->find(state.sName); if (attr == args[0]->attrs->end()) - throw EvalError(format("required attribute 'name' missing, at %1%") % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("required attribute 'name' missing"), + .nixCode = NixCode { .errPos = pos } + }); string drvName; Pos & posDrvName(*attr->pos); try { drvName = state.forceStringNoCtx(*attr->value, pos); } catch (Error & e) { - e.addPrefix(format("while evaluating the derivation attribute 'name' at %1%:\n") % posDrvName); + e.addPrefix(fmt("while evaluating the derivation attribute 'name' at %1%:\n", posDrvName)); throw; } @@ -575,25 +607,42 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * auto handleHashMode = [&](const std::string & s) { if (s == "recursive") ingestionMethod = FileIngestionMethod::Recursive; else if (s == "flat") ingestionMethod = FileIngestionMethod::Flat; - else throw EvalError("invalid value '%s' for 'outputHashMode' attribute, at %s", s, posDrvName); + else + throw EvalError( + ErrorInfo { + .hint = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s), + .nixCode = NixCode { .errPos = posDrvName } + }); }; auto handleOutputs = [&](const Strings & ss) { outputs.clear(); for (auto & j : ss) { if (outputs.find(j) != outputs.end()) - throw EvalError(format("duplicate derivation output '%1%', at %2%") % j % posDrvName); + throw EvalError( + ErrorInfo { + .hint = hintfmt("duplicate derivation output '%1%'", j), + .nixCode = NixCode { .errPos = posDrvName } + }); /* !!! Check whether j is a valid attribute name. */ /* Derivations cannot be named ‘drv’, because then we'd have an attribute ‘drvPath’ in the resulting set. */ if (j == "drv") - throw EvalError(format("invalid derivation output name 'drv', at %1%") % posDrvName); + throw EvalError( + ErrorInfo { + .hint = hintfmt("invalid derivation output name 'drv'" ), + .nixCode = NixCode { .errPos = posDrvName } + }); outputs.insert(j); } if (outputs.empty()) - throw EvalError(format("derivation cannot have an empty set of outputs, at %1%") % posDrvName); + throw EvalError( + ErrorInfo { + .hint = hintfmt("derivation cannot have an empty set of outputs"), + .nixCode = NixCode { .errPos = posDrvName } + }); }; try { @@ -705,18 +754,35 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * /* Do we have all required attributes? */ if (drv.builder == "") - throw EvalError(format("required attribute 'builder' missing, at %1%") % posDrvName); + throw EvalError( + ErrorInfo { + .hint = hintfmt("required attribute 'builder' missing"), + .nixCode = NixCode { .errPos = posDrvName } + }); + if (drv.platform == "") - throw EvalError(format("required attribute 'system' missing, at %1%") % posDrvName); + throw EvalError( + ErrorInfo { + .hint = hintfmt("required attribute 'system' missing"), + .nixCode = NixCode { .errPos = posDrvName } + }); /* Check whether the derivation name is valid. */ if (isDerivation(drvName)) - throw EvalError("derivation names are not allowed to end in '%s', at %s", drvExtension, posDrvName); + throw EvalError( + ErrorInfo { + .hint = hintfmt("derivation names are not allowed to end in '%s'", drvExtension), + .nixCode = NixCode { .errPos = posDrvName } + }); if (outputHash) { /* Handle fixed-output derivations. */ if (outputs.size() != 1 || *(outputs.begin()) != "out") - throw Error(format("multiple outputs are not supported in fixed-output derivations, at %1%") % posDrvName); + throw Error( + ErrorInfo { + .hint = hintfmt("multiple outputs are not supported in fixed-output derivations"), + .nixCode = NixCode { .errPos = posDrvName } + }); HashType ht = outputHashAlgo.empty() ? htUnknown : parseHashType(outputHashAlgo); @@ -821,7 +887,11 @@ static void prim_storePath(EvalState & state, const Pos & pos, Value * * args, V e.g. nix-push does the right thing. */ if (!state.store->isStorePath(path)) path = canonPath(path, true); if (!state.store->isInStore(path)) - throw EvalError(format("path '%1%' is not in the Nix store, at %2%") % path % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("path '%1%' is not in the Nix store", path), + .nixCode = NixCode { .errPos = pos } + }); Path path2 = state.store->toStorePath(path); if (!settings.readOnlyMode) state.store->ensurePath(state.store->parseStorePath(path2)); @@ -837,9 +907,13 @@ static void prim_pathExists(EvalState & state, const Pos & pos, Value * * args, try { state.realiseContext(context); } catch (InvalidPathError & e) { - throw EvalError(format( - "cannot check the existence of '%1%', since path '%2%' is not valid, at %3%") - % path % e.path % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt( + "cannot check the existence of '%1%', since path '%2%' is not valid", + path, e.path), + .nixCode = NixCode { .errPos = pos } + }); } try { @@ -882,12 +956,16 @@ static void prim_readFile(EvalState & state, const Pos & pos, Value * * args, Va try { state.realiseContext(context); } catch (InvalidPathError & e) { - throw EvalError(format("cannot read '%1%', since path '%2%' is not valid, at %3%") - % path % e.path % pos); - } + throw EvalError( + ErrorInfo { + .hint = hintfmt("cannot read '%1%', since path '%2%' is not valid" + , path, e.path), + .nixCode = NixCode { .errPos = pos } + }); + } string s = readFile(state.checkSourcePath(state.toRealPath(path, context))); if (s.find((char) 0) != string::npos) - throw Error(format("the contents of the file '%1%' cannot be represented as a Nix string") % path); + throw Error("the contents of the file '%1%' cannot be represented as a Nix string", path); mkString(v, s.c_str()); } @@ -911,7 +989,11 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va i = v2.attrs->find(state.symbols.create("path")); if (i == v2.attrs->end()) - throw EvalError(format("attribute 'path' missing, at %1%") % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("attribute 'path' missing"), + .nixCode = NixCode { .errPos = pos } + }); PathSet context; string path = state.coerceToString(pos, *i->value, context, false, false); @@ -919,8 +1001,12 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va try { state.realiseContext(context); } catch (InvalidPathError & e) { - throw EvalError(format("cannot find '%1%', since path '%2%' is not valid, at %3%") - % path % e.path % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("cannot find '%1%', since path '%2%' is not valid", + path, e.path), + .nixCode = NixCode { .errPos = pos } + }); } searchPath.emplace_back(prefix, path); @@ -937,7 +1023,11 @@ static void prim_hashFile(EvalState & state, const Pos & pos, Value * * args, Va string type = state.forceStringNoCtx(*args[0], pos); HashType ht = parseHashType(type); if (ht == htUnknown) - throw Error(format("unknown hash type '%1%', at %2%") % type % pos); + throw Error( + ErrorInfo { + .hint = hintfmt("unknown hash type '%1%'", type), + .nixCode = NixCode { .errPos = pos } + }); PathSet context; // discarded Path p = state.coerceToPath(pos, *args[1], context); @@ -953,8 +1043,12 @@ static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Val try { state.realiseContext(ctx); } catch (InvalidPathError & e) { - throw EvalError(format("cannot read '%1%', since path '%2%' is not valid, at %3%") - % path % e.path % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("cannot read '%1%', since path '%2%' is not valid", + path, e.path), + .nixCode = NixCode { .errPos = pos } + }); } DirEntries entries = readDirectory(state.checkSourcePath(path)); @@ -1024,9 +1118,15 @@ static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Valu for (auto path : context) { if (path.at(0) != '/') - throw EvalError(format( - "in 'toFile': the file named '%1%' must not contain a reference " - "to a derivation but contains (%2%), at %3%") % name % path % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt( + "in 'toFile': the file named '%1%' must not contain a reference " + "to a derivation but contains (%2%)", + name, + path), + .nixCode = NixCode { .errPos = pos } + }); refs.insert(state.store->parseStorePath(path)); } @@ -1094,11 +1194,21 @@ static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args PathSet context; Path path = state.coerceToPath(pos, *args[1], context); if (!context.empty()) - throw EvalError(format("string '%1%' cannot refer to other paths, at %2%") % path % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("string '%1%' cannot refer to other paths", path), + .nixCode = NixCode { .errPos = pos } + }); state.forceValue(*args[0], pos); if (args[0]->type != tLambda) - throw TypeError(format("first argument in call to 'filterSource' is not a function but %1%, at %2%") % showType(*args[0]) % pos); + throw TypeError( + ErrorInfo { + .hint = hintfmt( + "first argument in call to 'filterSource' is not a function but %1%", + showType(*args[0])), + .nixCode = NixCode { .errPos = pos } + }); addPath(state, pos, std::string(baseNameOf(path)), path, args[0], FileIngestionMethod::Recursive, Hash(), v); } @@ -1118,7 +1228,12 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value PathSet context; path = state.coerceToPath(*attr.pos, *attr.value, context); if (!context.empty()) - throw EvalError(format("string '%1%' cannot refer to other paths, at %2%") % path % *attr.pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("string '%1%' cannot refer to other paths", + path), + .nixCode = NixCode { .errPos = *attr.pos } + }); } else if (attr.name == state.sName) name = state.forceStringNoCtx(*attr.value, *attr.pos); else if (n == "filter") { @@ -1129,10 +1244,19 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value else if (n == "sha256") expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256); else - throw EvalError(format("unsupported argument '%1%' to 'addPath', at %2%") % attr.name % *attr.pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("unsupported argument '%1%' to 'addPath'", + attr.name), + .nixCode = NixCode { .errPos = *attr.pos } + }); } if (path.empty()) - throw EvalError(format("'path' required, at %1%") % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("'path' required"), + .nixCode = NixCode { .errPos = pos } + }); if (name.empty()) name = baseNameOf(path); @@ -1190,7 +1314,11 @@ void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v) // !!! Should we create a symbol here or just do a lookup? Bindings::iterator i = args[1]->attrs->find(state.symbols.create(attr)); if (i == args[1]->attrs->end()) - throw EvalError(format("attribute '%1%' missing, at %2%") % attr % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("attribute '%1%' missing", attr), + .nixCode = NixCode { .errPos = pos } + }); // !!! add to stack trace? if (state.countCalls && i->pos) state.attrSelects[*i->pos]++; state.forceValue(*i->value, pos); @@ -1270,15 +1398,22 @@ static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args, Bindings::iterator j = v2.attrs->find(state.sName); if (j == v2.attrs->end()) - throw TypeError(format("'name' attribute missing in a call to 'listToAttrs', at %1%") % pos); + throw TypeError( + ErrorInfo { + .hint = hintfmt("'name' attribute missing in a call to 'listToAttrs'"), + .nixCode = NixCode { .errPos = pos } + }); string name = state.forceStringNoCtx(*j->value, pos); Symbol sym = state.symbols.create(name); if (seen.insert(sym).second) { Bindings::iterator j2 = v2.attrs->find(state.symbols.create(state.sValue)); if (j2 == v2.attrs->end()) - throw TypeError(format("'value' attribute missing in a call to 'listToAttrs', at %1%") % pos); - + throw TypeError( + ErrorInfo { + .hint = hintfmt("'value' attribute missing in a call to 'listToAttrs'"), + .nixCode = NixCode { .errPos = pos } + }); v.attrs->push_back(Attr(sym, j2->value, j2->pos)); } } @@ -1351,7 +1486,11 @@ static void prim_functionArgs(EvalState & state, const Pos & pos, Value * * args { state.forceValue(*args[0], pos); if (args[0]->type != tLambda) - throw TypeError(format("'functionArgs' requires a function, at %1%") % pos); + throw TypeError( + ErrorInfo { + .hint = hintfmt("'functionArgs' requires a function"), + .nixCode = NixCode { .errPos = pos } + }); if (!args[0]->lambda.fun->matchAttrs) { state.mkAttrs(v, 0); @@ -1404,7 +1543,11 @@ static void elemAt(EvalState & state, const Pos & pos, Value & list, int n, Valu { state.forceList(list, pos); if (n < 0 || (unsigned int) n >= list.listSize()) - throw Error(format("list index %1% is out of bounds, at %2%") % n % pos); + throw Error( + ErrorInfo { + .hint = hintfmt("list index %1% is out of bounds", n), + .nixCode = NixCode { .errPos = pos } + }); state.forceValue(*list.listElems()[n], pos); v = *list.listElems()[n]; } @@ -1431,7 +1574,12 @@ static void prim_tail(EvalState & state, const Pos & pos, Value * * args, Value { state.forceList(*args[0], pos); if (args[0]->listSize() == 0) - throw Error(format("'tail' called on an empty list, at %1%") % pos); + throw Error( + ErrorInfo { + .hint = hintfmt("'tail' called on an empty list"), + .nixCode = NixCode { .errPos = pos } + }); + state.mkList(v, args[0]->listSize() - 1); for (unsigned int n = 0; n < v.listSize(); ++n) v.listElems()[n] = args[0]->listElems()[n + 1]; @@ -1572,7 +1720,12 @@ static void prim_genList(EvalState & state, const Pos & pos, Value * * args, Val auto len = state.forceInt(*args[1], pos); if (len < 0) - throw EvalError(format("cannot create list of size %1%, at %2%") % len % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("cannot create list of size %1%", len), + .nixCode = NixCode { .errPos = pos } + }); + state.mkList(v, len); @@ -1730,7 +1883,12 @@ static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value & state.forceValue(*args[1], pos); NixFloat f2 = state.forceFloat(*args[1], pos); - if (f2 == 0) throw EvalError(format("division by zero, at %1%") % pos); + if (f2 == 0) + throw EvalError( + ErrorInfo { + .hint = hintfmt("division by zero"), + .nixCode = NixCode { .errPos = pos } + }); if (args[0]->type == tFloat || args[1]->type == tFloat) { mkFloat(v, state.forceFloat(*args[0], pos) / state.forceFloat(*args[1], pos)); @@ -1739,7 +1897,12 @@ static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value & NixInt i2 = state.forceInt(*args[1], pos); /* Avoid division overflow as it might raise SIGFPE. */ if (i1 == std::numeric_limits::min() && i2 == -1) - throw EvalError(format("overflow in integer division, at %1%") % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("overflow in integer division"), + .nixCode = NixCode { .errPos = pos } + }); + mkInt(v, i1 / i2); } } @@ -1795,7 +1958,12 @@ static void prim_substring(EvalState & state, const Pos & pos, Value * * args, V PathSet context; string s = state.coerceToString(pos, *args[2], context); - if (start < 0) throw EvalError(format("negative start position in 'substring', at %1%") % pos); + if (start < 0) + throw EvalError( + ErrorInfo { + .hint = hintfmt("negative start position in 'substring'"), + .nixCode = NixCode { .errPos = pos } + }); mkString(v, (unsigned int) start >= s.size() ? "" : string(s, start, len), context); } @@ -1815,7 +1983,11 @@ static void prim_hashString(EvalState & state, const Pos & pos, Value * * args, string type = state.forceStringNoCtx(*args[0], pos); HashType ht = parseHashType(type); if (ht == htUnknown) - throw Error(format("unknown hash type '%1%', at %2%") % type % pos); + throw Error( + ErrorInfo { + .hint = hintfmt("unknown hash type '%1%'", type), + .nixCode = NixCode { .errPos = pos } + }); PathSet context; // discarded string s = state.forceString(*args[1], context, pos); @@ -1857,10 +2029,18 @@ void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v) } catch (std::regex_error &e) { if (e.code() == std::regex_constants::error_space) { - // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ - throw EvalError("memory limit exceeded by regular expression '%s', at %s", re, pos); + // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ + throw EvalError( + ErrorInfo { + .hint = hintfmt("memory limit exceeded by regular expression '%s'", re), + .nixCode = NixCode { .errPos = pos } + }); } else { - throw EvalError("invalid regular expression '%s', at %s", re, pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("invalid regular expression '%s'", re), + .nixCode = NixCode { .errPos = pos } + }); } } } @@ -1924,10 +2104,18 @@ static void prim_split(EvalState & state, const Pos & pos, Value * * args, Value } catch (std::regex_error &e) { if (e.code() == std::regex_constants::error_space) { - // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ - throw EvalError("memory limit exceeded by regular expression '%s', at %s", re, pos); + // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ + throw EvalError( + ErrorInfo { + .hint = hintfmt("memory limit exceeded by regular expression '%s'", re), + .nixCode = NixCode { .errPos = pos } + }); } else { - throw EvalError("invalid regular expression '%s', at %s", re, pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("invalid regular expression '%s'", re), + .nixCode = NixCode { .errPos = pos } + }); } } } @@ -1958,7 +2146,11 @@ static void prim_replaceStrings(EvalState & state, const Pos & pos, Value * * ar state.forceList(*args[0], pos); state.forceList(*args[1], pos); if (args[0]->listSize() != args[1]->listSize()) - throw EvalError(format("'from' and 'to' arguments to 'replaceStrings' have different lengths, at %1%") % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("'from' and 'to' arguments to 'replaceStrings' have different lengths"), + .nixCode = NixCode { .errPos = pos } + }); vector from; from.reserve(args[0]->listSize()); diff --git a/src/libexpr/primops/context.cc b/src/libexpr/primops/context.cc index 94fa0158c..7f895fc01 100644 --- a/src/libexpr/primops/context.cc +++ b/src/libexpr/primops/context.cc @@ -146,7 +146,11 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg auto sAllOutputs = state.symbols.create("allOutputs"); for (auto & i : *args[1]->attrs) { if (!state.store->isStorePath(i.name)) - throw EvalError("Context key '%s' is not a store path, at %s", i.name, i.pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("Context key '%s' is not a store path", i.name), + .nixCode = NixCode { .errPos = *i.pos } + }); if (!settings.readOnlyMode) state.store->ensurePath(state.store->parseStorePath(i.name)); state.forceAttrs(*i.value, *i.pos); @@ -160,7 +164,11 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg if (iter != i.value->attrs->end()) { if (state.forceBool(*iter->value, *iter->pos)) { if (!isDerivation(i.name)) { - throw EvalError("Tried to add all-outputs context of %s, which is not a derivation, to a string, at %s", i.name, i.pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", i.name), + .nixCode = NixCode { .errPos = *i.pos } + }); } context.insert("=" + string(i.name)); } @@ -170,7 +178,11 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg if (iter != i.value->attrs->end()) { state.forceList(*iter->value, *iter->pos); if (iter->value->listSize() && !isDerivation(i.name)) { - throw EvalError("Tried to add derivation output context of %s, which is not a derivation, to a string, at %s", i.name, i.pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", i.name), + .nixCode = NixCode { .errPos = *i.pos } + }); } for (unsigned int n = 0; n < iter->value->listSize(); ++n) { auto name = state.forceStringNoCtx(*iter->value->listElems()[n], *iter->pos); diff --git a/src/libexpr/primops/fetchGit.cc b/src/libexpr/primops/fetchGit.cc index 1a8798fcc..52826b56c 100644 --- a/src/libexpr/primops/fetchGit.cc +++ b/src/libexpr/primops/fetchGit.cc @@ -35,11 +35,19 @@ static void prim_fetchGit(EvalState & state, const Pos & pos, Value * * args, Va else if (n == "submodules") fetchSubmodules = state.forceBool(*attr.value, *attr.pos); else - throw EvalError("unsupported argument '%s' to 'fetchGit', at %s", attr.name, *attr.pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("unsupported argument '%s' to 'fetchGit'", attr.name), + .nixCode = NixCode { .errPos = *attr.pos } + }); } if (url.empty()) - throw EvalError(format("'url' argument required, at %1%") % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("'url' argument required"), + .nixCode = NixCode { .errPos = pos } + }); } else url = state.coerceToString(pos, *args[0], context, false, false); diff --git a/src/libexpr/primops/fetchMercurial.cc b/src/libexpr/primops/fetchMercurial.cc index 0a1ba49d5..bb008ba6b 100644 --- a/src/libexpr/primops/fetchMercurial.cc +++ b/src/libexpr/primops/fetchMercurial.cc @@ -38,11 +38,19 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar else if (n == "name") name = state.forceStringNoCtx(*attr.value, *attr.pos); else - throw EvalError("unsupported argument '%s' to 'fetchMercurial', at %s", attr.name, *attr.pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("unsupported argument '%s' to 'fetchMercurial'", attr.name), + .nixCode = NixCode { .errPos = *attr.pos } + }); } if (url.empty()) - throw EvalError(format("'url' argument required, at %1%") % pos); + throw EvalError( + ErrorInfo { + .hint = hintfmt("'url' argument required"), + .nixCode = NixCode { .errPos = pos } + }); } else url = state.coerceToString(pos, *args[0], context, false, false); diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc index 97254aa04..62bc4f433 100644 --- a/src/libexpr/primops/fetchTree.cc +++ b/src/libexpr/primops/fetchTree.cc @@ -66,7 +66,11 @@ static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, V } if (!attrs.count("type")) - throw Error("attribute 'type' is missing in call to 'fetchTree', at %s", pos); + throw Error( + ErrorInfo { + .hint = hintfmt("attribute 'type' is missing in call to 'fetchTree'"), + .nixCode = NixCode { .errPos = pos } + }); input = fetchers::inputFromAttrs(attrs); } else @@ -107,13 +111,20 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, else if (n == "name") name = state.forceStringNoCtx(*attr.value, *attr.pos); else - throw EvalError("unsupported argument '%s' to '%s', at %s", - attr.name, who, *attr.pos); - } + throw EvalError( + ErrorInfo { + .hint = hintfmt("unsupported argument '%s' to '%s'", + attr.name, who), + .nixCode = NixCode { .errPos = *attr.pos } + }); + } if (!url) - throw EvalError("'url' argument required, at %s", pos); - + throw EvalError( + ErrorInfo { + .hint = hintfmt("'url' argument required"), + .nixCode = NixCode { .errPos = pos } + }); } else url = state.forceStringNoCtx(*args[0], pos); diff --git a/src/libexpr/primops/fromTOML.cc b/src/libexpr/primops/fromTOML.cc index c43324dbb..948069401 100644 --- a/src/libexpr/primops/fromTOML.cc +++ b/src/libexpr/primops/fromTOML.cc @@ -81,7 +81,11 @@ static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Va try { visit(v, parser(tomlStream).parse()); } catch (std::runtime_error & e) { - throw EvalError("while parsing a TOML string at %s: %s", pos, e.what()); + throw EvalError( + ErrorInfo { + .hint = hintfmt("while parsing a TOML string: %s", e.what()), + .nixCode = NixCode { .errPos = pos } + }); } } diff --git a/src/libexpr/value-to-json.cc b/src/libexpr/value-to-json.cc index 5fe8570ad..6ec8315ba 100644 --- a/src/libexpr/value-to-json.cc +++ b/src/libexpr/value-to-json.cc @@ -79,7 +79,7 @@ void printValueAsJSON(EvalState & state, bool strict, break; default: - throw TypeError(format("cannot convert %1% to JSON") % showType(v)); + throw TypeError("cannot convert %1% to JSON", showType(v)); } } @@ -93,7 +93,7 @@ void printValueAsJSON(EvalState & state, bool strict, void ExternalValueBase::printValueAsJSON(EvalState & state, bool strict, JSONPlaceholder & out, PathSet & context) const { - throw TypeError(format("cannot convert %1% to JSON") % showType()); + throw TypeError("cannot convert %1% to JSON", showType()); } diff --git a/src/libmain/progress-bar.cc b/src/libmain/progress-bar.cc index b287de8a3..20d9915a0 100644 --- a/src/libmain/progress-bar.cc +++ b/src/libmain/progress-bar.cc @@ -129,6 +129,16 @@ public: log(*state, lvl, fs.s); } + void logEI(const ErrorInfo &ei) override + { + auto state(state_.lock()); + + std::stringstream oss; + oss << ei; + + log(*state, ei.level, oss.str()); + } + void log(State & state, Verbosity lvl, const std::string & s) { if (state.active) { diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index 3bbb5cf93..0f2c189a6 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -76,7 +76,7 @@ string getArg(const string & opt, Strings::iterator & i, const Strings::iterator & end) { ++i; - if (i == end) throw UsageError(format("'%1%' requires an argument") % opt); + if (i == end) throw UsageError("'%1%' requires an argument", opt); return *i; } @@ -235,7 +235,7 @@ bool LegacyArgs::processArgs(const Strings & args, bool finish) Strings ss(args); auto pos = ss.begin(); if (!parseArg(pos, ss.end())) - throw UsageError(format("unexpected argument '%1%'") % args.front()); + throw UsageError("unexpected argument '%1%'", args.front()); return true; } @@ -282,7 +282,7 @@ void showManPage(const string & name) restoreSignals(); setenv("MANPATH", settings.nixManDir.c_str(), 1); execlp("man", "man", name.c_str(), nullptr); - throw SysError(format("command 'man %1%' failed") % name.c_str()); + throw SysError("command 'man %1%' failed", name.c_str()); } @@ -290,6 +290,8 @@ int handleExceptions(const string & programName, std::function fun) { ReceiveInterrupts receiveInterrupts; // FIXME: need better place for this + ErrorInfo::programName = programName; + string error = ANSI_RED "error:" ANSI_NORMAL " "; try { try { @@ -305,12 +307,13 @@ int handleExceptions(const string & programName, std::function fun) } catch (Exit & e) { return e.status; } catch (UsageError & e) { - printError( - format(error + "%1%\nTry '%2% --help' for more information.") - % e.what() % programName); + logError(e.info()); + printError("Try '%1% --help' for more information.", programName); return 1; } catch (BaseError & e) { - printError(format(error + "%1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg()); + if (settings.showTrace && e.prefix() != "") + printError(e.prefix()); + logError(e.info()); if (e.prefix() != "" && !settings.showTrace) printError("(use '--show-trace' to show detailed location information)"); return e.status; @@ -347,7 +350,7 @@ RunPager::RunPager() execlp("pager", "pager", nullptr); execlp("less", "less", nullptr); execlp("more", "more", nullptr); - throw SysError(format("executing '%1%'") % pager); + throw SysError("executing '%1%'", pager); }); pid.setKillSignal(SIGINT); diff --git a/src/libmain/shared.hh b/src/libmain/shared.hh index b49574652..f558247c0 100644 --- a/src/libmain/shared.hh +++ b/src/libmain/shared.hh @@ -56,7 +56,7 @@ template N getIntArg(const string & opt, Strings::iterator & i, const Strings::iterator & end, bool allowUnit) { ++i; - if (i == end) throw UsageError(format("'%1%' requires an argument") % opt); + if (i == end) throw UsageError("'%1%' requires an argument", opt); string s = *i; N multiplier = 1; if (allowUnit && !s.empty()) { @@ -66,13 +66,13 @@ template N getIntArg(const string & opt, else if (u == 'M') multiplier = 1ULL << 20; else if (u == 'G') multiplier = 1ULL << 30; else if (u == 'T') multiplier = 1ULL << 40; - else throw UsageError(format("invalid unit specifier '%1%'") % u); + else throw UsageError("invalid unit specifier '%1%'", u); s.resize(s.size() - 1); } } N n; if (!string2Int(s, n)) - throw UsageError(format("'%1%' requires an integer argument") % opt); + throw UsageError("'%1%' requires an integer argument", opt); return n * multiplier; } diff --git a/src/libmain/stack.cc b/src/libmain/stack.cc index e6224de7d..b0a4a4c5d 100644 --- a/src/libmain/stack.cc +++ b/src/libmain/stack.cc @@ -1,4 +1,4 @@ -#include "types.hh" +#include "error.hh" #include #include diff --git a/src/libstore/binary-cache-store.cc b/src/libstore/binary-cache-store.cc index 357962007..649331495 100644 --- a/src/libstore/binary-cache-store.cc +++ b/src/libstore/binary-cache-store.cc @@ -40,14 +40,14 @@ void BinaryCacheStore::init() upsertFile(cacheInfoFile, "StoreDir: " + storeDir + "\n", "text/x-nix-cache-info"); } else { for (auto & line : tokenizeString(*cacheInfo, "\n")) { - size_t colon = line.find(':'); - if (colon == std::string::npos) continue; + size_t colon= line.find(':'); + if (colon ==std::string::npos) continue; auto name = line.substr(0, colon); auto value = trim(line.substr(colon + 1, std::string::npos)); if (name == "StoreDir") { if (value != storeDir) - throw Error(format("binary cache '%s' is for Nix stores with prefix '%s', not '%s'") - % getUri() % value % storeDir); + throw Error("binary cache '%s' is for Nix stores with prefix '%s', not '%s'", + getUri(), value, storeDir); } else if (name == "WantMassQuery") { wantMassQuery.setDefault(value == "1" ? "true" : "false"); } else if (name == "Priority") { @@ -287,7 +287,7 @@ void BinaryCacheStore::narFromPath(const StorePath & storePath, Sink & sink) try { getFile(info->url, *decompressor); } catch (NoSuchBinaryCacheFile & e) { - throw SubstituteGone(e.what()); + throw SubstituteGone(e.info()); } decompressor->finish(); diff --git a/src/libstore/build.cc b/src/libstore/build.cc index 3ab6220e3..de393e837 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -453,7 +453,7 @@ static void commonChildInit(Pipe & logPipe) that e.g. ssh cannot open /dev/tty) and it doesn't receive terminal signals. */ if (setsid() == -1) - throw SysError(format("creating a new session")); + throw SysError("creating a new session"); /* Dup the write side of the logger pipe into stderr. */ if (dup2(logPipe.writeSide.get(), STDERR_FILENO) == -1) @@ -466,7 +466,7 @@ static void commonChildInit(Pipe & logPipe) /* Reroute stdin to /dev/null. */ int fdDevNull = open(pathNullDevice.c_str(), O_RDWR); if (fdDevNull == -1) - throw SysError(format("cannot open '%1%'") % pathNullDevice); + throw SysError("cannot open '%1%'", pathNullDevice); if (dup2(fdDevNull, STDIN_FILENO) == -1) throw SysError("cannot dup null device into stdin"); close(fdDevNull); @@ -488,12 +488,18 @@ void handleDiffHook( auto diffRes = runProgram(diffHookOptions); if (!statusOk(diffRes.first)) - throw ExecError(diffRes.first, fmt("diff-hook program '%1%' %2%", diffHook, statusToString(diffRes.first))); + throw ExecError(diffRes.first, + "diff-hook program '%1%' %2%", + diffHook, + statusToString(diffRes.first)); if (diffRes.second != "") printError(chomp(diffRes.second)); } catch (Error & error) { - printError("diff hook execution failed: %s", error.what()); + ErrorInfo ei = error.info(); + ei.hint = hintfmt("diff hook execution failed: %s", + (error.info().hint.has_value() ? error.info().hint->str() : "")); + logError(ei); } } } @@ -542,37 +548,37 @@ bool UserLock::findFreeUser() { /* Get the members of the build-users-group. */ struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str()); if (!gr) - throw Error(format("the group '%1%' specified in 'build-users-group' does not exist") - % settings.buildUsersGroup); + throw Error("the group '%1%' specified in 'build-users-group' does not exist", + settings.buildUsersGroup); gid = gr->gr_gid; /* Copy the result of getgrnam. */ Strings users; for (char * * p = gr->gr_mem; *p; ++p) { - debug(format("found build user '%1%'") % *p); + debug("found build user '%1%'", *p); users.push_back(*p); } if (users.empty()) - throw Error(format("the build users group '%1%' has no members") - % settings.buildUsersGroup); + throw Error("the build users group '%1%' has no members", + settings.buildUsersGroup); /* Find a user account that isn't currently in use for another build. */ for (auto & i : users) { - debug(format("trying user '%1%'") % i); + debug("trying user '%1%'", i); struct passwd * pw = getpwnam(i.c_str()); if (!pw) - throw Error(format("the user '%1%' in the group '%2%' does not exist") - % i % settings.buildUsersGroup); + throw Error("the user '%1%' in the group '%2%' does not exist", + i, settings.buildUsersGroup); fnUserLock = (format("%1%/userpool/%2%") % settings.nixStateDir % pw->pw_uid).str(); AutoCloseFD fd = open(fnUserLock.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600); if (!fd) - throw SysError(format("opening user lock '%1%'") % fnUserLock); + throw SysError("opening user lock '%1%'", fnUserLock); if (lockFile(fd.get(), ltWrite, false)) { fdUserLock = std::move(fd); @@ -581,8 +587,8 @@ bool UserLock::findFreeUser() { /* Sanity check... */ if (uid == getuid() || uid == geteuid()) - throw Error(format("the Nix user should not be a member of '%1%'") - % settings.buildUsersGroup); + throw Error("the Nix user should not be a member of '%1%'", + settings.buildUsersGroup); #if __linux__ /* Get the list of supplementary groups of this build user. This @@ -592,7 +598,7 @@ bool UserLock::findFreeUser() { int err = getgrouplist(pw->pw_name, pw->pw_gid, supplementaryGIDs.data(), &ngroups); if (err == -1) - throw Error(format("failed to get list of supplementary groups for '%1%'") % pw->pw_name); + throw Error("failed to get list of supplementary groups for '%1%'", pw->pw_name); supplementaryGIDs.resize(ngroups); #endif @@ -601,6 +607,7 @@ bool UserLock::findFreeUser() { return true; } } + return false; } @@ -1151,7 +1158,10 @@ void DerivationGoal::loadDerivation() trace("loading derivation"); if (nrFailed != 0) { - printError("cannot build missing derivation '%s'", worker.store.printStorePath(drvPath)); + logError({ + .name = "missing derivation during build", + .hint = hintfmt("cannot build missing derivation '%s'", worker.store.printStorePath(drvPath)) + }); done(BuildResult::MiscFailure); return; } @@ -1302,8 +1312,12 @@ void DerivationGoal::repairClosure() /* Check each path (slow!). */ for (auto & i : outputClosure) { if (worker.pathContentsGood(i)) continue; - printError("found corrupted or missing path '%s' in the output closure of '%s'", - worker.store.printStorePath(i), worker.store.printStorePath(drvPath)); + logError({ + .name = "Corrupt path in closure", + .hint = hintfmt( + "found corrupted or missing path '%s' in the output closure of '%s'", + worker.store.printStorePath(i), worker.store.printStorePath(drvPath)) + }); auto drvPath2 = outputsToDrv.find(i); if (drvPath2 == outputsToDrv.end()) addWaitee(worker.makeSubstitutionGoal(i, Repair)); @@ -1337,8 +1351,12 @@ void DerivationGoal::inputsRealised() if (nrFailed != 0) { if (!useDerivation) throw Error("some dependencies of '%s' are missing", worker.store.printStorePath(drvPath)); - printError("cannot build derivation '%s': %s dependencies couldn't be built", - worker.store.printStorePath(drvPath), nrFailed); + logError({ + .name = "Dependencies could not be built", + .hint = hintfmt( + "cannot build derivation '%s': %s dependencies couldn't be built", + worker.store.printStorePath(drvPath), nrFailed) + }); done(BuildResult::DependencyFailed); return; } @@ -1523,7 +1541,7 @@ void DerivationGoal::tryLocalBuild() { startBuilder(); } catch (BuildError & e) { - printError(e.msg()); + logError(e.info()); outputLocks.unlock(); buildUser.reset(); worker.permanentFailure = true; @@ -1740,7 +1758,7 @@ void DerivationGoal::buildDone() outputLocks.unlock(); } catch (BuildError & e) { - printError(e.msg()); + logError(e.info()); outputLocks.unlock(); @@ -1803,7 +1821,7 @@ HookReply DerivationGoal::tryBuildHook() } } - debug(format("hook reply is '%1%'") % reply); + debug("hook reply is '%1%'", reply); if (reply == "decline") return rpDecline; @@ -1819,8 +1837,12 @@ HookReply DerivationGoal::tryBuildHook() } catch (SysError & e) { if (e.errNo == EPIPE) { - printError("build hook died unexpectedly: %s", - chomp(drainFD(worker.hook->fromHook.readSide.get()))); + logError({ + .name = "Build hook died", + .hint = hintfmt( + "build hook died unexpectedly: %s", + chomp(drainFD(worker.hook->fromHook.readSide.get()))) + }); worker.hook = 0; return rpDecline; } else @@ -2000,7 +2022,7 @@ void DerivationGoal::startBuilder() string s = get(drv->env, "exportReferencesGraph").value_or(""); Strings ss = tokenizeString(s); if (ss.size() % 2 != 0) - throw BuildError(format("odd number of tokens in 'exportReferencesGraph': '%1%'") % s); + throw BuildError("odd number of tokens in 'exportReferencesGraph': '%1%'", s); for (Strings::iterator i = ss.begin(); i != ss.end(); ) { string fileName = *i++; static std::regex regex("[A-Za-z_][A-Za-z0-9_.-]*"); @@ -2049,7 +2071,7 @@ void DerivationGoal::startBuilder() worker.store.computeFSClosure(worker.store.parseStorePath(worker.store.toStorePath(i.second.source)), closure); } catch (InvalidPath & e) { } catch (Error & e) { - throw Error(format("while processing 'sandbox-paths': %s") % e.what()); + throw Error("while processing 'sandbox-paths': %s", e.what()); } for (auto & i : closure) { auto p = worker.store.printStorePath(i); @@ -2096,10 +2118,10 @@ void DerivationGoal::startBuilder() printMsg(lvlChatty, format("setting up chroot environment in '%1%'") % chrootRootDir); if (mkdir(chrootRootDir.c_str(), 0750) == -1) - throw SysError(format("cannot create '%1%'") % chrootRootDir); + throw SysError("cannot create '%1%'", chrootRootDir); if (buildUser && chown(chrootRootDir.c_str(), 0, buildUser->getGID()) == -1) - throw SysError(format("cannot change ownership of '%1%'") % chrootRootDir); + throw SysError("cannot change ownership of '%1%'", chrootRootDir); /* Create a writable /tmp in the chroot. Many builders need this. (Of course they should really respect $TMPDIR @@ -2143,7 +2165,7 @@ void DerivationGoal::startBuilder() chmod_(chrootStoreDir, 01775); if (buildUser && chown(chrootStoreDir.c_str(), 0, buildUser->getGID()) == -1) - throw SysError(format("cannot change ownership of '%1%'") % chrootStoreDir); + throw SysError("cannot change ownership of '%1%'", chrootStoreDir); for (auto & i : inputPaths) { auto p = worker.store.printStorePath(i); @@ -2176,7 +2198,7 @@ void DerivationGoal::startBuilder() if (needsHashRewrite()) { if (pathExists(homeDir)) - throw Error(format("home directory '%1%' exists; please remove it to assure purity of builds without sandboxing") % homeDir); + throw Error("home directory '%1%' exists; please remove it to assure purity of builds without sandboxing", homeDir); /* We're not doing a chroot build, but we have some valid output paths. Since we can't just overwrite or delete @@ -2221,8 +2243,7 @@ void DerivationGoal::startBuilder() if (line == "extra-sandbox-paths" || line == "extra-chroot-dirs") { state = stExtraChrootDirs; } else { - throw Error(format("unknown pre-build hook command '%1%'") - % line); + throw Error("unknown pre-build hook command '%1%'", line); } } else if (state == stExtraChrootDirs) { if (line == "") { @@ -2244,7 +2265,7 @@ void DerivationGoal::startBuilder() startDaemon(); /* Run the builder. */ - printMsg(lvlChatty, format("executing builder '%1%'") % drv->builder); + printMsg(lvlChatty, "executing builder '%1%'", drv->builder); /* Create the log file. */ Path logFile = openLogFile(); @@ -2982,7 +3003,7 @@ void DerivationGoal::chownToBuilder(const Path & path) { if (!buildUser) return; if (chown(path.c_str(), buildUser->getUID(), buildUser->getGID()) == -1) - throw SysError(format("cannot change ownership of '%1%'") % path); + throw SysError("cannot change ownership of '%1%'", path); } @@ -3119,7 +3140,7 @@ void DerivationGoal::runChild() /* Bind-mount chroot directory to itself, to treat it as a different filesystem from /, as needed for pivot_root. */ if (mount(chrootRootDir.c_str(), chrootRootDir.c_str(), 0, MS_BIND, 0) == -1) - throw SysError(format("unable to bind mount '%1%'") % chrootRootDir); + throw SysError("unable to bind mount '%1%'", chrootRootDir); /* Bind-mount the sandbox's Nix store onto itself so that we can mark it as a "shared" subtree, allowing bind @@ -3181,7 +3202,7 @@ void DerivationGoal::runChild() filesystem that we want in the chroot environment. */ auto doBind = [&](const Path & source, const Path & target, bool optional = false) { - debug(format("bind mounting '%1%' to '%2%'") % source % target); + debug("bind mounting '%1%' to '%2%'", source, target); struct stat st; if (stat(source.c_str(), &st) == -1) { if (optional && errno == ENOENT) @@ -3253,16 +3274,16 @@ void DerivationGoal::runChild() /* Do the chroot(). */ if (chdir(chrootRootDir.c_str()) == -1) - throw SysError(format("cannot change directory to '%1%'") % chrootRootDir); + throw SysError("cannot change directory to '%1%'", chrootRootDir); if (mkdir("real-root", 0) == -1) throw SysError("cannot create real-root directory"); if (pivot_root(".", "real-root") == -1) - throw SysError(format("cannot pivot old root directory onto '%1%'") % (chrootRootDir + "/real-root")); + throw SysError("cannot pivot old root directory onto '%1%'", (chrootRootDir + "/real-root")); if (chroot(".") == -1) - throw SysError(format("cannot change root directory to '%1%'") % chrootRootDir); + throw SysError("cannot change root directory to '%1%'", chrootRootDir); if (umount2("real-root", MNT_DETACH) == -1) throw SysError("cannot unmount real root filesystem"); @@ -3283,7 +3304,7 @@ void DerivationGoal::runChild() #endif if (chdir(tmpDirInSandbox.c_str()) == -1) - throw SysError(format("changing into '%1%'") % tmpDir); + throw SysError("changing into '%1%'", tmpDir); /* Close all other file descriptors. */ closeMostFDs({STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}); @@ -3422,9 +3443,9 @@ void DerivationGoal::runChild() sandboxProfile += "(allow file-read* file-write* process-exec\n"; for (auto & i : dirsInChroot) { if (i.first != i.second.source) - throw Error(format( - "can't map '%1%' to '%2%': mismatched impure paths not supported on Darwin") - % i.first % i.second.source); + throw Error( + "can't map '%1%' to '%2%': mismatched impure paths not supported on Darwin", + i.first, i.second.source); string path = i.first; struct stat st; @@ -3515,7 +3536,7 @@ void DerivationGoal::runChild() else if (drv->builder == "builtin:unpack-channel") builtinUnpackChannel(drv2); else - throw Error(format("unsupported builtin function '%1%'") % string(drv->builder, 8)); + throw Error("unsupported builtin function '%1%'", string(drv->builder, 8)); _exit(0); } catch (std::exception & e) { writeFull(STDERR_FILENO, "error: " + string(e.what()) + "\n"); @@ -3525,7 +3546,7 @@ void DerivationGoal::runChild() execve(builder, stringsToCharPtrs(args).data(), stringsToCharPtrs(envStrs).data()); - throw SysError(format("executing '%1%'") % drv->builder); + throw SysError("executing '%1%'", drv->builder); } catch (std::exception & e) { writeFull(STDERR_FILENO, "\1while setting up the build environment: " + string(e.what()) + "\n"); @@ -3558,7 +3579,7 @@ static void moveCheckToStore(const Path & src, const Path & dst) directory's parent link ".."). */ struct stat st; if (lstat(src.c_str(), &st) == -1) { - throw SysError(format("getting attributes of path '%1%'") % src); + throw SysError("getting attributes of path '%1%'", src); } bool changePerm = (geteuid() && S_ISDIR(st.st_mode) && !(st.st_mode & S_IWUSR)); @@ -3567,7 +3588,7 @@ static void moveCheckToStore(const Path & src, const Path & dst) chmod_(src, st.st_mode | S_IWUSR); if (rename(src.c_str(), dst.c_str())) - throw SysError(format("renaming '%1%' to '%2%'") % src % dst); + throw SysError("renaming '%1%' to '%2%'", src, dst); if (changePerm) chmod_(dst, st.st_mode); @@ -3633,7 +3654,7 @@ void DerivationGoal::registerOutputs() replaceValidPath(path, actualPath); else if (buildMode != bmCheck && rename(actualPath.c_str(), worker.store.toRealPath(path).c_str()) == -1) - throw SysError(format("moving build output '%1%' from the sandbox to the Nix store") % path); + throw SysError("moving build output '%1%' from the sandbox to the Nix store", path); } if (buildMode != bmCheck) actualPath = worker.store.toRealPath(path); } @@ -3654,13 +3675,16 @@ void DerivationGoal::registerOutputs() user. */ if ((!S_ISLNK(st.st_mode) && (st.st_mode & (S_IWGRP | S_IWOTH))) || (buildUser && st.st_uid != buildUser->getUID())) - throw BuildError(format("suspicious ownership or permission on '%1%'; rejecting this build output") % path); + throw BuildError("suspicious ownership or permission on '%1%'; rejecting this build output", path); #endif /* Apply hash rewriting if necessary. */ bool rewritten = false; if (!outputRewrites.empty()) { - printError(format("warning: rewriting hashes in '%1%'; cross fingers") % path); + logWarning({ + .name = "Rewriting hashes", + .hint = hintfmt("rewriting hashes in '%1%'; cross fingers", path) + }); /* Canonicalise first. This ensures that the path we're rewriting doesn't contain a hard link to /etc/shadow or @@ -3692,8 +3716,9 @@ void DerivationGoal::registerOutputs() /* The output path should be a regular file without execute permission. */ if (!S_ISREG(st.st_mode) || (st.st_mode & S_IXUSR) != 0) throw BuildError( - format("output path '%1%' should be a non-executable regular file " - "since recursive hashing is not enabled (outputHashMode=flat)") % path); + "output path '%1%' should be a non-executable regular file " + "since recursive hashing is not enabled (outputHashMode=flat)", + path); } /* Check the hash. In hash mode, move the path produced by @@ -3823,10 +3848,10 @@ void DerivationGoal::registerOutputs() result.isNonDeterministic = true; Path prev = worker.store.printStorePath(i->second.path) + checkSuffix; bool prevExists = keepPreviousRound && pathExists(prev); - auto msg = prevExists - ? fmt("output '%s' of '%s' differs from '%s' from previous round", + hintformat hint = prevExists + ? hintfmt("output '%s' of '%s' differs from '%s' from previous round", worker.store.printStorePath(i->second.path), worker.store.printStorePath(drvPath), prev) - : fmt("output '%s' of '%s' differs from previous round", + : hintfmt("output '%s' of '%s' differs from previous round", worker.store.printStorePath(i->second.path), worker.store.printStorePath(drvPath)); handleDiffHook( @@ -3836,9 +3861,14 @@ void DerivationGoal::registerOutputs() worker.store.printStorePath(drvPath), tmpDir); if (settings.enforceDeterminism) - throw NotDeterministic(msg); + throw NotDeterministic(hint); + + logError({ + .name = "Output determinism error", + .hint = hint + }); + - printError(msg); curRound = nrRounds; // we know enough, bail out early } } @@ -4056,7 +4086,7 @@ Path DerivationGoal::openLogFile() settings.compressLog ? ".bz2" : ""); fdLogFile = open(logFileName.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0666); - if (!fdLogFile) throw SysError(format("creating log file '%1%'") % logFileName); + if (!fdLogFile) throw SysError("creating log file '%1%'", logFileName); logFileSink = std::make_shared(fdLogFile.get()); @@ -4102,9 +4132,12 @@ void DerivationGoal::handleChildOutput(int fd, const string & data) { logSize += data.size(); if (settings.maxLogSize && logSize > settings.maxLogSize) { - printError( - format("%1% killed after writing more than %2% bytes of log output") - % getName() % settings.maxLogSize); + logError({ + .name = "Max log size exceeded", + .hint = hintfmt( + "%1% killed after writing more than %2% bytes of log output", + getName(), settings.maxLogSize) + }); killChild(); done(BuildResult::LogLimitExceeded); return; @@ -4389,7 +4422,7 @@ void SubstitutionGoal::tryNext() throw; } catch (Error & e) { if (settings.tryFallback) { - printError(e.what()); + logError(e.info()); tryNext(); return; } @@ -4415,8 +4448,11 @@ void SubstitutionGoal::tryNext() && !sub->isTrusted && !info->checkSignatures(worker.store, worker.store.getPublicKeys())) { - printError("warning: substituter '%s' does not have a valid signature for path '%s'", - sub->getUri(), worker.store.printStorePath(storePath)); + logWarning({ + .name = "Invalid path signature", + .hint = hintfmt("substituter '%s' does not have a valid signature for path '%s'", + sub->getUri(), worker.store.printStorePath(storePath)) + }); tryNext(); return; } @@ -4559,7 +4595,6 @@ void SubstitutionGoal::handleEOF(int fd) if (fd == outPipe.readSide.get()) worker.wakeUp(shared_from_this()); } - ////////////////////////////////////////////////////////////////////// @@ -4776,9 +4811,9 @@ void Worker::run(const Goals & _topGoals) if (!children.empty() || !waitingForAWhile.empty()) waitForInput(); else { - if (awake.empty() && 0 == settings.maxBuildJobs) throw Error( - "unable to start any build; either increase '--max-jobs' " - "or enable remote builds"); + if (awake.empty() && 0 == settings.maxBuildJobs) + throw Error("unable to start any build; either increase '--max-jobs' " + "or enable remote builds"); assert(!awake.empty()); } } @@ -4791,7 +4826,6 @@ void Worker::run(const Goals & _topGoals) assert(!settings.keepGoing || children.empty()); } - void Worker::waitForInput() { printMsg(lvlVomit, "waiting for children"); @@ -4830,7 +4864,7 @@ void Worker::waitForInput() if (!waitingForAWhile.empty()) { useTimeout = true; if (lastWokenUp == steady_time_point::min()) - printError("waiting for locks, build slots or build users..."); + printInfo("waiting for locks, build slots or build users..."); if (lastWokenUp == steady_time_point::min() || lastWokenUp > before) lastWokenUp = before; timeout = std::max(1L, (long) std::chrono::duration_cast( @@ -4879,15 +4913,15 @@ void Worker::waitForInput() // FIXME: is there a cleaner way to handle pt close // than EIO? Is this even standard? if (rd == 0 || (rd == -1 && errno == EIO)) { - debug(format("%1%: got EOF") % goal->getName()); + debug("%1%: got EOF", goal->getName()); goal->handleEOF(k); j->fds.erase(k); } else if (rd == -1) { if (errno != EINTR) throw SysError("%s: read failed", goal->getName()); } else { - printMsg(lvlVomit, format("%1%: read %2% bytes") - % goal->getName() % rd); + printMsg(lvlVomit, "%1%: read %2% bytes", + goal->getName(), rd); string data((char *) buffer.data(), rd); j->lastOutput = after; goal->handleChildOutput(k, data); @@ -4900,9 +4934,12 @@ void Worker::waitForInput() j->respectTimeouts && after - j->lastOutput >= std::chrono::seconds(settings.maxSilentTime)) { - printError( - format("%1% timed out after %2% seconds of silence") - % goal->getName() % settings.maxSilentTime); + logError({ + .name = "Silent build timeout", + .hint = hintfmt( + "%1% timed out after %2% seconds of silence", + goal->getName(), settings.maxSilentTime) + }); goal->timedOut(); } @@ -4911,9 +4948,12 @@ void Worker::waitForInput() j->respectTimeouts && after - j->timeStarted >= std::chrono::seconds(settings.buildTimeout)) { - printError( - format("%1% timed out after %2% seconds") - % goal->getName() % settings.buildTimeout); + logError({ + .name = "Build timeout", + .hint = hintfmt( + "%1% timed out after %2% seconds", + goal->getName(), settings.buildTimeout) + }); goal->timedOut(); } } @@ -4972,7 +5012,11 @@ bool Worker::pathContentsGood(const StorePath & path) res = info->narHash == nullHash || info->narHash == current.first; } pathContentsGoodCache.insert_or_assign(path.clone(), res); - if (!res) printError("path '%s' is corrupted or missing!", store.printStorePath(path)); + if (!res) + logError({ + .name = "Corrupted path", + .hint = hintfmt("path '%s' is corrupted or missing!", store.printStorePath(path)) + }); return res; } @@ -5028,7 +5072,6 @@ void LocalStore::buildPaths(const std::vector & drvPaths, throw Error(worker.exitStatus(), "build of %s failed", showPaths(failed)); } - BuildResult LocalStore::buildDerivation(const StorePath & drvPath, const BasicDerivation & drv, BuildMode buildMode) { diff --git a/src/libstore/builtins/buildenv.cc b/src/libstore/builtins/buildenv.cc index 1b802d908..6c493ed77 100644 --- a/src/libstore/builtins/buildenv.cc +++ b/src/libstore/builtins/buildenv.cc @@ -22,7 +22,11 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir, srcFiles = readDirectory(srcDir); } catch (SysError & e) { if (e.errNo == ENOTDIR) { - printError("warning: not including '%s' in the user environment because it's not a directory", srcDir); + logWarning( + ErrorInfo { + .name = "Create links - directory", + .hint = hintfmt("not including '%s' in the user environment because it's not a directory", srcDir) + }); return; } throw; @@ -41,7 +45,11 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir, throw SysError("getting status of '%1%'", srcFile); } catch (SysError & e) { if (e.errNo == ENOENT || e.errNo == ENOTDIR) { - printError("warning: skipping dangling symlink '%s'", dstFile); + logWarning( + ErrorInfo { + .name = "Create links - skipping symlink", + .hint = hintfmt("skipping dangling symlink '%s'", dstFile) + }); continue; } throw; @@ -72,15 +80,15 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir, if (!S_ISDIR(lstat(target).st_mode)) throw Error("collision between '%1%' and non-directory '%2%'", srcFile, target); if (unlink(dstFile.c_str()) == -1) - throw SysError(format("unlinking '%1%'") % dstFile); + throw SysError("unlinking '%1%'", dstFile); if (mkdir(dstFile.c_str(), 0755) == -1) - throw SysError(format("creating directory '%1%'")); + throw SysError("creating directory '%1%'", dstFile); createLinks(state, target, dstFile, state.priorities[dstFile]); createLinks(state, srcFile, dstFile, priority); continue; } } else if (errno != ENOENT) - throw SysError(format("getting status of '%1%'") % dstFile); + throw SysError("getting status of '%1%'", dstFile); } else { @@ -99,11 +107,11 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir, if (prevPriority < priority) continue; if (unlink(dstFile.c_str()) == -1) - throw SysError(format("unlinking '%1%'") % dstFile); + throw SysError("unlinking '%1%'", dstFile); } else if (S_ISDIR(dstSt.st_mode)) throw Error("collision between non-directory '%1%' and directory '%2%'", srcFile, dstFile); } else if (errno != ENOENT) - throw SysError(format("getting status of '%1%'") % dstFile); + throw SysError("getting status of '%1%'", dstFile); } createSymlink(srcFile, dstFile); diff --git a/src/libstore/builtins/fetchurl.cc b/src/libstore/builtins/fetchurl.cc index 486babf14..2048f8f87 100644 --- a/src/libstore/builtins/fetchurl.cc +++ b/src/libstore/builtins/fetchurl.cc @@ -18,7 +18,7 @@ void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData) auto getAttr = [&](const string & name) { auto i = drv.env.find(name); - if (i == drv.env.end()) throw Error(format("attribute '%s' missing") % name); + if (i == drv.env.end()) throw Error("attribute '%s' missing", name); return i->second; }; @@ -54,7 +54,7 @@ void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData) auto executable = drv.env.find("executable"); if (executable != drv.env.end() && executable->second == "1") { if (chmod(storePath.c_str(), 0755) == -1) - throw SysError(format("making '%1%' executable") % storePath); + throw SysError("making '%1%' executable", storePath); } }; diff --git a/src/libstore/daemon.cc b/src/libstore/daemon.cc index 877c70a03..172cfe3cb 100644 --- a/src/libstore/daemon.cc +++ b/src/libstore/daemon.cc @@ -73,6 +73,18 @@ struct TunnelLogger : public Logger enqueueMsg(*buf.s); } + void logEI(const ErrorInfo & ei) override + { + if (ei.level > verbosity) return; + + std::stringstream oss; + oss << ei; + + StringSink buf; + buf << STDERR_NEXT << oss.str() << "\n"; // (fs.s + "\n"); + enqueueMsg(*buf.s); + } + /* startWork() means that we're starting an operation for which we want to send out stderr to the client. */ void startWork() @@ -744,7 +756,7 @@ static void performOp(TunnelLogger * logger, ref store, } default: - throw Error(format("invalid operation %1%") % op); + throw Error("invalid operation %1%", op); } } diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc index e268c65ff..915e02eed 100644 --- a/src/libstore/derivations.cc +++ b/src/libstore/derivations.cc @@ -87,7 +87,7 @@ static void expect(std::istream & str, const string & s) char s2[s.size()]; str.read(s2, s.size()); if (string(s2, s.size()) != s) - throw FormatError(format("expected string '%1%'") % s); + throw FormatError("expected string '%1%'", s); } @@ -114,7 +114,7 @@ static Path parsePath(std::istream & str) { string s = parseString(str); if (s.size() == 0 || s[0] != '/') - throw FormatError(format("bad path '%1%' in derivation") % s); + throw FormatError("bad path '%1%' in derivation", s); return s; } @@ -196,7 +196,7 @@ Derivation readDerivation(const Store & store, const Path & drvPath) try { return parseDerivation(store, readFile(drvPath)); } catch (FormatError & e) { - throw Error(format("error parsing derivation '%1%': %2%") % drvPath % e.msg()); + throw Error("error parsing derivation '%1%': %2%", drvPath, e.msg()); } } diff --git a/src/libstore/filetransfer.cc b/src/libstore/filetransfer.cc index e9684b3d4..6ca3393ab 100644 --- a/src/libstore/filetransfer.cc +++ b/src/libstore/filetransfer.cc @@ -112,7 +112,7 @@ struct curlFileTransfer : public FileTransfer if (requestHeaders) curl_slist_free_all(requestHeaders); try { if (!done) - fail(FileTransferError(Interrupted, format("download of '%s' was interrupted") % request.uri)); + fail(FileTransferError(Interrupted, "download of '%s' was interrupted", request.uri)); } catch (...) { ignoreException(); } @@ -517,7 +517,7 @@ struct curlFileTransfer : public FileTransfer int running; CURLMcode mc = curl_multi_perform(curlm, &running); if (mc != CURLM_OK) - throw nix::Error(format("unexpected error from curl_multi_perform(): %s") % curl_multi_strerror(mc)); + throw nix::Error("unexpected error from curl_multi_perform(): %s", curl_multi_strerror(mc)); /* Set the promises of any finished requests. */ CURLMsg * msg; @@ -547,7 +547,7 @@ struct curlFileTransfer : public FileTransfer vomit("download thread waiting for %d ms", sleepTimeMs); mc = curl_multi_wait(curlm, extraFDs, 1, sleepTimeMs, &numfds); if (mc != CURLM_OK) - throw nix::Error(format("unexpected error from curl_multi_wait(): %s") % curl_multi_strerror(mc)); + throw nix::Error("unexpected error from curl_multi_wait(): %s", curl_multi_strerror(mc)); nextWakeup = std::chrono::steady_clock::time_point(); @@ -599,7 +599,11 @@ struct curlFileTransfer : public FileTransfer workerThreadMain(); } catch (nix::Interrupted & e) { } catch (std::exception & e) { - printError("unexpected error in download thread: %s", e.what()); + logError({ + .name = "File transfer", + .hint = hintfmt("unexpected error in download thread: %s", + e.what()) + }); } { diff --git a/src/libstore/filetransfer.hh b/src/libstore/filetransfer.hh index 2347f363d..11dca2fe0 100644 --- a/src/libstore/filetransfer.hh +++ b/src/libstore/filetransfer.hh @@ -103,8 +103,9 @@ class FileTransferError : public Error { public: FileTransfer::Error error; - FileTransferError(FileTransfer::Error error, const FormatOrString & fs) - : Error(fs), error(error) + template + FileTransferError(FileTransfer::Error error, const Args & ... args) + : Error(args...), error(error) { } }; diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc index 95a4bc934..92f9328c3 100644 --- a/src/libstore/gc.cc +++ b/src/libstore/gc.cc @@ -38,10 +38,10 @@ AutoCloseFD LocalStore::openGCLock(LockType lockType) AutoCloseFD fdGCLock = open(fnGCLock.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600); if (!fdGCLock) - throw SysError(format("opening global GC lock '%1%'") % fnGCLock); + throw SysError("opening global GC lock '%1%'", fnGCLock); if (!lockFile(fdGCLock.get(), lockType, false)) { - printError(format("waiting for the big garbage collector lock...")); + printInfo("waiting for the big garbage collector lock..."); lockFile(fdGCLock.get(), lockType, true); } @@ -65,8 +65,8 @@ static void makeSymlink(const Path & link, const Path & target) /* Atomically replace the old one. */ if (rename(tempLink.c_str(), link.c_str()) == -1) - throw SysError(format("cannot rename '%1%' to '%2%'") - % tempLink % link); + throw SysError("cannot rename '%1%' to '%2%'", + tempLink , link); } @@ -91,15 +91,15 @@ Path LocalFSStore::addPermRoot(const StorePath & storePath, Path gcRoot(canonPath(_gcRoot)); if (isInStore(gcRoot)) - throw Error(format( + throw Error( "creating a garbage collector root (%1%) in the Nix store is forbidden " - "(are you running nix-build inside the store?)") % gcRoot); + "(are you running nix-build inside the store?)", gcRoot); if (indirect) { /* Don't clobber the link if it already exists and doesn't point to the Nix store. */ if (pathExists(gcRoot) && (!isLink(gcRoot) || !isInStore(readLink(gcRoot)))) - throw Error(format("cannot create symlink '%1%'; already exists") % gcRoot); + throw Error("cannot create symlink '%1%'; already exists", gcRoot); makeSymlink(gcRoot, printStorePath(storePath)); addIndirectRoot(gcRoot); } @@ -109,10 +109,10 @@ Path LocalFSStore::addPermRoot(const StorePath & storePath, Path rootsDir = canonPath((format("%1%/%2%") % stateDir % gcRootsDir).str()); if (string(gcRoot, 0, rootsDir.size() + 1) != rootsDir + "/") - throw Error(format( + throw Error( "path '%1%' is not a valid garbage collector root; " - "it's not in the directory '%2%'") - % gcRoot % rootsDir); + "it's not in the directory '%2%'", + gcRoot, rootsDir); } if (baseNameOf(gcRoot) == std::string(storePath.to_string())) @@ -129,10 +129,13 @@ Path LocalFSStore::addPermRoot(const StorePath & storePath, if (settings.checkRootReachability) { auto roots = findRoots(false); if (roots[storePath.clone()].count(gcRoot) == 0) - printError( - "warning: '%1%' is not in a directory where the garbage collector looks for roots; " + logWarning( + ErrorInfo { + .name = "GC root", + .hint = hintfmt("warning: '%1%' is not in a directory where the garbage collector looks for roots; " "therefore, '%2%' might be removed by the garbage collector", - gcRoot, printStorePath(storePath)); + gcRoot, printStorePath(storePath)) + }); } /* Grab the global GC root, causing us to block while a GC is in @@ -170,7 +173,7 @@ void LocalStore::addTempRoot(const StorePath & path) way. */ struct stat st; if (fstat(state->fdTempRoots.get(), &st) == -1) - throw SysError(format("statting '%1%'") % fnTempRoots); + throw SysError("statting '%1%'", fnTempRoots); if (st.st_size == 0) break; /* The garbage collector deleted this file before we could @@ -216,7 +219,7 @@ void LocalStore::findTempRoots(FDs & fds, Roots & tempRoots, bool censor) if (!*fd) { /* It's okay if the file has disappeared. */ if (errno == ENOENT) continue; - throw SysError(format("opening temporary roots file '%1%'") % path); + throw SysError("opening temporary roots file '%1%'", path); } /* This should work, but doesn't, for some reason. */ @@ -227,7 +230,7 @@ void LocalStore::findTempRoots(FDs & fds, Roots & tempRoots, bool censor) only succeed if the owning process has died. In that case we don't care about its temporary roots. */ if (lockFile(fd->get(), ltWrite, false)) { - printError(format("removing stale temporary roots file '%1%'") % path); + printInfo("removing stale temporary roots file '%1%'", path); unlink(path.c_str()); writeFull(fd->get(), "d"); continue; @@ -403,7 +406,7 @@ void LocalStore::findRuntimeRoots(Roots & roots, bool censor) if (!fdDir) { if (errno == ENOENT || errno == EACCES) continue; - throw SysError(format("opening %1%") % fdStr); + throw SysError("opening %1%", fdStr); } struct dirent * fd_ent; while (errno = 0, fd_ent = readdir(fdDir.get())) { @@ -413,7 +416,7 @@ void LocalStore::findRuntimeRoots(Roots & roots, bool censor) if (errno) { if (errno == ESRCH) continue; - throw SysError(format("iterating /proc/%1%/fd") % ent->d_name); + throw SysError("iterating /proc/%1%/fd", ent->d_name); } fdDir.reset(); @@ -541,7 +544,7 @@ void LocalStore::deletePathRecursive(GCState & state, const Path & path) struct stat st; if (lstat(realPath.c_str(), &st)) { if (errno == ENOENT) return; - throw SysError(format("getting status of %1%") % realPath); + throw SysError("getting status of %1%", realPath); } printInfo(format("deleting '%1%'") % path); @@ -559,10 +562,10 @@ void LocalStore::deletePathRecursive(GCState & state, const Path & path) // size. try { if (chmod(realPath.c_str(), st.st_mode | S_IWUSR) == -1) - throw SysError(format("making '%1%' writable") % realPath); + throw SysError("making '%1%' writable", realPath); Path tmp = trashDir + "/" + std::string(baseNameOf(path)); if (rename(realPath.c_str(), tmp.c_str())) - throw SysError(format("unable to rename '%1%' to '%2%'") % realPath % tmp); + throw SysError("unable to rename '%1%' to '%2%'", realPath, tmp); state.bytesInvalidated += size; } catch (SysError & e) { if (e.errNo == ENOSPC) { @@ -681,7 +684,7 @@ void LocalStore::tryToDelete(GCState & state, const Path & path) void LocalStore::removeUnusedLinks(const GCState & state) { AutoCloseDir dir(opendir(linksDir.c_str())); - if (!dir) throw SysError(format("opening directory '%1%'") % linksDir); + if (!dir) throw SysError("opening directory '%1%'", linksDir); long long actualSize = 0, unsharedSize = 0; @@ -694,7 +697,7 @@ void LocalStore::removeUnusedLinks(const GCState & state) struct stat st; if (lstat(path.c_str(), &st) == -1) - throw SysError(format("statting '%1%'") % path); + throw SysError("statting '%1%'", path); if (st.st_nlink != 1) { actualSize += st.st_size; @@ -705,14 +708,14 @@ void LocalStore::removeUnusedLinks(const GCState & state) printMsg(lvlTalkative, format("deleting unused link '%1%'") % path); if (unlink(path.c_str()) == -1) - throw SysError(format("deleting '%1%'") % path); + throw SysError("deleting '%1%'", path); state.results.bytesFreed += st.st_size; } struct stat st; if (stat(linksDir.c_str(), &st) == -1) - throw SysError(format("statting '%1%'") % linksDir); + throw SysError("statting '%1%'", linksDir); long long overhead = st.st_blocks * 512ULL; printInfo(format("note: currently hard linking saves %.2f MiB") @@ -747,7 +750,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results) /* Find the roots. Since we've grabbed the GC lock, the set of permanent roots cannot increase now. */ - printError("finding garbage collector roots..."); + printInfo("finding garbage collector roots..."); Roots rootMap; if (!options.ignoreLiveness) findRootsNoTemp(rootMap, true); @@ -799,14 +802,14 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results) } else if (options.maxFreed > 0) { if (state.shouldDelete) - printError("deleting garbage..."); + printInfo("deleting garbage..."); else - printError("determining live/dead paths..."); + printInfo("determining live/dead paths..."); try { AutoCloseDir dir(opendir(realStoreDir.c_str())); - if (!dir) throw SysError(format("opening directory '%1%'") % realStoreDir); + if (!dir) throw SysError("opening directory '%1%'", realStoreDir); /* Read the store and immediately delete all paths that aren't valid. When using --max-freed etc., deleting @@ -868,7 +871,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results) /* Clean up the links directory. */ if (options.action == GCOptions::gcDeleteDead || options.action == GCOptions::gcDeleteSpecific) { - printError("deleting unused links..."); + printInfo("deleting unused links..."); removeUnusedLinks(state); } diff --git a/src/libstore/local-binary-cache-store.cc b/src/libstore/local-binary-cache-store.cc index 363be1443..48aca478c 100644 --- a/src/libstore/local-binary-cache-store.cc +++ b/src/libstore/local-binary-cache-store.cc @@ -74,7 +74,7 @@ static void atomicWrite(const Path & path, const std::string & s) AutoDelete del(tmp, false); writeFile(tmp, s); if (rename(tmp.c_str(), path.c_str())) - throw SysError(format("renaming '%1%' to '%2%'") % tmp % path); + throw SysError("renaming '%1%' to '%2%'", tmp, path); del.cancel(); } diff --git a/src/libstore/local-fs-store.cc b/src/libstore/local-fs-store.cc index aa5abd835..2d564a0d7 100644 --- a/src/libstore/local-fs-store.cc +++ b/src/libstore/local-fs-store.cc @@ -22,7 +22,7 @@ struct LocalStoreAccessor : public FSAccessor { Path storePath = store->toStorePath(path); if (!store->isValidPath(store->parseStorePath(storePath))) - throw InvalidPath(format("path '%1%' is not a valid store path") % storePath); + throw InvalidPath("path '%1%' is not a valid store path", storePath); return store->getRealStoreDir() + std::string(path, store->storeDir.size()); } @@ -33,11 +33,11 @@ struct LocalStoreAccessor : public FSAccessor struct stat st; if (lstat(realPath.c_str(), &st)) { if (errno == ENOENT || errno == ENOTDIR) return {Type::tMissing, 0, false}; - throw SysError(format("getting status of '%1%'") % path); + throw SysError("getting status of '%1%'", path); } if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode) && !S_ISLNK(st.st_mode)) - throw Error(format("file '%1%' has unsupported type") % path); + throw Error("file '%1%' has unsupported type", path); return { S_ISREG(st.st_mode) ? Type::tRegular : diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index c2c45b4c2..5521b8633 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -87,18 +87,22 @@ LocalStore::LocalStore(const Params & params) struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str()); if (!gr) - printError(format("warning: the group '%1%' specified in 'build-users-group' does not exist") - % settings.buildUsersGroup); + logError({ + .name = "'build-users-group' not found", + .hint = hintfmt( + "warning: the group '%1%' specified in 'build-users-group' does not exist", + settings.buildUsersGroup) + }); else { struct stat st; if (stat(realStoreDir.c_str(), &st)) - throw SysError(format("getting attributes of path '%1%'") % realStoreDir); + throw SysError("getting attributes of path '%1%'", realStoreDir); if (st.st_uid != 0 || st.st_gid != gr->gr_gid || (st.st_mode & ~S_IFMT) != perm) { if (chown(realStoreDir.c_str(), 0, gr->gr_gid) == -1) - throw SysError(format("changing ownership of path '%1%'") % realStoreDir); + throw SysError("changing ownership of path '%1%'", realStoreDir); if (chmod(realStoreDir.c_str(), perm) == -1) - throw SysError(format("changing permissions on path '%1%'") % realStoreDir); + throw SysError("changing permissions on path '%1%'", realStoreDir); } } } @@ -109,12 +113,12 @@ LocalStore::LocalStore(const Params & params) struct stat st; while (path != "/") { if (lstat(path.c_str(), &st)) - throw SysError(format("getting status of '%1%'") % path); + throw SysError("getting status of '%1%'", path); if (S_ISLNK(st.st_mode)) - throw Error(format( + throw Error( "the path '%1%' is a symlink; " - "this is not allowed for the Nix store and its parent directories") - % path); + "this is not allowed for the Nix store and its parent directories", + path); path = dirOf(path); } } @@ -147,7 +151,7 @@ LocalStore::LocalStore(const Params & params) globalLock = openLockFile(globalLockPath.c_str(), true); if (!lockFile(globalLock.get(), ltRead, false)) { - printError("waiting for the big Nix store lock..."); + printInfo("waiting for the big Nix store lock..."); lockFile(globalLock.get(), ltRead, true); } @@ -155,8 +159,8 @@ LocalStore::LocalStore(const Params & params) upgrade. */ int curSchema = getSchema(); if (curSchema > nixSchemaVersion) - throw Error(format("current Nix store schema is version %1%, but I only support %2%") - % curSchema % nixSchemaVersion); + throw Error("current Nix store schema is version %1%, but I only support %2%", + curSchema, nixSchemaVersion); else if (curSchema == 0) { /* new store */ curSchema = nixSchemaVersion; @@ -178,7 +182,7 @@ LocalStore::LocalStore(const Params & params) "please upgrade Nix to version 1.11 first."); if (!lockFile(globalLock.get(), ltWrite, false)) { - printError("waiting for exclusive access to the Nix store..."); + printInfo("waiting for exclusive access to the Nix store..."); lockFile(globalLock.get(), ltWrite, true); } @@ -256,7 +260,7 @@ LocalStore::~LocalStore() } if (future.valid()) { - printError("waiting for auto-GC to finish on exit..."); + printInfo("waiting for auto-GC to finish on exit..."); future.get(); } @@ -284,7 +288,7 @@ int LocalStore::getSchema() if (pathExists(schemaPath)) { string s = readFile(schemaPath); if (!string2Int(s, curSchema)) - throw Error(format("'%1%' is corrupt") % schemaPath); + throw Error("'%1%' is corrupt", schemaPath); } return curSchema; } @@ -293,7 +297,7 @@ int LocalStore::getSchema() void LocalStore::openDB(State & state, bool create) { if (access(dbDir.c_str(), R_OK | W_OK)) - throw SysError(format("Nix database directory '%1%' is not writable") % dbDir); + throw SysError("Nix database directory '%1%' is not writable", dbDir); /* Open the Nix database. */ string dbPath = dbDir + "/db.sqlite"; @@ -367,7 +371,7 @@ void LocalStore::makeStoreWritable() throw SysError("setting up a private mount namespace"); if (mount(0, realStoreDir.c_str(), "none", MS_REMOUNT | MS_BIND, 0) == -1) - throw SysError(format("remounting %1% writable") % realStoreDir); + throw SysError("remounting %1% writable", realStoreDir); } #endif } @@ -388,7 +392,7 @@ static void canonicaliseTimestampAndPermissions(const Path & path, const struct | 0444 | (st.st_mode & S_IXUSR ? 0111 : 0); if (chmod(path.c_str(), mode) == -1) - throw SysError(format("changing mode of '%1%' to %2$o") % path % mode); + throw SysError("changing mode of '%1%' to %2$o", path, mode); } } @@ -406,7 +410,7 @@ static void canonicaliseTimestampAndPermissions(const Path & path, const struct #else if (!S_ISLNK(st.st_mode) && utimes(path.c_str(), times) == -1) #endif - throw SysError(format("changing modification time of '%1%'") % path); + throw SysError("changing modification time of '%1%'", path); } } @@ -415,7 +419,7 @@ void canonicaliseTimestampAndPermissions(const Path & path) { struct stat st; if (lstat(path.c_str(), &st)) - throw SysError(format("getting attributes of path '%1%'") % path); + throw SysError("getting attributes of path '%1%'", path); canonicaliseTimestampAndPermissions(path, st); } @@ -430,17 +434,17 @@ static void canonicalisePathMetaData_(const Path & path, uid_t fromUid, InodesSe setattrlist() to remove other attributes as well. */ if (lchflags(path.c_str(), 0)) { if (errno != ENOTSUP) - throw SysError(format("clearing flags of path '%1%'") % path); + throw SysError("clearing flags of path '%1%'", path); } #endif struct stat st; if (lstat(path.c_str(), &st)) - throw SysError(format("getting attributes of path '%1%'") % path); + throw SysError("getting attributes of path '%1%'", path); /* Really make sure that the path is of a supported type. */ if (!(S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))) - throw Error(format("file '%1%' has an unsupported type") % path); + throw Error("file '%1%' has an unsupported type", path); #if __linux__ /* Remove extended attributes / ACLs. */ @@ -474,7 +478,7 @@ static void canonicalisePathMetaData_(const Path & path, uid_t fromUid, InodesSe if (fromUid != (uid_t) -1 && st.st_uid != fromUid) { assert(!S_ISDIR(st.st_mode)); if (inodesSeen.find(Inode(st.st_dev, st.st_ino)) == inodesSeen.end()) - throw BuildError(format("invalid ownership on file '%1%'") % path); + throw BuildError("invalid ownership on file '%1%'", path); mode_t mode = st.st_mode & ~S_IFMT; assert(S_ISLNK(st.st_mode) || (st.st_uid == geteuid() && (mode == 0444 || mode == 0555) && st.st_mtime == mtimeStore)); return; @@ -498,8 +502,8 @@ static void canonicalisePathMetaData_(const Path & path, uid_t fromUid, InodesSe if (!S_ISLNK(st.st_mode) && chown(path.c_str(), geteuid(), getegid()) == -1) #endif - throw SysError(format("changing owner of '%1%' to %2%") - % path % geteuid()); + throw SysError("changing owner of '%1%' to %2%", + path, geteuid()); } if (S_ISDIR(st.st_mode)) { @@ -518,11 +522,11 @@ void canonicalisePathMetaData(const Path & path, uid_t fromUid, InodesSeen & ino be a symlink, since we can't change its ownership. */ struct stat st; if (lstat(path.c_str(), &st)) - throw SysError(format("getting attributes of path '%1%'") % path); + throw SysError("getting attributes of path '%1%'", path); if (st.st_uid != geteuid()) { assert(S_ISLNK(st.st_mode)); - throw Error(format("wrong ownership of top-level store path '%1%'") % path); + throw Error("wrong ownership of top-level store path '%1%'", path); } } @@ -859,7 +863,7 @@ void LocalStore::querySubstitutablePathInfos(const StorePathSet & paths, } catch (SubstituterDisabled &) { } catch (Error & e) { if (settings.tryFallback) - printError(e.what()); + logError(e.info()); else throw; } @@ -1187,7 +1191,7 @@ void LocalStore::invalidatePathChecked(const StorePath & path) bool LocalStore::verifyStore(bool checkContents, RepairFlag repair) { - printError(format("reading the Nix store...")); + printInfo(format("reading the Nix store...")); bool errors = false; @@ -1219,12 +1223,15 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair) Path linkPath = linksDir + "/" + link.name; string hash = hashPath(htSHA256, linkPath).first.to_string(Base32, false); if (hash != link.name) { - printError( - "link '%s' was modified! expected hash '%s', got '%s'", - linkPath, link.name, hash); + logError({ + .name = "Invalid hash", + .hint = hintfmt( + "link '%s' was modified! expected hash '%s', got '%s'", + linkPath, link.name, hash) + }); if (repair) { if (unlink(linkPath.c_str()) == 0) - printError("removed link '%s'", linkPath); + printInfo("removed link '%s'", linkPath); else throw SysError("removing corrupt link '%s'", linkPath); } else { @@ -1254,8 +1261,11 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair) auto current = hashSink->finish(); if (info->narHash != nullHash && info->narHash != current.first) { - printError("path '%s' was modified! expected hash '%s', got '%s'", - printStorePath(i), info->narHash.to_string(Base32, true), current.first.to_string(Base32, true)); + logError({ + .name = "Invalid hash - path modified", + .hint = hintfmt("path '%s' was modified! expected hash '%s', got '%s'", + printStorePath(i), info->narHash.to_string(Base32, true), current.first.to_string(Base32, true)) + }); if (repair) repairPath(i); else errors = true; } else { @@ -1263,14 +1273,14 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair) /* Fill in missing hashes. */ if (info->narHash == nullHash) { - printError("fixing missing hash on '%s'", printStorePath(i)); + printInfo("fixing missing hash on '%s'", printStorePath(i)); info->narHash = current.first; update = true; } /* Fill in missing narSize fields (from old stores). */ if (info->narSize == 0) { - printError("updating size field on '%s' to %s", printStorePath(i), current.second); + printInfo("updating size field on '%s' to %s", printStorePath(i), current.second); info->narSize = current.second; update = true; } @@ -1286,7 +1296,7 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair) /* It's possible that the path got GC'ed, so ignore errors on invalid paths. */ if (isValidPath(i)) - printError("error: %s", e.msg()); + logError(e.info()); else warn(e.msg()); errors = true; @@ -1306,7 +1316,10 @@ void LocalStore::verifyPath(const Path & pathS, const StringSet & store, if (!done.insert(pathS).second) return; if (!isStorePath(pathS)) { - printError("path '%s' is not in the Nix store", pathS); + logError({ + .name = "Nix path not found", + .hint = hintfmt("path '%s' is not in the Nix store", pathS) + }); return; } @@ -1325,16 +1338,19 @@ void LocalStore::verifyPath(const Path & pathS, const StringSet & store, } if (canInvalidate) { - printError("path '%s' disappeared, removing from database...", pathS); + printInfo("path '%s' disappeared, removing from database...", pathS); auto state(_state.lock()); invalidatePath(*state, path); } else { - printError("path '%s' disappeared, but it still has valid referrers!", pathS); + logError({ + .name = "Missing path with referrers", + .hint = hintfmt("path '%s' disappeared, but it still has valid referrers!", pathS) + }); if (repair) try { repairPath(path); } catch (Error & e) { - warn(e.msg()); + logWarning(e.info()); errors = true; } else errors = true; @@ -1374,7 +1390,7 @@ static void makeMutable(const Path & path) AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd == -1) { if (errno == ELOOP) return; // it's a symlink - throw SysError(format("opening file '%1%'") % path); + throw SysError("opening file '%1%'", path); } unsigned int flags = 0, old; @@ -1392,7 +1408,7 @@ static void makeMutable(const Path & path) void LocalStore::upgradeStore7() { if (getuid() != 0) return; - printError("removing immutable bits from the Nix store (this may take a while)..."); + printInfo("removing immutable bits from the Nix store (this may take a while)..."); makeMutable(realStoreDir); } diff --git a/src/libstore/nar-accessor.cc b/src/libstore/nar-accessor.cc index b74480684..ca663d837 100644 --- a/src/libstore/nar-accessor.cc +++ b/src/libstore/nar-accessor.cc @@ -184,7 +184,7 @@ struct NarAccessor : public FSAccessor auto i = get(path); if (i.type != FSAccessor::Type::tDirectory) - throw Error(format("path '%1%' inside NAR file is not a directory") % path); + throw Error("path '%1%' inside NAR file is not a directory", path); StringSet res; for (auto & child : i.children) @@ -197,7 +197,7 @@ struct NarAccessor : public FSAccessor { auto i = get(path); if (i.type != FSAccessor::Type::tRegular) - throw Error(format("path '%1%' inside NAR file is not a regular file") % path); + throw Error("path '%1%' inside NAR file is not a regular file", path); if (getNarBytes) return getNarBytes(i.start, i.size); @@ -209,7 +209,7 @@ struct NarAccessor : public FSAccessor { auto i = get(path); if (i.type != FSAccessor::Type::tSymlink) - throw Error(format("path '%1%' inside NAR file is not a symlink") % path); + throw Error("path '%1%' inside NAR file is not a symlink", path); return i.target; } }; diff --git a/src/libstore/nar-info.cc b/src/libstore/nar-info.cc index 6b16be08a..232284723 100644 --- a/src/libstore/nar-info.cc +++ b/src/libstore/nar-info.cc @@ -7,7 +7,7 @@ NarInfo::NarInfo(const Store & store, const std::string & s, const std::string & : ValidPathInfo(StorePath::dummy.clone()) // FIXME: hack { auto corrupt = [&]() { - throw Error(format("NAR info file '%1%' is corrupt") % whence); + throw Error("NAR info file '%1%' is corrupt", whence); }; auto parseHashField = [&](const string & s) { diff --git a/src/libstore/optimise-store.cc b/src/libstore/optimise-store.cc index 3f4b72b9c..d760d110c 100644 --- a/src/libstore/optimise-store.cc +++ b/src/libstore/optimise-store.cc @@ -19,9 +19,9 @@ static void makeWritable(const Path & path) { struct stat st; if (lstat(path.c_str(), &st)) - throw SysError(format("getting attributes of path '%1%'") % path); + throw SysError("getting attributes of path '%1%'", path); if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1) - throw SysError(format("changing writability of '%1%'") % path); + throw SysError("changing writability of '%1%'", path); } @@ -47,7 +47,7 @@ LocalStore::InodeHash LocalStore::loadInodeHash() InodeHash inodeHash; AutoCloseDir dir(opendir(linksDir.c_str())); - if (!dir) throw SysError(format("opening directory '%1%'") % linksDir); + if (!dir) throw SysError("opening directory '%1%'", linksDir); struct dirent * dirent; while (errno = 0, dirent = readdir(dir.get())) { /* sic */ @@ -55,7 +55,7 @@ LocalStore::InodeHash LocalStore::loadInodeHash() // We don't care if we hit non-hash files, anything goes inodeHash.insert(dirent->d_ino); } - if (errno) throw SysError(format("reading directory '%1%'") % linksDir); + if (errno) throw SysError("reading directory '%1%'", linksDir); printMsg(lvlTalkative, format("loaded %1% hash inodes") % inodeHash.size()); @@ -68,7 +68,7 @@ Strings LocalStore::readDirectoryIgnoringInodes(const Path & path, const InodeHa Strings names; AutoCloseDir dir(opendir(path.c_str())); - if (!dir) throw SysError(format("opening directory '%1%'") % path); + if (!dir) throw SysError("opening directory '%1%'", path); struct dirent * dirent; while (errno = 0, dirent = readdir(dir.get())) { /* sic */ @@ -83,7 +83,7 @@ Strings LocalStore::readDirectoryIgnoringInodes(const Path & path, const InodeHa if (name == "." || name == "..") continue; names.push_back(name); } - if (errno) throw SysError(format("reading directory '%1%'") % path); + if (errno) throw SysError("reading directory '%1%'", path); return names; } @@ -96,7 +96,7 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats, struct stat st; if (lstat(path.c_str(), &st)) - throw SysError(format("getting attributes of path '%1%'") % path); + throw SysError("getting attributes of path '%1%'", path); #if __APPLE__ /* HFS/macOS has some undocumented security feature disabling hardlinking for @@ -130,7 +130,10 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats, NixOS (example: $fontconfig/var/cache being modified). Skip those files. FIXME: check the modification time. */ if (S_ISREG(st.st_mode) && (st.st_mode & S_IWUSR)) { - printError(format("skipping suspicious writable file '%1%'") % path); + logWarning({ + .name = "Suspicious file", + .hint = hintfmt("skipping suspicious writable file '%1%'", path) + }); return; } @@ -186,7 +189,7 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats, current file with a hard link to that file. */ struct stat stLink; if (lstat(linkPath.c_str(), &stLink)) - throw SysError(format("getting attributes of path '%1%'") % linkPath); + throw SysError("getting attributes of path '%1%'", linkPath); if (st.st_ino == stLink.st_ino) { debug(format("'%1%' is already linked to '%2%'") % path % linkPath); @@ -194,7 +197,10 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats, } if (st.st_size != stLink.st_size) { - printError(format("removing corrupted link '%1%'") % linkPath); + logWarning({ + .name = "Corrupted link", + .hint = hintfmt("removing corrupted link '%1%'", linkPath) + }); unlink(linkPath.c_str()); goto retry; } @@ -229,7 +235,10 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats, /* Atomically replace the old file with the new hard link. */ if (rename(tempLink.c_str(), path.c_str()) == -1) { if (unlink(tempLink.c_str()) == -1) - printError(format("unable to unlink '%1%'") % tempLink); + logError({ + .name = "Unlink error", + .hint = hintfmt("unable to unlink '%1%'", tempLink) + }); if (errno == EMLINK) { /* Some filesystems generate too many links on the rename, rather than on the original link. (Probably it @@ -238,7 +247,7 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats, debug("'%s' has reached maximum number of links", linkPath); return; } - throw SysError(format("cannot rename '%1%' to '%2%'") % tempLink % path); + throw SysError("cannot rename '%1%' to '%2%'", tempLink, path); } stats.filesLinked++; diff --git a/src/libstore/pathlocks.cc b/src/libstore/pathlocks.cc index 2635e3940..926f4ea1e 100644 --- a/src/libstore/pathlocks.cc +++ b/src/libstore/pathlocks.cc @@ -20,7 +20,7 @@ AutoCloseFD openLockFile(const Path & path, bool create) fd = open(path.c_str(), O_CLOEXEC | O_RDWR | (create ? O_CREAT : 0), 0600); if (!fd && (create || errno != ENOENT)) - throw SysError(format("opening lock file '%1%'") % path); + throw SysError("opening lock file '%1%'", path); return fd; } @@ -51,7 +51,7 @@ bool lockFile(int fd, LockType lockType, bool wait) while (flock(fd, type) != 0) { checkInterrupt(); if (errno != EINTR) - throw SysError(format("acquiring/releasing lock")); + throw SysError("acquiring/releasing lock"); else return false; } @@ -60,7 +60,7 @@ bool lockFile(int fd, LockType lockType, bool wait) checkInterrupt(); if (errno == EWOULDBLOCK) return false; if (errno != EINTR) - throw SysError(format("acquiring/releasing lock")); + throw SysError("acquiring/releasing lock"); } } @@ -124,7 +124,7 @@ bool PathLocks::lockPaths(const PathSet & paths, hasn't been unlinked). */ struct stat st; if (fstat(fd.get(), &st) == -1) - throw SysError(format("statting lock file '%1%'") % lockPath); + throw SysError("statting lock file '%1%'", lockPath); if (st.st_size != 0) /* This lock file has been unlinked, so we're holding a lock on a deleted file. This means that other @@ -160,7 +160,8 @@ void PathLocks::unlock() if (close(i.first) == -1) printError( - format("error (ignored): cannot close lock file on '%1%'") % i.second); + "error (ignored): cannot close lock file on '%1%'", + i.second); debug(format("lock released on '%1%'") % i.second); } diff --git a/src/libstore/profiles.cc b/src/libstore/profiles.cc index 2bef51878..6cfe393a4 100644 --- a/src/libstore/profiles.cc +++ b/src/libstore/profiles.cc @@ -50,7 +50,7 @@ Generations findGenerations(Path profile, int & curGen) gen.number = n; struct stat st; if (lstat(gen.path.c_str(), &st) != 0) - throw SysError(format("statting '%1%'") % gen.path); + throw SysError("statting '%1%'", gen.path); gen.creationTime = st.st_mtime; gens.push_back(gen); } @@ -117,7 +117,7 @@ Path createGeneration(ref store, Path profile, Path outPath) static void removeFile(const Path & path) { if (remove(path.c_str()) == -1) - throw SysError(format("cannot unlink '%1%'") % path); + throw SysError("cannot unlink '%1%'", path); } @@ -149,7 +149,7 @@ void deleteGenerations(const Path & profile, const std::set & gens Generations gens = findGenerations(profile, curGen); if (gensToDelete.find(curGen) != gensToDelete.end()) - throw Error(format("cannot delete current generation of profile %1%'") % profile); + throw Error("cannot delete current generation of profile %1%'", profile); for (auto & i : gens) { if (gensToDelete.find(i.number) == gensToDelete.end()) continue; @@ -226,7 +226,7 @@ void deleteGenerationsOlderThan(const Path & profile, const string & timeSpec, b int days; if (!string2Int(strDays, days) || days < 1) - throw Error(format("invalid number of days specifier '%1%'") % timeSpec); + throw Error("invalid number of days specifier '%1%'", timeSpec); time_t oldTime = curTime - days * 24 * 3600; diff --git a/src/libstore/references.cc b/src/libstore/references.cc index 102e15921..a10d536a3 100644 --- a/src/libstore/references.cc +++ b/src/libstore/references.cc @@ -92,7 +92,7 @@ PathSet scanForReferences(const string & path, auto baseName = std::string(baseNameOf(i)); string::size_type pos = baseName.find('-'); if (pos == string::npos) - throw Error(format("bad reference '%1%'") % i); + throw Error("bad reference '%1%'", i); string s = string(baseName, 0, pos); assert(s.size() == refLength); assert(backMap.find(s) == backMap.end()); diff --git a/src/libstore/remote-fs-accessor.cc b/src/libstore/remote-fs-accessor.cc index 5a2d103b9..9277a8e6b 100644 --- a/src/libstore/remote-fs-accessor.cc +++ b/src/libstore/remote-fs-accessor.cc @@ -51,7 +51,7 @@ std::pair, Path> RemoteFSAccessor::fetch(const Path & path_) std::string restPath = std::string(path, storePath.size()); if (!store->isValidPath(store->parseStorePath(storePath))) - throw InvalidPath(format("path '%1%' is not a valid store path") % storePath); + throw InvalidPath("path '%1%' is not a valid store path", storePath); auto i = nars.find(storePath); if (i != nars.end()) return {i->second, restPath}; diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc index 735f59a91..99fee8150 100644 --- a/src/libstore/remote-store.cc +++ b/src/libstore/remote-store.cc @@ -116,11 +116,11 @@ ref UDSRemoteStore::openConnection() struct sockaddr_un addr; addr.sun_family = AF_UNIX; if (socketPath.size() + 1 >= sizeof(addr.sun_path)) - throw Error(format("socket path '%1%' is too long") % socketPath); + throw Error("socket path '%1%' is too long", socketPath); strcpy(addr.sun_path, socketPath.c_str()); if (::connect(conn->fd.get(), (struct sockaddr *) &addr, sizeof(addr)) == -1) - throw SysError(format("cannot connect to daemon at '%1%'") % socketPath); + throw SysError("cannot connect to daemon at '%1%'", socketPath); conn->from.fd = conn->fd.get(); conn->to.fd = conn->fd.get(); @@ -365,7 +365,7 @@ void RemoteStore::queryPathInfoUncached(const StorePath & path, } catch (Error & e) { // Ugly backwards compatibility hack. if (e.msg().find("is not valid") != std::string::npos) - throw InvalidPath(e.what()); + throw InvalidPath(e.info()); throw; } if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 17) { diff --git a/src/libstore/s3-binary-cache-store.cc b/src/libstore/s3-binary-cache-store.cc index b24e7b7d6..427dd48ce 100644 --- a/src/libstore/s3-binary-cache-store.cc +++ b/src/libstore/s3-binary-cache-store.cc @@ -32,8 +32,10 @@ namespace nix { struct S3Error : public Error { Aws::S3::S3Errors err; - S3Error(Aws::S3::S3Errors err, const FormatOrString & fs) - : Error(fs), err(err) { }; + + template + S3Error(Aws::S3::S3Errors err, const Args & ... args) + : Error(args...), err(err) { }; }; /* Helper: given an Outcome, return R in case of success, or @@ -109,7 +111,9 @@ class RetryStrategy : public Aws::Client::DefaultRetryStrategy auto retry = Aws::Client::DefaultRetryStrategy::ShouldRetry(error, attemptedRetries); if (retry) printError("AWS error '%s' (%s), will retry in %d ms", - error.GetExceptionName(), error.GetMessage(), CalculateDelayBeforeNextRetry(error, attemptedRetries)); + error.GetExceptionName(), + error.GetMessage(), + CalculateDelayBeforeNextRetry(error, attemptedRetries)); return retry; } }; @@ -249,7 +253,7 @@ struct S3BinaryCacheStoreImpl : public S3BinaryCacheStore // If bucket listing is disabled, 404s turn into 403s || error.GetErrorType() == Aws::S3::S3Errors::ACCESS_DENIED) return false; - throw Error(format("AWS error fetching '%s': %s") % path % error.GetMessage()); + throw Error("AWS error fetching '%s': %s", path, error.GetMessage()); } return true; diff --git a/src/libstore/sqlite.cc b/src/libstore/sqlite.cc index eb1daafc5..16cdb6619 100644 --- a/src/libstore/sqlite.cc +++ b/src/libstore/sqlite.cc @@ -29,7 +29,7 @@ SQLite::SQLite(const Path & path, bool create) { if (sqlite3_open_v2(path.c_str(), &db, SQLITE_OPEN_READWRITE | (create ? SQLITE_OPEN_CREATE : 0), 0) != SQLITE_OK) - throw Error(format("cannot open SQLite database '%s'") % path); + throw Error("cannot open SQLite database '%s'", path); if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK) throwSQLiteError(db, "setting timeout"); @@ -204,7 +204,10 @@ void handleSQLiteBusy(const SQLiteBusy & e) if (now > lastWarned + 10) { lastWarned = now; - printError("warning: %s", e.what()); + logWarning( + ErrorInfo { .name = "Sqlite busy", + .hint = hintfmt(e.what()) + }); } /* Sleep for a while since retrying the transaction right away diff --git a/src/libstore/sqlite.hh b/src/libstore/sqlite.hh index fd04c9b07..dd81ab051 100644 --- a/src/libstore/sqlite.hh +++ b/src/libstore/sqlite.hh @@ -3,7 +3,7 @@ #include #include -#include "types.hh" +#include "error.hh" struct sqlite3; struct sqlite3_stmt; diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc index f6901bf42..464261010 100644 --- a/src/libstore/store-api.cc +++ b/src/libstore/store-api.cc @@ -23,7 +23,7 @@ bool Store::isInStore(const Path & path) const Path Store::toStorePath(const Path & path) const { if (!isInStore(path)) - throw Error(format("path '%1%' is not in the Nix store") % path); + throw Error("path '%1%' is not in the Nix store", path); Path::size_type slash = path.find('/', storeDir.size() + 1); if (slash == Path::npos) return path; @@ -775,7 +775,11 @@ void ValidPathInfo::sign(const Store & store, const SecretKey & secretKey) bool ValidPathInfo::isContentAddressed(const Store & store) const { auto warn = [&]() { - printError("warning: path '%s' claims to be content-addressed but isn't", store.printStorePath(path)); + logWarning( + ErrorInfo{ + .name = "Path not content-addressed", + .hint = hintfmt("path '%s' claims to be content-addressed but isn't", store.printStorePath(path)) + }); }; if (hasPrefix(ca, "text:")) { @@ -934,7 +938,7 @@ std::list> getDefaultSubstituters() try { stores.push_back(openStore(uri)); } catch (Error & e) { - printError("warning: %s", e.what()); + logWarning(e.info()); } }; diff --git a/src/libutil/affinity.cc b/src/libutil/affinity.cc index 98f8287ad..ac2295e4a 100644 --- a/src/libutil/affinity.cc +++ b/src/libutil/affinity.cc @@ -12,6 +12,17 @@ namespace nix { #if __linux__ static bool didSaveAffinity = false; static cpu_set_t savedAffinity; + +std::ostream& operator<<(std::ostream &os, const cpu_set_t &cset) +{ + auto count = CPU_COUNT(&cset); + for (int i=0; i < count; ++i) + { + os << (CPU_ISSET(i,&cset) ? "1" : "0"); + } + + return os; +} #endif @@ -25,7 +36,7 @@ void setAffinityTo(int cpu) CPU_ZERO(&newAffinity); CPU_SET(cpu, &newAffinity); if (sched_setaffinity(0, sizeof(cpu_set_t), &newAffinity) == -1) - printError(format("failed to lock thread to CPU %1%") % cpu); + printError("failed to lock thread to CPU %1%", cpu); #endif } @@ -47,7 +58,11 @@ void restoreAffinity() #if __linux__ if (!didSaveAffinity) return; if (sched_setaffinity(0, sizeof(cpu_set_t), &savedAffinity) == -1) - printError("failed to restore affinity %1%"); + { + std::ostringstream oss; + oss << savedAffinity; + printError("failed to restore CPU affinity %1%", oss.str()); + } #endif } diff --git a/src/libutil/archive.cc b/src/libutil/archive.cc index db544a212..6a8484705 100644 --- a/src/libutil/archive.cc +++ b/src/libutil/archive.cc @@ -46,7 +46,7 @@ static void dumpContents(const Path & path, size_t size, sink << "contents" << size; AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC); - if (!fd) throw SysError(format("opening file '%1%'") % path); + if (!fd) throw SysError("opening file '%1%'", path); std::vector buf(65536); size_t left = size; @@ -68,7 +68,7 @@ static void dump(const Path & path, Sink & sink, PathFilter & filter) struct stat st; if (lstat(path.c_str(), &st)) - throw SysError(format("getting attributes of path '%1%'") % path); + throw SysError("getting attributes of path '%1%'", path); sink << "("; @@ -94,8 +94,9 @@ static void dump(const Path & path, Sink & sink, PathFilter & filter) name.erase(pos); } if (unhacked.find(name) != unhacked.end()) - throw Error(format("file name collision in between '%1%' and '%2%'") - % (path + "/" + unhacked[name]) % (path + "/" + i.name)); + throw Error("file name collision in between '%1%' and '%2%'", + (path + "/" + unhacked[name]), + (path + "/" + i.name)); unhacked[name] = i.name; } else unhacked[i.name] = i.name; @@ -111,7 +112,7 @@ static void dump(const Path & path, Sink & sink, PathFilter & filter) else if (S_ISLNK(st.st_mode)) sink << "type" << "symlink" << "target" << readLink(path); - else throw Error(format("file '%1%' has an unsupported type") % path); + else throw Error("file '%1%' has an unsupported type", path); sink << ")"; } @@ -247,7 +248,7 @@ static void parse(ParseSink & sink, Source & source, const Path & path) } else if (s == "name") { name = readString(source); if (name.empty() || name == "." || name == ".." || name.find('/') != string::npos || name.find((char) 0) != string::npos) - throw Error(format("NAR contains invalid file name '%1%'") % name); + throw Error("NAR contains invalid file name '%1%'", name); if (name <= prevName) throw Error("NAR directory is not sorted"); prevName = name; @@ -303,14 +304,14 @@ struct RestoreSink : ParseSink { Path p = dstPath + path; if (mkdir(p.c_str(), 0777) == -1) - throw SysError(format("creating directory '%1%'") % p); + throw SysError("creating directory '%1%'", p); }; void createRegularFile(const Path & path) { Path p = dstPath + path; fd = open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666); - if (!fd) throw SysError(format("creating file '%1%'") % p); + if (!fd) throw SysError("creating file '%1%'", p); } void isExecutable() @@ -332,7 +333,7 @@ struct RestoreSink : ParseSink OpenSolaris). Since preallocation is just an optimisation, ignore it. */ if (errno && errno != EINVAL && errno != EOPNOTSUPP && errno != ENOSYS) - throw SysError(format("preallocating file of %1% bytes") % len); + throw SysError("preallocating file of %1% bytes", len); } #endif } diff --git a/src/libutil/args.cc b/src/libutil/args.cc index afeaf4cea..ce6580119 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -45,7 +45,7 @@ void Args::parseCmdline(const Strings & _cmdline) } else if (!dashDash && std::string(arg, 0, 1) == "-") { if (!processFlag(pos, cmdline.end())) - throw UsageError(format("unrecognised flag '%1%'") % arg); + throw UsageError("unrecognised flag '%1%'", arg); } else { pendingArgs.push_back(*pos++); @@ -130,7 +130,7 @@ bool Args::processArgs(const Strings & args, bool finish) { if (expectedArgs.empty()) { if (!args.empty()) - throw UsageError(format("unexpected argument '%1%'") % args.front()); + throw UsageError("unexpected argument '%1%'", args.front()); return true; } diff --git a/src/libutil/compression.cc b/src/libutil/compression.cc index 860b04adb..a117ddc72 100644 --- a/src/libutil/compression.cc +++ b/src/libutil/compression.cc @@ -481,7 +481,7 @@ ref makeCompressionSink(const std::string & method, Sink & next else if (method == "br") return make_ref(nextSink); else - throw UnknownCompressionMethod(format("unknown compression method '%s'") % method); + throw UnknownCompressionMethod("unknown compression method '%s'", method); } ref compress(const std::string & method, const std::string & in, const bool parallel) diff --git a/src/libutil/error.cc b/src/libutil/error.cc index a5571d4ec..1fcb8111c 100644 --- a/src/libutil/error.cc +++ b/src/libutil/error.cc @@ -2,9 +2,38 @@ #include #include +#include "serialise.hh" +#include -namespace nix +namespace nix { + + +const std::string nativeSystem = SYSTEM; + +// addPrefix is used for show-trace. Strings added with addPrefix +// will print ahead of the error itself. +BaseError & BaseError::addPrefix(const FormatOrString & fs) { + prefix_ = fs.s + prefix_; + return *this; +} + +// c++ std::exception descendants must have a 'const char* what()' function. +// This stringifies the error and caches it for use by what(), or similarly by msg(). +const string& BaseError::calcWhat() const +{ + if (what_.has_value()) + return *what_; + else { + err.name = sname(); + + std::ostringstream oss; + oss << err; + what_ = oss.str(); + + return *what_; + } +} std::optional ErrorInfo::programName = std::nullopt; @@ -15,80 +44,118 @@ std::ostream& operator<<(std::ostream &os, const hintformat &hf) string showErrPos(const ErrPos &errPos) { - if (errPos.column > 0) { - return fmt("(%1%:%2%)", errPos.lineNumber, errPos.column); - } else { - return fmt("(%1%)", errPos.lineNumber); - }; + if (errPos.line > 0) { + if (errPos.column > 0) { + return fmt("(%1%:%2%)", errPos.line, errPos.column); + } else { + return fmt("(%1%)", errPos.line); + } + } + else { + return ""; + } } -void printCodeLines(const string &prefix, const NixCode &nixCode) +// if nixCode contains lines of code, print them to the ostream, indicating the error column. +void printCodeLines(std::ostream &out, const string &prefix, const NixCode &nixCode) { // previous line of code. if (nixCode.prevLineOfCode.has_value()) { - std::cout << fmt("%1% %|2$5d|| %3%", - prefix, - (nixCode.errPos.lineNumber - 1), - *nixCode.prevLineOfCode) - << std::endl; + out << std::endl + << fmt("%1% %|2$5d|| %3%", + prefix, + (nixCode.errPos.line - 1), + *nixCode.prevLineOfCode); } - // line of code containing the error.%2$+5d% - std::cout << fmt("%1% %|2$5d|| %3%", - prefix, - (nixCode.errPos.lineNumber), - nixCode.errLineOfCode) - << std::endl; + if (nixCode.errLineOfCode.has_value()) { + // line of code containing the error. + out << std::endl + << fmt("%1% %|2$5d|| %3%", + prefix, + (nixCode.errPos.line), + *nixCode.errLineOfCode); + // error arrows for the column range. + if (nixCode.errPos.column > 0) { + int start = nixCode.errPos.column; + std::string spaces; + for (int i = 0; i < start; ++i) { + spaces.append(" "); + } - // error arrows for the column range. - if (nixCode.errPos.column > 0) { - int start = nixCode.errPos.column; - std::string spaces; - for (int i = 0; i < start; ++i) { - spaces.append(" "); + std::string arrows("^"); + + out << std::endl + << fmt("%1% |%2%" ANSI_RED "%3%" ANSI_NORMAL, + prefix, + spaces, + arrows); } - - std::string arrows("^"); - - std::cout << fmt("%1% |%2%" ANSI_RED "%3%" ANSI_NORMAL, - prefix, - spaces, - arrows) << std::endl; } // next line of code. if (nixCode.nextLineOfCode.has_value()) { - std::cout << fmt("%1% %|2$5d|| %3%", - prefix, - (nixCode.errPos.lineNumber + 1), - *nixCode.nextLineOfCode) - << std::endl; + out << std::endl + << fmt("%1% %|2$5d|| %3%", + prefix, + (nixCode.errPos.line + 1), + *nixCode.nextLineOfCode); } } -void printErrorInfo(const ErrorInfo &einfo) +std::ostream& operator<<(std::ostream &out, const ErrorInfo &einfo) { int errwidth = 80; - string prefix = " "; + string prefix = ""; string levelString; switch (einfo.level) { - case ErrLevel::elError: { - levelString = ANSI_RED; - levelString += "error:"; - levelString += ANSI_NORMAL; - break; - } - case ErrLevel::elWarning: { - levelString = ANSI_YELLOW; - levelString += "warning:"; - levelString += ANSI_NORMAL; - break; - } - default: { - levelString = fmt("invalid error level: %1%", einfo.level); - break; - } + case Verbosity::lvlError: { + levelString = ANSI_RED; + levelString += "error:"; + levelString += ANSI_NORMAL; + break; + } + case Verbosity::lvlWarn: { + levelString = ANSI_YELLOW; + levelString += "warning:"; + levelString += ANSI_NORMAL; + break; + } + case Verbosity::lvlInfo: { + levelString = ANSI_GREEN; + levelString += "info:"; + levelString += ANSI_NORMAL; + break; + } + case Verbosity::lvlTalkative: { + levelString = ANSI_GREEN; + levelString += "talk:"; + levelString += ANSI_NORMAL; + break; + } + case Verbosity::lvlChatty: { + levelString = ANSI_GREEN; + levelString += "chat:"; + levelString += ANSI_NORMAL; + break; + } + case Verbosity::lvlVomit: { + levelString = ANSI_GREEN; + levelString += "vomit:"; + levelString += ANSI_NORMAL; + break; + } + case Verbosity::lvlDebug: { + levelString = ANSI_YELLOW; + levelString += "debug:"; + levelString += ANSI_NORMAL; + break; + } + default: { + levelString = fmt("invalid error level: %1%", einfo.level); + break; + } } int ndl = prefix.length() + levelString.length() + 3 + einfo.name.length() + einfo.programName.value_or("").length(); @@ -99,48 +166,58 @@ void printErrorInfo(const ErrorInfo &einfo) dashes.append("-"); // divider. - std::cout << fmt("%1%%2%" ANSI_BLUE " %3% %4% %5% %6%" ANSI_NORMAL, - prefix, - levelString, - "---", - einfo.name, - dashes, - einfo.programName.value_or("")) - << std::endl; + if (einfo.name != "") + out << fmt("%1%%2%" ANSI_BLUE " --- %3% %4% %5%" ANSI_NORMAL, + prefix, + levelString, + einfo.name, + dashes, + einfo.programName.value_or("")); + else + out << fmt("%1%%2%" ANSI_BLUE " -----%3% %4%" ANSI_NORMAL, + prefix, + levelString, + dashes, + einfo.programName.value_or("")); - // filename. + bool nl = false; // intersperse newline between sections. if (einfo.nixCode.has_value()) { - if (einfo.nixCode->errPos.nixFile != "") { - string eline = einfo.nixCode->errLineOfCode != "" - ? string(" ") + showErrPos(einfo.nixCode->errPos) - : ""; - - std::cout << fmt("%1%in file: " ANSI_BLUE "%2%%3%" ANSI_NORMAL, - prefix, - einfo.nixCode->errPos.nixFile, - eline) << std::endl; - std::cout << prefix << std::endl; + if (einfo.nixCode->errPos.file != "") { + // filename, line, column. + out << std::endl << fmt("%1%in file: " ANSI_BLUE "%2% %3%" ANSI_NORMAL, + prefix, + einfo.nixCode->errPos.file, + showErrPos(einfo.nixCode->errPos)); } else { - std::cout << fmt("%1%from command line argument", prefix) << std::endl; - std::cout << prefix << std::endl; + out << std::endl << fmt("%1%from command line argument", prefix); } + nl = true; } // description - std::cout << prefix << einfo.description << std::endl; - std::cout << prefix << std::endl; + if (einfo.description != "") { + if (nl) + out << std::endl << prefix; + out << std::endl << prefix << einfo.description; + nl = true; + } // lines of code. - if (einfo.nixCode->errLineOfCode != "") { - printCodeLines(prefix, *einfo.nixCode); - std::cout << prefix << std::endl; + if (einfo.nixCode.has_value() && einfo.nixCode->errLineOfCode.has_value()) { + if (nl) + out << std::endl << prefix; + printCodeLines(out, prefix, *einfo.nixCode); + nl = true; } // hint if (einfo.hint.has_value()) { - std::cout << prefix << *einfo.hint << std::endl; - std::cout << prefix << std::endl; + if (nl) + out << std::endl << prefix; + out << std::endl << prefix << *einfo.hint; + nl = true; } -} + return out; +} } diff --git a/src/libutil/error.hh b/src/libutil/error.hh index f402b692e..8a48fa105 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -1,107 +1,91 @@ -#ifndef error_hh -#define error_hh +#pragma once -#include "ansicolor.hh" -#include -#include -#include + +#include "ref.hh" #include "types.hh" -namespace nix -{ +#include +#include +#include +#include + +#include "fmt.hh" + +/* Before 4.7, gcc's std::exception uses empty throw() specifiers for + * its (virtual) destructor and what() in c++11 mode, in violation of spec + */ +#ifdef __GNUC__ +#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 7) +#define EXCEPTION_NEEDS_THROW_SPEC +#endif +#endif + +namespace nix { + +/* + +This file defines two main structs/classes used in nix error handling. + +ErrorInfo provides a standard payload of error information, with conversion to string +happening in the logger rather than at the call site. + +BaseError is the ancestor of nix specific exceptions (and Interrupted), and contains +an ErrorInfo. + +ErrorInfo structs are sent to the logger as part of an exception, or directly with the +logError or logWarning macros. + +See the error-demo.cc program for usage examples. + +*/ typedef enum { - elWarning, - elError -} ErrLevel; + lvlError = 0, + lvlWarn, + lvlInfo, + lvlTalkative, + lvlChatty, + lvlDebug, + lvlVomit +} Verbosity; -struct ErrPos -{ - int lineNumber; - int column; - string nixFile; +// ErrPos indicates the location of an error in a nix file. +struct ErrPos { + int line = 0; + int column = 0; + string file; + operator bool() const + { + return line != 0; + } + + // convert from the Pos struct, found in libexpr. template ErrPos& operator=(const P &pos) { - lineNumber = pos.line; + line = pos.line; column = pos.column; - nixFile = pos.file; + file = pos.file; return *this; } template ErrPos(const P &p) { - *this = p; + *this = p; } }; -struct NixCode -{ +struct NixCode { ErrPos errPos; std::optional prevLineOfCode; - string errLineOfCode; + std::optional errLineOfCode; std::optional nextLineOfCode; }; -// ---------------------------------------------------------------- -// format function for hints. same as fmt, except templated values -// are always in yellow. - -template -struct yellowify -{ - yellowify(T &s) : value(s) {} - T &value; -}; - -template -std::ostream& operator<<(std::ostream &out, const yellowify &y) -{ - return out << ANSI_YELLOW << y.value << ANSI_NORMAL; -} - -class hintformat -{ -public: - hintformat(string format) :fmt(format) - { - fmt.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit); - } - template - hintformat& operator%(const T &value) - { - fmt % yellowify(value); - return *this; - } - - std::string str() const - { - return fmt.str(); - } - - template - friend class AddHint; -private: - format fmt; -}; - -std::ostream& operator<<(std::ostream &os, const hintformat &hf); - -template -inline hintformat hintfmt(const std::string & fs, const Args & ... args) -{ - hintformat f(fs); - formatHelper(f, args...); - return f; -} - -// ------------------------------------------------- -// ErrorInfo. -struct ErrorInfo -{ - ErrLevel level; +struct ErrorInfo { + Verbosity level; string name; string description; std::optional hint; @@ -110,12 +94,88 @@ struct ErrorInfo static std::optional programName; }; -// -------------------------------------------------------- -// error printing +std::ostream& operator<<(std::ostream &out, const ErrorInfo &einfo); -// just to cout for now. -void printErrorInfo(const ErrorInfo &einfo); +/* BaseError should generally not be caught, as it has Interrupted as + a subclass. Catch Error instead. */ +class BaseError : public std::exception +{ +protected: + string prefix_; // used for location traces etc. + mutable ErrorInfo err; + + mutable std::optional what_; + const string& calcWhat() const; + +public: + unsigned int status = 1; // exit status + + template + BaseError(unsigned int status, const Args & ... args) + : err { .level = lvlError, + .hint = hintfmt(args...) + } + , status(status) + { } + + template + BaseError(const Args & ... args) + : err { .level = lvlError, + .hint = hintfmt(args...) + } + { } + + BaseError(hintformat hint) + : err { .level = lvlError, + .hint = hint + } + { } + + BaseError(ErrorInfo e) + : err(e) + { } + + virtual const char* sname() const { return "BaseError"; } + +#ifdef EXCEPTION_NEEDS_THROW_SPEC + ~BaseError() throw () { }; + const char * what() const throw () { return calcWhat().c_str(); } +#else + const char * what() const noexcept override { return calcWhat().c_str(); } +#endif + + const string & msg() const { return calcWhat(); } + const string & prefix() const { return prefix_; } + BaseError & addPrefix(const FormatOrString & fs); + + const ErrorInfo & info() { calcWhat(); return err; } +}; + +#define MakeError(newClass, superClass) \ + class newClass : public superClass \ + { \ + public: \ + using superClass::superClass; \ + virtual const char* sname() const override { return #newClass; } \ + } + +MakeError(Error, BaseError); + +class SysError : public Error +{ +public: + int errNo; + + template + SysError(const Args & ... args) + :Error("") + { + errNo = errno; + auto hf = hintfmt(args...); + err.hint = hintfmt("%1%: %2%", normaltxt(hf.str()), strerror(errNo)); + } + + virtual const char* sname() const override { return "SysError"; } +}; } - -#endif diff --git a/src/libutil/fmt.hh b/src/libutil/fmt.hh new file mode 100644 index 000000000..12ab9c407 --- /dev/null +++ b/src/libutil/fmt.hh @@ -0,0 +1,139 @@ +#pragma once + +#include +#include +#include "ansicolor.hh" + + +namespace nix { + + +/* Inherit some names from other namespaces for convenience. */ +using std::string; +using boost::format; + + +/* A variadic template that does nothing. Useful to call a function + for all variadic arguments but ignoring the result. */ +struct nop { template nop(T...) {} }; + + +struct FormatOrString +{ + string s; + FormatOrString(const string & s) : s(s) { }; + template + FormatOrString(const F & f) : s(f.str()) { }; + FormatOrString(const char * s) : s(s) { }; +}; + + +/* A helper for formatting strings. ‘fmt(format, a_0, ..., a_n)’ is + equivalent to ‘boost::format(format) % a_0 % ... % + ... a_n’. However, ‘fmt(s)’ is equivalent to ‘s’ (so no %-expansion + takes place). */ + +template +inline void formatHelper(F & f) +{ +} + +template +inline void formatHelper(F & f, const T & x, const Args & ... args) +{ + formatHelper(f % x, args...); +} + +inline std::string fmt(const std::string & s) +{ + return s; +} + +inline std::string fmt(const char * s) +{ + return s; +} + +inline std::string fmt(const FormatOrString & fs) +{ + return fs.s; +} + +template +inline std::string fmt(const std::string & fs, const Args & ... args) +{ + boost::format f(fs); + f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit); + formatHelper(f, args...); + return f.str(); +} + +// ----------------------------------------------------------------------------- +// format function for hints in errors. same as fmt, except templated values +// are always in yellow. + +template +struct yellowtxt +{ + yellowtxt(const T &s) : value(s) {} + const T &value; +}; + +template +std::ostream& operator<<(std::ostream &out, const yellowtxt &y) +{ + return out << ANSI_YELLOW << y.value << ANSI_NORMAL; +} + +template +struct normaltxt +{ + normaltxt(const T &s) : value(s) {} + const T &value; +}; + +template +std::ostream& operator<<(std::ostream &out, const normaltxt &y) +{ + return out << ANSI_NORMAL << y.value; +} + +class hintformat +{ +public: + hintformat(const string &format) :fmt(format) + { + fmt.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit); + } + + hintformat(const hintformat &hf) + : fmt(hf.fmt) + {} + + template + hintformat& operator%(const T &value) + { + fmt % yellowtxt(value); + return *this; + } + + std::string str() const + { + return fmt.str(); + } + +private: + format fmt; +}; + +std::ostream& operator<<(std::ostream &os, const hintformat &hf); + +template +inline hintformat hintfmt(const std::string & fs, const Args & ... args) +{ + hintformat f(fs); + formatHelper(f, args...); + return f; +} + +} diff --git a/src/libutil/logging.cc b/src/libutil/logging.cc index 15cbc1589..108dc3bd1 100644 --- a/src/libutil/logging.cc +++ b/src/libutil/logging.cc @@ -69,9 +69,17 @@ public: writeToStderr(prefix + filterANSIEscapes(fs.s, !tty) + "\n"); } + void logEI(const ErrorInfo & ei) override + { + std::stringstream oss; + oss << ei; + + log(ei.level, oss.str()); + } + void startActivity(ActivityId act, Verbosity lvl, ActivityType type, const std::string & s, const Fields & fields, ActivityId parent) - override + override { if (lvl <= verbosity && !s.empty()) log(lvl, s + "..."); @@ -126,8 +134,7 @@ Activity::Activity(Logger & logger, Verbosity lvl, ActivityType type, logger.startActivity(id, lvl, type, s, fields, parent); } -struct JSONLogger : Logger -{ +struct JSONLogger : Logger { Logger & prevLogger; JSONLogger(Logger & prevLogger) : prevLogger(prevLogger) { } @@ -163,6 +170,19 @@ struct JSONLogger : Logger write(json); } + void logEI(const ErrorInfo & ei) override + { + std::ostringstream oss; + oss << ei; + + nlohmann::json json; + json["action"] = "msg"; + json["level"] = ei.level; + json["msg"] = oss.str(); + + write(json); + } + void startActivity(ActivityId act, Verbosity lvl, ActivityType type, const std::string & s, const Fields & fields, ActivityId parent) override { @@ -253,13 +273,17 @@ bool handleJSONLogMessage(const std::string & msg, } } catch (std::exception & e) { - printError("bad log message from builder: %s", e.what()); + logError({ + .name = "Json log message", + .hint = hintfmt("bad log message from builder: %s", e.what()) + }); } return true; } -Activity::~Activity() { +Activity::~Activity() +{ try { logger.stopActivity(id); } catch (...) { diff --git a/src/libutil/logging.hh b/src/libutil/logging.hh index e3d91e01f..b99b246c3 100644 --- a/src/libutil/logging.hh +++ b/src/libutil/logging.hh @@ -1,19 +1,10 @@ #pragma once #include "types.hh" +#include "error.hh" namespace nix { -typedef enum { - lvlError = 0, - lvlWarn, - lvlInfo, - lvlTalkative, - lvlChatty, - lvlDebug, - lvlVomit -} Verbosity; - typedef enum { actUnknown = 0, actCopyPath = 100, @@ -75,6 +66,14 @@ public: log(lvlInfo, fs); } + virtual void logEI(const ErrorInfo &ei) = 0; + + void logEI(Verbosity lvl, ErrorInfo ei) + { + ei.level = lvl; + logEI(ei); + } + virtual void warn(const std::string & msg); virtual void startActivity(ActivityId act, Verbosity lvl, ActivityType type, @@ -156,9 +155,23 @@ bool handleJSONLogMessage(const std::string & msg, extern Verbosity verbosity; /* suppress msgs > this */ -/* Print a message if the current log level is at least the specified - level. Note that this has to be implemented as a macro to ensure - that the arguments are evaluated lazily. */ +/* Print a message with the standard ErrorInfo format. + In general, use these 'log' macros for reporting problems that may require user + intervention or that need more explanation. Use the 'print' macros for more + lightweight status messages. */ +#define logErrorInfo(level, errorInfo...) \ + do { \ + if (level <= nix::verbosity) { \ + logger->logEI(level, errorInfo); \ + } \ + } while (0) + +#define logError(errorInfo...) logErrorInfo(lvlError, errorInfo) +#define logWarning(errorInfo...) logErrorInfo(lvlWarn, errorInfo) + +/* Print a string message if the current log level is at least the specified + level. Note that this has to be implemented as a macro to ensure that the + arguments are evaluated lazily. */ #define printMsg(level, args...) \ do { \ if (level <= nix::verbosity) { \ @@ -172,6 +185,7 @@ extern Verbosity verbosity; /* suppress msgs > this */ #define debug(args...) printMsg(lvlDebug, args) #define vomit(args...) printMsg(lvlVomit, args) +/* if verbosity >= lvlWarn, print a message with a yellow 'warning:' prefix. */ template inline void warn(const std::string & fs, const Args & ... args) { diff --git a/src/libutil/serialise.cc b/src/libutil/serialise.cc index 8201549fd..35f7ee917 100644 --- a/src/libutil/serialise.cc +++ b/src/libutil/serialise.cc @@ -52,7 +52,10 @@ size_t threshold = 256 * 1024 * 1024; static void warnLargeDump() { - printError("warning: dumping very large path (> 256 MiB); this may run out of memory"); + logWarning(ErrorInfo { + .name = "Large path", + .description = "dumping very large path (> 256 MiB); this may run out of memory" + }); } diff --git a/src/libutil/tests/local.mk b/src/libutil/tests/local.mk index a297edb64..815e18560 100644 --- a/src/libutil/tests/local.mk +++ b/src/libutil/tests/local.mk @@ -8,7 +8,7 @@ libutil-tests_INSTALL_DIR := libutil-tests_SOURCES := $(wildcard $(d)/*.cc) -libutil-tests_CXXFLAGS += -I src/libutil +libutil-tests_CXXFLAGS += -I src/libutil -I src/libexpr libutil-tests_LIBS = libutil diff --git a/src/libutil/tests/logging.cc b/src/libutil/tests/logging.cc new file mode 100644 index 000000000..fbdc91253 --- /dev/null +++ b/src/libutil/tests/logging.cc @@ -0,0 +1,255 @@ +#include "logging.hh" +#include "nixexpr.hh" +#include "util.hh" + +#include + +namespace nix { + + /* ---------------------------------------------------------------------------- + * logEI + * --------------------------------------------------------------------------*/ + + TEST(logEI, catpuresBasicProperties) { + + MakeError(TestError, Error); + ErrorInfo::programName = std::optional("error-unit-test"); + + try { + throw TestError("an error for testing purposes"); + } catch (Error &e) { + testing::internal::CaptureStderr(); + logger->logEI(e.info()); + auto str = testing::internal::GetCapturedStderr(); + + ASSERT_STREQ(str.c_str(),"\x1B[31;1merror:\x1B[0m\x1B[34;1m --- TestError ------------------------------------ error-unit-test\x1B[0m\nan error for testing purposes\n"); + } + } + + TEST(logEI, appendingHintsToPreviousError) { + + MakeError(TestError, Error); + ErrorInfo::programName = std::optional("error-unit-test"); + + try { + auto e = Error("initial error"); + throw TestError(e.info()); + } catch (Error &e) { + ErrorInfo ei = e.info(); + ei.hint = hintfmt("%s; subsequent error message.", normaltxt(e.info().hint ? e.info().hint->str() : "")); + + testing::internal::CaptureStderr(); + logger->logEI(ei); + auto str = testing::internal::GetCapturedStderr(); + + ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- TestError ------------------------------------ error-unit-test\x1B[0m\n\x1B[33;1m\x1B[0minitial error\x1B[0m; subsequent error message.\n"); + } + + } + + TEST(logEI, picksUpSysErrorExitCode) { + + MakeError(TestError, Error); + ErrorInfo::programName = std::optional("error-unit-test"); + + try { + auto x = readFile(-1); + } + catch (SysError &e) { + testing::internal::CaptureStderr(); + logError(e.info()); + auto str = testing::internal::GetCapturedStderr(); + + ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- SysError ------------------------------------- error-unit-test\x1B[0m\n\x1B[33;1m\x1B[0mstatting file\x1B[0m: \x1B[33;1mBad file descriptor\x1B[0m\n"); + + } + } + + TEST(logEI, loggingErrorOnInfoLevel) { + testing::internal::CaptureStderr(); + + logger->logEI({ .level = lvlInfo, + .name = "Info name", + .description = "Info description", + }); + + auto str = testing::internal::GetCapturedStderr(); + ASSERT_STREQ(str.c_str(), "\x1B[32;1minfo:\x1B[0m\x1B[34;1m --- Info name ------------------------------------- error-unit-test\x1B[0m\nInfo description\n"); + } + + TEST(logEI, loggingErrorOnTalkativeLevel) { + verbosity = lvlTalkative; + + testing::internal::CaptureStderr(); + + logger->logEI({ .level = lvlTalkative, + .name = "Talkative name", + .description = "Talkative description", + }); + + auto str = testing::internal::GetCapturedStderr(); + ASSERT_STREQ(str.c_str(), "\x1B[32;1mtalk:\x1B[0m\x1B[34;1m --- Talkative name -------------------------------- error-unit-test\x1B[0m\nTalkative description\n"); + } + + TEST(logEI, loggingErrorOnChattyLevel) { + verbosity = lvlChatty; + + testing::internal::CaptureStderr(); + + logger->logEI({ .level = lvlChatty, + .name = "Chatty name", + .description = "Talkative description", + }); + + auto str = testing::internal::GetCapturedStderr(); + ASSERT_STREQ(str.c_str(), "\x1B[32;1mchat:\x1B[0m\x1B[34;1m --- Chatty name ----------------------------------- error-unit-test\x1B[0m\nTalkative description\n"); + } + + TEST(logEI, loggingErrorOnDebugLevel) { + verbosity = lvlDebug; + + testing::internal::CaptureStderr(); + + logger->logEI({ .level = lvlDebug, + .name = "Debug name", + .description = "Debug description", + }); + + auto str = testing::internal::GetCapturedStderr(); + ASSERT_STREQ(str.c_str(), "\x1B[33;1mdebug:\x1B[0m\x1B[34;1m --- Debug name ----------------------------------- error-unit-test\x1B[0m\nDebug description\n"); + } + + TEST(logEI, loggingErrorOnVomitLevel) { + verbosity = lvlVomit; + + testing::internal::CaptureStderr(); + + logger->logEI({ .level = lvlVomit, + .name = "Vomit name", + .description = "Vomit description", + }); + + auto str = testing::internal::GetCapturedStderr(); + ASSERT_STREQ(str.c_str(), "\x1B[32;1mvomit:\x1B[0m\x1B[34;1m --- Vomit name ----------------------------------- error-unit-test\x1B[0m\nVomit description\n"); + } + + /* ---------------------------------------------------------------------------- + * logError + * --------------------------------------------------------------------------*/ + + + TEST(logError, logErrorWithoutHintOrCode) { + testing::internal::CaptureStderr(); + + logError({ + .name = "name", + .description = "error description", + }); + + auto str = testing::internal::GetCapturedStderr(); + ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- name ----------------------------------------- error-unit-test\x1B[0m\nerror description\n"); + } + + TEST(logError, logErrorWithPreviousAndNextLinesOfCode) { + SymbolTable testTable; + auto problem_file = testTable.create("myfile.nix"); + + testing::internal::CaptureStderr(); + + logError({ + .name = "error name", + .description = "error with code lines", + .hint = hintfmt("this hint has %1% templated %2%!!", + "yellow", + "values"), + .nixCode = NixCode { + .errPos = Pos(problem_file, 40, 13), + .prevLineOfCode = "previous line of code", + .errLineOfCode = "this is the problem line of code", + .nextLineOfCode = "next line of code", + }}); + + + auto str = testing::internal::GetCapturedStderr(); + ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- error name ----------------------------------- error-unit-test\x1B[0m\nin file: \x1B[34;1mmyfile.nix (40:13)\x1B[0m\n\nerror with code lines\n\n 39| previous line of code\n 40| this is the problem line of code\n | \x1B[31;1m^\x1B[0m\n 41| next line of code\n\nthis hint has \x1B[33;1myellow\x1B[0m templated \x1B[33;1mvalues\x1B[0m!!\n"); + } + + TEST(logError, logErrorWithoutLinesOfCode) { + SymbolTable testTable; + auto problem_file = testTable.create("myfile.nix"); + testing::internal::CaptureStderr(); + + logError({ + .name = "error name", + .description = "error without any code lines.", + .hint = hintfmt("this hint has %1% templated %2%!!", + "yellow", + "values"), + .nixCode = NixCode { + .errPos = Pos(problem_file, 40, 13) + }}); + + auto str = testing::internal::GetCapturedStderr(); + ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- error name ----------------------------------- error-unit-test\x1B[0m\nin file: \x1B[34;1mmyfile.nix (40:13)\x1B[0m\n\nerror without any code lines.\n\nthis hint has \x1B[33;1myellow\x1B[0m templated \x1B[33;1mvalues\x1B[0m!!\n"); + } + + TEST(logError, logErrorWithOnlyHintAndName) { + SymbolTable testTable; + auto problem_file = testTable.create("myfile.nix"); + testing::internal::CaptureStderr(); + + logError({ + .name = "error name", + .hint = hintfmt("hint %1%", "only"), + .nixCode = NixCode { + .errPos = Pos(problem_file, 40, 13) + }}); + + auto str = testing::internal::GetCapturedStderr(); + ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- error name ----------------------------------- error-unit-test\x1B[0m\nin file: \x1B[34;1mmyfile.nix (40:13)\x1B[0m\n\nhint \x1B[33;1monly\x1B[0m\n"); + + } + + /* ---------------------------------------------------------------------------- + * logWarning + * --------------------------------------------------------------------------*/ + + TEST(logWarning, logWarningWithNameDescriptionAndHint) { + testing::internal::CaptureStderr(); + + logWarning({ + .name = "name", + .description = "error description", + .hint = hintfmt("there was a %1%", "warning"), + }); + + auto str = testing::internal::GetCapturedStderr(); + ASSERT_STREQ(str.c_str(), "\x1B[33;1mwarning:\x1B[0m\x1B[34;1m --- name --------------------------------------- error-unit-test\x1B[0m\nerror description\n\nthere was a \x1B[33;1mwarning\x1B[0m\n"); + } + + TEST(logWarning, logWarningWithFileLineNumAndCode) { + + SymbolTable testTable; + auto problem_file = testTable.create("myfile.nix"); + + testing::internal::CaptureStderr(); + + logWarning({ + .name = "warning name", + .description = "warning description", + .hint = hintfmt("this hint has %1% templated %2%!!", + "yellow", + "values"), + .nixCode = NixCode { + .errPos = Pos(problem_file, 40, 13), + .prevLineOfCode = std::nullopt, + .errLineOfCode = "this is the problem line of code", + .nextLineOfCode = std::nullopt + }}); + + + auto str = testing::internal::GetCapturedStderr(); + ASSERT_STREQ(str.c_str(), "\x1B[33;1mwarning:\x1B[0m\x1B[34;1m --- warning name ------------------------------- error-unit-test\x1B[0m\nin file: \x1B[34;1mmyfile.nix (40:13)\x1B[0m\n\nwarning description\n\n 40| this is the problem line of code\n | \x1B[31;1m^\x1B[0m\n\nthis hint has \x1B[33;1myellow\x1B[0m templated \x1B[33;1mvalues\x1B[0m!!\n"); + } + +} diff --git a/src/libutil/types.hh b/src/libutil/types.hh index 250c9581d..89ae108f9 100644 --- a/src/libutil/types.hh +++ b/src/libutil/types.hh @@ -3,157 +3,24 @@ #include "ref.hh" -#include #include #include -#include #include - -#include - -/* Before 4.7, gcc's std::exception uses empty throw() specifiers for - * its (virtual) destructor and what() in c++11 mode, in violation of spec - */ -#ifdef __GNUC__ -#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 7) -#define EXCEPTION_NEEDS_THROW_SPEC -#endif -#endif - +#include namespace nix { - -/* Inherit some names from other namespaces for convenience. */ -using std::string; using std::list; using std::set; using std::vector; -using boost::format; - - -/* A variadic template that does nothing. Useful to call a function - for all variadic arguments but ignoring the result. */ -struct nop { template nop(T...) {} }; - - -struct FormatOrString -{ - string s; - FormatOrString(const string & s) : s(s) { }; - template - FormatOrString(const F & f) : s(f.str()) { }; - FormatOrString(const char * s) : s(s) { }; -}; - - -/* A helper for formatting strings. ‘fmt(format, a_0, ..., a_n)’ is - equivalent to ‘boost::format(format) % a_0 % ... % - ... a_n’. However, ‘fmt(s)’ is equivalent to ‘s’ (so no %-expansion - takes place). */ - -template -inline void formatHelper(F & f) -{ -} - -template -inline void formatHelper(F & f, const T & x, const Args & ... args) -{ - formatHelper(f % x, args...); -} - -inline std::string fmt(const std::string & s) -{ - return s; -} - -inline std::string fmt(const char * s) -{ - return s; -} - -inline std::string fmt(const FormatOrString & fs) -{ - return fs.s; -} - -template -inline std::string fmt(const std::string & fs, const Args & ... args) -{ - boost::format f(fs); - f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit); - formatHelper(f, args...); - return f.str(); -} - - -/* BaseError should generally not be caught, as it has Interrupted as - a subclass. Catch Error instead. */ -class BaseError : public std::exception -{ -protected: - string prefix_; // used for location traces etc. - string err; -public: - unsigned int status = 1; // exit status - - template - BaseError(unsigned int status, const Args & ... args) - : err(fmt(args...)) - , status(status) - { - } - - template - BaseError(const Args & ... args) - : err(fmt(args...)) - { - } - -#ifdef EXCEPTION_NEEDS_THROW_SPEC - ~BaseError() throw () { }; - const char * what() const throw () { return err.c_str(); } -#else - const char * what() const noexcept { return err.c_str(); } -#endif - - const string & msg() const { return err; } - const string & prefix() const { return prefix_; } - BaseError & addPrefix(const FormatOrString & fs); -}; - -#define MakeError(newClass, superClass) \ - class newClass : public superClass \ - { \ - public: \ - using superClass::superClass; \ - } - -MakeError(Error, BaseError); - -class SysError : public Error -{ -public: - int errNo; - - template - SysError(const Args & ... args) - : Error(addErrno(fmt(args...))) - { } - -private: - - std::string addErrno(const std::string & s); -}; - +using std::string; typedef list Strings; typedef set StringSet; -typedef std::map StringMap; - +typedef std::map StringMap; /* Paths are just strings. */ + typedef string Path; typedef list Paths; typedef set PathSet; diff --git a/src/libutil/url.hh b/src/libutil/url.hh index 4a0d4071b..2ef88ef2a 100644 --- a/src/libutil/url.hh +++ b/src/libutil/url.hh @@ -1,6 +1,6 @@ #pragma once -#include "types.hh" +#include "error.hh" #include diff --git a/src/libutil/util.cc b/src/libutil/util.cc index b66447e08..a2281237b 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -40,24 +40,6 @@ extern char * * environ; namespace nix { - -const std::string nativeSystem = SYSTEM; - - -BaseError & BaseError::addPrefix(const FormatOrString & fs) -{ - prefix_ = fs.s + prefix_; - return *this; -} - - -std::string SysError::addErrno(const std::string & s) -{ - errNo = errno; - return s + ": " + strerror(errNo); -} - - std::optional getEnv(const std::string & key) { char * value = getenv(key.c_str()); @@ -129,7 +111,7 @@ Path canonPath(const Path & path, bool resolveSymlinks) string s; if (path[0] != '/') - throw Error(format("not an absolute path: '%1%'") % path); + throw Error("not an absolute path: '%1%'", path); string::const_iterator i = path.begin(), end = path.end(); string temp; @@ -165,7 +147,7 @@ Path canonPath(const Path & path, bool resolveSymlinks) the symlink target might contain new symlinks). */ if (resolveSymlinks && isLink(s)) { if (++followCount >= maxFollow) - throw Error(format("infinite symlink recursion in path '%1%'") % path); + throw Error("infinite symlink recursion in path '%1%'", path); temp = absPath(readLink(s), dirOf(s)) + string(i, end); i = temp.begin(); /* restart */ @@ -226,7 +208,7 @@ struct stat lstat(const Path & path) { struct stat st; if (lstat(path.c_str(), &st)) - throw SysError(format("getting status of '%1%'") % path); + throw SysError("getting status of '%1%'", path); return st; } @@ -238,7 +220,7 @@ bool pathExists(const Path & path) res = lstat(path.c_str(), &st); if (!res) return true; if (errno != ENOENT && errno != ENOTDIR) - throw SysError(format("getting status of %1%") % path); + throw SysError("getting status of %1%", path); return false; } @@ -286,7 +268,7 @@ DirEntries readDirectory(DIR *dir, const Path & path) #endif ); } - if (errno) throw SysError(format("reading directory '%1%'") % path); + if (errno) throw SysError("reading directory '%1%'", path); return entries; } @@ -294,7 +276,7 @@ DirEntries readDirectory(DIR *dir, const Path & path) DirEntries readDirectory(const Path & path) { AutoCloseDir dir(opendir(path.c_str())); - if (!dir) throw SysError(format("opening directory '%1%'") % path); + if (!dir) throw SysError("opening directory '%1%'", path); return readDirectory(dir.get(), path); } @@ -324,7 +306,7 @@ string readFile(const Path & path) { AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC); if (!fd) - throw SysError(format("opening file '%1%'") % path); + throw SysError("opening file '%1%'", path); return readFile(fd.get()); } @@ -332,7 +314,8 @@ string readFile(const Path & path) void readFile(const Path & path, Sink & sink) { AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC); - if (!fd) throw SysError("opening file '%s'", path); + if (!fd) + throw SysError("opening file '%s'", path); drainFD(fd.get(), sink); } @@ -341,7 +324,7 @@ void writeFile(const Path & path, const string & s, mode_t mode) { AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode); if (!fd) - throw SysError(format("opening file '%1%'") % path); + throw SysError("opening file '%1%'", path); writeFull(fd.get(), s); } @@ -350,7 +333,7 @@ void writeFile(const Path & path, Source & source, mode_t mode) { AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode); if (!fd) - throw SysError(format("opening file '%1%'") % path); + throw SysError("opening file '%1%'", path); std::vector buf(64 * 1024); @@ -400,7 +383,7 @@ static void _deletePath(int parentfd, const Path & path, unsigned long long & by struct stat st; if (fstatat(parentfd, name.c_str(), &st, AT_SYMLINK_NOFOLLOW) == -1) { if (errno == ENOENT) return; - throw SysError(format("getting status of '%1%'") % path); + throw SysError("getting status of '%1%'", path); } if (!S_ISDIR(st.st_mode) && st.st_nlink == 1) @@ -411,15 +394,15 @@ static void _deletePath(int parentfd, const Path & path, unsigned long long & by const auto PERM_MASK = S_IRUSR | S_IWUSR | S_IXUSR; if ((st.st_mode & PERM_MASK) != PERM_MASK) { if (fchmodat(parentfd, name.c_str(), st.st_mode | PERM_MASK, 0) == -1) - throw SysError(format("chmod '%1%'") % path); + throw SysError("chmod '%1%'", path); } int fd = openat(parentfd, path.c_str(), O_RDONLY); if (!fd) - throw SysError(format("opening directory '%1%'") % path); + throw SysError("opening directory '%1%'", path); AutoCloseDir dir(fdopendir(fd)); if (!dir) - throw SysError(format("opening directory '%1%'") % path); + throw SysError("opening directory '%1%'", path); for (auto & i : readDirectory(dir.get(), path)) _deletePath(dirfd(dir.get()), path + "/" + i.name, bytesFreed); } @@ -427,7 +410,7 @@ static void _deletePath(int parentfd, const Path & path, unsigned long long & by int flags = S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0; if (unlinkat(parentfd, name.c_str(), flags) == -1) { if (errno == ENOENT) return; - throw SysError(format("cannot unlink '%1%'") % path); + throw SysError("cannot unlink '%1%'", path); } } @@ -443,7 +426,7 @@ static void _deletePath(const Path & path, unsigned long long & bytesFreed) // for backwards compatibility. if (errno == ENOENT) return; - throw SysError(format("opening directory '%1%'") % path); + throw SysError("opening directory '%1%'", path); } _deletePath(dirfd.get(), path, bytesFreed); @@ -497,12 +480,12 @@ Path createTempDir(const Path & tmpRoot, const Path & prefix, "wheel", then "tar" will fail to unpack archives that have the setgid bit set on directories. */ if (chown(tmpDir.c_str(), (uid_t) -1, getegid()) != 0) - throw SysError(format("setting group of directory '%1%'") % tmpDir); + throw SysError("setting group of directory '%1%'", tmpDir); #endif return tmpDir; } if (errno != EEXIST) - throw SysError(format("creating directory '%1%'") % tmpDir); + throw SysError("creating directory '%1%'", tmpDir); } } @@ -584,15 +567,15 @@ Paths createDirs(const Path & path) if (lstat(path.c_str(), &st) == -1) { created = createDirs(dirOf(path)); if (mkdir(path.c_str(), 0777) == -1 && errno != EEXIST) - throw SysError(format("creating directory '%1%'") % path); + throw SysError("creating directory '%1%'", path); st = lstat(path); created.push_back(path); } if (S_ISLNK(st.st_mode) && stat(path.c_str(), &st) == -1) - throw SysError(format("statting symlink '%1%'") % path); + throw SysError("statting symlink '%1%'", path); - if (!S_ISDIR(st.st_mode)) throw Error(format("'%1%' is not a directory") % path); + if (!S_ISDIR(st.st_mode)) throw Error("'%1%' is not a directory", path); return created; } @@ -601,7 +584,7 @@ Paths createDirs(const Path & path) void createSymlink(const Path & target, const Path & link) { if (symlink(target.c_str(), link.c_str())) - throw SysError(format("creating symlink from '%1%' to '%2%'") % link % target); + throw SysError("creating symlink from '%1%' to '%2%'", link, target); } @@ -618,7 +601,7 @@ void replaceSymlink(const Path & target, const Path & link) } if (rename(tmp.c_str(), link.c_str()) != 0) - throw SysError(format("renaming '%1%' to '%2%'") % tmp % link); + throw SysError("renaming '%1%' to '%2%'", tmp, link); break; } @@ -723,7 +706,7 @@ AutoDelete::~AutoDelete() deletePath(path); else { if (remove(path.c_str()) == -1) - throw SysError(format("cannot unlink '%1%'") % path); + throw SysError("cannot unlink '%1%'", path); } } } catch (...) { @@ -789,7 +772,7 @@ void AutoCloseFD::close() if (fd != -1) { if (::close(fd) == -1) /* This should never happen. */ - throw SysError(format("closing file descriptor %1%") % fd); + throw SysError("closing file descriptor %1%", fd); } } @@ -862,7 +845,7 @@ int Pid::kill() { assert(pid != -1); - debug(format("killing process %1%") % pid); + debug("killing process %1%", pid); /* Send the requested signal to the child. If it has its own process group, send the signal to every process in the child @@ -874,7 +857,7 @@ int Pid::kill() #if __FreeBSD__ || __APPLE__ if (errno != EPERM || ::kill(pid, 0) != 0) #endif - printError((SysError("killing process %d", pid).msg())); + logError(SysError("killing process %d", pid).info()); } return wait(); @@ -920,7 +903,7 @@ pid_t Pid::release() void killUser(uid_t uid) { - debug(format("killing all processes running under uid '%1%'") % uid); + debug("killing all processes running under uid '%1%'", uid); assert(uid != 0); /* just to be safe... */ @@ -949,7 +932,7 @@ void killUser(uid_t uid) #endif if (errno == ESRCH) break; /* no more processes */ if (errno != EINTR) - throw SysError(format("cannot kill processes for uid '%1%'") % uid); + throw SysError("cannot kill processes for uid '%1%'", uid); } _exit(0); @@ -957,7 +940,7 @@ void killUser(uid_t uid) int status = pid.wait(); if (status != 0) - throw Error(format("cannot kill processes for uid '%1%': %2%") % uid % statusToString(status)); + throw Error("cannot kill processes for uid '%1%': %2%", uid, statusToString(status)); /* !!! We should really do some check to make sure that there are no processes left running under `uid', but there is no portable @@ -1351,7 +1334,7 @@ void ignoreException() try { throw; } catch (std::exception & e) { - printError(format("error (ignored): %1%") % e.what()); + printError("error (ignored): %1%", e.what()); } } @@ -1464,17 +1447,6 @@ string base64Decode(std::string_view s) } -void callFailure(const std::function & failure, std::exception_ptr exc) -{ - try { - failure(exc); - } catch (std::exception & e) { - printError(format("uncaught exception: %s") % e.what()); - abort(); - } -} - - static Sync> windowSize{{0, 0}}; diff --git a/src/libutil/util.hh b/src/libutil/util.hh index c95232317..3641daaec 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -1,6 +1,7 @@ #pragma once #include "types.hh" +#include "error.hh" #include "logging.hh" #include "ansicolor.hh" diff --git a/src/nix-build/nix-build.cc b/src/nix-build/nix-build.cc index 8649de5e9..591fff999 100755 --- a/src/nix-build/nix-build.cc +++ b/src/nix-build/nix-build.cc @@ -368,7 +368,12 @@ static void _main(int argc, char * * argv) shell = drv->queryOutPath() + "/bin/bash"; } catch (Error & e) { - printError("warning: %s; will use bash from your environment", e.what()); + logWarning( + ErrorInfo { + .name = "bashInteractive", + .hint = hintfmt("%s; will use bash from your environment", + (e.info().hint ? e.info().hint->str() : "")) + }); shell = "bash"; } } diff --git a/src/nix-channel/nix-channel.cc b/src/nix-channel/nix-channel.cc index abd390414..3ccf620c9 100755 --- a/src/nix-channel/nix-channel.cc +++ b/src/nix-channel/nix-channel.cc @@ -38,7 +38,7 @@ static void writeChannels() { auto channelsFD = AutoCloseFD{open(channelsList.c_str(), O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC, 0644)}; if (!channelsFD) - throw SysError(format("opening '%1%' for writing") % channelsList); + throw SysError("opening '%1%' for writing", channelsList); for (const auto & channel : channels) writeFull(channelsFD.get(), channel.second + " " + channel.first + "\n"); } @@ -47,9 +47,9 @@ static void writeChannels() static void addChannel(const string & url, const string & name) { if (!regex_search(url, std::regex("^(file|http|https)://"))) - throw Error(format("invalid channel URL '%1%'") % url); + throw Error("invalid channel URL '%1%'", url); if (!regex_search(name, std::regex("^[a-zA-Z0-9_][a-zA-Z0-9_\\.-]*$"))) - throw Error(format("invalid channel identifier '%1%'") % name); + throw Error("invalid channel identifier '%1%'", name); readChannels(); channels[name] = url; writeChannels(); @@ -137,9 +137,9 @@ static void update(const StringSet & channelNames) if (S_ISLNK(st.st_mode)) // old-skool ~/.nix-defexpr if (unlink(nixDefExpr.c_str()) == -1) - throw SysError(format("unlinking %1%") % nixDefExpr); + throw SysError("unlinking %1%", nixDefExpr); } else if (errno != ENOENT) { - throw SysError(format("getting status of %1%") % nixDefExpr); + throw SysError("getting status of %1%", nixDefExpr); } createDirs(nixDefExpr); auto channelLink = nixDefExpr + "/channels"; diff --git a/src/nix-daemon/nix-daemon.cc b/src/nix-daemon/nix-daemon.cc index e68d1b1be..582c78d14 100644 --- a/src/nix-daemon/nix-daemon.cc +++ b/src/nix-daemon/nix-daemon.cc @@ -36,7 +36,7 @@ using namespace nix::daemon; #define SPLICE_F_MOVE 0 static ssize_t splice(int fd_in, void *off_in, int fd_out, void *off_out, size_t len, unsigned int flags) { - /* We ignore most parameters, we just have them for conformance with the linux syscall */ + // We ignore most parameters, we just have them for conformance with the linux syscall std::vector buf(8192); auto read_count = read(fd_in, buf.data(), buf.size()); if (read_count == -1) @@ -57,7 +57,7 @@ static void sigChldHandler(int sigNo) { // Ensure we don't modify errno of whatever we've interrupted auto saved_errno = errno; - /* Reap all dead children. */ + // Reap all dead children. while (waitpid(-1, 0, WNOHANG) > 0) ; errno = saved_errno; } @@ -106,7 +106,7 @@ struct PeerInfo }; -/* Get the identity of the caller, if possible. */ +// Get the identity of the caller, if possible. static PeerInfo getPeerInfo(int remote) { PeerInfo peer = { false, 0, false, 0, false, 0 }; @@ -154,13 +154,12 @@ static void daemonLoop(char * * argv) if (chdir("/") == -1) throw SysError("cannot change current directory"); - /* Get rid of children automatically; don't let them become - zombies. */ + // Get rid of children automatically; don't let them become zombies. setSigChldAction(true); AutoCloseFD fdSocket; - /* Handle socket-based activation by systemd. */ + // Handle socket-based activation by systemd. auto listenFds = getEnv("LISTEN_FDS"); if (listenFds) { if (getEnv("LISTEN_PID") != std::to_string(getpid()) || listenFds != "1") @@ -169,17 +168,17 @@ static void daemonLoop(char * * argv) closeOnExec(fdSocket.get()); } - /* Otherwise, create and bind to a Unix domain socket. */ + // Otherwise, create and bind to a Unix domain socket. else { createDirs(dirOf(settings.nixDaemonSocketFile)); fdSocket = createUnixDomainSocket(settings.nixDaemonSocketFile, 0666); } - /* Loop accepting connections. */ + // Loop accepting connections. while (1) { try { - /* Accept a connection. */ + // Accept a connection. struct sockaddr_un remoteAddr; socklen_t remoteAddrLen = sizeof(remoteAddr); @@ -209,13 +208,13 @@ static void daemonLoop(char * * argv) trusted = Trusted; if ((!trusted && !matchUser(user, group, allowedUsers)) || group == settings.buildUsersGroup) - throw Error(format("user '%1%' is not allowed to connect to the Nix daemon") % user); + throw Error("user '%1%' is not allowed to connect to the Nix daemon", user); printInfo(format((string) "accepted connection from pid %1%, user %2%" + (trusted ? " (trusted)" : "")) % (peer.pidKnown ? std::to_string(peer.pid) : "") % (peer.uidKnown ? user : "")); - /* Fork a child to handle the connection. */ + // Fork a child to handle the connection. ProcessOptions options; options.errorPrefix = "unexpected Nix daemon error: "; options.dieWithParent = false; @@ -224,20 +223,20 @@ static void daemonLoop(char * * argv) startProcess([&]() { fdSocket = -1; - /* Background the daemon. */ + // Background the daemon. if (setsid() == -1) - throw SysError(format("creating a new session")); + throw SysError("creating a new session"); - /* Restore normal handling of SIGCHLD. */ + // Restore normal handling of SIGCHLD. setSigChldAction(false); - /* For debugging, stuff the pid into argv[1]. */ + // For debugging, stuff the pid into argv[1]. if (peer.pidKnown && argv[1]) { string processName = std::to_string(peer.pid); strncpy(argv[1], processName.c_str(), strlen(argv[1])); } - /* Handle the connection. */ + // Handle the connection. FdSource from(remote.get()); FdSink to(remote.get()); processConnection(openUncachedStore(), from, to, trusted, NotRecursive, user, peer.uid); @@ -247,8 +246,11 @@ static void daemonLoop(char * * argv) } catch (Interrupted & e) { return; - } catch (Error & e) { - printError(format("error processing connection: %1%") % e.msg()); + } catch (Error & error) { + ErrorInfo ei = error.info(); + ei.hint = std::optional(hintfmt("error processing connection: %1%", + (error.info().hint.has_value() ? error.info().hint->str() : ""))); + logError(ei); } } } @@ -261,7 +263,7 @@ static int _main(int argc, char * * argv) parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) { if (*arg == "--daemon") - ; /* ignored for backwards compatibility */ + ; // ignored for backwards compatibility else if (*arg == "--help") showManPage("nix-daemon"); else if (*arg == "--version") @@ -276,7 +278,7 @@ static int _main(int argc, char * * argv) if (stdio) { if (getStoreType() == tDaemon) { - /* Forward on this connection to the real daemon */ + // Forward on this connection to the real daemon auto socketPath = settings.nixDaemonSocketFile; auto s = socket(PF_UNIX, SOCK_STREAM, 0); if (s == -1) @@ -284,17 +286,17 @@ static int _main(int argc, char * * argv) auto socketDir = dirOf(socketPath); if (chdir(socketDir.c_str()) == -1) - throw SysError(format("changing to socket directory '%1%'") % socketDir); + throw SysError("changing to socket directory '%1%'", socketDir); auto socketName = std::string(baseNameOf(socketPath)); auto addr = sockaddr_un{}; addr.sun_family = AF_UNIX; if (socketName.size() + 1 >= sizeof(addr.sun_path)) - throw Error(format("socket name %1% is too long") % socketName); + throw Error("socket name %1% is too long", socketName); strcpy(addr.sun_path, socketName.c_str()); if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) == -1) - throw SysError(format("cannot connect to daemon at %1%") % socketPath); + throw SysError("cannot connect to daemon at %1%", socketPath); auto nfds = (s > STDIN_FILENO ? s : STDIN_FILENO) + 1; while (true) { diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc index f7b04eb2b..7ab5ba500 100644 --- a/src/nix-env/nix-env.cc +++ b/src/nix-env/nix-env.cc @@ -25,7 +25,6 @@ #include #include - using namespace nix; using std::cout; @@ -70,8 +69,7 @@ typedef void (* Operation) (Globals & globals, static string needArg(Strings::iterator & i, Strings & args, const string & arg) { - if (i == args.end()) throw UsageError( - format("'%1%' requires an argument") % arg); + if (i == args.end()) throw UsageError("'%1%' requires an argument", arg); return *i++; } @@ -125,7 +123,10 @@ static void getAllExprs(EvalState & state, if (hasSuffix(attrName, ".nix")) attrName = string(attrName, 0, attrName.size() - 4); if (!attrs.insert(attrName).second) { - printError(format("warning: name collision in input Nix expressions, skipping '%1%'") % path2); + logError({ + .name = "Name collision", + .hint = hintfmt("warning: name collision in input Nix expressions, skipping '%1%'", path2) + }); continue; } /* Load the expression on demand. */ @@ -133,7 +134,7 @@ static void getAllExprs(EvalState & state, Value & vArg(*state.allocValue()); mkString(vArg, path2); if (v.attrs->size() == v.attrs->capacity()) - throw Error(format("too many Nix expressions in directory '%1%'") % path); + throw Error("too many Nix expressions in directory '%1%'", path); mkApp(*state.allocAttr(v, state.symbols.create(attrName)), vFun, vArg); } else if (S_ISDIR(st.st_mode)) @@ -144,11 +145,12 @@ static void getAllExprs(EvalState & state, } + static void loadSourceExpr(EvalState & state, const Path & path, Value & v) { struct stat st; if (stat(path.c_str(), &st) == -1) - throw SysError(format("getting information about '%1%'") % path); + throw SysError("getting information about '%1%'", path); if (isNixExpr(path, st)) state.evalFile(path, v); @@ -221,7 +223,7 @@ static void checkSelectorUse(DrvNames & selectors) /* Check that all selectors have been used. */ for (auto & i : selectors) if (i.hits == 0 && i.fullName != "*") - throw Error(format("selector '%1%' matches no derivations") % i.fullName); + throw Error("selector '%1%' matches no derivations", i.fullName); } @@ -507,7 +509,7 @@ static void opInstall(Globals & globals, Strings opFlags, Strings opArgs) globals.preserveInstalled = true; else if (arg == "--remove-all" || arg == "-r") globals.removeAll = true; - else throw UsageError(format("unknown flag '%1%'") % arg); + else throw UsageError("unknown flag '%1%'", arg); } installDerivations(globals, opArgs, globals.profile); @@ -618,7 +620,7 @@ static void opUpgrade(Globals & globals, Strings opFlags, Strings opArgs) else if (arg == "--leq") upgradeType = utLeq; else if (arg == "--eq") upgradeType = utEq; else if (arg == "--always") upgradeType = utAlways; - else throw UsageError(format("unknown flag '%1%'") % arg); + else throw UsageError("unknown flag '%1%'", arg); } upgradeDerivations(globals, opArgs, upgradeType); @@ -637,7 +639,7 @@ static void setMetaFlag(EvalState & state, DrvInfo & drv, static void opSetFlag(Globals & globals, Strings opFlags, Strings opArgs) { if (opFlags.size() > 0) - throw UsageError(format("unknown flag '%1%'") % opFlags.front()); + throw UsageError("unknown flag '%1%'", opFlags.front()); if (opArgs.size() < 2) throw UsageError("not enough arguments to '--set-flag'"); @@ -680,7 +682,7 @@ static void opSet(Globals & globals, Strings opFlags, Strings opArgs) for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) { string arg = *i++; if (parseInstallSourceOptions(globals, i, opFlags, arg)) ; - else throw UsageError(format("unknown flag '%1%'") % arg); + else throw UsageError("unknown flag '%1%'", arg); } DrvInfos elems; @@ -759,7 +761,7 @@ static void uninstallDerivations(Globals & globals, Strings & selectors, static void opUninstall(Globals & globals, Strings opFlags, Strings opArgs) { if (opFlags.size() > 0) - throw UsageError(format("unknown flag '%1%'") % opFlags.front()); + throw UsageError("unknown flag '%1%'", opFlags.front()); uninstallDerivations(globals, opArgs, globals.profile); } @@ -872,7 +874,11 @@ static void queryJSON(Globals & globals, vector & elems) auto placeholder = metaObj.placeholder(j); Value * v = i.queryMeta(j); if (!v) { - printError("derivation '%s' has invalid meta attribute '%s'", i.queryName(), j); + logError({ + .name = "Invalid meta attribute", + .hint = hintfmt("derivation '%s' has invalid meta attribute '%s'", + i.queryName(), j) + }); placeholder.write(nullptr); } else { PathSet context; @@ -922,7 +928,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs) else if (arg == "--attr" || arg == "-A") attrPath = needArg(i, opFlags, arg); else - throw UsageError(format("unknown flag '%1%'") % arg); + throw UsageError("unknown flag '%1%'", arg); } if (printAttrPath && source != sAvailable) @@ -1122,8 +1128,13 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs) XMLAttrs attrs2; attrs2["name"] = j; Value * v = i.queryMeta(j); - if (!v) - printError("derivation '%s' has invalid meta attribute '%s'", i.queryName(), j); + if (!v) + logError({ + .name = "Invalid meta attribute", + .hint = hintfmt( + "derivation '%s' has invalid meta attribute '%s'", + i.queryName(), j) + }); else { if (v->type == tString) { attrs2["type"] = "string"; @@ -1188,9 +1199,9 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs) static void opSwitchProfile(Globals & globals, Strings opFlags, Strings opArgs) { if (opFlags.size() > 0) - throw UsageError(format("unknown flag '%1%'") % opFlags.front()); + throw UsageError("unknown flag '%1%'", opFlags.front()); if (opArgs.size() != 1) - throw UsageError(format("exactly one argument expected")); + throw UsageError("exactly one argument expected"); Path profile = absPath(opArgs.front()); Path profileLink = getHome() + "/.nix-profile"; @@ -1218,10 +1229,10 @@ static void switchGeneration(Globals & globals, int dstGen) if (!dst) { if (dstGen == prevGen) - throw Error(format("no generation older than the current (%1%) exists") - % curGen); + throw Error("no generation older than the current (%1%) exists", + curGen); else - throw Error(format("generation %1% does not exist") % dstGen); + throw Error("generation %1% does not exist", dstGen); } printInfo(format("switching from generation %1% to %2%") @@ -1236,13 +1247,13 @@ static void switchGeneration(Globals & globals, int dstGen) static void opSwitchGeneration(Globals & globals, Strings opFlags, Strings opArgs) { if (opFlags.size() > 0) - throw UsageError(format("unknown flag '%1%'") % opFlags.front()); + throw UsageError("unknown flag '%1%'", opFlags.front()); if (opArgs.size() != 1) - throw UsageError(format("exactly one argument expected")); + throw UsageError("exactly one argument expected"); int dstGen; if (!string2Int(opArgs.front(), dstGen)) - throw UsageError(format("expected a generation number")); + throw UsageError("expected a generation number"); switchGeneration(globals, dstGen); } @@ -1251,9 +1262,9 @@ static void opSwitchGeneration(Globals & globals, Strings opFlags, Strings opArg static void opRollback(Globals & globals, Strings opFlags, Strings opArgs) { if (opFlags.size() > 0) - throw UsageError(format("unknown flag '%1%'") % opFlags.front()); + throw UsageError("unknown flag '%1%'", opFlags.front()); if (opArgs.size() != 0) - throw UsageError(format("no arguments expected")); + throw UsageError("no arguments expected"); switchGeneration(globals, prevGen); } @@ -1262,9 +1273,9 @@ static void opRollback(Globals & globals, Strings opFlags, Strings opArgs) static void opListGenerations(Globals & globals, Strings opFlags, Strings opArgs) { if (opFlags.size() > 0) - throw UsageError(format("unknown flag '%1%'") % opFlags.front()); + throw UsageError("unknown flag '%1%'", opFlags.front()); if (opArgs.size() != 0) - throw UsageError(format("no arguments expected")); + throw UsageError("no arguments expected"); PathLocks lock; lockProfile(lock, globals.profile); @@ -1289,7 +1300,7 @@ static void opListGenerations(Globals & globals, Strings opFlags, Strings opArgs static void opDeleteGenerations(Globals & globals, Strings opFlags, Strings opArgs) { if (opFlags.size() > 0) - throw UsageError(format("unknown flag '%1%'") % opFlags.front()); + throw UsageError("unknown flag '%1%'", opFlags.front()); if (opArgs.size() == 1 && opArgs.front() == "old") { deleteOldGenerations(globals.profile, globals.dryRun); @@ -1297,18 +1308,18 @@ static void opDeleteGenerations(Globals & globals, Strings opFlags, Strings opAr deleteGenerationsOlderThan(globals.profile, opArgs.front(), globals.dryRun); } else if (opArgs.size() == 1 && opArgs.front().find('+') != string::npos) { if(opArgs.front().size() < 2) - throw Error(format("invalid number of generations ‘%1%’") % opArgs.front()); + throw Error("invalid number of generations ‘%1%’", opArgs.front()); string str_max = string(opArgs.front(), 1, opArgs.front().size()); int max; if (!string2Int(str_max, max) || max == 0) - throw Error(format("invalid number of generations to keep ‘%1%’") % opArgs.front()); + throw Error("invalid number of generations to keep ‘%1%’", opArgs.front()); deleteGenerationsGreaterThan(globals.profile, max, globals.dryRun); } else { std::set gens; for (auto & i : opArgs) { unsigned int n; if (!string2Int(i, n)) - throw UsageError(format("invalid generation number '%1%'") % i); + throw UsageError("invalid generation number '%1%'", i); gens.insert(n); } deleteGenerations(globals.profile, gens, globals.dryRun); diff --git a/src/nix-env/user-env.cc b/src/nix-env/user-env.cc index f852916d8..f804b77a0 100644 --- a/src/nix-env/user-env.cc +++ b/src/nix-env/user-env.cc @@ -146,7 +146,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems, Path lockTokenCur = optimisticLockProfile(profile); if (lockToken != lockTokenCur) { - printError(format("profile '%1%' changed while we were busy; restarting") % profile); + printInfo("profile '%1%' changed while we were busy; restarting", profile); return false; } diff --git a/src/nix-instantiate/nix-instantiate.cc b/src/nix-instantiate/nix-instantiate.cc index 6c99d1181..bf353677a 100644 --- a/src/nix-instantiate/nix-instantiate.cc +++ b/src/nix-instantiate/nix-instantiate.cc @@ -66,7 +66,7 @@ void processExpr(EvalState & state, const Strings & attrPaths, /* What output do we want? */ string outputName = i.queryOutputName(); if (outputName == "") - throw Error(format("derivation '%1%' lacks an 'outputName' attribute ") % drvPath); + throw Error("derivation '%1%' lacks an 'outputName' attribute ", drvPath); if (gcRoot == "") printGCWarning(); @@ -166,7 +166,7 @@ static int _main(int argc, char * * argv) if (findFile) { for (auto & i : files) { Path p = state->findFile(i); - if (p == "") throw Error(format("unable to find '%1%'") % i); + if (p == "") throw Error("unable to find '%1%'", i); std::cout << p << std::endl; } return 0; diff --git a/src/nix-prefetch-url/nix-prefetch-url.cc b/src/nix-prefetch-url/nix-prefetch-url.cc index b645bdc1b..55b72bda6 100644 --- a/src/nix-prefetch-url/nix-prefetch-url.cc +++ b/src/nix-prefetch-url/nix-prefetch-url.cc @@ -37,11 +37,11 @@ string resolveMirrorUri(EvalState & state, string uri) auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName)); if (mirrorList == vMirrors.attrs->end()) - throw Error(format("unknown mirror name '%1%'") % mirrorName); + throw Error("unknown mirror name '%1%'", mirrorName); state.forceList(*mirrorList->value); if (mirrorList->value->listSize() < 1) - throw Error(format("mirror URI '%1%' did not expand to anything") % uri); + throw Error("mirror URI '%1%' did not expand to anything", uri); string mirror = state.forceString(*mirrorList->value->listElems()[0]); return mirror + (hasSuffix(mirror, "/") ? "" : "/") + string(s, p + 1); @@ -73,7 +73,7 @@ static int _main(int argc, char * * argv) string s = getArg(*arg, arg, end); ht = parseHashType(s); if (ht == htUnknown) - throw UsageError(format("unknown hash type '%1%'") % s); + throw UsageError("unknown hash type '%1%'", s); } else if (*arg == "--print-path") printPath = true; @@ -151,7 +151,7 @@ static int _main(int argc, char * * argv) if (name.empty()) name = baseNameOf(uri); if (name.empty()) - throw Error(format("cannot figure out file name for '%1%'") % uri); + throw Error("cannot figure out file name for '%1%'", uri); /* If an expected hash is given, the file may already exist in the store. */ @@ -207,7 +207,7 @@ static int _main(int argc, char * * argv) hash = unpack ? hashPath(ht, tmpFile).first : hashFile(ht, tmpFile); if (expectedHash != Hash(ht) && expectedHash != hash) - throw Error(format("hash mismatch for '%1%'") % uri); + throw Error("hash mismatch for '%1%'", uri); const auto recursive = unpack ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat; diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index ef8fb0951..dea53b52f 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -124,7 +124,7 @@ static void opRealise(Strings opFlags, Strings opArgs) else if (i == "--repair") buildMode = bmRepair; else if (i == "--check") buildMode = bmCheck; else if (i == "--ignore-unknown") ignoreUnknown = true; - else throw UsageError(format("unknown flag '%1%'") % i); + else throw UsageError("unknown flag '%1%'", i); std::vector paths; for (auto & i : opArgs) @@ -178,7 +178,7 @@ static void opAddFixed(Strings opFlags, Strings opArgs) for (auto & i : opFlags) if (i == "--recursive") recursive = FileIngestionMethod::Recursive; - else throw UsageError(format("unknown flag '%1%'") % i); + else throw UsageError("unknown flag '%1%'", i); if (opArgs.empty()) throw UsageError("first argument must be hash algorithm"); @@ -198,10 +198,10 @@ static void opPrintFixedPath(Strings opFlags, Strings opArgs) for (auto i : opFlags) if (i == "--recursive") recursive = FileIngestionMethod::Recursive; - else throw UsageError(format("unknown flag '%1%'") % i); + else throw UsageError("unknown flag '%1%'", i); if (opArgs.size() != 3) - throw UsageError(format("'--print-fixed-path' requires three arguments")); + throw UsageError("'--print-fixed-path' requires three arguments"); Strings::iterator i = opArgs.begin(); HashType hashAlgo = parseHashType(*i++); @@ -296,9 +296,9 @@ static void opQuery(Strings opFlags, Strings opArgs) else if (i == "--use-output" || i == "-u") useOutput = true; else if (i == "--force-realise" || i == "--force-realize" || i == "-f") forceRealise = true; else if (i == "--include-outputs") includeOutputs = true; - else throw UsageError(format("unknown flag '%1%'") % i); + else throw UsageError("unknown flag '%1%'", i); if (prev != qDefault && prev != query) - throw UsageError(format("query type '%1%' conflicts with earlier flag") % i); + throw UsageError("query type '%1%' conflicts with earlier flag", i); } if (query == qDefault) query = qOutputs; @@ -444,7 +444,7 @@ static void opPrintEnv(Strings opFlags, Strings opArgs) Derivation drv = store->derivationFromPath(store->parseStorePath(drvPath)); /* Print each environment variable in the derivation in a format - that can be sourced by the shell. */ + * that can be sourced by the shell. */ for (auto & i : drv.env) cout << format("export %1%; %1%=%2%\n") % i.first % shellEscape(i.second); @@ -531,7 +531,7 @@ static void opRegisterValidity(Strings opFlags, Strings opArgs) for (auto & i : opFlags) if (i == "--reregister") reregister = true; else if (i == "--hash-given") hashGiven = true; - else throw UsageError(format("unknown flag '%1%'") % i); + else throw UsageError("unknown flag '%1%'", i); if (!opArgs.empty()) throw UsageError("no arguments expected"); @@ -545,7 +545,7 @@ static void opCheckValidity(Strings opFlags, Strings opArgs) for (auto & i : opFlags) if (i == "--print-invalid") printInvalid = true; - else throw UsageError(format("unknown flag '%1%'") % i); + else throw UsageError("unknown flag '%1%'", i); for (auto & i : opArgs) { auto path = store->followLinksToStorePath(i); @@ -576,7 +576,7 @@ static void opGC(Strings opFlags, Strings opArgs) long long maxFreed = getIntArg(*i, i, opFlags.end(), true); options.maxFreed = maxFreed >= 0 ? maxFreed : 0; } - else throw UsageError(format("bad sub-operation '%1%' in GC") % *i); + else throw UsageError("bad sub-operation '%1%' in GC", *i); if (!opArgs.empty()) throw UsageError("no arguments expected"); @@ -612,7 +612,7 @@ static void opDelete(Strings opFlags, Strings opArgs) for (auto & i : opFlags) if (i == "--ignore-liveness") options.ignoreLiveness = true; - else throw UsageError(format("unknown flag '%1%'") % i); + else throw UsageError("unknown flag '%1%'", i); for (auto & i : opArgs) options.pathsToDelete.insert(store->followLinksToStorePath(i)); @@ -650,7 +650,7 @@ static void opRestore(Strings opFlags, Strings opArgs) static void opExport(Strings opFlags, Strings opArgs) { for (auto & i : opFlags) - throw UsageError(format("unknown flag '%1%'") % i); + throw UsageError("unknown flag '%1%'", i); StorePathSet paths; @@ -666,7 +666,7 @@ static void opExport(Strings opFlags, Strings opArgs) static void opImport(Strings opFlags, Strings opArgs) { for (auto & i : opFlags) - throw UsageError(format("unknown flag '%1%'") % i); + throw UsageError("unknown flag '%1%'", i); if (!opArgs.empty()) throw UsageError("no arguments expected"); @@ -701,10 +701,13 @@ static void opVerify(Strings opFlags, Strings opArgs) for (auto & i : opFlags) if (i == "--check-contents") checkContents = true; else if (i == "--repair") repair = Repair; - else throw UsageError(format("unknown flag '%1%'") % i); + else throw UsageError("unknown flag '%1%'", i); if (store->verifyStore(checkContents, repair)) { - printError("warning: not all errors were fixed"); + logWarning({ + .name = "Store consistency", + .description = "not all errors were fixed" + }); throw Exit(1); } } @@ -726,9 +729,14 @@ static void opVerifyPath(Strings opFlags, Strings opArgs) store->narFromPath(path, sink); auto current = sink.finish(); if (current.first != info->narHash) { - printError( - "path '%s' was modified! expected hash '%s', got '%s'", - store->printStorePath(path), info->narHash.to_string(Base32, true), current.first.to_string(Base32, true)); + logError({ + .name = "Hash mismatch", + .hint = hintfmt( + "path '%s' was modified! expected hash '%s', got '%s'", + store->printStorePath(path), + info->narHash.to_string(Base32, true), + current.first.to_string(Base32, true)) + }); status = 1; } } @@ -764,7 +772,7 @@ static void opServe(Strings opFlags, Strings opArgs) bool writeAllowed = false; for (auto & i : opFlags) if (i == "--write") writeAllowed = true; - else throw UsageError(format("unknown flag '%1%'") % i); + else throw UsageError("unknown flag '%1%'", i); if (!opArgs.empty()) throw UsageError("no arguments expected"); @@ -835,7 +843,7 @@ static void opServe(Strings opFlags, Strings opArgs) for (auto & p : willSubstitute) subs.emplace_back(p.clone()); store->buildPaths(subs); } catch (Error & e) { - printError(format("warning: %1%") % e.msg()); + logWarning(e.info()); } } @@ -962,7 +970,7 @@ static void opServe(Strings opFlags, Strings opArgs) } default: - throw Error(format("unknown serve command %1%") % cmd); + throw Error("unknown serve command %1%", cmd); } out.flush(); @@ -973,7 +981,7 @@ static void opServe(Strings opFlags, Strings opArgs) static void opGenerateBinaryCacheKey(Strings opFlags, Strings opArgs) { for (auto & i : opFlags) - throw UsageError(format("unknown flag '%1%'") % i); + throw UsageError("unknown flag '%1%'", i); if (opArgs.size() != 3) throw UsageError("three arguments expected"); auto i = opArgs.begin(); diff --git a/src/nix/cat.cc b/src/nix/cat.cc index fd91f2036..c82819af8 100644 --- a/src/nix/cat.cc +++ b/src/nix/cat.cc @@ -13,9 +13,9 @@ struct MixCat : virtual Args { auto st = accessor->stat(path); if (st.type == FSAccessor::Type::tMissing) - throw Error(format("path '%1%' does not exist") % path); + throw Error("path '%1%' does not exist", path); if (st.type != FSAccessor::Type::tRegular) - throw Error(format("path '%1%' is not a regular file") % path); + throw Error("path '%1%' is not a regular file", path); std::cout << accessor->readFile(path); } diff --git a/src/nix/hash.cc b/src/nix/hash.cc index 0a7c343d0..0dd4998d9 100644 --- a/src/nix/hash.cc +++ b/src/nix/hash.cc @@ -133,7 +133,7 @@ static int compatNixHash(int argc, char * * argv) string s = getArg(*arg, arg, end); ht = parseHashType(s); if (ht == htUnknown) - throw UsageError(format("unknown hash type '%1%'") % s); + throw UsageError("unknown hash type '%1%'", s); } else if (*arg == "--to-base16") op = opTo16; else if (*arg == "--to-base32") op = opTo32; diff --git a/src/nix/ls.cc b/src/nix/ls.cc index b9716a6a1..d2157f2d4 100644 --- a/src/nix/ls.cc +++ b/src/nix/ls.cc @@ -63,7 +63,7 @@ struct MixLs : virtual Args, MixJSON auto st = accessor->stat(path); if (st.type == FSAccessor::Type::tMissing) - throw Error(format("path '%1%' does not exist") % path); + throw Error("path '%1%' does not exist", path); doPath(st, path, st.type == FSAccessor::Type::tDirectory ? "." : std::string(baseNameOf(path)), showDirectory); diff --git a/src/nix/make-content-addressable.cc b/src/nix/make-content-addressable.cc index 3e7ff544d..719ea4fd1 100644 --- a/src/nix/make-content-addressable.cc +++ b/src/nix/make-content-addressable.cc @@ -85,7 +85,7 @@ struct CmdMakeContentAddressable : StorePathsCommand, MixJSON info.ca = makeFixedOutputCA(FileIngestionMethod::Recursive, info.narHash); if (!json) - printError("rewrote '%s' to '%s'", pathS, store->printStorePath(info.path)); + printInfo("rewrote '%s' to '%s'", pathS, store->printStorePath(info.path)); auto source = sinkToSource([&](Sink & nextSink) { RewritingSink rsink2(oldHashPart, storePathToHash(store->printStorePath(info.path)), nextSink); diff --git a/src/nix/repl.cc b/src/nix/repl.cc index ea8ff1553..4bcaaeebf 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -218,12 +218,12 @@ void NixRepl::mainLoop(const std::vector & files) // input without clearing the input so far. continue; } else { - printMsg(lvlError, format(error + "%1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg()); + printMsg(lvlError, error + "%1%%2%", (settings.showTrace ? e.prefix() : ""), e.msg()); } } catch (Error & e) { - printMsg(lvlError, format(error + "%1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg()); + printMsg(lvlError, error + "%1%%2%", (settings.showTrace ? e.prefix() : ""), e.msg()); } catch (Interrupted & e) { - printMsg(lvlError, format(error + "%1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg()); + printMsg(lvlError, error + "%1%%2%", (settings.showTrace ? e.prefix() : ""), e.msg()); } // We handled the current input fully, so we should clear it @@ -512,7 +512,7 @@ bool NixRepl::processLine(string line) return false; else if (command != "") - throw Error(format("unknown command '%1%'") % command); + throw Error("unknown command '%1%'", command); else { size_t p = line.find('='); diff --git a/src/nix/run.cc b/src/nix/run.cc index b888281a5..c9b69aec7 100644 --- a/src/nix/run.cc +++ b/src/nix/run.cc @@ -197,10 +197,10 @@ void chrootHelper(int argc, char * * argv) Finally freeCwd([&]() { free(cwd); }); if (chroot(tmpDir.c_str()) == -1) - throw SysError(format("chrooting into '%s'") % tmpDir); + throw SysError("chrooting into '%s'", tmpDir); if (chdir(cwd) == -1) - throw SysError(format("chdir to '%s' in chroot") % cwd); + throw SysError("chdir to '%s' in chroot", cwd); } else if (mount(realStoreDir.c_str(), storeDir.c_str(), "", MS_BIND, 0) == -1) throw SysError("mounting '%s' on '%s'", realStoreDir, storeDir); diff --git a/src/nix/upgrade-nix.cc b/src/nix/upgrade-nix.cc index 678780f33..fdf94e5a3 100644 --- a/src/nix/upgrade-nix.cc +++ b/src/nix/upgrade-nix.cc @@ -68,7 +68,11 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand if (dryRun) { stopProgressBar(); - printError("would upgrade to version %s", version); + logWarning( + ErrorInfo { + .name = "Version update", + .hint = hintfmt("would upgrade to version %s", version) + }); return; } @@ -94,7 +98,7 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand {"--profile", profileDir, "-i", store->printStorePath(storePath), "--no-sandbox"}); } - printError(ANSI_GREEN "upgrade to version %s done" ANSI_NORMAL, version); + printInfo(ANSI_GREEN "upgrade to version %s done" ANSI_NORMAL, version); } /* Return the profile in which Nix is installed. */ diff --git a/src/nix/verify.cc b/src/nix/verify.cc index 287dad101..246b4b480 100644 --- a/src/nix/verify.cc +++ b/src/nix/verify.cc @@ -99,11 +99,15 @@ struct CmdVerify : StorePathsCommand if (hash.first != info->narHash) { corrupted++; act2.result(resCorruptedPath, store->printStorePath(info->path)); - printError( - "path '%s' was modified! expected hash '%s', got '%s'", - store->printStorePath(info->path), info->narHash.to_string(Base32, true), hash.first.to_string(Base32, true)); + logError({ + .name = "Hash error - path modified", + .hint = hintfmt( + "path '%s' was modified! expected hash '%s', got '%s'", + store->printStorePath(info->path), + info->narHash.to_string(Base32, true), + hash.first.to_string(Base32, true)) + }); } - } if (!noTrust) { @@ -139,7 +143,7 @@ struct CmdVerify : StorePathsCommand doSigs(info2->sigs); } catch (InvalidPath &) { } catch (Error & e) { - printError(format(ANSI_RED "error:" ANSI_NORMAL " %s") % e.what()); + logError(e.info()); } } @@ -150,7 +154,12 @@ struct CmdVerify : StorePathsCommand if (!good) { untrusted++; act2.result(resUntrustedPath, store->printStorePath(info->path)); - printError("path '%s' is untrusted", store->printStorePath(info->path)); + logError({ + .name = "Untrusted path", + .hint = hintfmt("path '%s' is untrusted", + store->printStorePath(info->path)) + }); + } } @@ -158,7 +167,7 @@ struct CmdVerify : StorePathsCommand done++; } catch (Error & e) { - printError(format(ANSI_RED "error:" ANSI_NORMAL " %s") % e.what()); + logError(e.info()); failed++; } diff --git a/src/resolve-system-dependencies/resolve-system-dependencies.cc b/src/resolve-system-dependencies/resolve-system-dependencies.cc index 8f0c99c84..82feacb3d 100644 --- a/src/resolve-system-dependencies/resolve-system-dependencies.cc +++ b/src/resolve-system-dependencies/resolve-system-dependencies.cc @@ -39,12 +39,18 @@ std::set runResolver(const Path & filename) throw SysError("statting '%s'", filename); if (!S_ISREG(st.st_mode)) { - printError("file '%s' is not a regular file", filename); + logError({ + .name = "Regular MACH file", + .hint = hintfmt("file '%s' is not a regular file", filename) + }); return {}; } if (st.st_size < sizeof(mach_header_64)) { - printError("file '%s' is too short for a MACH binary", filename); + logError({ + .name = "File too short", + .hint = hintfmt("file '%s' is too short for a MACH binary", filename) + }); return {}; } @@ -66,13 +72,19 @@ std::set runResolver(const Path & filename) } } if (mach64_offset == 0) { - printError(format("Could not find any mach64 blobs in file '%1%', continuing...") % filename); + logError({ + .name = "No mach64 blobs", + .hint = hintfmt("Could not find any mach64 blobs in file '%1%', continuing...", filename) + }); return {}; } } else if (magic == MH_MAGIC_64 || magic == MH_CIGAM_64) { mach64_offset = 0; } else { - printError(format("Object file has unknown magic number '%1%', skipping it...") % magic); + logError({ + .name = "Magic number", + .hint = hintfmt("Object file has unknown magic number '%1%', skipping it...", magic) + }); return {}; } diff --git a/tests/fetchGitRefs.sh b/tests/fetchGitRefs.sh index 93993ae90..23934698e 100644 --- a/tests/fetchGitRefs.sh +++ b/tests/fetchGitRefs.sh @@ -56,7 +56,7 @@ invalid_ref() { else (! git check-ref-format --branch "$1" >/dev/null 2>&1) fi - nix --debug eval --raw "(builtins.fetchGit { url = $repo; ref = ''$1''; }).outPath" 2>&1 | grep 'error: invalid Git branch/tag name' >/dev/null + nix --debug eval --raw "(builtins.fetchGit { url = $repo; ref = ''$1''; }).outPath" 2>&1 | grep 'invalid Git branch/tag name' >/dev/null } diff --git a/tests/misc.sh b/tests/misc.sh index eda016416..fd4908e25 100644 --- a/tests/misc.sh +++ b/tests/misc.sh @@ -16,4 +16,6 @@ nix-env --foo 2>&1 | grep "no operation" nix-env -q --foo 2>&1 | grep "unknown flag" # Eval Errors. -nix-instantiate --eval -E 'let a = {} // a; in a.foo' 2>&1 | grep "infinite recursion encountered, at .*(string).*:1:15$" +eval_res=$(nix-instantiate --eval -E 'let a = {} // a; in a.foo' 2>&1 || true) +echo $eval_res | grep "(string) (1:15)" +echo $eval_res | grep "infinite recursion encountered"