1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-10 04:26:01 +01:00

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>
This commit is contained in:
Tom Bereknyei 2024-08-23 23:52:21 -04:00
parent fa49d2e356
commit 50b00b0194
16 changed files with 165 additions and 118 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

@ -859,6 +859,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 +1978,21 @@ void EvalState::concatLists(Value & v, size_t nrLists, Value * const * lists, co
v.mkList(list);
}
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 +2026,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 +2036,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 +2085,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);
}
@ -3053,12 +3073,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 / suffix;
if (res.pathExists()) return res;
}
if (hasPrefix(path, "nix/"))
@ -3073,18 +3095,20 @@ 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(
@ -3092,7 +3116,7 @@ std::optional<std::string> EvalState::resolveLookupPathPath(const LookupPath::Pa
fetchSettings,
EvalSettings::resolvePseudoUrl(value));
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 +3135,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,12 @@ 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, 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,6 +178,9 @@ 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: corruption?
// TODO(tomberek): re-enable this code?
#if 0
// FIXME
auto isValidDerivationInStore = [&]() -> std::optional<StorePath> {
if (!state.store->isStorePath(path2))
@ -218,7 +221,9 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
state.forceAttrs(v, pos, "while calling imported-drv-to-derivation.nix.gen.hh");
}
else {
else
#endif
{
if (!vScope)
state.evalFile(path, v);
else {

View file

@ -19,20 +19,19 @@ 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);
if (auto narHash = input.getNarHash())
attrs.alloc("narHash").mkString(narHash->to_string(HashFormat::SRI, true));
if (input.getType() == "git")
@ -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 or 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],
@ -197,7 +216,14 @@ static void fetchTree(
state.allowPath(storePath);
emitTreeAttrs(state, storePath, input2, v, params.emptyRevFallback, false);
emitTreeAttrs(state, input2, v,
[&](Value & vOutPath) {
if (params.returnPath)
vOutPath.mkPath(state.rootPath(state.store->toRealPath(storePath)));
else
state.mkStorePathString(storePath, vOutPath);
},
params.emptyRevFallback, false);
}
static void prim_fetchTree(EvalState & state, const PosIdx pos, Value * * args, Value & v)
@ -614,7 +640,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,7 +19,7 @@ using namespace flake;
namespace flake {
typedef std::pair<StorePath, FlakeRef> FetchedFlake;
typedef std::pair<ref<SourceAccessor>, FlakeRef> FetchedFlake;
typedef std::vector<std::pair<FlakeRef, FetchedFlake>> FlakeCache;
static std::optional<FetchedFlake> lookupInFlakeCache(
@ -38,43 +38,17 @@ static std::optional<FetchedFlake> lookupInFlakeCache(
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)
@ -319,10 +293,12 @@ static Flake getFlake(
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);
return readFlake(state, originalRef, resolvedRef, lockedRef, SourcePath {accessor, CanonPath::root}, lockRootPath);
}
Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup, FlakeCache & flakeCache)
@ -615,12 +591,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);
}
@ -768,21 +749,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 +776,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

@ -213,9 +213,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 +233,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 +249,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",
@ -1523,21 +1516,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) ]]