mirror of
https://github.com/NixOS/nix
synced 2025-06-24 22:11:15 +02:00
New CLI flag --replace-eval-errors
Co-authored-by: Farid Zakaria <farid.m.zakaria@gmail.com>
This commit is contained in:
parent
cdbe788c1f
commit
88705f848b
14 changed files with 93 additions and 20 deletions
|
@ -112,6 +112,11 @@ standard input.
|
|||
When used with `--eval`, print the resulting value as an JSON
|
||||
representation of the abstract syntax tree rather than as a Nix expression.
|
||||
|
||||
- `--replace-eval-errors`
|
||||
|
||||
When used with `--eval` and `--json`, replace any evaluation errors with the string
|
||||
`"«evaluation error»"`.
|
||||
|
||||
- `--xml`
|
||||
|
||||
When used with `--eval`, print the resulting value as an XML
|
||||
|
@ -205,3 +210,10 @@ $ nix-instantiate --eval --xml --strict --expr '{ x = {}; }'
|
|||
</attrs>
|
||||
</expr>
|
||||
```
|
||||
|
||||
Replacing evaluation errors:
|
||||
|
||||
```console
|
||||
$ nix-instantiate --eval --json --replace-eval-errors --expr '{ a = throw "fail"; }'
|
||||
{"a":"«evaluation error»"}
|
||||
```
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace nix {
|
|||
std::string getJSONValue(Value& value) {
|
||||
std::stringstream ss;
|
||||
NixStringContext ps;
|
||||
printValueAsJSON(state, true, value, noPos, ss, ps);
|
||||
printValueAsJSON(state, true, false, value, noPos, ss, ps);
|
||||
return ss.str();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
nlohmann::json printValueAsJSON(EvalState & state, bool strict,
|
||||
nlohmann::json printValueAsJSON(EvalState & state, bool strict, bool replaceEvalErrors,
|
||||
Value & v, const PosIdx pos, NixStringContext & context, bool copyToStore = true);
|
||||
|
||||
void printValueAsJSON(EvalState & state, bool strict,
|
||||
void printValueAsJSON(EvalState & state, bool strict, bool replaceEvalErrors,
|
||||
Value & v, const PosIdx pos, std::ostream & str, NixStringContext & context, bool copyToStore = true);
|
||||
|
||||
|
||||
|
|
|
@ -1388,7 +1388,7 @@ static void derivationStrictInternal(
|
|||
|
||||
if (i->name == state.sStructuredAttrs) continue;
|
||||
|
||||
jsonObject->emplace(key, printValueAsJSON(state, true, *i->value, pos, context));
|
||||
jsonObject->emplace(key, printValueAsJSON(state, true, false, *i->value, pos, context));
|
||||
|
||||
if (i->name == state.sBuilder)
|
||||
drv.builder = state.forceString(*i->value, context, pos, context_below);
|
||||
|
@ -2328,7 +2328,7 @@ static void prim_toJSON(EvalState & state, const PosIdx pos, Value * * args, Val
|
|||
{
|
||||
std::ostringstream out;
|
||||
NixStringContext context;
|
||||
printValueAsJSON(state, true, *args[0], pos, out, context);
|
||||
printValueAsJSON(state, true, false, *args[0], pos, out, context);
|
||||
v.mkString(toView(out), context);
|
||||
}
|
||||
|
||||
|
|
|
@ -135,7 +135,7 @@ static void fetchTree(
|
|||
attrs.emplace(state.symbols[attr.name], uint64_t(intValue));
|
||||
} else if (state.symbols[attr.name] == "publicKeys") {
|
||||
experimentalFeatureSettings.require(Xp::VerifiedFetches);
|
||||
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, context).dump());
|
||||
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, false, *attr.value, pos, context).dump());
|
||||
}
|
||||
else
|
||||
state.error<TypeError>("argument '%s' to '%s' is %s while a string, Boolean or integer is expected",
|
||||
|
|
|
@ -6,12 +6,13 @@
|
|||
#include <cstdlib>
|
||||
#include <iomanip>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <typeinfo>
|
||||
|
||||
|
||||
namespace nix {
|
||||
using json = nlohmann::json;
|
||||
// TODO: rename. It doesn't print.
|
||||
json printValueAsJSON(EvalState & state, bool strict,
|
||||
json printValueAsJSON(EvalState & state, bool strict, bool replaceEvalErrors,
|
||||
Value & v, const PosIdx pos, NixStringContext & context, bool copyToStore)
|
||||
{
|
||||
checkInterrupt();
|
||||
|
@ -54,13 +55,27 @@ json printValueAsJSON(EvalState & state, bool strict,
|
|||
break;
|
||||
}
|
||||
if (auto i = v.attrs()->get(state.sOutPath))
|
||||
return printValueAsJSON(state, strict, *i->value, i->pos, context, copyToStore);
|
||||
return printValueAsJSON(state, strict, replaceEvalErrors, *i->value, i->pos, context, copyToStore);
|
||||
else {
|
||||
out = json::object();
|
||||
for (auto & a : v.attrs()->lexicographicOrder(state.symbols)) {
|
||||
try {
|
||||
out.emplace(state.symbols[a->name], printValueAsJSON(state, strict, *a->value, a->pos, context, copyToStore));
|
||||
out.emplace(state.symbols[a->name], printValueAsJSON(state, strict, replaceEvalErrors, *a->value, a->pos, context, copyToStore));
|
||||
} catch (Error & e) {
|
||||
std::cerr << "Caught an Error of type: " << typeid(e).name() << std::endl;
|
||||
// std::cerr << "Caught an Error of type: " << e.message() << std::endl;
|
||||
// std::cerr << "Caught an Error of type: " << e.what() << std::endl;
|
||||
|
||||
// TODO: Figure out what Error is here?
|
||||
// We seem to be not catching FileNotFoundError.
|
||||
bool isEvalError = dynamic_cast<EvalError *>(&e);
|
||||
bool isFileNotFoundError = dynamic_cast<FileNotFound *>(&e);
|
||||
// Restrict replaceEvalErrors only only evaluation errors
|
||||
if (replaceEvalErrors && (isEvalError || isFileNotFoundError)) {
|
||||
out.emplace(state.symbols[a->name], "«evaluation error»");
|
||||
continue;
|
||||
}
|
||||
|
||||
e.addTrace(state.positions[a->pos],
|
||||
HintFmt("while evaluating attribute '%1%'", state.symbols[a->name]));
|
||||
throw;
|
||||
|
@ -75,8 +90,9 @@ json printValueAsJSON(EvalState & state, bool strict,
|
|||
int i = 0;
|
||||
for (auto elem : v.listItems()) {
|
||||
try {
|
||||
out.push_back(printValueAsJSON(state, strict, *elem, pos, context, copyToStore));
|
||||
out.push_back(printValueAsJSON(state, strict, replaceEvalErrors, *elem, pos, context, copyToStore));
|
||||
} catch (Error & e) {
|
||||
// TODO: Missing catch
|
||||
e.addTrace(state.positions[pos],
|
||||
HintFmt("while evaluating list element at index %1%", i));
|
||||
throw;
|
||||
|
@ -106,11 +122,11 @@ json printValueAsJSON(EvalState & state, bool strict,
|
|||
return out;
|
||||
}
|
||||
|
||||
void printValueAsJSON(EvalState & state, bool strict,
|
||||
void printValueAsJSON(EvalState & state, bool strict, bool replaceEvalErrors,
|
||||
Value & v, const PosIdx pos, std::ostream & str, NixStringContext & context, bool copyToStore)
|
||||
{
|
||||
try {
|
||||
str << printValueAsJSON(state, strict, v, pos, context, copyToStore);
|
||||
str << printValueAsJSON(state, strict, replaceEvalErrors, v, pos, context, copyToStore);
|
||||
} catch (nlohmann::json::exception & e) {
|
||||
throw JSONSerializationError("JSON serialization error: %s", e.what());
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ static void parseFlakeInputAttr(
|
|||
if (attr.name == state.symbols.create("publicKeys")) {
|
||||
experimentalFeatureSettings.require(Xp::VerifiedFetches);
|
||||
NixStringContext emptyContext = {};
|
||||
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, attr.pos, emptyContext).dump());
|
||||
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, false, *attr.value, attr.pos, emptyContext).dump());
|
||||
} else
|
||||
state.error<TypeError>("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
|
||||
state.symbols[attr.name], showType(*attr.value)).debugThrow();
|
||||
|
|
|
@ -54,7 +54,7 @@ void PosixSourceAccessor::readFile(
|
|||
#endif
|
||||
));
|
||||
if (!fd)
|
||||
throw SysError("opening file '%1%'", ap.string());
|
||||
throw SysError("opening file9 '%1%'", ap.string());
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fromDescriptorReadOnly(fd.get()), &st) == -1)
|
||||
|
|
|
@ -984,7 +984,7 @@ static void queryJSON(Globals & globals, std::vector<PackageInfo> & elems, bool
|
|||
metaObj[j] = nullptr;
|
||||
} else {
|
||||
NixStringContext context;
|
||||
metaObj[j] = printValueAsJSON(*globals.state, true, *v, noPos, context);
|
||||
metaObj[j] = printValueAsJSON(*globals.state, true, false, *v, noPos, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ static int rootNr = 0;
|
|||
enum OutputKind { okPlain, okRaw, okXML, okJSON };
|
||||
|
||||
void processExpr(EvalState & state, const Strings & attrPaths,
|
||||
bool parseOnly, bool strict, Bindings & autoArgs,
|
||||
bool parseOnly, bool strict, bool replaceEvalErrors, Bindings & autoArgs,
|
||||
bool evalOnly, OutputKind output, bool location, Expr * e)
|
||||
{
|
||||
if (parseOnly) {
|
||||
|
@ -58,7 +58,7 @@ void processExpr(EvalState & state, const Strings & attrPaths,
|
|||
else if (output == okXML)
|
||||
printValueAsXML(state, strict, location, vRes, std::cout, context, noPos);
|
||||
else if (output == okJSON) {
|
||||
printValueAsJSON(state, strict, vRes, v.determinePos(noPos), std::cout, context);
|
||||
printValueAsJSON(state, strict, replaceEvalErrors, vRes, v.determinePos(noPos), std::cout, context);
|
||||
std::cout << std::endl;
|
||||
} else {
|
||||
if (strict) state.forceValueDeep(vRes);
|
||||
|
@ -106,6 +106,7 @@ static int main_nix_instantiate(int argc, char * * argv)
|
|||
OutputKind outputKind = okPlain;
|
||||
bool xmlOutputSourceLocation = true;
|
||||
bool strict = false;
|
||||
bool replaceEvalErrors = false;
|
||||
Strings attrPaths;
|
||||
bool wantsReadWrite = false;
|
||||
|
||||
|
@ -147,6 +148,8 @@ static int main_nix_instantiate(int argc, char * * argv)
|
|||
xmlOutputSourceLocation = false;
|
||||
else if (*arg == "--strict")
|
||||
strict = true;
|
||||
else if (*arg == "--replace-eval-errors")
|
||||
replaceEvalErrors = true;
|
||||
else if (*arg == "--dry-run")
|
||||
settings.readOnlyMode = true;
|
||||
else if (*arg != "" && arg->at(0) == '-')
|
||||
|
@ -184,7 +187,7 @@ static int main_nix_instantiate(int argc, char * * argv)
|
|||
|
||||
if (readStdin) {
|
||||
Expr * e = state->parseStdin();
|
||||
processExpr(*state, attrPaths, parseOnly, strict, autoArgs,
|
||||
processExpr(*state, attrPaths, parseOnly, strict, replaceEvalErrors, autoArgs,
|
||||
evalOnly, outputKind, xmlOutputSourceLocation, e);
|
||||
} else if (files.empty() && !fromArgs)
|
||||
files.push_back("./default.nix");
|
||||
|
@ -193,7 +196,7 @@ static int main_nix_instantiate(int argc, char * * argv)
|
|||
Expr * e = fromArgs
|
||||
? state->parseExprFromString(i, state->rootPath("."))
|
||||
: state->parseExprFromFile(resolveExprPath(lookupFileArg(*state, i)));
|
||||
processExpr(*state, attrPaths, parseOnly, strict, autoArgs,
|
||||
processExpr(*state, attrPaths, parseOnly, strict, replaceEvalErrors, autoArgs,
|
||||
evalOnly, outputKind, xmlOutputSourceLocation, e);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace nix::fs { using namespace std::filesystem; }
|
|||
struct CmdEval : MixJSON, InstallableValueCommand, MixReadOnlyOption
|
||||
{
|
||||
bool raw = false;
|
||||
bool replaceEvalErrors = false;
|
||||
std::optional<std::string> apply;
|
||||
std::optional<std::filesystem::path> writeTo;
|
||||
|
||||
|
@ -39,6 +40,12 @@ struct CmdEval : MixJSON, InstallableValueCommand, MixReadOnlyOption
|
|||
.labels = {"path"},
|
||||
.handler = {&writeTo},
|
||||
});
|
||||
|
||||
addFlag({
|
||||
.longName = "replace-eval-errors",
|
||||
.description = "When used with `--json` the Nix evaluator will replace evaluation errors with a fixed value.",
|
||||
.handler = {&replaceEvalErrors, true},
|
||||
});
|
||||
}
|
||||
|
||||
std::string description() override
|
||||
|
@ -118,7 +125,7 @@ struct CmdEval : MixJSON, InstallableValueCommand, MixReadOnlyOption
|
|||
}
|
||||
|
||||
else if (json) {
|
||||
printJSON(printValueAsJSON(*state, true, *v, pos, context, false));
|
||||
printJSON(printValueAsJSON(*state, true, replaceEvalErrors, *v, pos, context, false));
|
||||
}
|
||||
|
||||
else {
|
||||
|
|
|
@ -48,6 +48,13 @@ R""(
|
|||
# cat ./out/subdir/bla
|
||||
123
|
||||
|
||||
* Replace evaluation errors:
|
||||
|
||||
```console
|
||||
$ nix eval --json --replace-eval-errors --expr '{ a = throw "fail"; }'
|
||||
{"a":"«evaluation error»"}
|
||||
```
|
||||
|
||||
# Description
|
||||
|
||||
This command evaluates the given Nix expression, and prints the result on standard output.
|
||||
|
|
|
@ -35,6 +35,23 @@ nix-instantiate --eval -E 'assert 1 + 2 == 3; true'
|
|||
[[ $(nix-instantiate -A int --eval - < "./eval.nix") == 123 ]]
|
||||
[[ "$(nix-instantiate --eval -E '{"assert"=1;bar=2;}')" == '{ "assert" = 1; bar = 2; }' ]]
|
||||
|
||||
expected="$(echo '{
|
||||
"missingAttr":"«evaluation error»",
|
||||
"insideAList":["«evaluation error»"],
|
||||
"deeper":{v:"«evaluation error»"},
|
||||
"failedAssertion":"«evaluation error»",
|
||||
"missingFile":"«evaluation error»",
|
||||
"missingImport":"«evaluation error»",
|
||||
"outOfBounds":"«evaluation error»",
|
||||
"failedCoersion":"«evaluation error»",
|
||||
"failedAddition":"«evaluation error»"
|
||||
}' | tr -d '\n')"
|
||||
actual="$(nix-instantiate --eval --json --strict --replace-eval-errors "./replace-eval-errors.nix")"
|
||||
[[ $actual == "$expected" ]] || diff --unified <(echo "$actual") <(echo "$expected") >&2
|
||||
|
||||
actual="$(nix eval --json --replace-eval-errors -f "./replace-eval-errors.nix")"
|
||||
[[ $actual == "$expected" ]] || diff --unified <(echo "$actual") <(echo "$expected") >&2
|
||||
|
||||
# Check that symlink cycles don't cause a hang.
|
||||
ln -sfn cycle.nix "$TEST_ROOT/cycle.nix"
|
||||
(! nix eval --file "$TEST_ROOT/cycle.nix")
|
||||
|
|
11
tests/functional/replace-eval-errors.nix
Normal file
11
tests/functional/replace-eval-errors.nix
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
missingAttr = let bar = { }; in bar.notExist;
|
||||
insideAList = [ (throw "a throw") ];
|
||||
deeper = { v = throw "v"; };
|
||||
failedAssertion = assert true; assert false; null;
|
||||
missingFile = builtins.readFile ./missing-file.txt;
|
||||
missingImport = import ./missing-import.nix;
|
||||
outOfBounds = builtins.elemAt [ 1 2 3 ] 100;
|
||||
failedCoersion = "${1}";
|
||||
failedAddition = 1.0 + "a string";
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue