Checkpoint

This commit is contained in:
Eelco Dolstra 2022-03-01 19:08:20 +01:00
parent 00b0fb27c1
commit 06c1edf889
18 changed files with 134 additions and 120 deletions

View file

@ -396,8 +396,11 @@ Value & AttrCursor::forceValue()
if (v.type() == nString)
cachedValue = {root->db->setString(getKey(), v.string.s, v.string.context),
string_t{v.string.s, {}}};
else if (v.type() == nPath)
cachedValue = {root->db->setString(getKey(), v.path), string_t{v.path, {}}};
else if (v.type() == nPath) {
// FIXME: take accessor into account?
auto path = v.path().path;
cachedValue = {root->db->setString(getKey(), path), string_t{path, {}}};
}
else if (v.type() == nBool)
cachedValue = {root->db->setBool(getKey(), v.boolean), v.boolean};
else if (v.type() == nAttrs)
@ -537,7 +540,7 @@ std::string AttrCursor::getString()
if (v.type() != nString && v.type() != nPath)
throw TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type()));
return v.type() == nString ? v.string.s : v.path;
return v.type() == nString ? v.string.s : v.path().to_string();
}
string_t AttrCursor::getStringWithContext()
@ -568,7 +571,7 @@ string_t AttrCursor::getStringWithContext()
if (v.type() == nString)
return {v.string.s, v.getContext(*root->state.store)};
else if (v.type() == nPath)
return {v.path, {}};
return {v.path().to_string(), {}};
else
throw TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type()));
}

View file

@ -119,7 +119,7 @@ void Value::print(std::ostream & str, std::set<const void *> * seen) const
str << "\"";
break;
case tPath:
str << path; // !!! escaping?
str << path().to_string(); // !!! escaping?
break;
case tNull:
str << "null";
@ -721,11 +721,6 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
evaluator. So here are some helper functions for throwing
exceptions. */
LocalNoInlineNoReturn(void throwEvalError(const char * s, const std::string & s2))
{
throw EvalError(s, s2);
}
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const Suggestions & suggestions, const char * s, const std::string & s2))
{
throw EvalError(ErrorInfo {
@ -862,9 +857,12 @@ void Value::mkStringMove(const char * s, const PathSet & context)
}
void Value::mkPath(std::string_view s)
void Value::mkPath(const SourcePath & path)
{
mkPath(makeImmutableString(s));
clearValue();
internalType = tPath;
_path.accessor = &path.accessor;
_path.path = makeImmutableString(path.path);
}
@ -978,24 +976,19 @@ Value * ExprPath::maybeThunk(EvalState & state, Env & env)
}
void EvalState::evalFile(const SourcePath & path_, Value & v, bool mustBeTrivial)
void EvalState::evalFile(const SourcePath & path, Value & v, bool mustBeTrivial)
{
#if 0
auto path = checkSourcePath(path_);
#endif
auto path = packPath(path_);
// FIXME: use SourcePath as cache key
auto pathKey = path.to_string();
FileEvalCache::iterator i;
if ((i = fileEvalCache.find(path)) != fileEvalCache.end()) {
if ((i = fileEvalCache.find(pathKey)) != fileEvalCache.end()) {
v = i->second;
return;
}
auto resolvedPath_ = resolveExprPath(path_);
auto resolvedPath = packPath(resolvedPath_);
if ((i = fileEvalCache.find(resolvedPath)) != fileEvalCache.end()) {
auto resolvedPath = resolveExprPath(path);
auto resolvedPathKey = resolvedPath.to_string();
if ((i = fileEvalCache.find(resolvedPathKey)) != fileEvalCache.end()) {
v = i->second;
return;
}
@ -1003,17 +996,17 @@ void EvalState::evalFile(const SourcePath & path_, Value & v, bool mustBeTrivial
printTalkative("evaluating file '%1%'", resolvedPath);
Expr * e = nullptr;
auto j = fileParseCache.find(resolvedPath);
auto j = fileParseCache.find(resolvedPathKey);
if (j != fileParseCache.end())
e = j->second;
if (!e)
e = parseExprFromFile(resolvedPath_);
e = parseExprFromFile(resolvedPath);
#if 0
e = parseExprFromFile(checkSourcePath(resolvedPath));
#endif
cacheFile(path, resolvedPath, e, v, mustBeTrivial);
cacheFile(pathKey, resolvedPathKey, e, v, mustBeTrivial);
}
@ -1790,9 +1783,9 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp));
} else {
if (s.empty()) s.reserve(es->size());
/* skip canonization of first path, which would only be not
canonized in the first place if it's coming from a ./${foo} type
path */
/* Skip canonization of first path, which would only be
non-canonical in the first place if it's coming from a
./${foo} type path. */
auto part = state.coerceToString(i_pos, vTmp, context, false, firstType == nString, !first);
sSize += part->size();
s.emplace_back(std::move(part));
@ -1808,7 +1801,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
else if (firstType == nPath) {
if (!context.empty())
throwEvalError(pos, "a string that refers to a store path cannot be appended to a path");
v.mkPath(canonPath(str()));
v.mkPath({.accessor = *values[0]._path.accessor, .path = canonPath(str())});
} else
v.mkStringMove(c_str(), context);
}
@ -2005,11 +1998,12 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet &
}
if (v.type() == nPath) {
BackedStringView path(PathView(v.path));
auto path = v.path().to_string();
if (canonicalizePath)
path = canonPath(*path);
// FIXME: unnecessary?
path = canonPath(path);
if (copyToStore)
path = copyPathToStore(context, std::move(path).toOwned());
path = copyPathToStore(context, path);
return path;
}
@ -2054,6 +2048,7 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet &
std::string EvalState::copyPathToStore(PathSet & context, const Path & path)
{
#if 0
if (nix::isDerivation(path))
throwEvalError("file names are not allowed to end in '%1%'", drvExtension);
@ -2070,7 +2065,7 @@ std::string EvalState::copyPathToStore(PathSet & context, const Path & path)
: store->addToStore(path2.baseName(), canonPath(path), FileIngestionMethod::Recursive, htSHA256, defaultPathFilter, repair);
#endif
auto source = sinkToSource([&](Sink & sink) {
path2.accessor->dumpPath(path2.path, sink);
path2.dumpPath(sink);
});
// FIXME: readOnlyMode
auto p = store->addToStoreFromDump(*source, path2.baseName(), FileIngestionMethod::Recursive, htSHA256, repair);
@ -2082,15 +2077,18 @@ std::string EvalState::copyPathToStore(PathSet & context, const Path & path)
context.insert(dstPath);
return dstPath;
#endif
abort();
}
Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context)
SourcePath EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context)
{
auto path = coerceToString(pos, v, context, false, false).toOwned();
if (path == "" || path[0] != '/')
throwEvalError(pos, "string '%1%' doesn't represent an absolute path", path);
return path;
// FIXME
return rootPath(path);
}
@ -2137,7 +2135,9 @@ bool EvalState::eqValues(Value & v1, Value & v2)
return strcmp(v1.string.s, v2.string.s) == 0;
case nPath:
return strcmp(v1.path, v2.path) == 0;
return
v1._path.accessor == v2._path.accessor
&& strcmp(v1._path.path, v2._path.path) == 0;
case nNull:
return true;

View file

@ -96,7 +96,7 @@ public:
ref<FSInputAccessor> rootFS;
ref<MemoryInputAccessor> corepkgsFS;
std::unordered_map<size_t, ref<InputAccessor>> inputAccessors;
std::unordered_map<InputAccessor *, ref<InputAccessor>> inputAccessors;
/* Store used to materialise .drv files. */
const ref<Store> store;
@ -156,11 +156,9 @@ public:
SearchPath getSearchPath() { return searchPath; }
Path packPath(const SourcePath & path);
SourcePath rootPath(Path path);
SourcePath unpackPath(const Path & path);
SourcePath rootPath(const Path & path);
InputAccessor & registerAccessor(ref<InputAccessor> accessor);
/* Allow access to a path. */
void allowPath(const Path & path);
@ -274,8 +272,7 @@ public:
/* Path coercion. Converts strings, paths and derivations to a
path. The result is guaranteed to be a canonicalised, absolute
path. Nothing is copied to the store. */
// FIXME: return SourcePath
Path coerceToPath(const Pos & pos, Value & v, PathSet & context);
SourcePath coerceToPath(const Pos & pos, Value & v, PathSet & context);
/* Like coerceToPath, but the result must be a store path. */
StorePath coerceToStorePath(const Pos & pos, Value & v, PathSet & context);

View file

@ -201,7 +201,7 @@ static std::map<FlakeId, FlakeInput> parseFlakeInputs(
static Flake readFlake(
EvalState & state,
const FlakeRef & lockedRef,
nix::ref<InputAccessor> accessor,
InputAccessor & accessor,
const InputPath & lockRootPath)
{
auto flakeDir = canonPath("/" + lockedRef.subdir);
@ -213,14 +213,14 @@ static Flake readFlake(
Value vInfo;
state.evalFile(flakePath, vInfo, true);
expectType(state, nAttrs, vInfo, Pos(foFile, state.symbols.create(state.packPath(flakePath)), 0, 0));
expectType(state, nAttrs, vInfo, Pos(foFile, state.symbols.create(flakePath.to_string()), 0, 0));
Flake flake {
// FIXME
.originalRef = lockedRef,
.resolvedRef = lockedRef,
.lockedRef = lockedRef,
.accessor = accessor,
.accessor = ptr(&accessor),
.flakePath = dirOf(flakePath.path),
};
@ -308,7 +308,7 @@ static Flake getFlake(
// FIXME: resolve
auto [accessor, input] = originalRef.input.lazyFetch(state.store);
return readFlake(state, originalRef, accessor, lockRootPath);
return readFlake(state, originalRef, state.registerAccessor(accessor), lockRootPath);
}
Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup, FlakeCache & flakeCache)
@ -324,7 +324,7 @@ Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup
static LockFile readLockFile(const Flake & flake)
{
SourcePath lockFilePath{flake.accessor, canonPath(flake.flakePath + "/flake.lock")};
SourcePath lockFilePath{*flake.accessor, canonPath(flake.flakePath + "/flake.lock")};
return lockFilePath.pathExists()
? LockFile(lockFilePath.readFile(), fmt("%s", lockFilePath))
: LockFile();
@ -703,7 +703,7 @@ void callFlake(EvalState & state,
emitTreeAttrs(
state,
{lockedFlake.flake.accessor, lockedFlake.flake.flakePath},
{*lockedFlake.flake.accessor, lockedFlake.flake.flakePath},
lockedFlake.flake.lockedRef.input,
*vRootSrc,
false,

View file

@ -61,7 +61,7 @@ struct Flake
FlakeRef originalRef; // the original flake specification (by the user)
FlakeRef resolvedRef; // registry references and caching resolved to the specific underlying flake
FlakeRef lockedRef; // the specific local store result of invoking the fetcher
ref<InputAccessor> accessor;
ptr<InputAccessor> accessor;
Path flakePath;
bool forceDirty = false; // pretend that 'lockedRef' is dirty
std::optional<std::string> description;

View file

@ -121,9 +121,13 @@ struct ExprString : Expr
struct ExprPath : Expr
{
std::string s;
std::string s; // FIXME: remove
Value v;
ExprPath(std::string s) : s(std::move(s)) { v.mkPath(this->s.c_str()); };
ExprPath(InputAccessor & accessor, std::string s)
: s(std::move(s))
{
v.mkPath({accessor, this->s});
}
COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env);
};

View file

@ -517,11 +517,11 @@ path_start
/* add back in the trailing '/' to the first segment */
if ($1.p[$1.l-1] == '/' && $1.l > 1)
path += "/";
$$ = new ExprPath(path);
$$ = new ExprPath(*data->state.rootFS, path);
}
| HPATH {
Path path(getHome() + std::string($1.p + 1, $1.l - 1));
$$ = new ExprPath(path);
$$ = new ExprPath(*data->state.rootFS, path);
}
;
@ -700,7 +700,7 @@ SourcePath resolveExprPath(const SourcePath & path)
// FIXME
auto path2 = path.path + "/default.nix";
if (path.accessor->pathExists(path2))
if (path.pathExists())
return {path.accessor, path2};
return path;
@ -715,11 +715,11 @@ Expr * EvalState::parseExprFromFile(const SourcePath & path)
Expr * EvalState::parseExprFromFile(const SourcePath & path, StaticEnv & staticEnv)
{
auto packed = packPath(path);
auto buffer = path.readFile();
// readFile hopefully have left some extra space for terminators
buffer.append("\0\0", 2);
return parse(buffer.data(), buffer.size(), foFile, packed, dirOf(packed), staticEnv);
// FIXME: pass SourcePaths
return parse(buffer.data(), buffer.size(), foFile, path.path, dirOf(path.path), staticEnv);
}
@ -788,7 +788,8 @@ Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, c
}
if (hasPrefix(path, "nix/"))
return packPath(SourcePath {corepkgsFS, (std::string) path.substr(3)});
abort();
//return packPath(SourcePath {corepkgsFS, (std::string) path.substr(3)});
throw ThrownError({
.msg = hintfmt(evalSettings.pureEval

View file

@ -3,34 +3,15 @@
namespace nix {
static constexpr std::string_view marker = "/__virtual/";
Path EvalState::packPath(const SourcePath & path)
SourcePath EvalState::rootPath(Path path)
{
// FIXME: canonPath(path) ?
assert(hasPrefix(path.path, "/"));
inputAccessors.emplace(path.accessor->number, path.accessor);
return std::string(marker) + std::to_string(path.accessor->number) + path.path;
return {*rootFS, std::move(path)};
}
SourcePath EvalState::unpackPath(const Path & path)
InputAccessor & EvalState::registerAccessor(ref<InputAccessor> accessor)
{
if (hasPrefix(path, marker)) {
auto s = path.substr(marker.size());
auto slash = s.find('/');
auto n = std::stoi(s.substr(0, slash));
auto i = inputAccessors.find(n);
assert(i != inputAccessors.end());
return {i->second, slash != std::string::npos ? s.substr(slash) : "/"};
} else {
printError("FIXME: %s", path);
return rootPath(path);
}
}
SourcePath EvalState::rootPath(const Path & path)
{
return {rootFS, path};
inputAccessors.emplace(&*accessor, accessor);
return *accessor;
}
}

View file

@ -104,7 +104,7 @@ static SourcePath realisePath(EvalState & state, const Pos & pos, Value & v, con
auto path = [&]()
{
try {
return state.unpackPath(state.coerceToPath(pos, v, context));
return state.coerceToPath(pos, v, context);
} catch (Error & e) {
e.addTrace(pos, "while realising the context of a path");
throw;
@ -557,7 +557,8 @@ struct CompareValues
case nString:
return strcmp(v1->string.s, v2->string.s) < 0;
case nPath:
return strcmp(v1->path, v2->path) < 0;
// FIXME: handle accessor?
return strcmp(v1->_path.path, v2->_path.path) < 0;
case nList:
// Lexicographic comparison
for (size_t i = 0;; i++) {
@ -1315,8 +1316,8 @@ static RegisterPrimOp primop_placeholder({
static void prim_toPath(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
PathSet context;
Path path = state.coerceToPath(pos, *args[0], context);
v.mkString(canonPath(path), context);
auto path = state.coerceToPath(pos, *args[0], context);
v.mkString(canonPath(path.path), context);
}
static RegisterPrimOp primop_toPath({
@ -1347,7 +1348,7 @@ static void prim_storePath(EvalState & state, const Pos & pos, Value * * args, V
PathSet context;
// FIXME: check rootPath
Path path = state.coerceToPath(pos, *args[0], context);
auto path = state.coerceToPath(pos, *args[0], context).path;
/* Resolve symlinks in path, unless path itself is a symlink
directly in the store. The latter condition is necessary so
e.g. nix-push does the right thing. */
@ -1439,7 +1440,10 @@ static void prim_dirOf(EvalState & state, const Pos & pos, Value * * args, Value
PathSet context;
auto path = state.coerceToString(pos, *args[0], context, false, false);
auto dir = dirOf(*path);
abort();
#if 0
if (args[0]->type() == nPath) v.mkPath(dir); else v.mkString(dir, context);
#endif
}
static RegisterPrimOp primop_dirOf({
@ -1520,8 +1524,11 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va
auto path = state.forceStringNoCtx(*args[1], pos);
#if 0
// FIXME: checkSourcePath?
v.mkPath(state.findFile(searchPath, path, pos));
#endif
abort();
}
static RegisterPrimOp primop_findFile(RegisterPrimOp::Info {
@ -1563,7 +1570,7 @@ static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Val
{
auto path = realisePath(state, pos, *args[0]);
auto entries = path.accessor->readDirectory(path.path);
auto entries = path.readDirectory();
auto attrs = state.buildBindings(entries.size());
for (auto & [name, type] : entries) {
@ -1881,7 +1888,7 @@ static RegisterPrimOp primop_toFile({
static void addPath(
EvalState & state,
const Pos & pos,
const std::string & name,
std::string_view name,
Path path,
Value * filterFun,
FileIngestionMethod method,
@ -1959,7 +1966,7 @@ static void addPath(
static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
PathSet context;
Path path = state.coerceToPath(pos, *args[1], context);
auto path = state.coerceToPath(pos, *args[1], context);
state.forceValue(*args[0], pos);
if (args[0]->type() != nFunction)
@ -1970,7 +1977,8 @@ static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args
.errPos = pos
});
addPath(state, pos, std::string(baseNameOf(path)), path, args[0], FileIngestionMethod::Recursive, std::nullopt, v, context);
// FIXME: use SourcePath
addPath(state, pos, path.baseName(), path.path, args[0], FileIngestionMethod::Recursive, std::nullopt, v, context);
}
static RegisterPrimOp primop_filterSource({
@ -2031,7 +2039,7 @@ static RegisterPrimOp primop_filterSource({
static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
state.forceAttrs(*args[0], pos);
Path path;
std::optional<SourcePath> path;
std::string name;
Value * filterFun = nullptr;
auto method = FileIngestionMethod::Recursive;
@ -2041,7 +2049,7 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value
for (auto & attr : *args[0]->attrs) {
auto & n(attr.name);
if (n == "path")
path = state.coerceToPath(*attr.pos, *attr.value, context);
path.emplace(state.coerceToPath(*attr.pos, *attr.value, context));
else if (attr.name == state.sName)
name = state.forceStringNoCtx(*attr.value, *attr.pos);
else if (n == "filter") {
@ -2057,15 +2065,16 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value
.errPos = *attr.pos
});
}
if (path.empty())
if (!path)
throw EvalError({
.msg = hintfmt("'path' required"),
.errPos = pos
});
if (name.empty())
name = baseNameOf(path);
name = path->baseName();
addPath(state, pos, name, path, filterFun, method, expectedHash, v, context);
// FIXME: use SourcePath
addPath(state, pos, name, path->path, filterFun, method, expectedHash, v, context);
}
static RegisterPrimOp primop_path({

View file

@ -30,7 +30,7 @@ void emitTreeAttrs(
attrs.alloc(state.sOutPath).mkString(storePath, {storePath});
#endif
attrs.alloc(state.sOutPath).mkPath(state.packPath(path));
attrs.alloc(state.sOutPath).mkPath(path);
// FIXME: support arbitrary input attributes.
@ -138,8 +138,8 @@ static void fetchTree(
for (auto elem : attr.value->listItems()) {
// FIXME: use realisePath
PathSet context;
auto patchFile = state.unpackPath(state.coerceToPath(pos, *elem, context));
patches.push_back(patchFile.accessor->readFile(patchFile.path));
auto patchFile = state.coerceToPath(pos, *elem, context);
patches.push_back(patchFile.readFile());
}
continue;
@ -201,7 +201,7 @@ static void fetchTree(
emitTreeAttrs(
state,
{accessor, "/"},
{state.registerAccessor(accessor), "/"},
input2,
v,
params.emptyRevFallback,

View file

@ -32,7 +32,8 @@ void printValueAsJSON(EvalState & state, bool strict,
break;
case nPath:
out.write(state.copyPathToStore(context, v.path));
// FIXME: handle accessors
out.write(state.copyPathToStore(context, v.path().path));
break;
case nNull:

View file

@ -77,7 +77,7 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
break;
case nPath:
doc.writeEmptyElement("path", singletonAttrs("value", v.path));
doc.writeEmptyElement("path", singletonAttrs("value", v.path().to_string()));
break;
case nNull:

View file

@ -3,6 +3,7 @@
#include <cassert>
#include "symbol-table.hh"
#include "input-accessor.hh"
#if HAVE_BOEHMGC
#include <gc/gc_allocator.h>
@ -170,7 +171,11 @@ public:
const char * * context; // must be in sorted order
} string;
const char * path;
struct {
InputAccessor * accessor;
const char * path;
} _path;
Bindings * attrs;
struct {
size_t size;
@ -255,14 +260,7 @@ public:
mkString(((const std::string &) s).c_str());
}
inline void mkPath(const char * s)
{
clearValue();
internalType = tPath;
path = s;
}
void mkPath(std::string_view s);
void mkPath(const SourcePath & path);
inline void mkNull()
{
@ -404,6 +402,12 @@ public:
auto begin = listElems();
return ConstListIterable { begin, begin + listSize() };
}
SourcePath path() const
{
assert(internalType == tPath);
return SourcePath { .accessor = *_path.accessor, .path = _path.path };
}
};

View file

@ -216,9 +216,14 @@ ref<FSInputAccessor> makeFSInputAccessor(
return make_ref<FSInputAccessorImpl>(root, std::move(allowedPaths));
}
std::string SourcePath::to_string() const
{
return path; // FIXME
}
std::ostream & operator << (std::ostream & str, const SourcePath & path)
{
str << path.path; // FIXME
str << path.to_string();
return str;
}

View file

@ -72,16 +72,26 @@ ref<InputAccessor> makePatchingInputAccessor(
struct SourcePath
{
ref<InputAccessor> accessor;
InputAccessor & accessor;
Path path;
std::string_view baseName() const;
std::string readFile() const
{ return accessor->readFile(path); }
{ return accessor.readFile(path); }
bool pathExists() const
{ return accessor->pathExists(path); }
{ return accessor.pathExists(path); }
InputAccessor::DirEntries readDirectory() const
{ return accessor.readDirectory(path); }
void dumpPath(
Sink & sink,
PathFilter & filter = defaultPathFilter) const
{ return accessor.dumpPath(path, sink, filter); }
std::string to_string() const;
};
std::ostream & operator << (std::ostream & str, const SourcePath & path);

View file

@ -452,9 +452,7 @@ struct CmdFlakeCheck : FlakeCommand
if (auto attr = v.attrs->get(state->symbols.create("path"))) {
if (attr->name == state->symbols.create("path")) {
PathSet context;
auto path = state->coerceToPath(*attr->pos, *attr->value, context);
if (!store->isInStore(path))
throw Error("template '%s' has a bad 'path' attribute");
state->coerceToStorePath(*attr->pos, *attr->value, context);
// TODO: recursively check the flake in 'path'.
}
} else

View file

@ -178,6 +178,7 @@ static void showHelp(std::vector<std::string> subcommand, MultiCommand & topleve
#include "generate-manpage.nix.gen.hh"
, "/"), *vGenerateManpage);
// FIXME: use MemoryAccessor
auto vUtils = state.allocValue();
state.cacheFile(
"/utils.nix", "/utils.nix",

View file

@ -772,7 +772,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
break;
case nPath:
str << ANSI_GREEN << v.path << ANSI_NORMAL; // !!! escaping?
str << ANSI_GREEN << v.path().path << ANSI_NORMAL; // !!! escaping?
break;
case nNull: