1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-06-26 20:01:15 +02:00

Lazy trees v2

This adds a setting 'lazy-trees' that causes flake inputs to be
"mounted" as virtual filesystems on top of /nix/store as random
"virtual" store paths. Only when the store path is actually used as a
dependency of a store derivation do we materialize ("devirtualize")
the input by copying it to its content-addressed location in the
store.

String contexts determine when devirtualization happens. One wrinkle
is that there are cases where we had store paths without proper
contexts, in particular when the user does `toString <path>` (where
<path> is a source tree in the Nix store) and passes the result to a
derivation. This usage was always broken, since it can result in
derivations that lack correct references. But to ensure that we don't
change evaluation results, we introduce a new type of context that
results in devirtualization but not in store references. We also now
print a warning about this.
This commit is contained in:
Eelco Dolstra 2025-05-19 16:45:53 +02:00
parent 8c10104e9e
commit c98026e982
41 changed files with 539 additions and 133 deletions

View file

@ -15,6 +15,7 @@
#include "nix/expr/print.hh"
#include "nix/fetchers/filtering-source-accessor.hh"
#include "nix/util/memory-source-accessor.hh"
#include "nix/util/mounted-source-accessor.hh"
#include "nix/expr/gc-small-vector.hh"
#include "nix/util/url.hh"
#include "nix/fetchers/fetch-to-store.hh"
@ -269,7 +270,7 @@ EvalState::EvalState(
exception, and make union source accessor
catch it, so we don't need to do this hack.
*/
{CanonPath(store->storeDir), store->getFSAccessor(settings.pureEval)},
{CanonPath(store->storeDir), makeFSSourceAccessor(dirOf(store->toRealPath(StorePath::dummy)))}
}))
, rootFS(
({
@ -284,12 +285,9 @@ EvalState::EvalState(
/nix/store while using a chroot store. */
auto accessor = getFSSourceAccessor();
auto realStoreDir = dirOf(store->toRealPath(StorePath::dummy));
if (settings.pureEval || store->storeDir != realStoreDir) {
accessor = settings.pureEval
? storeFS
: makeUnionSourceAccessor({accessor, storeFS});
}
accessor = settings.pureEval
? storeFS.cast<SourceAccessor>()
: makeUnionSourceAccessor({accessor, storeFS});
/* Apply access control if needed. */
if (settings.restrictEval || settings.pureEval)
@ -972,7 +970,16 @@ void EvalState::mkPos(Value & v, PosIdx p)
auto origin = positions.originOf(p);
if (auto path = std::get_if<SourcePath>(&origin)) {
auto attrs = buildBindings(3);
attrs.alloc(sFile).mkString(path->path.abs());
if (path->accessor == rootFS && store->isInStore(path->path.abs()))
// FIXME: only do this for virtual store paths?
attrs.alloc(sFile).mkString(path->path.abs(),
{
NixStringContextElem::Path{
.storePath = store->toStorePath(path->path.abs()).first
}
});
else
attrs.alloc(sFile).mkString(path->path.abs());
makePositionThunks(*this, p, attrs.alloc(sLine), attrs.alloc(sColumn));
v.mkAttrs(attrs);
} else
@ -2098,7 +2105,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
else if (firstType == nFloat)
v.mkFloat(nf);
else if (firstType == nPath) {
if (!context.empty())
if (hasContext(context))
state.error<EvalError>("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow();
v.mkPath(state.rootPath(CanonPath(str())));
} else
@ -2297,7 +2304,10 @@ std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos, std::s
{
auto s = forceString(v, pos, errorCtx);
if (v.context()) {
error<EvalError>("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string_view(), v.context()[0]).withTrace(pos, errorCtx).debugThrow();
NixStringContext context;
copyContext(v, context);
if (hasContext(context))
error<EvalError>("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string_view(), v.context()[0]).withTrace(pos, errorCtx).debugThrow();
}
return s;
}
@ -2346,6 +2356,9 @@ BackedStringView EvalState::coerceToString(
}
if (v.type() == nPath) {
// FIXME: instead of copying the path to the store, we could
// return a virtual store path that lazily copies the path to
// the store in devirtualize().
return
!canonicalizePath && !copyToStore
? // FIXME: hack to preserve path literals that end in a
@ -2353,7 +2366,16 @@ BackedStringView EvalState::coerceToString(
v.payload.path.path
: copyToStore
? store->printStorePath(copyPathToStore(context, v.path()))
: std::string(v.path().path.abs());
: ({
auto path = v.path();
if (path.accessor == rootFS && store->isInStore(path.path.abs())) {
context.insert(
NixStringContextElem::Path{
.storePath = store->toStorePath(path.path.abs()).first
});
}
std::string(path.path.abs());
});
}
if (v.type() == nAttrs) {
@ -2436,7 +2458,7 @@ StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePat
*store,
path.resolveSymlinks(SymlinkResolution::Ancestors),
settings.readOnlyMode ? FetchMode::DryRun : FetchMode::Copy,
path.baseName(),
computeBaseName(path),
ContentAddressMethod::Raw::NixArchive,
nullptr,
repair);
@ -2491,7 +2513,7 @@ StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, NixStringCon
auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
if (auto storePath = store->maybeParseStorePath(path))
return *storePath;
error<EvalError>("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow();
error<EvalError>("cannot coerce '%s' to a store path because it is not a subpath of the Nix store", path).withTrace(pos, errorCtx).debugThrow();
}
@ -2517,6 +2539,11 @@ std::pair<SingleDerivedPath, std::string_view> EvalState::coerceToSingleDerivedP
[&](NixStringContextElem::Built && b) -> SingleDerivedPath {
return std::move(b);
},
[&](NixStringContextElem::Path && p) -> SingleDerivedPath {
error<EvalError>(
"string '%s' has no context",
s).withTrace(pos, errorCtx).debugThrow();
},
}, ((NixStringContextElem &&) *context.begin()).raw);
return {
std::move(derivedPath),
@ -3100,6 +3127,11 @@ SourcePath EvalState::findFile(const LookupPath & lookupPath, const std::string_
auto res = (r / CanonPath(suffix)).resolveSymlinks();
if (res.pathExists()) return res;
// Backward compatibility hack: throw an exception if access
// to this path is not allowed.
if (auto accessor = res.accessor.dynamic_pointer_cast<FilteringSourceAccessor>())
accessor->checkAccess(res.path);
}
if (hasPrefix(path, "nix/"))
@ -3170,6 +3202,11 @@ std::optional<SourcePath> EvalState::resolveLookupPathPath(const LookupPath::Pat
if (path.resolveSymlinks().pathExists())
return finish(std::move(path));
else {
// Backward compatibility hack: throw an exception if access
// to this path is not allowed.
if (auto accessor = path.accessor.dynamic_pointer_cast<FilteringSourceAccessor>())
accessor->checkAccess(path.path);
logWarning({
.msg = HintFmt("Nix search path entry '%1%' does not exist, ignoring", value)
});