1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-08 19:46:02 +01:00

Mount inputs on storeFS to restore fetchToStore() caching

fetchToStore() caching was broken because it uses the fingerprint of
the accessor, but now that the accessor (typically storeFS) is a
composite (like MountedSourceAccessor or AllowListSourceAccessor),
there was no fingerprint anymore. So fetchToStore now uses the new
getFingerprint() method to get the specific fingerprint for the
subpath.
This commit is contained in:
Eelco Dolstra 2025-09-22 23:04:58 +02:00 committed by John Ericson
parent ec6d5c7de3
commit 1d130492d7
8 changed files with 69 additions and 50 deletions

View file

@ -227,24 +227,17 @@ EvalState::EvalState(
{CanonPath(store->storeDir), store->getFSAccessor(settings.pureEval)}, {CanonPath(store->storeDir), store->getFSAccessor(settings.pureEval)},
})) }))
, rootFS([&] { , rootFS([&] {
auto accessor = [&]() -> decltype(rootFS) { /* In pure eval mode, we provide a filesystem that only
/* In pure eval mode, we provide a filesystem that only contains the Nix store.
contains the Nix store. */
if (settings.pureEval)
return storeFS;
/* If we have a chroot store and pure eval is not enabled, Otherwise, use a union accessor to make the augmented store
use a union accessor to make the chroot store available available at its logical location while still having the
at its logical location while still having the underlying underlying directory available. This is necessary for
directory available. This is necessary for instance if instance if we're evaluating a file from the physical
we're evaluating a file from the physical /nix/store /nix/store while using a chroot store, and also for lazy
while using a chroot store. */ mounted fetchTree. */
auto realStoreDir = dirOf(store->toRealPath(StorePath::dummy)); auto accessor = settings.pureEval ? storeFS.cast<SourceAccessor>()
if (store->storeDir != realStoreDir) : makeUnionSourceAccessor({getFSSourceAccessor(), storeFS});
return makeUnionSourceAccessor({getFSSourceAccessor(), storeFS});
return getFSSourceAccessor();
}();
/* Apply access control if needed. */ /* Apply access control if needed. */
if (settings.restrictEval || settings.pureEval) if (settings.restrictEval || settings.pureEval)

View file

@ -42,6 +42,7 @@ class Store;
namespace fetchers { namespace fetchers {
struct Settings; struct Settings;
struct InputCache; struct InputCache;
struct Input;
} // namespace fetchers } // namespace fetchers
struct EvalSettings; struct EvalSettings;
class EvalState; class EvalState;
@ -514,6 +515,11 @@ public:
void checkURI(const std::string & uri); void checkURI(const std::string & uri);
/**
* Mount an input on the Nix store.
*/
StorePath mountInput(fetchers::Input & input, const fetchers::Input & originalInput, ref<SourceAccessor> accessor);
/** /**
* Parse a Nix expression from the specified file. * Parse a Nix expression from the specified file.
*/ */

View file

@ -1,5 +1,7 @@
#include "nix/store/store-api.hh" #include "nix/store/store-api.hh"
#include "nix/expr/eval.hh" #include "nix/expr/eval.hh"
#include "nix/util/mounted-source-accessor.hh"
#include "nix/fetchers/fetch-to-store.hh"
namespace nix { namespace nix {
@ -18,4 +20,27 @@ SourcePath EvalState::storePath(const StorePath & path)
return {rootFS, CanonPath{store->printStorePath(path)}}; return {rootFS, CanonPath{store->printStorePath(path)}};
} }
StorePath
EvalState::mountInput(fetchers::Input & input, const fetchers::Input & originalInput, ref<SourceAccessor> accessor)
{
auto storePath = fetchToStore(fetchSettings, *store, accessor, FetchMode::Copy, input.getName());
allowPath(storePath); // FIXME: should just whitelist the entire virtual store
storeFS->mount(CanonPath(store->printStorePath(storePath)), accessor);
auto narHash = store->queryPathInfo(storePath)->narHash;
input.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
if (originalInput.getNarHash() && narHash != *originalInput.getNarHash())
throw Error(
(unsigned int) 102,
"NAR hash mismatch in input '%s', expected '%s' but got '%s'",
originalInput.to_string(),
narHash.to_string(HashFormat::SRI, true),
originalInput.getNarHash()->to_string(HashFormat::SRI, true));
return storePath;
}
} // namespace nix } // namespace nix

View file

@ -10,6 +10,7 @@
#include "nix/util/url.hh" #include "nix/util/url.hh"
#include "nix/expr/value-to-json.hh" #include "nix/expr/value-to-json.hh"
#include "nix/fetchers/fetch-to-store.hh" #include "nix/fetchers/fetch-to-store.hh"
#include "nix/fetchers/input-cache.hh"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@ -218,11 +219,11 @@ static void fetchTree(
throw Error("input '%s' is not allowed to use the '__final' attribute", input.to_string()); throw Error("input '%s' is not allowed to use the '__final' attribute", input.to_string());
} }
auto [storePath, input2] = input.fetchToStore(state.store); auto cachedInput = state.inputCache->getAccessor(state.store, input, fetchers::UseRegistries::No);
state.allowPath(storePath); auto storePath = state.mountInput(cachedInput.lockedInput, input, cachedInput.accessor);
emitTreeAttrs(state, storePath, input2, v, params.emptyRevFallback, false); emitTreeAttrs(state, storePath, cachedInput.lockedInput, v, params.emptyRevFallback, false);
} }
static void prim_fetchTree(EvalState & state, const PosIdx pos, Value ** args, Value & v) static void prim_fetchTree(EvalState & state, const PosIdx pos, Value ** args, Value & v)

View file

@ -27,14 +27,19 @@ StorePath fetchToStore(
std::optional<fetchers::Cache::Key> cacheKey; std::optional<fetchers::Cache::Key> cacheKey;
if (!filter && path.accessor->fingerprint) { auto [subpath, fingerprint] = filter ? std::pair<CanonPath, std::optional<std::string>>{path.path, std::nullopt}
cacheKey = makeFetchToStoreCacheKey(std::string{name}, *path.accessor->fingerprint, method, path.path.abs()); : path.accessor->getFingerprint(path.path);
if (fingerprint) {
cacheKey = makeFetchToStoreCacheKey(std::string{name}, *fingerprint, method, subpath.abs());
if (auto res = settings.getCache()->lookupStorePath(*cacheKey, store)) { if (auto res = settings.getCache()->lookupStorePath(*cacheKey, store)) {
debug("store path cache hit for '%s'", path); debug("store path cache hit for '%s'", path);
return res->storePath; return res->storePath;
} }
} else } else {
// FIXME: could still provide in-memory caching keyed on `SourcePath`.
debug("source path '%s' is uncacheable", path); debug("source path '%s' is uncacheable", path);
}
Activity act( Activity act(
*logger, *logger,

View file

@ -356,8 +356,10 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(ref<Store> sto
auto [accessor, result] = scheme->getAccessor(store, *this); auto [accessor, result] = scheme->getAccessor(store, *this);
assert(!accessor->fingerprint); if (!accessor->fingerprint)
accessor->fingerprint = result.getFingerprint(store); accessor->fingerprint = result.getFingerprint(store);
else
result.cachedFingerprint = accessor->fingerprint;
return {accessor, std::move(result)}; return {accessor, std::move(result)};
} }

View file

@ -24,21 +24,6 @@ using namespace flake;
namespace flake { namespace flake {
static StorePath copyInputToStore(
EvalState & state, fetchers::Input & input, const fetchers::Input & originalInput, ref<SourceAccessor> accessor)
{
auto storePath = fetchToStore(*input.settings, *state.store, accessor, FetchMode::Copy, input.getName());
state.allowPath(storePath);
auto narHash = state.store->queryPathInfo(storePath)->narHash;
input.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
assert(!originalInput.getNarHash() || storePath == originalInput.computeStorePath(*state.store));
return storePath;
}
static void forceTrivialValue(EvalState & state, Value & value, const PosIdx pos) static void forceTrivialValue(EvalState & state, Value & value, const PosIdx pos)
{ {
if (value.isThunk() && value.isTrivial()) if (value.isThunk() && value.isTrivial())
@ -360,11 +345,14 @@ static Flake getFlake(
lockedRef = FlakeRef(std::move(cachedInput2.lockedInput), newLockedRef.subdir); lockedRef = FlakeRef(std::move(cachedInput2.lockedInput), newLockedRef.subdir);
} }
// Copy the tree to the store.
auto storePath = copyInputToStore(state, lockedRef.input, originalRef.input, cachedInput.accessor);
// Re-parse flake.nix from the store. // Re-parse flake.nix from the store.
return readFlake(state, originalRef, resolvedRef, lockedRef, state.storePath(storePath), lockRootAttrPath); return readFlake(
state,
originalRef,
resolvedRef,
lockedRef,
state.storePath(state.mountInput(lockedRef.input, originalRef.input, cachedInput.accessor)),
lockRootAttrPath);
} }
Flake getFlake(EvalState & state, const FlakeRef & originalRef, fetchers::UseRegistries useRegistries) Flake getFlake(EvalState & state, const FlakeRef & originalRef, fetchers::UseRegistries useRegistries)
@ -721,11 +709,10 @@ lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef,
auto lockedRef = FlakeRef(std::move(cachedInput.lockedInput), input.ref->subdir); auto lockedRef = FlakeRef(std::move(cachedInput.lockedInput), input.ref->subdir);
// FIXME: allow input to be lazy. return {
auto storePath = copyInputToStore( state.storePath(
state, lockedRef.input, input.ref->input, cachedInput.accessor); state.mountInput(lockedRef.input, input.ref->input, cachedInput.accessor)),
lockedRef};
return {state.storePath(storePath), lockedRef};
} }
}(); }();

View file

@ -10,4 +10,4 @@ error:
… while calling the 'hashFile' builtin … while calling the 'hashFile' builtin
error: opening file '/pwd/lang/this-file-is-definitely-not-there-7392097': No such file or directory error: path '/pwd/lang/this-file-is-definitely-not-there-7392097' does not exist