Skip to content

Commit

Permalink
Introduce MemoryInputAccessor and use it for corepkgs
Browse files Browse the repository at this point in the history
MemoryInputAccessor is an in-memory virtual filesystem that returns
files like <nix/fetchurl.nix>. This removes the need for special hacks
to handle those files.
  • Loading branch information
edolstra committed Oct 18, 2023
1 parent ea38605 commit df73c6e
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 100 deletions.
2 changes: 1 addition & 1 deletion doc/manual/generate-manpage.nix
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ let
inherit (builtins)
attrNames attrValues fromJSON listToAttrs mapAttrs groupBy
concatStringsSep concatMap length lessThan replaceStrings sort;
inherit (import ./utils.nix) attrsToList concatStrings optionalString filterAttrs trim squash unique;
inherit (import <nix/utils.nix>) attrsToList concatStrings optionalString filterAttrs trim squash unique;
showStoreDocs = import ./generate-store-info.nix;
in

Expand Down
2 changes: 1 addition & 1 deletion doc/manual/local.mk
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ dummy-env = env -i \
NIX_STATE_DIR=/dummy \
NIX_CONFIG='cores = 0'

nix-eval = $(dummy-env) $(bindir)/nix eval --experimental-features nix-command -I nix/corepkgs=corepkgs --store dummy:// --impure --raw
nix-eval = $(dummy-env) $(bindir)/nix eval --experimental-features nix-command -I nix=doc/manual --store dummy:// --impure --raw

# re-implement mdBook's include directive to make it usable for terminal output and for proper @docroot@ substitution
define process-includes
Expand Down
77 changes: 51 additions & 26 deletions src/libexpr/eval.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "profiles.hh"
#include "print.hh"
#include "fs-input-accessor.hh"
#include "memory-input-accessor.hh"

#include <algorithm>
#include <chrono>
Expand Down Expand Up @@ -516,7 +517,16 @@ EvalState::EvalState(
: "in restricted mode";
throw RestrictedPathError("access to absolute path '%1%' is forbidden %2%", path, modeInformation);
}))
, derivationInternal(rootPath(CanonPath("/builtin/derivation.nix")))
, corepkgsFS(makeMemoryInputAccessor())
, internalFS(makeMemoryInputAccessor())
, derivationInternal{corepkgsFS->addFile(
CanonPath("derivation-internal.nix"),
#include "primops/derivation.nix.gen.hh"
)}
, callFlakeInternal{internalFS->addFile(
CanonPath("call-flake.nix"),
#include "flake/call-flake.nix.gen.hh"
)}
, store(store)
, buildStore(buildStore ? buildStore : store)
, debugRepl(nullptr)
Expand All @@ -531,7 +541,8 @@ EvalState::EvalState(
, baseEnv(allocEnv(128))
, staticBaseEnv{std::make_shared<StaticEnv>(false, nullptr)}
{
rootFS->allowPath(CanonPath::root); // FIXME
// For now, don't rely on FSInputAccessor for access control.
rootFS->allowPath(CanonPath::root);

countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0";

Expand Down Expand Up @@ -570,6 +581,11 @@ EvalState::EvalState(
}
}

corepkgsFS->addFile(
CanonPath("fetchurl.nix"),
#include "fetchurl.nix.gen.hh"
);

createBaseEnv();
}

