diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index c0842dbbd79..e117ce73fc6 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -14,6 +14,7 @@ #include "profiles.hh" #include "print.hh" #include "filtering-source-accessor.hh" +#include "forwarding-source-accessor.hh" #include "memory-source-accessor.hh" #include "gc-small-vector.hh" #include "url.hh" @@ -180,6 +181,34 @@ static Symbol getName(const AttrName & name, EvalState & state, Env & env) } } +struct PathDisplaySourceAccessor : ForwardingSourceAccessor +{ + ref storePathAccessors; + + PathDisplaySourceAccessor( + ref next, + ref storePathAccessors) + : ForwardingSourceAccessor(next) + , storePathAccessors(storePathAccessors) + { + } + + std::string showPath(const CanonPath & path) override + { + /* Find the accessor that produced `path`, if any, and use it + to render a more informative path + (e.g. `«github:foo/bar»/flake.nix` rather than + `/nix/store/hash.../flake.nix`). */ + auto ub = storePathAccessors->upper_bound(path); + if (ub != storePathAccessors->begin()) + ub--; + if (ub != storePathAccessors->end() && path.isWithin(ub->first)) + return ub->second->showPath(path.removePrefix(ub->first)); + else + return next->showPath(path); + } +}; + static constexpr size_t BASE_ENV_SIZE = 128; EvalState::EvalState( @@ -245,6 +274,7 @@ EvalState::EvalState( } , repair(NoRepair) , emptyBindings(0) + , storePathAccessors(make_ref()) , rootFS( ({ /* In pure eval mode, we provide a filesystem that only @@ -270,6 +300,8 @@ EvalState::EvalState( : makeUnionSourceAccessor({accessor, storeFS}); } + accessor = make_ref(accessor, storePathAccessors); + /* Apply access control if needed. */ if (settings.restrictEval || settings.pureEval) accessor = AllowListSourceAccessor::create(accessor, {}, diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index aca2e8bfd51..d6a5d4b3e6f 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -245,6 +245,16 @@ public: /** `"unknown"` */ Value vStringUnknown; + using StorePathAccessors = std::map>; + + /** + * A map back to the original `SourceAccessor`s used to produce + * store paths. We keep track of this to produce error messages + * that refer to the original flakerefs. + * FIXME: use Sync. + */ + ref storePathAccessors; + /** * The accessor for the root filesystem. */ diff --git a/src/libexpr/primops/fetchMercurial.cc b/src/libexpr/primops/fetchMercurial.cc index 64e3abf2db4..96800d9efa9 100644 --- a/src/libexpr/primops/fetchMercurial.cc +++ b/src/libexpr/primops/fetchMercurial.cc @@ -64,7 +64,7 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a if (rev) attrs.insert_or_assign("rev", rev->gitRev()); auto input = fetchers::Input::fromAttrs(state.fetchSettings, std::move(attrs)); - auto [storePath, input2] = input.fetchToStore(state.store); + auto [storePath, accessor, input2] = input.fetchToStore(state.store); auto attrs2 = state.buildBindings(8); state.mkStorePathString(storePath, attrs2.alloc(state.sOutPath)); diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc index bd013eab294..5219400f294 100644 --- a/src/libexpr/primops/fetchTree.cc +++ b/src/libexpr/primops/fetchTree.cc @@ -204,10 +204,12 @@ static void fetchTree( throw Error("input '%s' is not allowed to use the '__final' attribute", input.to_string()); } - auto [storePath, input2] = input.fetchToStore(state.store); + auto [storePath, accessor, input2] = input.fetchToStore(state.store); state.allowPath(storePath); + state.storePathAccessors->insert_or_assign(CanonPath(state.store->printStorePath(storePath)), accessor); + emitTreeAttrs(state, storePath, input2, v, params.emptyRevFallback, false); } diff --git a/src/libfetchers/fetchers.cc b/src/libfetchers/fetchers.cc index abf021554e7..67728501e6e 100644 --- a/src/libfetchers/fetchers.cc +++ b/src/libfetchers/fetchers.cc @@ -187,34 +187,30 @@ bool Input::contains(const Input & other) const } // FIXME: remove -std::pair Input::fetchToStore(ref store) const +std::tuple, Input> Input::fetchToStore(ref store) const { if (!scheme) throw Error("cannot fetch unsupported input '%s'", attrsToJSON(toAttrs())); - auto [storePath, input] = [&]() -> std::pair { - try { - auto [accessor, result] = getAccessorUnchecked(store); - - auto storePath = nix::fetchToStore(*store, SourcePath(accessor), FetchMode::Copy, result.getName()); + try { + auto [accessor, result] = getAccessorUnchecked(store); - auto narHash = store->queryPathInfo(storePath)->narHash; - result.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true)); + auto storePath = nix::fetchToStore(*store, SourcePath(accessor), FetchMode::Copy, result.getName()); - result.attrs.insert_or_assign("__final", Explicit(true)); + auto narHash = store->queryPathInfo(storePath)->narHash; + result.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true)); - assert(result.isFinal()); + result.attrs.insert_or_assign("__final", Explicit(true)); - checkLocks(*this, result); + assert(result.isFinal()); - return {storePath, result}; - } catch (Error & e) { - e.addTrace({}, "while fetching the input '%s'", to_string()); - throw; - } - }(); + checkLocks(*this, result); - return {std::move(storePath), input}; + return {std::move(storePath), accessor, result}; + } catch (Error & e) { + e.addTrace({}, "while fetching the input '%s'", to_string()); + throw; + } } void Input::checkLocks(Input specified, Input & result) @@ -323,6 +319,8 @@ std::pair, Input> Input::getAccessorUnchecked(ref sto accessor->fingerprint = getFingerprint(store); + accessor->setPathDisplay("«" + to_string() + "»"); + return {accessor, *this}; } catch (Error & e) { debug("substitution of input '%s' failed: %s", to_string(), e.what()); diff --git a/src/libfetchers/fetchers.hh b/src/libfetchers/fetchers.hh index 37de1f507d9..e2ce4028449 100644 --- a/src/libfetchers/fetchers.hh +++ b/src/libfetchers/fetchers.hh @@ -121,7 +121,7 @@ public: * Fetch the entire input into the Nix store, returning the * location in the Nix store and the locked input. */ - std::pair fetchToStore(ref store) const; + std::tuple, Input> fetchToStore(ref store) const; /** * Check the locking attributes in `result` against diff --git a/src/libflake/flake/flake.cc b/src/libflake/flake/flake.cc index 4a72f0399f9..ea2c456202c 100644 --- a/src/libflake/flake/flake.cc +++ b/src/libflake/flake/flake.cc @@ -92,6 +92,8 @@ static StorePath copyInputToStore( state.allowPath(storePath); + state.storePathAccessors->insert_or_assign(CanonPath(state.store->printStorePath(storePath)), accessor); + auto narHash = state.store->queryPathInfo(storePath)->narHash; input.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true)); diff --git a/src/libutil/forwarding-source-accessor.hh b/src/libutil/forwarding-source-accessor.hh new file mode 100644 index 00000000000..bdba2addcb0 --- /dev/null +++ b/src/libutil/forwarding-source-accessor.hh @@ -0,0 +1,57 @@ +#pragma once + +#include "source-accessor.hh" + +namespace nix { + +/** + * A source accessor that just forwards every operation to another + * accessor. This is not useful in itself but can be used as a + * superclass for accessors that do change some operations. + */ +struct ForwardingSourceAccessor : SourceAccessor +{ + ref next; + + ForwardingSourceAccessor(ref next) + : next(next) + { + } + + std::string readFile(const CanonPath & path) override + { + return next->readFile(path); + } + + void readFile(const CanonPath & path, Sink & sink, std::function sizeCallback) override + { + next->readFile(path, sink, sizeCallback); + } + + std::optional maybeLstat(const CanonPath & path) override + { + return next->maybeLstat(path); + } + + DirEntries readDirectory(const CanonPath & path) override + { + return next->readDirectory(path); + } + + std::string readLink(const CanonPath & path) override + { + return next->readLink(path); + } + + std::string showPath(const CanonPath & path) override + { + return next->showPath(path); + } + + std::optional getPhysicalPath(const CanonPath & path) override + { + return next->getPhysicalPath(path); + } +}; + +} diff --git a/src/libutil/meson.build b/src/libutil/meson.build index df459f0e57f..b9fbaabc7ee 100644 --- a/src/libutil/meson.build +++ b/src/libutil/meson.build @@ -213,6 +213,7 @@ headers = [config_h] + files( 'file-system.hh', 'finally.hh', 'fmt.hh', + 'forwarding-source-accessor.hh', 'fs-sink.hh', 'git.hh', 'hash.hh', diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 4615dabaade..be09fbf073c 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -1093,9 +1093,9 @@ struct CmdFlakeArchive : FlakeCommand, MixJSON, MixDryRun auto storePath = dryRun ? (*inputNode)->lockedRef.input.computeStorePath(*store) - : (*inputNode)->lockedRef.input.fetchToStore(store).first; + : std::get<0>((*inputNode)->lockedRef.input.fetchToStore(store)); if (json) { - auto& jsonObj3 = jsonObj2[inputName]; + auto & jsonObj3 = jsonObj2[inputName]; jsonObj3["path"] = store->printStorePath(storePath); sources.insert(std::move(storePath)); jsonObj3["inputs"] = traverse(**inputNode);