Compare commits

...

10 Commits

Author SHA1 Message Date
Robert Hensing
359d124bbf fixup: Re-enable import .drv code 2024-08-26 12:46:01 +02:00
Robert Hensing
68a5cfd30e tests: nix:derivation-internal.nix renders with a scheme now 2024-08-26 10:37:42 +02:00
Robert Hensing
1008847630 fix findFile assertion failure
A string is only allowed to create one path component; containing
no slashes.
2024-08-24 21:35:01 +02:00
Robert Hensing
52c0ef24c5 WIP fix baseNameOf (needs test maybe)
https://github.com/NixOS/nix/pull/10252#issuecomment-2275035429
2024-08-24 00:54:31 -04:00
Robert Hensing
f13dc79904 Fix nix flake init eval for path value 2024-08-24 00:53:06 -04:00
Robert Hensing
f3d2a16eff Fix evalState::rootFS paths' to_string()
This showPath is getting a little too ad hoc, but it works for now.
2024-08-24 00:51:46 -04:00
Robert Hensing
5def18e53b SourceAccessor: insert colon after prefix
This allows clever editors/IDEs to discern the path more easily
for Ctrl+Click navigate to functionality, e.g. when building
.?ref=HEAD
2024-08-24 00:51:10 -04:00
Robert Hensing
ee6a764988 Make path values lazy
This fixes the double copy problem and improves performance
for expressions that don't force the whole source to be added to the
store.

Rules for fast expressions:

- Use path literals where possible
   - import ./foo.nix
- Use + operator with slash in string
   - src = fetchTree foo + "/src";
- Use source filtering, lib.fileset

- AVOID toString
- If possible, AVOID interpolations ("${./.}")
- If possible, move slashes into the interpolation to add less to the store
   - "${./src}/foo" -> "${./src/foo}"

toString may be improved later as part of lazy-trees, so these
recommendations are a snapshot. Path values are quite nice though.
2024-08-23 23:53:55 -04:00
Tom Bereknyei
b79c918e09 fixup: remove FlakeCache 2024-08-23 23:53:52 -04:00
Tom Bereknyei
50b00b0194 fetchTree: Return a path instead of a store path
Co-authored-by: Eelco Dolstra <edolstra@gmail.com>
Co-authored-by: Robert Hensing <robert@roberthensing.nl>
2024-08-23 23:52:21 -04:00
21 changed files with 227 additions and 163 deletions

View File

@@ -32,8 +32,7 @@ EvalSettings evalSettings {
// FIXME `parseFlakeRef` should take a `std::string_view`.
auto flakeRef = parseFlakeRef(fetchSettings, std::string { rest }, {}, true, false);
debug("fetching flake search path element '%s''", rest);
auto storePath = flakeRef.resolve(store).fetchTree(store).first;
return store->toRealPath(storePath);
return flakeRef.resolve(store).lazyFetch(store).first;
},
},
},
@@ -174,15 +173,15 @@ SourcePath lookupFileArg(EvalState & state, std::string_view s, const Path * bas
state.store,
state.fetchSettings,
EvalSettings::resolvePseudoUrl(s));
auto storePath = fetchToStore(*state.store, SourcePath(accessor), FetchMode::Copy);
return state.rootPath(CanonPath(state.store->toRealPath(storePath)));
state.registerAccessor(accessor);
return SourcePath(accessor);
}
else if (hasPrefix(s, "flake:")) {
experimentalFeatureSettings.require(Xp::Flakes);
auto flakeRef = parseFlakeRef(fetchSettings, std::string(s.substr(6)), {}, true, false);
auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first;
return state.rootPath(CanonPath(state.store->toRealPath(storePath)));
auto accessor = flakeRef.resolve(state.store).lazyFetch(state.store).first;
return SourcePath(accessor);
}
else if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') {

View File