Expand Down Expand Up @@ -600,6 +616,7 @@ void EvalState::allowAndSetStorePathString(const StorePath & storePath, Value &

SourcePath EvalState::checkSourcePath(const SourcePath & path_)
{
if (path_.accessor != rootFS) return path_;
if (!allowedPaths) return path_;

auto i = resolvedPaths.find(path_.path.abs());
Expand All @@ -614,8 +631,6 @@ SourcePath EvalState::checkSourcePath(const SourcePath & path_)
*/
Path abspath = canonPath(path_.path.abs());

if (hasPrefix(abspath, corepkgsPrefix)) return rootPath(CanonPath(abspath));

for (auto & i : *allowedPaths) {
if (isDirOrInDir(abspath, i)) {
found = true;
Expand Down Expand Up @@ -1180,24 +1195,6 @@ void EvalState::evalFile(const SourcePath & path_, Value & v, bool mustBeTrivial
if (!e)
e = parseExprFromFile(checkSourcePath(resolvedPath));

cacheFile(path, resolvedPath, e, v, mustBeTrivial);
}


void EvalState::resetFileCache()
{
fileEvalCache.clear();
fileParseCache.clear();
}


void EvalState::cacheFile(
const SourcePath & path,
const SourcePath & resolvedPath,
Expr * e,
Value & v,
bool mustBeTrivial)
{
fileParseCache[resolvedPath] = e;

try {
Expand Down Expand Up @@ -1226,6 +1223,13 @@ void EvalState::cacheFile(
}


void EvalState::resetFileCache()
{
fileEvalCache.clear();
fileParseCache.clear();
}


void EvalState::eval(Expr * e, Value & v)
{
e->eval(*this, baseEnv, v);
Expand Down Expand Up @@ -2341,10 +2345,31 @@ StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePat

SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext & context, std::string_view errorCtx)
{
auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
if (path == "" || path[0] != '/')
error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
return rootPath(CanonPath(path));
try {
forceValue(v, pos);

if (v.type() == nString) {
copyContext(v, context);
auto s = v.string_view();
if (!hasPrefix(s, "/"))
error("string '%s' doesn't represent an absolute path", s).atPos(pos).debugThrow<EvalError>();
return rootPath(CanonPath(s));
}
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}

if (v.type() == nPath)
return v.path();

if (v.type() == nAttrs) {
auto i = v.attrs->find(sOutPath);
if (i != v.attrs->end())
return coerceToPath(pos, *i->value, context, errorCtx);
}

error("cannot coerce %1% to a path", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
}


Expand Down
32 changes: 18 additions & 14 deletions src/libexpr/eval.hh
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class StorePath;
struct SingleDerivedPath;
enum RepairFlag : bool;
struct FSInputAccessor;
struct MemoryInputAccessor;


/**
Expand Down Expand Up @@ -212,10 +213,26 @@ public:

Bindings emptyBindings;

/**
* The accessor for the root filesystem.
*/
const ref<FSInputAccessor> rootFS;

/**
* The in-memory filesystem for <nix/...> paths.
*/
const ref<MemoryInputAccessor> corepkgsFS;

/**
* In-memory filesystem for internal, non-user-callable Nix
* expressions like call-flake.nix.
*/
const ref<MemoryInputAccessor> internalFS;

const SourcePath derivationInternal;

const SourcePath callFlakeInternal;

/**
* Store used to materialise .drv files.
*/
Expand All @@ -226,7 +243,6 @@ public:
*/
const ref<Store> buildStore;

RootValue vCallFlake = nullptr;
RootValue vImportedDrvToDerivation = nullptr;

/**
Expand Down Expand Up @@ -408,16 +424,6 @@ public:
*/
void evalFile(const SourcePath & path, Value & v, bool mustBeTrivial = false);

/**
* Like `evalFile`, but with an already parsed expression.
*/
void cacheFile(
const SourcePath & path,
const SourcePath & resolvedPath,
Expr * e,
Value & v,
bool mustBeTrivial = false);

void resetFileCache();

/**
Expand All @@ -427,7 +433,7 @@ public:
SourcePath findFile(const SearchPath & searchPath, const std::string_view path, const PosIdx pos = noPos);

/**
* Try to resolve a search path value (not the optinal key part)
* Try to resolve a search path value (not the optional key part)
*
* If the specified search path element is a URI, download it.
*
Expand Down Expand Up @@ -832,8 +838,6 @@ struct InvalidPathError : EvalError
#endif
};

static const std::string corepkgsPrefix{"/__corepkgs__/"};

template<class ErrorType>
void ErrorBuilder::debugThrow()
{
Expand Down
10 changes: 3 additions & 7 deletions src/libexpr/flake/flake.cc
Original file line number Diff line number Diff line change
Expand Up @@ -737,14 +737,10 @@ void callFlake(EvalState & state,

vRootSubdir->mkString(lockedFlake.flake.lockedRef.subdir);

if (!state.vCallFlake) {
state.vCallFlake = allocRootValue(state.allocValue());
state.eval(state.parseExprFromString(
#include "call-flake.nix.gen.hh"
, state.rootPath(CanonPath::root)), **state.vCallFlake);
}
auto vCallFlake = state.allocValue();
state.evalFile(state.callFlakeInternal, *vCallFlake);

state.callFunction(**state.vCallFlake, *vLocks, *vTmp1, noPos);
state.callFunction(*vCallFlake, *vLocks, *vTmp1, noPos);
state.callFunction(*vTmp1, *vRootSrc, *vTmp2, noPos);
state.callFunction(*vTmp2, *vRootSubdir, vRes, noPos);
}
Expand Down
5 changes: 3 additions & 2 deletions src/libexpr/parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ struct StringToken {

#include "parser-tab.hh"
#include "lexer-tab.hh"
#include "fs-input-accessor.hh"

YY_DECL;

Expand Down Expand Up @@ -650,6 +649,8 @@ formal
#include "fetchers.hh"
#include "store-api.hh"
#include "flake/flake.hh"
#include "fs-input-accessor.hh"
#include "memory-input-accessor.hh"


namespace nix {
Expand Down Expand Up @@ -761,7 +762,7 @@ SourcePath EvalState::findFile(const SearchPath & searchPath, const std::string_
}

if (hasPrefix(path, "nix/"))
return rootPath(CanonPath(concatStrings(corepkgsPrefix, path.substr(4))));
return {corepkgsFS, CanonPath(path.substr(3))};

debugThrow(ThrownError({
.msg = hintfmt(evalSettings.pureEval
Expand Down
29 changes: 10 additions & 19 deletions src/libexpr/primops.cc
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,15 @@ static SourcePath realisePath(EvalState & state, const PosIdx pos, Value & v, co
auto path = state.coerceToPath(noPos, v, context, "while realising the context of a path");

try {
StringMap rewrites = state.realiseContext(context);

auto realPath = state.rootPath(CanonPath(state.toRealPath(rewriteStrings(path.path.abs(), rewrites), context)));
if (!context.empty()) {
auto rewrites = state.realiseContext(context);
auto realPath = state.toRealPath(rewriteStrings(path.path.abs(), rewrites), context);
return {path.accessor, CanonPath(realPath)};
}

return flags.checkForPureEval
? state.checkSourcePath(realPath)
: realPath;
? state.checkSourcePath(path)
: path;
} catch (Error & e) {
e.addTrace(state.positions[pos], "while realising the context of path '%s'", path);
throw;
Expand Down Expand Up @@ -210,12 +212,6 @@ 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 if (path2 == corepkgsPrefix + "fetchurl.nix") {
state.eval(state.parseExprFromString(
#include "fetchurl.nix.gen.hh"
, state.rootPath(CanonPath::root)), v);
}

else {
if (!vScope)
state.evalFile(path, v);
Expand Down Expand Up @@ -1486,7 +1482,7 @@ static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args,
}));

NixStringContext context;
auto path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.storePath")).path;
auto path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to 'builtins.storePath'")).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. */
Expand Down Expand Up @@ -2257,7 +2253,7 @@ static void prim_filterSource(EvalState & state, const PosIdx pos, Value * * arg
{
NixStringContext context;
auto path = state.coerceToPath(pos, *args[1], context,
"while evaluating the second argument (the path to filter) passed to builtins.filterSource");
"while evaluating the second argument (the path to filter) passed to 'builtins.filterSource'");
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.filterSource");
addPath(state, pos, path.baseName(), path.path.abs(), args[0], FileIngestionMethod::Recursive, std::nullopt, v, context);
}
Expand Down Expand Up @@ -4444,12 +4440,7 @@ void EvalState::createBaseEnv()

/* Note: we have to initialize the 'derivation' constant *after*
building baseEnv/staticBaseEnv because it uses 'builtins'. */
char code[] =
#include "primops/derivation.nix.gen.hh"
// the parser needs two NUL bytes as terminators; one of them
// is implied by being a C string.
"\0";
eval(parse(code, sizeof(code), derivationInternal, rootPath(CanonPath::root), staticBaseEnv), *vDerivation);
evalFile(derivationInternal, *vDerivation);
}


Expand Down
14 changes: 7 additions & 7 deletions src/libexpr/tests/error_traces.cc
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ namespace nix {
TEST_F(ErrorTraceTest, toPath) {
ASSERT_TRACE2("toPath []",
TypeError,
hintfmt("cannot coerce %s to a string", "a list"),
hintfmt("cannot coerce %s to a path", "a list"),
hintfmt("while evaluating the first argument passed to builtins.toPath"));

ASSERT_TRACE2("toPath \"foo\"",
Expand All @@ -309,16 +309,16 @@ namespace nix {
TEST_F(ErrorTraceTest, storePath) {
ASSERT_TRACE2("storePath true",
TypeError,
hintfmt("cannot coerce %s to a string", "a Boolean"),
hintfmt("while evaluating the first argument passed to builtins.storePath"));
hintfmt("cannot coerce %s to a path", "a Boolean"),
hintfmt("while evaluating the first argument passed to 'builtins.storePath'"));

}


TEST_F(ErrorTraceTest, pathExists) {
ASSERT_TRACE2("pathExists []",
TypeError,
hintfmt("cannot coerce %s to a string", "a list"),
hintfmt("cannot coerce %s to a path", "a list"),
hintfmt("while realising the context of a path"));

ASSERT_TRACE2("pathExists \"zorglub\"",
Expand Down Expand Up @@ -377,13 +377,13 @@ namespace nix {
TEST_F(ErrorTraceTest, filterSource) {
ASSERT_TRACE2("filterSource [] []",
TypeError,
hintfmt("cannot coerce %s to a string", "a list"),
hintfmt("while evaluating the second argument (the path to filter) passed to builtins.filterSource"));
hintfmt("cannot coerce %s to a path", "a list"),
hintfmt("while evaluating the second argument (the path to filter) passed to 'builtins.filterSource'"));

ASSERT_TRACE2("filterSource [] \"foo\"",
EvalError,
hintfmt("string '%s' doesn't represent an absolute path", "foo"),
hintfmt("while evaluating the second argument (the path to filter) passed to builtins.filterSource"));
hintfmt("while evaluating the second argument (the path to filter) passed to 'builtins.filterSource'"));

ASSERT_TRACE2("filterSource [] ./.",
TypeError,
Expand Down
Loading

0 comments on commit df73c6e

Please sign in to comment.