1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-06-24 22:11:15 +02:00
This commit is contained in:
Shahar "Dawn" Or 2025-06-23 10:10:54 -07:00 committed by GitHub
commit a2784e510f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 93 additions and 20 deletions

View file

@ -112,6 +112,11 @@ standard input.
When used with `--eval`, print the resulting value as an JSON When used with `--eval`, print the resulting value as an JSON
representation of the abstract syntax tree rather than as a Nix expression. 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` - `--xml`
When used with `--eval`, print the resulting value as an XML When used with `--eval`, print the resulting value as an XML
@ -205,3 +210,10 @@ $ nix-instantiate --eval --xml --strict --expr '{ x = {}; }'
</attrs> </attrs>
</expr> </expr>
``` ```
Replacing evaluation errors:
```console
$ nix-instantiate --eval --json --replace-eval-errors --expr '{ a = throw "fail"; }'
{"a":"«evaluation error»"}
```

View file

@ -9,7 +9,7 @@ namespace nix {
std::string getJSONValue(Value& value) { std::string getJSONValue(Value& value) {
std::stringstream ss; std::stringstream ss;
NixStringContext ps; NixStringContext ps;
printValueAsJSON(state, true, value, noPos, ss, ps); printValueAsJSON(state, true, false, value, noPos, ss, ps);
return ss.str(); return ss.str();
} }
}; };

View file

@ -10,10 +10,10 @@
namespace nix { 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); 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); Value & v, const PosIdx pos, std::ostream & str, NixStringContext & context, bool copyToStore = true);

View file

@ -1383,7 +1383,7 @@ static void derivationStrictInternal(
if (i->name == state.sStructuredAttrs) continue; 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) if (i->name == state.sBuilder)
drv.builder = state.forceString(*i->value, context, pos, context_below); drv.builder = state.forceString(*i->value, context, pos, context_below);
@ -2325,7 +2325,7 @@ static void prim_toJSON(EvalState & state, const PosIdx pos, Value * * args, Val
{ {
std::ostringstream out; std::ostringstream out;
NixStringContext context; NixStringContext context;
printValueAsJSON(state, true, *args[0], pos, out, context); printValueAsJSON(state, true, false, *args[0], pos, out, context);
v.mkString(toView(out), context); v.mkString(toView(out), context);
} }

View file

@ -135,7 +135,7 @@ static void fetchTree(
attrs.emplace(state.symbols[attr.name], uint64_t(intValue)); attrs.emplace(state.symbols[attr.name], uint64_t(intValue));
} else if (state.symbols[attr.name] == "publicKeys") { } else if (state.symbols[attr.name] == "publicKeys") {
experimentalFeatureSettings.require(Xp::VerifiedFetches); 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 else
state.error<TypeError>("argument '%s' to '%s' is %s while a string, Boolean or integer is expected", state.error<TypeError>("argument '%s' to '%s' is %s while a string, Boolean or integer is expected",

View file

@ -6,12 +6,13 @@
#include <cstdlib> #include <cstdlib>
#include <iomanip> #include <iomanip>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <typeinfo>
namespace nix { namespace nix {
using json = nlohmann::json; using json = nlohmann::json;
// TODO: rename. It doesn't print. // 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) Value & v, const PosIdx pos, NixStringContext & context, bool copyToStore)
{ {
checkInterrupt(); checkInterrupt();
@ -54,13 +55,27 @@ json printValueAsJSON(EvalState & state, bool strict,
break; break;
} }
if (auto i = v.attrs()->get(state.sOutPath)) 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 { else {
out = json::object(); out = json::object();
for (auto & a : v.attrs()->lexicographicOrder(state.symbols)) { for (auto & a : v.attrs()->lexicographicOrder(state.symbols)) {
try { 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) { } 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], e.addTrace(state.positions[a->pos],
HintFmt("while evaluating attribute '%1%'", state.symbols[a->name])); HintFmt("while evaluating attribute '%1%'", state.symbols[a->name]));
throw; throw;
@ -75,8 +90,9 @@ json printValueAsJSON(EvalState & state, bool strict,
int i = 0; int i = 0;
for (auto elem : v.listItems()) { for (auto elem : v.listItems()) {
try { 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) { } catch (Error & e) {
// TODO: Missing catch
e.addTrace(state.positions[pos], e.addTrace(state.positions[pos],
HintFmt("while evaluating list element at index %1%", i)); HintFmt("while evaluating list element at index %1%", i));
throw; throw;
@ -106,11 +122,11 @@ json printValueAsJSON(EvalState & state, bool strict,
return out; 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) Value & v, const PosIdx pos, std::ostream & str, NixStringContext & context, bool copyToStore)
{ {
try { try {
str << printValueAsJSON(state, strict, v, pos, context, copyToStore); str << printValueAsJSON(state, strict, replaceEvalErrors, v, pos, context, copyToStore);
} catch (nlohmann::json::exception & e) { } catch (nlohmann::json::exception & e) {
throw JSONSerializationError("JSON serialization error: %s", e.what()); throw JSONSerializationError("JSON serialization error: %s", e.what());
} }

View file

@ -91,7 +91,7 @@ static void parseFlakeInputAttr(
if (attr.name == state.symbols.create("publicKeys")) { if (attr.name == state.symbols.create("publicKeys")) {
experimentalFeatureSettings.require(Xp::VerifiedFetches); experimentalFeatureSettings.require(Xp::VerifiedFetches);
NixStringContext emptyContext = {}; 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 } else
state.error<TypeError>("flake input attribute '%s' is %s while a string, Boolean, or integer is expected", 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(); state.symbols[attr.name], showType(*attr.value)).debugThrow();

View file

@ -54,7 +54,7 @@ void PosixSourceAccessor::readFile(
#endif #endif
)); ));
if (!fd) if (!fd)
throw SysError("opening file '%1%'", ap.string()); throw SysError("opening file9 '%1%'", ap.string());
struct stat st; struct stat st;
if (fstat(fromDescriptorReadOnly(fd.get()), &st) == -1) if (fstat(fromDescriptorReadOnly(fd.get()), &st) == -1)

View file

@ -984,7 +984,7 @@ static void queryJSON(Globals & globals, std::vector<PackageInfo> & elems, bool
metaObj[j] = nullptr; metaObj[j] = nullptr;
} else { } else {
NixStringContext context; NixStringContext context;
metaObj[j] = printValueAsJSON(*globals.state, true, *v, noPos, context); metaObj[j] = printValueAsJSON(*globals.state, true, false, *v, noPos, context);
} }
} }
} }

View file

@ -28,7 +28,7 @@ static int rootNr = 0;
enum OutputKind { okPlain, okRaw, okXML, okJSON }; enum OutputKind { okPlain, okRaw, okXML, okJSON };
void processExpr(EvalState & state, const Strings & attrPaths, 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) bool evalOnly, OutputKind output, bool location, Expr * e)
{ {
if (parseOnly) { if (parseOnly) {
@ -58,7 +58,7 @@ void processExpr(EvalState & state, const Strings & attrPaths,
else if (output == okXML) else if (output == okXML)
printValueAsXML(state, strict, location, vRes, std::cout, context, noPos); printValueAsXML(state, strict, location, vRes, std::cout, context, noPos);
else if (output == okJSON) { 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; std::cout << std::endl;
} else { } else {
if (strict) state.forceValueDeep(vRes); if (strict) state.forceValueDeep(vRes);
@ -106,6 +106,7 @@ static int main_nix_instantiate(int argc, char * * argv)
OutputKind outputKind = okPlain; OutputKind outputKind = okPlain;
bool xmlOutputSourceLocation = true; bool xmlOutputSourceLocation = true;
bool strict = false; bool strict = false;
bool replaceEvalErrors = false;
Strings attrPaths; Strings attrPaths;
bool wantsReadWrite = false; bool wantsReadWrite = false;
@ -147,6 +148,8 @@ static int main_nix_instantiate(int argc, char * * argv)
xmlOutputSourceLocation = false; xmlOutputSourceLocation = false;
else if (*arg == "--strict") else if (*arg == "--strict")
strict = true; strict = true;
else if (*arg == "--replace-eval-errors")
replaceEvalErrors = true;
else if (*arg == "--dry-run") else if (*arg == "--dry-run")
settings.readOnlyMode = true; settings.readOnlyMode = true;
else if (*arg != "" && arg->at(0) == '-') else if (*arg != "" && arg->at(0) == '-')
@ -184,7 +187,7 @@ static int main_nix_instantiate(int argc, char * * argv)
if (readStdin) { if (readStdin) {
Expr * e = state->parseStdin(); Expr * e = state->parseStdin();
processExpr(*state, attrPaths, parseOnly, strict, autoArgs, processExpr(*state, attrPaths, parseOnly, strict, replaceEvalErrors, autoArgs,
evalOnly, outputKind, xmlOutputSourceLocation, e); evalOnly, outputKind, xmlOutputSourceLocation, e);
} else if (files.empty() && !fromArgs) } else if (files.empty() && !fromArgs)
files.push_back("./default.nix"); files.push_back("./default.nix");
@ -193,7 +196,7 @@ static int main_nix_instantiate(int argc, char * * argv)
Expr * e = fromArgs Expr * e = fromArgs
? state->parseExprFromString(i, state->rootPath(".")) ? state->parseExprFromString(i, state->rootPath("."))
: state->parseExprFromFile(resolveExprPath(lookupFileArg(*state, i))); : state->parseExprFromFile(resolveExprPath(lookupFileArg(*state, i)));
processExpr(*state, attrPaths, parseOnly, strict, autoArgs, processExpr(*state, attrPaths, parseOnly, strict, replaceEvalErrors, autoArgs,
evalOnly, outputKind, xmlOutputSourceLocation, e); evalOnly, outputKind, xmlOutputSourceLocation, e);
} }

View file

@ -15,6 +15,7 @@ namespace nix::fs { using namespace std::filesystem; }
struct CmdEval : MixJSON, InstallableValueCommand, MixReadOnlyOption struct CmdEval : MixJSON, InstallableValueCommand, MixReadOnlyOption
{ {
bool raw = false; bool raw = false;
bool replaceEvalErrors = false;
std::optional<std::string> apply; std::optional<std::string> apply;
std::optional<std::filesystem::path> writeTo; std::optional<std::filesystem::path> writeTo;
@ -39,6 +40,12 @@ struct CmdEval : MixJSON, InstallableValueCommand, MixReadOnlyOption
.labels = {"path"}, .labels = {"path"},
.handler = {&writeTo}, .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 std::string description() override
@ -118,7 +125,7 @@ struct CmdEval : MixJSON, InstallableValueCommand, MixReadOnlyOption
} }
else if (json) { else if (json) {
printJSON(printValueAsJSON(*state, true, *v, pos, context, false)); printJSON(printValueAsJSON(*state, true, replaceEvalErrors, *v, pos, context, false));
} }
else { else {

View file

@ -48,6 +48,13 @@ R""(
# cat ./out/subdir/bla # cat ./out/subdir/bla
123 123
* Replace evaluation errors:
```console
$ nix eval --json --replace-eval-errors --expr '{ a = throw "fail"; }'
{"a":"«evaluation error»"}
```
# Description # Description
This command evaluates the given Nix expression, and prints the result on standard output. This command evaluates the given Nix expression, and prints the result on standard output.

View file

@ -35,6 +35,23 @@ nix-instantiate --eval -E 'assert 1 + 2 == 3; true'
[[ $(nix-instantiate -A int --eval - < "./eval.nix") == 123 ]] [[ $(nix-instantiate -A int --eval - < "./eval.nix") == 123 ]]
[[ "$(nix-instantiate --eval -E '{"assert"=1;bar=2;}')" == '{ "assert" = 1; bar = 2; }' ]] [[ "$(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. # Check that symlink cycles don't cause a hang.
ln -sfn cycle.nix "$TEST_ROOT/cycle.nix" ln -sfn cycle.nix "$TEST_ROOT/cycle.nix"
(! nix eval --file "$TEST_ROOT/cycle.nix") (! nix eval --file "$TEST_ROOT/cycle.nix")

View 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";
}