1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-06-25 10:41:16 +02:00

Merge pull request #13261 from xokdvium/eval-profiler-derivations

libexpr: Include derivation names in the call stack profile
This commit is contained in:
Jörg Thalheim 2025-05-25 21:15:54 +02:00 committed by GitHub
commit 4777734974
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 112 additions and 63 deletions

View file

@ -5,18 +5,14 @@
namespace nix { namespace nix {
void EvalProfiler::preFunctionCallHook( void EvalProfiler::preFunctionCallHook(EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) {}
const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
{
}
void EvalProfiler::postFunctionCallHook( void EvalProfiler::postFunctionCallHook(EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
{ {
} }
void MultiEvalProfiler::preFunctionCallHook( void MultiEvalProfiler::preFunctionCallHook(
const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
{ {
for (auto & profiler : profilers) { for (auto & profiler : profilers) {
if (profiler->getNeededHooks().test(Hook::preFunctionCall)) if (profiler->getNeededHooks().test(Hook::preFunctionCall))
@ -25,7 +21,7 @@ void MultiEvalProfiler::preFunctionCallHook(
} }
void MultiEvalProfiler::postFunctionCallHook( void MultiEvalProfiler::postFunctionCallHook(
const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
{ {
for (auto & profiler : profilers) { for (auto & profiler : profilers) {
if (profiler->getNeededHooks().test(Hook::postFunctionCall)) if (profiler->getNeededHooks().test(Hook::postFunctionCall))
@ -99,6 +95,14 @@ struct FunctorFrameInfo
auto operator<=>(const FunctorFrameInfo & rhs) const = default; auto operator<=>(const FunctorFrameInfo & rhs) const = default;
}; };
struct DerivationStrictFrameInfo
{
PosIdx callPos = noPos;
std::string drvName;
std::ostream & symbolize(const EvalState & state, std::ostream & os, PosCache & posCache) const;
auto operator<=>(const DerivationStrictFrameInfo & rhs) const = default;
};
/** Fallback frame info. */ /** Fallback frame info. */
struct GenericFrameInfo struct GenericFrameInfo
{ {
@ -107,7 +111,8 @@ struct GenericFrameInfo
auto operator<=>(const GenericFrameInfo & rhs) const = default; auto operator<=>(const GenericFrameInfo & rhs) const = default;
}; };
using FrameInfo = std::variant<LambdaFrameInfo, PrimOpFrameInfo, FunctorFrameInfo, GenericFrameInfo>; using FrameInfo =
std::variant<LambdaFrameInfo, PrimOpFrameInfo, FunctorFrameInfo, DerivationStrictFrameInfo, GenericFrameInfo>;
using FrameStack = std::vector<FrameInfo>; using FrameStack = std::vector<FrameInfo>;
/** /**
@ -125,8 +130,10 @@ class SampleStack : public EvalProfiler
return Hooks().set(preFunctionCall).set(postFunctionCall); return Hooks().set(preFunctionCall).set(postFunctionCall);
} }
FrameInfo getPrimOpFrameInfo(const PrimOp & primOp, std::span<Value *> args, PosIdx pos);
public: public:
SampleStack(const EvalState & state, std::filesystem::path profileFile, std::chrono::nanoseconds period) SampleStack(EvalState & state, std::filesystem::path profileFile, std::chrono::nanoseconds period)
: state(state) : state(state)
, sampleInterval(period) , sampleInterval(period)
, profileFd([&]() { , profileFd([&]() {
@ -140,23 +147,22 @@ public:
} }
[[gnu::noinline]] void [[gnu::noinline]] void
preFunctionCallHook(const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) override; preFunctionCallHook(EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) override;
[[gnu::noinline]] void [[gnu::noinline]] void
postFunctionCallHook(const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) override; postFunctionCallHook(EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) override;
void maybeSaveProfile(std::chrono::time_point<std::chrono::high_resolution_clock> now); void maybeSaveProfile(std::chrono::time_point<std::chrono::high_resolution_clock> now);
void saveProfile(); void saveProfile();
FrameInfo getFrameInfoFromValueAndPos(const Value & v, PosIdx pos); FrameInfo getFrameInfoFromValueAndPos(const Value & v, std::span<Value *> args, PosIdx pos);
SampleStack(SampleStack &&) = default; SampleStack(SampleStack &&) = default;
SampleStack & operator=(SampleStack &&) = delete; SampleStack & operator=(SampleStack &&) = delete;
SampleStack(const SampleStack &) = delete; SampleStack(const SampleStack &) = delete;
SampleStack & operator=(const SampleStack &) = delete; SampleStack & operator=(const SampleStack &) = delete;
~SampleStack(); ~SampleStack();
private: private:
/** Hold on to an instance of EvalState for symbolizing positions. */ /** Hold on to an instance of EvalState for symbolizing positions. */
const EvalState & state; EvalState & state;
std::chrono::nanoseconds sampleInterval; std::chrono::nanoseconds sampleInterval;
AutoCloseFD profileFd; AutoCloseFD profileFd;
FrameStack stack; FrameStack stack;
@ -167,15 +173,41 @@ private:
PosCache posCache; PosCache posCache;
}; };
FrameInfo SampleStack::getFrameInfoFromValueAndPos(const Value & v, PosIdx pos) FrameInfo SampleStack::getPrimOpFrameInfo(const PrimOp & primOp, std::span<Value *> args, PosIdx pos)
{
auto derivationInfo = [&]() -> std::optional<FrameInfo> {
/* Here we rely a bit on the implementation details of libexpr/primops/derivation.nix
and derivationStrict primop. This is not ideal, but is necessary for
the usefulness of the profiler. This might actually affect the evaluation,
but the cost shouldn't be that high as to make the traces entirely inaccurate. */
if (primOp.name == "derivationStrict") {
try {
/* Error context strings don't actually matter, since we ignore all eval errors. */
state.forceAttrs(*args[0], pos, "");
auto attrs = args[0]->attrs();
auto nameAttr = state.getAttr(state.sName, attrs, "");
auto drvName = std::string(state.forceStringNoCtx(*nameAttr->value, pos, ""));
return DerivationStrictFrameInfo{.callPos = pos, .drvName = std::move(drvName)};
} catch (...) {
/* Ignore all errors, since those will be diagnosed by the evaluator itself. */
}
}
return std::nullopt;
}();
return derivationInfo.value_or(PrimOpFrameInfo{.expr = &primOp, .callPos = pos});
}
FrameInfo SampleStack::getFrameInfoFromValueAndPos(const Value & v, std::span<Value *> args, PosIdx pos)
{ {
/* NOTE: No actual references to garbage collected values are not held in /* NOTE: No actual references to garbage collected values are not held in
the profiler. */ the profiler. */
if (v.isLambda()) if (v.isLambda())
return LambdaFrameInfo{.expr = v.payload.lambda.fun, .callPos = pos}; return LambdaFrameInfo{.expr = v.payload.lambda.fun, .callPos = pos};
else if (v.isPrimOp()) else if (v.isPrimOp()) {
return PrimOpFrameInfo{.expr = v.primOp(), .callPos = pos}; return getPrimOpFrameInfo(*v.primOp(), args, pos);
else if (v.isPrimOpApp()) } else if (v.isPrimOpApp())
/* Resolve primOp eagerly. Must not hold on to a reference to a Value. */ /* Resolve primOp eagerly. Must not hold on to a reference to a Value. */
return PrimOpFrameInfo{.expr = v.primOpAppPrimOp(), .callPos = pos}; return PrimOpFrameInfo{.expr = v.primOpAppPrimOp(), .callPos = pos};
else if (state.isFunctor(v)) { else if (state.isFunctor(v)) {
@ -190,10 +222,10 @@ FrameInfo SampleStack::getFrameInfoFromValueAndPos(const Value & v, PosIdx pos)
return GenericFrameInfo{.pos = pos}; return GenericFrameInfo{.pos = pos};
} }
[[gnu::noinline]] void SampleStack::preFunctionCallHook( [[gnu::noinline]] void
const EvalState & state, const Value & v, [[maybe_unused]] std::span<Value *> args, const PosIdx pos) SampleStack::preFunctionCallHook(EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
{ {
stack.push_back(getFrameInfoFromValueAndPos(v, pos)); stack.push_back(getFrameInfoFromValueAndPos(v, args, pos));
auto now = std::chrono::high_resolution_clock::now(); auto now = std::chrono::high_resolution_clock::now();
@ -208,9 +240,8 @@ FrameInfo SampleStack::getFrameInfoFromValueAndPos(const Value & v, PosIdx pos)
} }
[[gnu::noinline]] void [[gnu::noinline]] void
SampleStack::postFunctionCallHook(const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) SampleStack::postFunctionCallHook(EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
{ {
if (!stack.empty()) if (!stack.empty())
stack.pop_back(); stack.pop_back();
} }
@ -251,6 +282,18 @@ std::ostream & PrimOpFrameInfo::symbolize(const EvalState & state, std::ostream
return os; return os;
} }
std::ostream &
DerivationStrictFrameInfo::symbolize(const EvalState & state, std::ostream & os, PosCache & posCache) const
{
/* Sometimes callsite position can have an unresolved origin, which
leads to confusing «none»:0 locations in the profile. */
auto pos = posCache.lookup(callPos);
if (!std::holds_alternative<std::monostate>(pos.origin))
os << posCache.lookup(callPos) << ":";
os << "primop derivationStrict:" << drvName;
return os;
}
void SampleStack::maybeSaveProfile(std::chrono::time_point<std::chrono::high_resolution_clock> now) void SampleStack::maybeSaveProfile(std::chrono::time_point<std::chrono::high_resolution_clock> now)
{ {
if (now - lastDump >= profileDumpInterval) if (now - lastDump >= profileDumpInterval)
@ -300,8 +343,7 @@ SampleStack::~SampleStack()
} // namespace } // namespace
ref<EvalProfiler> ref<EvalProfiler> makeSampleStackProfiler(EvalState & state, std::filesystem::path profileFile, uint64_t frequency)
makeSampleStackProfiler(const EvalState & state, std::filesystem::path profileFile, uint64_t frequency)
{ {
/* 0 is a special value for sampling stack after each call. */ /* 0 is a special value for sampling stack after each call. */
std::chrono::nanoseconds period = frequency == 0 std::chrono::nanoseconds period = frequency == 0

View file

@ -2243,6 +2243,16 @@ bool EvalState::forceBool(Value & v, const PosIdx pos, std::string_view errorCtx
return v.boolean(); return v.boolean();
} }
Bindings::const_iterator EvalState::getAttr(Symbol attrSym, const Bindings * attrSet, std::string_view errorCtx)
{
auto value = attrSet->find(attrSym);
if (value == attrSet->end()) {
error<TypeError>("attribute '%s' missing", symbols[attrSym])
.withTrace(noPos, errorCtx)
.debugThrow();
}
return value;
}
bool EvalState::isFunctor(const Value & fun) const bool EvalState::isFunctor(const Value & fun) const
{ {

View file

@ -4,7 +4,7 @@
namespace nix { namespace nix {
void FunctionCallTrace::preFunctionCallHook( void FunctionCallTrace::preFunctionCallHook(
const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
{ {
auto duration = std::chrono::high_resolution_clock::now().time_since_epoch(); auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration); auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration);
@ -12,7 +12,7 @@ void FunctionCallTrace::preFunctionCallHook(
} }
void FunctionCallTrace::postFunctionCallHook( void FunctionCallTrace::postFunctionCallHook(
const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
{ {
auto duration = std::chrono::high_resolution_clock::now().time_since_epoch(); auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration); auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration);

View file

@ -62,8 +62,7 @@ public:
* @param args Function arguments. * @param args Function arguments.
* @param pos Function position. * @param pos Function position.
*/ */
virtual void virtual void preFunctionCallHook(EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos);
preFunctionCallHook(const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos);
/** /**
* Hook called on EvalState::callFunction exit. * Hook called on EvalState::callFunction exit.
@ -74,8 +73,7 @@ public:
* @param args Function arguments. * @param args Function arguments.
* @param pos Function position. * @param pos Function position.
*/ */
virtual void virtual void postFunctionCallHook(EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos);
postFunctionCallHook(const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos);
virtual ~EvalProfiler() = default; virtual ~EvalProfiler() = default;
@ -106,12 +104,11 @@ public:
void addProfiler(ref<EvalProfiler> profiler); void addProfiler(ref<EvalProfiler> profiler);
[[gnu::noinline]] void [[gnu::noinline]] void
preFunctionCallHook(const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) override; preFunctionCallHook(EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) override;
[[gnu::noinline]] void [[gnu::noinline]] void
postFunctionCallHook(const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) override; postFunctionCallHook(EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) override;
}; };
ref<EvalProfiler> ref<EvalProfiler> makeSampleStackProfiler(EvalState & state, std::filesystem::path profileFile, uint64_t frequency);
makeSampleStackProfiler(const EvalState & state, std::filesystem::path profileFile, uint64_t frequency);
} }

View file

@ -542,6 +542,11 @@ public:
std::string_view forceString(Value & v, NixStringContext & context, const PosIdx pos, std::string_view errorCtx, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings); std::string_view forceString(Value & v, NixStringContext & context, const PosIdx pos, std::string_view errorCtx, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
std::string_view forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx); std::string_view forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx);
/**
* Get attribute from an attribute set and throw an error if it doesn't exist.
*/
Bindings::const_iterator getAttr(Symbol attrSym, const Bindings * attrSet, std::string_view errorCtx);
template<typename... Args> template<typename... Args>
[[gnu::noinline]] [[gnu::noinline]]
void addErrorTrace(Error & e, const Args & ... formatArgs) const; void addErrorTrace(Error & e, const Args & ... formatArgs) const;

View file

@ -17,9 +17,9 @@ public:
FunctionCallTrace() = default; FunctionCallTrace() = default;
[[gnu::noinline]] void [[gnu::noinline]] void
preFunctionCallHook(const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) override; preFunctionCallHook(EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) override;
[[gnu::noinline]] void [[gnu::noinline]] void
postFunctionCallHook(const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) override; postFunctionCallHook(EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) override;
}; };
} }

View file

@ -670,26 +670,12 @@ struct CompareValues
typedef std::list<Value *, gc_allocator<Value *>> ValueList; typedef std::list<Value *, gc_allocator<Value *>> ValueList;
static Bindings::const_iterator getAttr(
EvalState & state,
Symbol attrSym,
const Bindings * attrSet,
std::string_view errorCtx)
{
auto value = attrSet->find(attrSym);
if (value == attrSet->end()) {
state.error<TypeError>("attribute '%s' missing", state.symbols[attrSym]).withTrace(noPos, errorCtx).debugThrow();
}
return value;
}
static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
state.forceAttrs(*args[0], noPos, "while evaluating the first argument passed to builtins.genericClosure"); state.forceAttrs(*args[0], noPos, "while evaluating the first argument passed to builtins.genericClosure");
/* Get the start set. */ /* Get the start set. */
auto startSet = getAttr(state, state.sStartSet, args[0]->attrs(), "in the attrset passed as argument to builtins.genericClosure"); auto startSet = state.getAttr(state.sStartSet, args[0]->attrs(), "in the attrset passed as argument to builtins.genericClosure");
state.forceList(*startSet->value, noPos, "while evaluating the 'startSet' attribute passed as argument to builtins.genericClosure"); state.forceList(*startSet->value, noPos, "while evaluating the 'startSet' attribute passed as argument to builtins.genericClosure");
@ -703,7 +689,7 @@ static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * a
} }
/* Get the operator. */ /* Get the operator. */
auto op = getAttr(state, state.sOperator, args[0]->attrs(), "in the attrset passed as argument to builtins.genericClosure"); auto op = state.getAttr(state.sOperator, args[0]->attrs(), "in the attrset passed as argument to builtins.genericClosure");
state.forceFunction(*op->value, noPos, "while evaluating the 'operator' attribute passed as argument to builtins.genericClosure"); state.forceFunction(*op->value, noPos, "while evaluating the 'operator' attribute passed as argument to builtins.genericClosure");
/* Construct the closure by applying the operator to elements of /* Construct the closure by applying the operator to elements of
@ -720,7 +706,7 @@ static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * a
state.forceAttrs(*e, noPos, "while evaluating one of the elements generated by (or initially passed to) builtins.genericClosure"); state.forceAttrs(*e, noPos, "while evaluating one of the elements generated by (or initially passed to) builtins.genericClosure");
auto key = getAttr(state, state.sKey, e->attrs(), "in one of the attrsets generated by (or initially passed to) builtins.genericClosure"); auto key = state.getAttr(state.sKey, e->attrs(), "in one of the attrsets generated by (or initially passed to) builtins.genericClosure");
state.forceValue(*key->value, noPos); state.forceValue(*key->value, noPos);
if (!doneKeys.insert(key->value).second) continue; if (!doneKeys.insert(key->value).second) continue;
@ -1203,7 +1189,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
auto attrs = args[0]->attrs(); auto attrs = args[0]->attrs();
/* Figure out the name first (for stack backtraces). */ /* Figure out the name first (for stack backtraces). */
auto nameAttr = getAttr(state, state.sName, attrs, "in the attrset passed as argument to builtins.derivationStrict"); auto nameAttr = state.getAttr(state.sName, attrs, "in the attrset passed as argument to builtins.derivationStrict");
std::string drvName; std::string drvName;
try { try {
@ -1893,7 +1879,7 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, V
if (i != v2->attrs()->end()) if (i != v2->attrs()->end())
prefix = state.forceStringNoCtx(*i->value, pos, "while evaluating the `prefix` attribute of an element of the list passed to builtins.findFile"); prefix = state.forceStringNoCtx(*i->value, pos, "while evaluating the `prefix` attribute of an element of the list passed to builtins.findFile");
i = getAttr(state, state.sPath, v2->attrs(), "in an element of the __nixPath"); i = state.getAttr(state.sPath, v2->attrs(), "in an element of the __nixPath");
NixStringContext context; NixStringContext context;
auto path = state.coerceToString(pos, *i->value, context, auto path = state.coerceToString(pos, *i->value, context,
@ -2782,8 +2768,7 @@ void prim_getAttr(EvalState & state, const PosIdx pos, Value * * args, Value & v
{ {
auto attr = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.getAttr"); auto attr = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.getAttr");
state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.getAttr"); state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.getAttr");
auto i = getAttr( auto i = state.getAttr(
state,
state.symbols.create(attr), state.symbols.create(attr),
args[1]->attrs(), args[1]->attrs(),
"in the attribute set under consideration" "in the attribute set under consideration"
@ -2973,13 +2958,13 @@ static void prim_listToAttrs(EvalState & state, const PosIdx pos, Value * * args
for (auto v2 : args[0]->listItems()) { for (auto v2 : args[0]->listItems()) {
state.forceAttrs(*v2, pos, "while evaluating an element of the list passed to builtins.listToAttrs"); state.forceAttrs(*v2, pos, "while evaluating an element of the list passed to builtins.listToAttrs");
auto j = getAttr(state, state.sName, v2->attrs(), "in a {name=...; value=...;} pair"); auto j = state.getAttr(state.sName, v2->attrs(), "in a {name=...; value=...;} pair");
auto name = state.forceStringNoCtx(*j->value, j->pos, "while evaluating the `name` attribute of an element of the list passed to builtins.listToAttrs"); auto name = state.forceStringNoCtx(*j->value, j->pos, "while evaluating the `name` attribute of an element of the list passed to builtins.listToAttrs");
auto sym = state.symbols.create(name); auto sym = state.symbols.create(name);
if (seen.insert(sym).second) { if (seen.insert(sym).second) {
auto j2 = getAttr(state, state.sValue, v2->attrs(), "in a {name=...; value=...;} pair"); auto j2 = state.getAttr(state.sValue, v2->attrs(), "in a {name=...; value=...;} pair");
attrs.insert(sym, j2->value, j2->pos); attrs.insert(sym, j2->value, j2->pos);
} }
} }
@ -4224,7 +4209,7 @@ static void prim_convertHash(EvalState & state, const PosIdx pos, Value * * args
state.forceAttrs(*args[0], pos, "while evaluating the first argument passed to builtins.convertHash"); state.forceAttrs(*args[0], pos, "while evaluating the first argument passed to builtins.convertHash");
auto inputAttrs = args[0]->attrs(); auto inputAttrs = args[0]->attrs();
auto iteratorHash = getAttr(state, state.symbols.create("hash"), inputAttrs, "while locating the attribute 'hash'"); auto iteratorHash = state.getAttr(state.symbols.create("hash"), inputAttrs, "while locating the attribute 'hash'");
auto hash = state.forceStringNoCtx(*iteratorHash->value, pos, "while evaluating the attribute 'hash'"); auto hash = state.forceStringNoCtx(*iteratorHash->value, pos, "while evaluating the attribute 'hash'");
auto iteratorHashAlgo = inputAttrs->get(state.symbols.create("hashAlgo")); auto iteratorHashAlgo = inputAttrs->get(state.symbols.create("hashAlgo"));
@ -4232,7 +4217,7 @@ static void prim_convertHash(EvalState & state, const PosIdx pos, Value * * args
if (iteratorHashAlgo) if (iteratorHashAlgo)
ha = parseHashAlgo(state.forceStringNoCtx(*iteratorHashAlgo->value, pos, "while evaluating the attribute 'hashAlgo'")); ha = parseHashAlgo(state.forceStringNoCtx(*iteratorHashAlgo->value, pos, "while evaluating the attribute 'hashAlgo'"));
auto iteratorToHashFormat = getAttr(state, state.symbols.create("toHashFormat"), args[0]->attrs(), "while locating the attribute 'toHashFormat'"); auto iteratorToHashFormat = state.getAttr(state.symbols.create("toHashFormat"), args[0]->attrs(), "while locating the attribute 'toHashFormat'");
HashFormat hf = parseHashFormat(state.forceStringNoCtx(*iteratorToHashFormat->value, pos, "while evaluating the attribute 'toHashFormat'")); HashFormat hf = parseHashFormat(state.forceStringNoCtx(*iteratorToHashFormat->value, pos, "while evaluating the attribute 'toHashFormat'"));
v.mkString(Hash::parseAny(hash, ha).to_string(hf, hf == HashFormat::SRI)); v.mkString(Hash::parseAny(hash, ha).to_string(hf, hf == HashFormat::SRI));

View file

@ -89,3 +89,13 @@ expect_trace 'let f2 = (x: x); in f2 1 2' "
expect_trace '1 2' " expect_trace '1 2' "
«string»:1:1 1 «string»:1:1 1
" "
# Derivation
expect_trace 'builtins.derivationStrict { name = "somepackage"; }' "
«string»:1:1:primop derivationStrict:somepackage 1
"
# Derivation without name attr
expect_trace 'builtins.derivationStrict { }' "
«string»:1:1:primop derivationStrict 1
"