libexpr/libutil: summarize derivations in stack traces regardless of --show-trace
Eval errors will now print a simple, no frills chain of involved
derivations at the bottom. For example, trying to evaluate
`pkgs.xonsh.override { python3 = pkgs.python2; }` has the usual
Package ‘python-2.7.18.8’ in /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/pkgs/development/interpreters/python/cpython/2.7/default.nix:398 is marked as insecure, refusing to evaluate.
message, but now also includes the following:
note: trace involved the following derivations:
derivation 'xonsh-0.19.9'
derivation 'python2.7-xonsh-0.19.9'
derivation 'python2.7-setuptools-44.0.0'
To give the user information about why the erroring derivation was
involved in the first place.
We would like more structured information in the future, but this should
still be a significant improvement.
Change-Id: Icf6da52abd0a043cfb63943bf0b0c160c21ee59e
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
---
|
||||
synopsis: Stack traces now summarize involved derivations at the bottom
|
||||
cls: [4493]
|
||||
category: Improvements
|
||||
credits: [Qyriad]
|
||||
---
|
||||
|
||||
When evaluation errors and a stack trace is printed,
|
||||
|
||||
For example, if I add Nheko to a NixOS `environment.systemPackages` without adding `olm-3.2.16` `nixpkgs.config.permittedInsecurePackages`, then without `--show-trace`, I previously got this:
|
||||
|
||||
```
|
||||
error:
|
||||
… while calling the 'head' builtin
|
||||
at /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/lib/attrsets.nix:1701:13:
|
||||
1700| if length values == 1 || pred here (elemAt values 1) (head values) then
|
||||
1701| head values
|
||||
| ^
|
||||
1702| else
|
||||
|
||||
… while evaluating the attribute 'value'
|
||||
at /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/lib/modules.nix:1118:7:
|
||||
1117| // {
|
||||
1118| value = addErrorContext "while evaluating the option `${showOption loc}':" value;
|
||||
| ^
|
||||
1119| inherit (res.defsFinal') highestPrio;
|
||||
|
||||
(stack trace truncated; use '--show-trace' to show the full trace)
|
||||
|
||||
error: Package ‘olm-3.2.16’ in /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/pkgs/by-name/ol/olm/package.nix:37 is marked as insecure, refusing to evaluate.
|
||||
|
||||
< -snip the whole explanation about olm's CVEs- >
|
||||
```
|
||||
|
||||
This doesn't tell me anything about where `olm-3.2.16` came from.
|
||||
With `--show-trace`, there's 1 155 lines to sift through, but does contain lines like "while evaluating derivation 'nheko-0.12.1'".
|
||||
|
||||
With this change, those lines are summarized and collected at the bottom, regardless of `--show-trace`:
|
||||
|
||||
```
|
||||
error:
|
||||
… while calling the 'head' builtin
|
||||
at /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/lib/attrsets.nix:1701:13:
|
||||
1700| if length values == 1 || pred here (elemAt values 1) (head values) then
|
||||
1701| head values
|
||||
| ^
|
||||
1702| else
|
||||
|
||||
… while evaluating the attribute 'value'
|
||||
at /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/lib/modules.nix:1118:7:
|
||||
1117| // {
|
||||
1118| value = addErrorContext "while evaluating the option `${showOption loc}':" value;
|
||||
| ^
|
||||
1119| inherit (res.defsFinal') highestPrio;
|
||||
|
||||
(stack trace truncated; use '--show-trace' to show the full trace)
|
||||
|
||||
error: Package ‘olm-3.2.16’ in /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/pkgs/by-name/ol/olm/package.nix:37 is marked as insecure, refusing to evaluate.
|
||||
|
||||
|
||||
< -snip the whole explanation about olm's CVEs- >
|
||||
|
||||
|
||||
note: trace involved the following derivations:
|
||||
derivation 'etc'
|
||||
derivation 'dbus-1'
|
||||
derivation 'system-path'
|
||||
derivation 'nheko-0.12.1'
|
||||
derivation 'mtxclient-0.10.1'
|
||||
```
|
||||
|
||||
Now we finally know that olm was evaluated because of Nheko, without sifting through *thousands* of lines of error message.
|
||||
+11
-6
@@ -853,10 +853,12 @@ static void prim_derivationStrict(EvalState & state, Value * * args, Value & v)
|
||||
* often results from the composition of several functions
|
||||
* (derivationStrict, derivation, mkDerivation, mkPythonModule, etc.)
|
||||
*/
|
||||
e.addTrace(nullptr, HintFmt(
|
||||
"while evaluating derivation '%s'\n"
|
||||
" whose name attribute is located at %s",
|
||||
drvName, pos));
|
||||
|
||||
e.pushTrace(Trace::fromDrv(
|
||||
state.ctx.positions[nameAttr->pos],
|
||||
drvName
|
||||
));
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -1082,8 +1084,11 @@ drvName, Bindings * attrs, Value & v)
|
||||
}
|
||||
|
||||
} catch (Error & e) {
|
||||
e.addTrace(state.ctx.positions[i->pos],
|
||||
HintFmt("while evaluating attribute '%1%' of derivation '%2%'", key, drvName));
|
||||
e.pushTrace(Trace::fromDrvAttr(
|
||||
state.ctx.positions[i->pos],
|
||||
std::string(drvName),
|
||||
std::string(key)
|
||||
));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "lix/libutil/concepts.hh"
|
||||
#include "lix/libutil/environment-variables.hh"
|
||||
#include "lix/libutil/error.hh"
|
||||
#include "lix/libutil/logging.hh"
|
||||
@@ -5,10 +6,12 @@
|
||||
#include "lix/libutil/terminal.hh"
|
||||
#include "lix/libutil/strings.hh"
|
||||
#include "lix/libutil/signals.hh"
|
||||
#include "lix/libutil/position.hh"
|
||||
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <ranges>
|
||||
|
||||
#include <boost/core/demangle.hpp>
|
||||
|
||||
@@ -38,6 +41,39 @@ std::ostream & operator <<(std::ostream & os, const HintFmt & hf)
|
||||
return os << hf.str();
|
||||
}
|
||||
|
||||
Trace Trace::fromDrv(std::shared_ptr<Pos> pos, std::string drvName)
|
||||
{
|
||||
DrvTrace dt(drvName);
|
||||
|
||||
HintFmt h(
|
||||
"while evaluating derivation '%s'\n"
|
||||
" whose name attribute is located at %s",
|
||||
dt.drvName,
|
||||
*pos
|
||||
);
|
||||
|
||||
return Trace{
|
||||
.pos = pos,
|
||||
.hint = h,
|
||||
.drvTrace = dt,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Trace Trace::fromDrvAttr(std::shared_ptr<Pos> pos, std::string drvName, std::string attrOfDrv)
|
||||
{
|
||||
DrvTrace dt(drvName);
|
||||
|
||||
HintFmt h("while evaluating attribute '%s' of derivation '%s'", attrOfDrv, dt.drvName);
|
||||
|
||||
return Trace{
|
||||
.pos = pos,
|
||||
.hint = h,
|
||||
.drvTrace = dt,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* An arbitrarily defined value comparison for the purpose of using traces in the key of a sorted container.
|
||||
*/
|
||||
@@ -222,6 +258,29 @@ void printSkippedTracesMaybe(
|
||||
skippedTraces.clear();
|
||||
}
|
||||
|
||||
template<ViewOf<DrvTrace const &> Range>
|
||||
requires std::ranges::sized_range<Range>
|
||||
void printDerivationTracesMaybe(std::ostream & oss, Range && elems)
|
||||
{
|
||||
if (std::ranges::empty(elems)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::set<std::reference_wrapper<DrvTrace const>> seen;
|
||||
|
||||
oss << "\n"
|
||||
<< ANSI_BLUE << "note:" << ANSI_NORMAL << " trace involved the following derivations: \n";
|
||||
|
||||
for (DrvTrace const & dt : elems) {
|
||||
if (!seen.contains(dt)) {
|
||||
oss << HintFmt("derivation '%s'", dt.drvName) << "\n";
|
||||
}
|
||||
seen.insert(dt);
|
||||
}
|
||||
|
||||
oss << "\n";
|
||||
}
|
||||
|
||||
std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool showTrace)
|
||||
{
|
||||
std::string prefix;
|
||||
@@ -401,6 +460,13 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
|
||||
|
||||
printPosMaybe(oss, "", einfo.pos);
|
||||
|
||||
// Summarize derivations involved in the trace.
|
||||
auto const drvTraces = einfo.traces
|
||||
| std::views::filter([](auto const & trace) { return trace.drvTrace.has_value(); })
|
||||
| std::views::transform([](auto const & trace) { return *trace.drvTrace; })
|
||||
| std::ranges::to<std::vector>();
|
||||
printDerivationTracesMaybe(oss, drvTraces);
|
||||
|
||||
auto suggestions = einfo.suggestions.trim();
|
||||
if (!suggestions.suggestions.empty()) {
|
||||
oss << "Did you mean " <<
|
||||
|
||||
@@ -67,9 +67,45 @@ void printCodeLines(std::ostream & out,
|
||||
const Pos & errPos,
|
||||
const LinesOfCode & loc);
|
||||
|
||||
/** @brief Information for a @ref Trace that encountered a derivation.
|
||||
*
|
||||
* This is used for summarizing the derivations involved in an eval error
|
||||
* at the end of a trace-print.
|
||||
*/
|
||||
struct DrvTrace
|
||||
{
|
||||
std::string drvName;
|
||||
// TODO: include more structured information like "element 6 of nativeBuildInputs".
|
||||
|
||||
DrvTrace() = delete;
|
||||
|
||||
explicit DrvTrace(std::string drvName) : drvName(drvName) {}
|
||||
|
||||
friend std::strong_ordering operator<=>(DrvTrace const & lhs, DrvTrace const & rhs) noexcept = default;
|
||||
};
|
||||
|
||||
struct Trace;
|
||||
|
||||
// n.b.: std::shared_ptr<Pos> can't be dereferenced in this header,
|
||||
// because we can't include position.hh without circular includes.
|
||||
// Yay.
|
||||
Trace traceFromDrv(std::shared_ptr<Pos> pos, std::string drvName);
|
||||
|
||||
struct Trace {
|
||||
std::shared_ptr<Pos> pos;
|
||||
HintFmt hint;
|
||||
std::optional<DrvTrace> drvTrace;
|
||||
|
||||
/** Construct a Trace and canned format message assuming a derivation's
|
||||
* position and name.
|
||||
*/
|
||||
static Trace fromDrv(std::shared_ptr<Pos> pos, std::string drvName);
|
||||
|
||||
/** Construct a Trace and canned format message assuming a derivation's
|
||||
* position, name, and the attribute of that derivation which caused the
|
||||
* trace.
|
||||
*/
|
||||
static Trace fromDrvAttr(std::shared_ptr<Pos> pos, std::string drvName, std::string attrOfDrv);
|
||||
};
|
||||
|
||||
inline bool operator<(const Trace& lhs, const Trace& rhs);
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
error:
|
||||
… while calling the 'getAttr' builtin
|
||||
at «internal»:1:500:
|
||||
… while calling the 'derivationStrict' builtin
|
||||
at «internal»:1:208:
|
||||
… while evaluating derivation 'package-you-care-about'
|
||||
whose name attribute is located at /pwd/in.nix:22:5
|
||||
at /pwd/in.nix:22:5:
|
||||
21| package-you-care-about = derivation {
|
||||
22| name = "package-you-care-about";
|
||||
| ^
|
||||
23| __structuredAttrs = true;
|
||||
|
||||
… while evaluating attribute 'buildInputs' of derivation 'package-you-care-about'
|
||||
at /pwd/in.nix:25:5:
|
||||
24|
|
||||
25| buildInputs = [
|
||||
| ^
|
||||
26| direct-dependency
|
||||
|
||||
… while evaluating list element at index 0
|
||||
|
||||
… while calling the 'getAttr' builtin
|
||||
at «internal»:1:500:
|
||||
… while calling the 'derivationStrict' builtin
|
||||
at «internal»:1:208:
|
||||
… while evaluating derivation 'direct-dependency'
|
||||
whose name attribute is located at /pwd/in.nix:15:5
|
||||
at /pwd/in.nix:15:5:
|
||||
14| direct-dependency = derivation {
|
||||
15| name = "direct-dependency";
|
||||
| ^
|
||||
16| __structuredAttrs = true;
|
||||
|
||||
… while evaluating attribute 'libtrans' of derivation 'direct-dependency'
|
||||
at /pwd/in.nix:18:5:
|
||||
17|
|
||||
18| libtrans = transitive-dependency;
|
||||
| ^
|
||||
19| };
|
||||
|
||||
… while calling the 'getAttr' builtin
|
||||
at «internal»:1:500:
|
||||
… while calling the 'derivationStrict' builtin
|
||||
at «internal»:1:208:
|
||||
… while evaluating derivation 'transitive-dependency'
|
||||
whose name attribute is located at /pwd/in.nix:4:5
|
||||
at /pwd/in.nix:4:5:
|
||||
3| transitive-dependency = mkDerivation {
|
||||
4| name = "transitive-dependency";
|
||||
| ^
|
||||
5| __structuredAttrs = true;
|
||||
|
||||
… while evaluating attribute 'nativeBuildInputs' of derivation 'transitive-dependency'
|
||||
at /pwd/in.nix:7:5:
|
||||
6|
|
||||
7| nativeBuildInputs = [
|
||||
| ^
|
||||
8| null
|
||||
|
||||
… while evaluating list element at index 1
|
||||
|
||||
… caused by explicit throw
|
||||
at /pwd/in.nix:9:8:
|
||||
8| null
|
||||
9| (throw "transitive dependency growls")
|
||||
| ^
|
||||
10| ];
|
||||
|
||||
error: transitive dependency growls
|
||||
|
||||
note: trace involved the following derivations:
|
||||
derivation 'package-you-care-about'
|
||||
derivation 'direct-dependency'
|
||||
derivation 'transitive-dependency'
|
||||
@@ -0,0 +1,30 @@
|
||||
with import ./config.nix;
|
||||
let
|
||||
transitive-dependency = mkDerivation {
|
||||
name = "transitive-dependency";
|
||||
__structuredAttrs = true;
|
||||
|
||||
nativeBuildInputs = [
|
||||
null
|
||||
(throw "transitive dependency growls")
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
direct-dependency = derivation {
|
||||
name = "direct-dependency";
|
||||
__structuredAttrs = true;
|
||||
|
||||
libtrans = transitive-dependency;
|
||||
};
|
||||
|
||||
package-you-care-about = derivation {
|
||||
name = "package-you-care-about";
|
||||
__structuredAttrs = true;
|
||||
|
||||
buildInputs = [
|
||||
direct-dependency
|
||||
];
|
||||
};
|
||||
|
||||
in package-you-care-about.outPath
|
||||
@@ -0,0 +1,3 @@
|
||||
[[test]]
|
||||
runner = "eval-fail"
|
||||
global-assets = ["config.nix"]
|
||||
Reference in New Issue
Block a user