@@ -7,6 +7,7 @@
namespace nix {
class Store;
struct SourcePath;
struct EvalSettings : Config
{
@@ -22,7 +23,7 @@ struct EvalSettings : Config
* @todo Return (`std::optional` of) `SourceAccssor` or something
* more structured instead of mere `std::string`?
*/
using LookupPathHook = std::optional<std::string>(ref<Store> store, std::string_view);
using LookupPathHook = std::optional<SourcePath>(ref<Store> store, std::string_view);
/**
* Map from "scheme" to a `LookupPathHook`.

View File

@@ -315,6 +315,7 @@ EvalState::EvalState(
{
corepkgsFS->setPathDisplay("<nix", ">");
internalFS->setPathDisplay("«nix-internal»", "");
rootFS->setPathDisplay("/", "");
countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0";
@@ -859,6 +860,11 @@ void Value::mkPath(const SourcePath & path)
mkPath(&*path.accessor, makeImmutableString(path.path.abs()));
}
void EvalState::registerAccessor(const ref<SourceAccessor> accessor)
{
sourceAccessors.push_back(accessor);
}
inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
{
@@ -1973,10 +1979,22 @@ void EvalState::concatLists(Value & v, size_t nrLists, Value * const * lists, co
v.mkList(list);
}
// FIXME limit recursion
Value * resolveOutPath(EvalState & state, Value * v, const PosIdx pos)
{
state.forceValue(*v, pos);
if (v->type() != nAttrs)
return v;
auto found = v->attrs()->find(state.sOutPath);
if (found != v->attrs()->end())
return resolveOutPath(state, found->value, pos);
return v;
}
void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
{
NixStringContext context;
std::shared_ptr<SourceAccessor> accessor;
std::vector<BackedStringView> s;
size_t sSize = 0;
NixInt n{0};
@@ -2010,8 +2028,9 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
Value * vTmpP = values.data();
for (auto & [i_pos, i] : *es) {
Value & vTmp = *vTmpP++;
i->eval(state, env, vTmp);
Value & vTmp0 = *vTmpP++;
i->eval(state, env, vTmp0);
Value & vTmp = *resolveOutPath(state, &vTmp0, i_pos);
/* If the first element is a path, then the result will also
be a path, we don't copy anything (yet - that's done later,
@@ -2019,6 +2038,9 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
and none of the strings are allowed to have contexts. */
if (first) {
firstType = vTmp.type();
if (firstType == nPath) {
accessor = vTmp.path().accessor;
}
}
if (firstType == nInt) {
@@ -2065,7 +2087,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
else if (firstType == nPath) {
if (!context.empty())
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(canonPath(str()))));
v.mkPath({ref(accessor), CanonPath(str())});
} else
v.mkStringMove(c_str(), context);
}
@@ -2315,6 +2337,8 @@ BackedStringView EvalState::coerceToString(
v.payload.path.path
: copyToStore
? store->printStorePath(copyPathToStore(context, v.path()))
: v.path().accessor->toStringReturnsStorePath()
? store->printStorePath(copyPathToStore(context, SourcePath(v.path().accessor, CanonPath::root))) + v.path().path.absOrEmpty()
: std::string(v.path().path.abs());
}
@@ -2427,10 +2451,14 @@ SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext
if (v.type() == nPath)
return v.path();
/* Similarly, handle __toString where the result may be a path
/* Similarly, handle outPath and __toString where the result may be a path
value. */
if (v.type() == nAttrs) {
auto i = v.attrs()->find(sToString);
auto i = v.attrs()->find(sOutPath);
if (i != v.attrs()->end()) {
return coerceToPath(pos, *i->value, context, errorCtx);
}
i = v.attrs()->find(sToString);
if (i != v.attrs()->end()) {
Value v1;
callFunction(*i->value, v, v1, pos);
@@ -3053,12 +3081,14 @@ SourcePath EvalState::findFile(const LookupPath & lookupPath, const std::string_
if (!suffixOpt) continue;
auto suffix = *suffixOpt;
auto rOpt = resolveLookupPathPath(i.path);
auto rOpt = resolveLookupPathPath(
i.path,
true);
if (!rOpt) continue;
auto r = *rOpt;
Path res = suffix == "" ? r : concatStrings(r, "/", suffix);
if (pathExists(res)) return rootPath(CanonPath(canonPath(res)));
auto res = r / CanonPath(suffix);
if (res.pathExists()) return res;
}
if (hasPrefix(path, "nix/"))
@@ -3073,26 +3103,30 @@ SourcePath EvalState::findFile(const LookupPath & lookupPath, const std::string_
}
std::optional<std::string> EvalState::resolveLookupPathPath(const LookupPath::Path & value0, bool initAccessControl)
std::optional<SourcePath> EvalState::resolveLookupPathPath(const LookupPath::Path & value0, bool initAccessControl)
{
auto & value = value0.s;
auto i = lookupPathResolved.find(value);
if (i != lookupPathResolved.end()) return i->second;
auto finish = [&](std::string res) {
auto finish = [&](SourcePath res) {
debug("resolved search path element '%s' to '%s'", value, res);
lookupPathResolved.emplace(value, res);
return res;
};
std::optional<SourcePath> res;
if (EvalSettings::isPseudoUrl(value)) {
try {
auto accessor = fetchers::downloadTarball(
store,
fetchSettings,
EvalSettings::resolvePseudoUrl(value));
// Traditional search path lookups use the absolute path space for
// historical consistency.
auto storePath = fetchToStore(*store, SourcePath(accessor), FetchMode::Copy);
return finish(store->toRealPath(storePath));
res.emplace(rootPath(CanonPath(store->toRealPath(storePath))));
} catch (Error & e) {
logWarning({
.msg = HintFmt("Nix search path entry '%1%' cannot be downloaded, ignoring", value)
@@ -3111,22 +3145,22 @@ std::optional<std::string> EvalState::resolveLookupPathPath(const LookupPath::Pa
}
{
auto path = absPath(value);
auto path = rootPath(value);
/* Allow access to paths in the search path. */
if (initAccessControl) {
allowPath(path);
if (store->isInStore(path)) {
allowPath(path.path.abs());
if (store->isInStore(path.path.abs())) {
try {
StorePathSet closure;
store->computeFSClosure(store->toStorePath(path).first, closure);
store->computeFSClosure(store->toStorePath(path.path.abs()).first, closure);
for (auto & p : closure)
allowPath(p);
} catch (InvalidPath &) { }
}
}
if (pathExists(path))
if (path.pathExists())
return finish(std::move(path));
else {
logWarning({

View File

@@ -249,6 +249,9 @@ public:
const SourcePath callFlakeInternal;
/* A collection of InputAccessors, just to keep them alive. */
std::list<ref<SourceAccessor>> sourceAccessors;
/**
* Store used to materialise .drv files.
*/
@@ -339,7 +342,7 @@ private:
LookupPath lookupPath;
std::map<std::string, std::optional<std::string>> lookupPathResolved;
std::map<std::string, std::optional<SourcePath>> lookupPathResolved;
/**
* Cache used by prim_match().
@@ -381,6 +384,8 @@ public:
*/
SourcePath rootPath(PathView path);
void registerAccessor(ref<SourceAccessor> accessor);
/**
* Allow access to a path.
*/
@@ -446,7 +451,7 @@ public:
*
* If it is not found, return `std::nullopt`
*/
std::optional<std::string> resolveLookupPathPath(
std::optional<SourcePath> resolveLookupPathPath(
const LookupPath::Path & elem,
bool initAccessControl = false);

View File

@@ -45,7 +45,7 @@ void ExprString::show(const SymbolTable & symbols, std::ostream & str) const
void ExprPath::show(const SymbolTable & symbols, std::ostream & str) const
{
str << s;
str << path;
}
void ExprVar::show(const SymbolTable & symbols, std::ostream & str) const

View File

@@ -131,12 +131,15 @@ struct ExprString : Expr
struct ExprPath : Expr
{
ref<SourceAccessor> accessor;
std::string s;
const SourcePath path;
Value v;
ExprPath(ref<SourceAccessor> accessor, std::string s) : accessor(accessor), s(std::move(s))
ExprPath(SourcePath && path)
: path(path)
{
v.mkPath(&*accessor, this->s.c_str());
v.mkPath(
&*path.accessor,
// TODO: GC_STRDUP
strdup(path.path.abs().c_str()));
}
Value * maybeThunk(EvalState & state, Env & env) override;
COMMON_METHODS

View File

@@ -136,6 +136,10 @@ static Expr * makeCall(PosIdx pos, Expr * fn, Expr * arg) {
std::vector<std::pair<nix::AttrName, nix::PosIdx>> * inheritAttrs;
std::vector<std::pair<nix::PosIdx, nix::Expr *>> * string_parts;
std::vector<std::pair<nix::PosIdx, std::variant<nix::Expr *, nix::StringToken>>> * ind_string_parts;
struct {
nix::Expr * e;
bool appendSlash;
} pathStart;
}
%type <e> start expr expr_function expr_if expr_op
@@ -149,7 +153,8 @@ static Expr * makeCall(PosIdx pos, Expr * fn, Expr * arg) {
%type <inheritAttrs> attrs
%type <string_parts> string_parts_interpolated
%type <ind_string_parts> ind_string_parts
%type <e> path_start string_parts string_attr
%type <pathStart> path_start
%type <e> string_parts string_attr
%type <id> attr
%token <id> ID
%token <str> STR IND_STR
@@ -295,9 +300,11 @@ expr_simple
$$ = state->stripIndentation(CUR_POS, std::move(*$2));
delete $2;
}
| path_start PATH_END
| path_start PATH_END { $$ = $1.e; }
| path_start string_parts_interpolated PATH_END {
$2->insert($2->begin(), {state->at(@1), $1});
if ($1.appendSlash)
$2->insert($2->begin(), {noPos, new ExprString("/")});
$2->insert($2->begin(), {state->at(@1), $1.e});
$$ = new ExprConcatStrings(CUR_POS, false, $2);
}
| SPATH {
@@ -350,11 +357,17 @@ string_parts_interpolated
path_start
: PATH {
Path path(absPath({$1.p, $1.l}, state->basePath.path.abs()));
/* add back in the trailing '/' to the first segment */
if ($1.p[$1.l-1] == '/' && $1.l > 1)
path += "/";
$$ = new ExprPath(ref<SourceAccessor>(state->rootFS), std::move(path));
std::string_view path({$1.p, $1.l});
$$ = {
.e = new ExprPath(
/* Absolute paths are always interpreted relative to the
root filesystem accessor, rather than the accessor of the
current Nix expression. */
hasPrefix(path, "/")
? SourcePath{state->rootFS, CanonPath(path)}
: SourcePath{state->basePath.accessor, CanonPath(path, state->basePath.path)}),
.appendSlash = hasSuffix(path, "/")
};
}
| HPATH {
if (state->settings.pureEval) {
@@ -363,8 +376,8 @@ path_start
std::string_view($1.p, $1.l)
);
}
Path path(getHome() + std::string($1.p + 1, $1.l - 1));
$$ = new ExprPath(ref<SourceAccessor>(state->rootFS), std::move(path));
CanonPath path(getHome() + std::string($1.p + 1, $1.l - 1));
$$ = {.e = new ExprPath(SourcePath{state->rootFS, std::move(path)}), .appendSlash = true};
}
;

View File

@@ -178,7 +178,6 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
auto path = realisePath(state, pos, vPath, std::nullopt);
auto path2 = path.path.abs();
// FIXME
auto isValidDerivationInStore = [&]() -> std::optional<StorePath> {
if (!state.store->isStorePath(path2))
return std::nullopt;
@@ -216,9 +215,7 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
state.forceFunction(**state.vImportedDrvToDerivation, pos, "while evaluating imported-drv-to-derivation.nix.gen.hh");
v.mkApp(*state.vImportedDrvToDerivation, w);
state.forceAttrs(v, pos, "while calling imported-drv-to-derivation.nix.gen.hh");
}
else {
} else {
if (!vScope)
state.evalFile(path, v);
else {
@@ -1700,7 +1697,10 @@ static std::string_view legacyBaseNameOf(std::string_view path)
static void prim_baseNameOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
NixStringContext context;
v.mkString(legacyBaseNameOf(*state.coerceToString(pos, *args[0], context,
if (v.type() == nPath)
v.mkString(baseNameOf(v.path().path.abs()), context);
else
v.mkString(legacyBaseNameOf(*state.coerceToString(pos, *args[0], context,
"while evaluating the first argument passed to builtins.baseNameOf",
false, false)), context);
}

View File

@@ -19,21 +19,20 @@ namespace nix {
void emitTreeAttrs(
EvalState & state,
const StorePath & storePath,
const fetchers::Input & input,
Value & v,
std::function<void(Value &)> setOutPath,
bool emptyRevFallback,
bool forceDirty)
{
auto attrs = state.buildBindings(100);
state.mkStorePathString(storePath, attrs.alloc(state.sOutPath));
setOutPath(attrs.alloc(state.sOutPath));
// FIXME: support arbitrary input attributes.
auto narHash = input.getNarHash();
assert(narHash);
attrs.alloc("narHash").mkString(narHash->to_string(HashFormat::SRI, true));
if (auto narHash = input.getNarHash())
attrs.alloc("narHash").mkString(narHash->to_string(HashFormat::SRI, true));
if (input.getType() == "git")
attrs.alloc("submodules").mkBool(
@@ -72,10 +71,28 @@ void emitTreeAttrs(
v.mkAttrs(attrs);
}
void emitTreeAttrs(
EvalState & state,
const SourcePath & storePath,
const fetchers::Input & input,
Value & v,
bool emptyRevFallback,
bool forceDirty)
{
emitTreeAttrs(state, input, v,
[&](Value & vOutPath) {
state.registerAccessor(storePath.accessor);
vOutPath.mkPath(storePath);
},
emptyRevFallback,
forceDirty);
}
struct FetchTreeParams {
bool emptyRevFallback = false;
bool allowNameArgument = false;
bool isFetchGit = false;
bool returnPath = true; // whether to return a SourcePath instead of a StorePath
};
static void fetchTree(
@@ -112,7 +129,9 @@ static void fetchTree(
for (auto & attr : *args[0]->attrs()) {
if (attr.name == state.sType) continue;
state.forceValue(*attr.value, attr.pos);
if (attr.value->type() == nPath || attr.value->type() == nString) {
auto s = state.coerceToString(attr.pos, *attr.value, context, "", false, false).toOwned();
attrs.emplace(state.symbols[attr.name],
@@ -193,11 +212,31 @@ static void fetchTree(
state.checkURI(input.toURLString());
auto [storePath, input2] = input.fetchToStore(state.store);
if (params.returnPath) {
// Clang16+: change to `auto [accessor, input2] =`
auto pair = input.getAccessor(state.store);
auto & accessor = pair.first;
auto & input2 = pair.second;
state.allowPath(storePath);
emitTreeAttrs(state, input2, v,
[&](Value & vOutPath) {
state.registerAccessor(accessor);
vOutPath.mkPath(SourcePath { accessor, CanonPath::root });
},
params.emptyRevFallback, false);
} else {
auto pair = input.fetchToStore(state.store);
auto & storePath = pair.first;
auto & input2 = pair.second;
emitTreeAttrs(state, storePath, input2, v, params.emptyRevFallback, false);
state.allowPath(storePath);
emitTreeAttrs(state, input2, v,
[&](Value & vOutPath) {
state.mkStorePathString(storePath, vOutPath);
},
params.emptyRevFallback, false);
}
}
static void prim_fetchTree(EvalState & state, const PosIdx pos, Value * * args, Value & v)
@@ -614,7 +653,8 @@ static void prim_fetchGit(EvalState & state, const PosIdx pos, Value * * args, V
FetchTreeParams {
.emptyRevFallback = true,
.allowNameArgument = true,
.isFetchGit = true
.isFetchGit = true,
.returnPath = false,
});
}

View File

@@ -167,6 +167,8 @@ bool Input::contains(const Input & other) const
std::pair<StorePath, Input> Input::fetchToStore(ref<Store> store) const
{
// TODO: lazy-trees gets rid of this. Why?
#if 0
if (!scheme)
throw Error("cannot fetch unsupported input '%s'", attrsToJSON(toAttrs()));
@@ -187,6 +189,7 @@ std::pair<StorePath, Input> Input::fetchToStore(ref<Store> store) const
debug("substitution of input '%s' failed: %s", to_string(), e.what());
}
}
#endif
auto [storePath, input] = [&]() -> std::pair<StorePath, Input> {
try {
@@ -194,8 +197,9 @@ std::pair<StorePath, Input> Input::fetchToStore(ref<Store> store) const
auto storePath = nix::fetchToStore(*store, SourcePath(accessor), FetchMode::Copy, final.getName());
auto narHash = store->queryPathInfo(storePath)->narHash;
final.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
// TODO: do we really want to throw this out?
// auto narHash = store->queryPathInfo(storePath)->narHash;
// final.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
scheme->checkLocks(*this, final);

View File

@@ -19,62 +19,17 @@ using namespace flake;
namespace flake {
typedef std::pair<StorePath, FlakeRef> FetchedFlake;
typedef std::vector<std::pair<FlakeRef, FetchedFlake>> FlakeCache;
static std::optional<FetchedFlake> lookupInFlakeCache(
const FlakeCache & flakeCache,
const FlakeRef & flakeRef)
{
// FIXME: inefficient.
for (auto & i : flakeCache) {
if (flakeRef == i.first) {
debug("mapping '%s' to previously seen input '%s' -> '%s",
flakeRef, i.first, i.second.second);
return i.second;
}
}
return std::nullopt;
}
static std::tuple<StorePath, FlakeRef, FlakeRef> fetchOrSubstituteTree(
static FlakeRef maybeResolve(
EvalState & state,
const FlakeRef & originalRef,
bool allowLookup,
FlakeCache & flakeCache)
bool useRegistries)
{
auto fetched = lookupInFlakeCache(flakeCache, originalRef);
FlakeRef resolvedRef = originalRef;
if (!fetched) {
if (originalRef.input.isDirect()) {
fetched.emplace(originalRef.fetchTree(state.store));
} else {
if (allowLookup) {
resolvedRef = originalRef.resolve(state.store);
auto fetchedResolved = lookupInFlakeCache(flakeCache, originalRef);
if (!fetchedResolved) fetchedResolved.emplace(resolvedRef.fetchTree(state.store));
flakeCache.push_back({resolvedRef, *fetchedResolved});
fetched.emplace(*fetchedResolved);
}
else {
if (!useRegistries)
throw Error("'%s' is an indirect flake reference, but registry lookups are not allowed", originalRef);
}
}
flakeCache.push_back({originalRef, *fetched});
}
auto [storePath, lockedRef] = *fetched;
debug("got tree '%s' from '%s'",
state.store->printStorePath(storePath), lockedRef);
state.allowPath(storePath);
assert(!originalRef.input.getNarHash() || storePath == originalRef.input.computeStorePath(*state.store));
return {std::move(storePath), resolvedRef, lockedRef};
return originalRef.resolve(state.store);
} else
return originalRef;
}
static void forceTrivialValue(EvalState & state, Value & value, const PosIdx pos)
@@ -316,24 +271,19 @@ static Flake getFlake(
EvalState & state,
const FlakeRef & originalRef,
bool allowLookup,
FlakeCache & flakeCache,
InputPath lockRootPath)
{
auto [storePath, resolvedRef, lockedRef] = fetchOrSubstituteTree(
state, originalRef, allowLookup, flakeCache);
auto resolvedRef = maybeResolve(state, originalRef, allowLookup);
auto [accessor, lockedRef] = resolvedRef.lazyFetch(state.store);
return readFlake(state, originalRef, resolvedRef, lockedRef, state.rootPath(state.store->toRealPath(storePath)), lockRootPath);
}
state.registerAccessor(accessor);
Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup, FlakeCache & flakeCache)
{
return getFlake(state, originalRef, allowLookup, flakeCache, {});
return readFlake(state, originalRef, resolvedRef, lockedRef, SourcePath {accessor, CanonPath::root}, lockRootPath);
}
Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup)
{
FlakeCache flakeCache;
return getFlake(state, originalRef, allowLookup, flakeCache);
return getFlake(state, originalRef, allowLookup, {});
}
static LockFile readLockFile(
@@ -355,11 +305,9 @@ LockedFlake lockFlake(
{
experimentalFeatureSettings.require(Xp::Flakes);
FlakeCache flakeCache;
auto useRegistries = lockFlags.useRegistries.value_or(settings.useRegistries);
auto flake = getFlake(state, topRef, useRegistries, flakeCache);
auto flake = getFlake(state, topRef, useRegistries);
if (lockFlags.applyNixConfig) {
flake.config.apply(settings);
@@ -553,7 +501,7 @@ LockedFlake lockFlake(
}
if (mustRefetch) {
auto inputFlake = getFlake(state, oldLock->lockedRef, false, flakeCache, inputPath);
auto inputFlake = getFlake(state, oldLock->lockedRef, false, inputPath);
nodePaths.emplace(childNode, inputFlake.path.parent());
computeLocks(inputFlake.inputs, childNode, inputPath, oldLock, lockRootPath, parentPath, false);
} else {
@@ -586,7 +534,7 @@ LockedFlake lockFlake(
if (localRef.input.getType() == "path")
localPath = absPath(*input.ref->input.getSourcePath(), parentPath);
auto inputFlake = getFlake(state, localRef, useRegistries, flakeCache, inputPath);
auto inputFlake = getFlake(state, localRef, useRegistries, inputPath);
auto childNode = make_ref<LockedNode>(inputFlake.lockedRef, ref);
@@ -615,12 +563,17 @@ LockedFlake lockFlake(
}
else {
auto [storePath, resolvedRef, lockedRef] = fetchOrSubstituteTree(
state, *input.ref, useRegistries, flakeCache);
auto [path, lockedRef] = [&]() -> std::tuple<SourcePath, FlakeRef>
{
auto resolvedRef = maybeResolve(state, *input.ref, useRegistries);
auto [accessor, lockedRef] = resolvedRef.lazyFetch(state.store);
state.registerAccessor(accessor);
return {SourcePath(accessor), lockedRef};
}();
auto childNode = make_ref<LockedNode>(lockedRef, ref, false);
nodePaths.emplace(childNode, state.rootPath(state.store->toRealPath(storePath)));
nodePaths.emplace(childNode, path);
node->inputs.insert_or_assign(id, childNode);
}
@@ -723,8 +676,7 @@ LockedFlake lockFlake(
repo, so we should re-read it. FIXME: we could
also just clear the 'rev' field... */
auto prevLockedRef = flake.lockedRef;
FlakeCache dummyCache;
flake = getFlake(state, topRef, useRegistries, dummyCache);
flake = getFlake(state, topRef, useRegistries);
if (lockFlags.commitLockFile &&
flake.lockedRef.input.getRev() &&
@@ -768,21 +720,23 @@ void callFlake(EvalState & state,
auto lockedNode = node.dynamic_pointer_cast<const LockedNode>();
/*
// FIXME: This is a hack to support chroot stores. Remove this
// once we can pass a sourcePath rather than a storePath to
// call-flake.nix.
auto path = sourcePath.path.abs();
if (auto store = state.store.dynamic_pointer_cast<LocalFSStore>()) {
auto realStoreDir = store->getRealStoreDir();
if (isInDir(path, realStoreDir))
path = store->storeDir + path.substr(realStoreDir.size());
}
auto [storePath, subdir] = state.store->toStorePath(path);
// auto path = sourcePath.path.abs();
// if (auto store = state.store.dynamic_pointer_cast<LocalFSStore>()) {
// auto realStoreDir = store->getRealStoreDir();
// if (isInDir(path, realStoreDir))
// path = store->storeDir + path.substr(realStoreDir.size());
// }
//
// auto [storePath, subdir] = state.store->toStorePath(path);
*/
emitTreeAttrs(
state,
storePath,
SourcePath(sourcePath.accessor),
lockedNode ? lockedNode->lockedRef.input : lockedFlake.flake.lockedRef.input,
vSourceInfo,
false,
@@ -793,7 +747,7 @@ void callFlake(EvalState & state,
override
.alloc(state.symbols.create("dir"))
.mkString(CanonPath(subdir).rel());
.mkString(sourcePath.path.rel());
overrides.alloc(state.symbols.create(key->second)).mkAttrs(override);
}

View File

@@ -218,7 +218,7 @@ void callFlake(
void emitTreeAttrs(
EvalState & state,
const StorePath & storePath,
const SourcePath & storePath,
const fetchers::Input & input,
Value & v,
bool emptyRevFallback = false,

View File

@@ -287,10 +287,10 @@ FlakeRef FlakeRef::fromAttrs(
fetchers::maybeGetStrAttr(attrs, "dir").value_or(""));
}
std::pair<StorePath, FlakeRef> FlakeRef::fetchTree(ref<Store> store) const
std::pair<ref<SourceAccessor>, FlakeRef> FlakeRef::lazyFetch(ref<Store> store) const
{
auto [storePath, lockedInput] = input.fetchToStore(store);
return {std::move(storePath), FlakeRef(std::move(lockedInput), subdir)};
auto [accessor, lockedInput] = input.getAccessor(store);
return {accessor, FlakeRef(std::move(lockedInput), subdir)};
}
std::tuple<FlakeRef, std::string, ExtendedOutputsSpec> parseFlakeRefWithFragmentAndExtendedOutputsSpec(

View File

@@ -63,7 +63,7 @@ struct FlakeRef
const fetchers::Settings & fetchSettings,
const fetchers::Attrs & attrs);
std::pair<StorePath, FlakeRef> fetchTree(ref<Store> store) const;
std::pair<ref<SourceAccessor>, FlakeRef> lazyFetch(ref<Store> store) const;
};
std::ostream & operator << (std::ostream & str, const FlakeRef & flakeRef);

View File

@@ -195,4 +195,9 @@ ref<SourceAccessor> makeFSSourceAccessor(std::filesystem::path root)
{
return make_ref<PosixSourceAccessor>(std::move(root));
}
bool PosixSourceAccessor::toStringReturnsStorePath() const {
return false;
}
}

View File

@@ -57,6 +57,8 @@ struct PosixSourceAccessor : virtual SourceAccessor
*/
static SourcePath createAtRoot(const std::filesystem::path & path);
virtual bool toStringReturnsStorePath() const override;
private:
/**

View File

@@ -64,7 +64,9 @@ void SourceAccessor::setPathDisplay(std::string displayPrefix, std::string displ
std::string SourceAccessor::showPath(const CanonPath & path)
{
return displayPrefix + path.abs() + displaySuffix;
return displayPrefix
+ ((displayPrefix.empty() || displayPrefix.ends_with("/")) ? "" : ":") + path.rel()
+ displaySuffix;
}
CanonPath SourceAccessor::resolveSymlinks(
@@ -105,4 +107,8 @@ CanonPath SourceAccessor::resolveSymlinks(
return res;
}
bool SourceAccessor::toStringReturnsStorePath() const {
return true;
}
}

View File

@@ -161,6 +161,15 @@ struct SourceAccessor : std::enable_shared_from_this<SourceAccessor>
virtual std::string showPath(const CanonPath & path);
/**
* System paths: `toString /foo/bar = "/foo/bar"`
* Virtual paths: fetched to the store
*
* In both cases, the returned string functionally identifies the path,
* and can still be read.
*/
virtual bool toStringReturnsStorePath() const;
/**
* Resolve any symlinks in `path` according to the given
* resolution mode.

View File

@@ -18,6 +18,7 @@
#include "markdown.hh"
#include "users.hh"
#include "terminal.hh"
#include "value/context.hh"
#include <filesystem>
#include <nlohmann/json.hpp>
@@ -213,9 +214,6 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON
auto lockedFlake = lockFlake();
auto & flake = lockedFlake.flake;
// Currently, all flakes are in the Nix store via the rootFS accessor.
auto storePath = store->printStorePath(store->toStorePath(flake.path.path.abs()).first);
if (json) {
nlohmann::json j;
if (flake.description)
@@ -236,7 +234,6 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON
j["revCount"] = *revCount;
if (auto lastModified = flake.lockedRef.input.getLastModified())
j["lastModified"] = *lastModified;
j["path"] = storePath;
j["locks"] = lockedFlake.lockFile.toJSON().first;
if (auto fingerprint = lockedFlake.getFingerprint(store))
j["fingerprint"] = fingerprint->to_string(HashFormat::Base16, false);
@@ -253,9 +250,6 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON
logger->cout(
ANSI_BOLD "Description:" ANSI_NORMAL " %s",
*flake.description);
logger->cout(
ANSI_BOLD "Path:" ANSI_NORMAL " %s",
storePath);
if (auto rev = flake.lockedRef.input.getRev())
logger->cout(
ANSI_BOLD "Revision:" ANSI_NORMAL " %s",
@@ -891,7 +885,9 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
auto cursor = installable.getCursor(*evalState);
auto templateDirAttr = cursor->getAttr("path");
auto templateDir = templateDirAttr->getString();
auto & v = templateDirAttr->forceValue();
NixStringContext ctx;
auto templateDir = evalState->coerceToString(noPos, v, ctx, "while casting the template value to a path", false, true).toOwned();
if (!store->isInStore(templateDir))
evalState->error<TypeError>(
@@ -1523,21 +1519,15 @@ struct CmdFlakePrefetch : FlakeCommand, MixJSON
{
auto originalRef = getFlakeRef();
auto resolvedRef = originalRef.resolve(store);
auto [storePath, lockedRef] = resolvedRef.fetchTree(store);
auto hash = store->queryPathInfo(storePath)->narHash;
auto [accessor, lockedRef] = resolvedRef.lazyFetch(store);
if (json) {
auto res = nlohmann::json::object();
res["storePath"] = store->printStorePath(storePath);
res["hash"] = hash.to_string(HashFormat::SRI, true);
res["original"] = fetchers::attrsToJSON(resolvedRef.toAttrs());
res["locked"] = fetchers::attrsToJSON(lockedRef.toAttrs());
logger->cout(res.dump());
} else {
notice("Downloaded '%s' to '%s' (hash '%s').",
lockedRef.to_string(),
store->printStorePath(storePath),
hash.to_string(HashFormat::SRI, true));
notice("Fetched '%s'.", lockedRef.to_string());
}
}
};

View File

@@ -192,7 +192,6 @@ nix flake metadata "$flake1Dir" | grepQuiet 'URL:.*flake1.*'
# Test 'nix flake metadata --json'.
json=$(nix flake metadata flake1 --json | jq .)
[[ $(echo "$json" | jq -r .description) = 'Bla bla' ]]
[[ -d $(echo "$json" | jq -r .path) ]]
[[ $(echo "$json" | jq -r .lastModified) = $(git -C "$flake1Dir" log -n1 --format=%ct) ]]
hash1=$(echo "$json" | jq -r .revision)
[[ -n $(echo "$json" | jq -r .fingerprint) ]]

View File

@@ -1,20 +1,20 @@
error:
… while evaluating the attribute 'outPath'
at <nix/derivation-internal.nix>:<number>:<number>:
at <nix:derivation-internal.nix>:<number>:<number>:
<number>| value = commonAttrs // {
<number>| outPath = builtins.getAttr outputName strict;
| ^
<number>| drvPath = strict.drvPath;
… while calling the 'getAttr' builtin
at <nix/derivation-internal.nix>:<number>:<number>:
at <nix:derivation-internal.nix>:<number>:<number>:
<number>| value = commonAttrs // {
<number>| outPath = builtins.getAttr outputName strict;
| ^
<number>| drvPath = strict.drvPath;
… while calling the 'derivationStrict' builtin
at <nix/derivation-internal.nix>:<number>:<number>:
at <nix:derivation-internal.nix>:<number>:<number>:
<number>|
<number>| strict = derivationStrict drvAttrs;
| ^