Merge pull request #8172 from edolstra/source-path

Backport `SourcePath` from the lazy-trees branch
This commit is contained in:
Eelco Dolstra 2023-04-24 14:05:51 +02:00 committed by GitHub
commit 249ce28332
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 608 additions and 290 deletions

View file

@ -153,7 +153,7 @@ Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
for (auto & i : autoArgs) { for (auto & i : autoArgs) {
auto v = state.allocValue(); auto v = state.allocValue();
if (i.second[0] == 'E') if (i.second[0] == 'E')
state.mkThunk_(*v, state.parseExprFromString(i.second.substr(1), absPath("."))); state.mkThunk_(*v, state.parseExprFromString(i.second.substr(1), state.rootPath(CanonPath::fromCwd())));
else else
v->mkString(((std::string_view) i.second).substr(1)); v->mkString(((std::string_view) i.second).substr(1));
res.insert(state.symbols.create(i.first), v); res.insert(state.symbols.create(i.first), v);
@ -161,19 +161,19 @@ Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
return res.finish(); return res.finish();
} }
Path lookupFileArg(EvalState & state, std::string_view s) SourcePath lookupFileArg(EvalState & state, std::string_view s)
{ {
if (EvalSettings::isPseudoUrl(s)) { if (EvalSettings::isPseudoUrl(s)) {
auto storePath = fetchers::downloadTarball( auto storePath = fetchers::downloadTarball(
state.store, EvalSettings::resolvePseudoUrl(s), "source", false).first.storePath; state.store, EvalSettings::resolvePseudoUrl(s), "source", false).first.storePath;
return state.store->toRealPath(storePath); return state.rootPath(CanonPath(state.store->toRealPath(storePath)));
} }
else if (hasPrefix(s, "flake:")) { else if (hasPrefix(s, "flake:")) {
experimentalFeatureSettings.require(Xp::Flakes); experimentalFeatureSettings.require(Xp::Flakes);
auto flakeRef = parseFlakeRef(std::string(s.substr(6)), {}, true, false); auto flakeRef = parseFlakeRef(std::string(s.substr(6)), {}, true, false);
auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first.storePath; auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first.storePath;
return state.store->toRealPath(storePath); return state.rootPath(CanonPath(state.store->toRealPath(storePath)));
} }
else if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') { else if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') {
@ -182,7 +182,7 @@ Path lookupFileArg(EvalState & state, std::string_view s)
} }
else else
return absPath(std::string(s)); return state.rootPath(CanonPath::fromCwd(s));
} }
} }

View file

@ -8,6 +8,7 @@ namespace nix {
class Store; class Store;
class EvalState; class EvalState;
class Bindings; class Bindings;
struct SourcePath;
struct MixEvalArgs : virtual Args struct MixEvalArgs : virtual Args
{ {
@ -25,6 +26,6 @@ private:
std::map<std::string, std::string> autoArgs; std::map<std::string, std::string> autoArgs;
}; };
Path lookupFileArg(EvalState & state, std::string_view s); SourcePath lookupFileArg(EvalState & state, std::string_view s);
} }

View file

@ -3,8 +3,11 @@
namespace nix { namespace nix {
Strings editorFor(const Path & file, uint32_t line) Strings editorFor(const SourcePath & file, uint32_t line)
{ {
auto path = file.getPhysicalPath();
if (!path)
throw Error("cannot open '%s' in an editor because it has no physical path", file);
auto editor = getEnv("EDITOR").value_or("cat"); auto editor = getEnv("EDITOR").value_or("cat");
auto args = tokenizeString<Strings>(editor); auto args = tokenizeString<Strings>(editor);
if (line > 0 && ( if (line > 0 && (
@ -13,7 +16,7 @@ Strings editorFor(const Path & file, uint32_t line)
editor.find("vim") != std::string::npos || editor.find("vim") != std::string::npos ||
editor.find("kak") != std::string::npos)) editor.find("kak") != std::string::npos))
args.push_back(fmt("+%d", line)); args.push_back(fmt("+%d", line));
args.push_back(file); args.push_back(path->abs());
return args; return args;
} }

View file

@ -2,6 +2,7 @@
///@file ///@file
#include "types.hh" #include "types.hh"
#include "input-accessor.hh"
namespace nix { namespace nix {
@ -9,6 +10,6 @@ namespace nix {
* Helper function to generate args that invoke $EDITOR on * Helper function to generate args that invoke $EDITOR on
* filename:lineno. * filename:lineno.
*/ */
Strings editorFor(const Path & file, uint32_t line); Strings editorFor(const SourcePath & file, uint32_t line);
} }

View file

@ -96,8 +96,7 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
auto v = attr->forceValue(); auto v = attr->forceValue();
if (v.type() == nPath) { if (v.type() == nPath) {
NixStringContext context; auto storePath = v.path().fetchToStore(state->store);
auto storePath = state->copyPathToStore(context, Path(v.path));
return {{ return {{
.path = DerivedPath::Opaque { .path = DerivedPath::Opaque {
.path = std::move(storePath), .path = std::move(storePath),

View file

@ -449,7 +449,7 @@ Installables SourceExprCommand::parseInstallables(
else if (file) else if (file)
state->evalFile(lookupFileArg(*state, *file), *vFile); state->evalFile(lookupFileArg(*state, *file), *vFile);
else { else {
auto e = state->parseExprFromString(*expr, absPath(".")); auto e = state->parseExprFromString(*expr, state->rootPath(CanonPath::fromCwd()));
state->eval(e, *vFile); state->eval(e, *vFile);
} }

View file

@ -55,8 +55,6 @@ struct NixRepl
, gc , gc
#endif #endif
{ {
std::string curDir;
size_t debugTraceIndex; size_t debugTraceIndex;
Strings loadedFiles; Strings loadedFiles;
@ -114,7 +112,6 @@ NixRepl::NixRepl(const Strings & searchPath, nix::ref<Store> store, ref<EvalStat
, staticEnv(new StaticEnv(false, state->staticBaseEnv.get())) , staticEnv(new StaticEnv(false, state->staticBaseEnv.get()))
, historyFile(getDataDir() + "/nix/repl-history") , historyFile(getDataDir() + "/nix/repl-history")
{ {
curDir = absPath(".");
} }
@ -594,14 +591,14 @@ bool NixRepl::processLine(std::string line)
Value v; Value v;
evalString(arg, v); evalString(arg, v);
const auto [path, line] = [&] () -> std::pair<Path, uint32_t> { const auto [path, line] = [&] () -> std::pair<SourcePath, uint32_t> {
if (v.type() == nPath || v.type() == nString) { if (v.type() == nPath || v.type() == nString) {
NixStringContext context; NixStringContext context;
auto path = state->coerceToPath(noPos, v, context, "while evaluating the filename to edit"); auto path = state->coerceToPath(noPos, v, context, "while evaluating the filename to edit");
return {path, 0}; return {path, 0};
} else if (v.isLambda()) { } else if (v.isLambda()) {
auto pos = state->positions[v.lambda.fun->pos]; auto pos = state->positions[v.lambda.fun->pos];
if (auto path = std::get_if<Path>(&pos.origin)) if (auto path = std::get_if<SourcePath>(&pos.origin))
return {*path, pos.line}; return {*path, pos.line};
else else
throw Error("'%s' cannot be shown in an editor", pos); throw Error("'%s' cannot be shown in an editor", pos);
@ -876,8 +873,7 @@ void NixRepl::addVarToScope(const Symbol name, Value & v)
Expr * NixRepl::parseString(std::string s) Expr * NixRepl::parseString(std::string s)
{ {
Expr * e = state->parseExprFromString(std::move(s), curDir, staticEnv); return state->parseExprFromString(std::move(s), state->rootPath(CanonPath::fromCwd()), staticEnv);
return e;
} }
@ -925,7 +921,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
break; break;
case nPath: case nPath:
str << ANSI_GREEN << v.path << ANSI_NORMAL; // !!! escaping? str << ANSI_GREEN << v.path().to_string() << ANSI_NORMAL; // !!! escaping?
break; break;
case nNull: case nNull:

View file

@ -106,7 +106,7 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
} }
std::pair<std::string, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what) std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what)
{ {
Value * v2; Value * v2;
try { try {
@ -118,21 +118,25 @@ std::pair<std::string, uint32_t> findPackageFilename(EvalState & state, Value &
// FIXME: is it possible to extract the Pos object instead of doing this // FIXME: is it possible to extract the Pos object instead of doing this
// toString + parsing? // toString + parsing?
auto pos = state.forceString(*v2, noPos, "while evaluating the 'meta.position' attribute of a derivation"); NixStringContext context;
auto path = state.coerceToPath(noPos, *v2, context, "while evaluating the 'meta.position' attribute of a derivation");
auto colon = pos.rfind(':'); auto fn = path.path.abs();
if (colon == std::string::npos)
throw ParseError("cannot parse meta.position attribute '%s'", pos); auto fail = [fn]() {
throw ParseError("cannot parse 'meta.position' attribute '%s'", fn);
};
std::string filename(pos, 0, colon);
unsigned int lineno;
try { try {
lineno = std::stoi(std::string(pos, colon + 1, std::string::npos)); auto colon = fn.rfind(':');
if (colon == std::string::npos) fail();
std::string filename(fn, 0, colon);
auto lineno = std::stoi(std::string(fn, colon + 1, std::string::npos));
return {CanonPath(fn.substr(0, colon)), lineno};
} catch (std::invalid_argument & e) { } catch (std::invalid_argument & e) {
throw ParseError("cannot parse line number '%s'", pos); fail();
abort();
} }
return { std::move(filename), lineno };
} }

View file

@ -20,7 +20,7 @@ std::pair<Value *, PosIdx> findAlongAttrPath(
/** /**
* Heuristic to find the filename and lineno or a nix value. * Heuristic to find the filename and lineno or a nix value.
*/ */
std::pair<std::string, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what); std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what);
std::vector<Symbol> parseAttrPath(EvalState & state, std::string_view s); std::vector<Symbol> parseAttrPath(EvalState & state, std::string_view s);

View file

@ -442,8 +442,10 @@ Value & AttrCursor::forceValue()
if (v.type() == nString) if (v.type() == nString)
cachedValue = {root->db->setString(getKey(), v.string.s, v.string.context), cachedValue = {root->db->setString(getKey(), v.string.s, v.string.context),
string_t{v.string.s, {}}}; string_t{v.string.s, {}}};
else if (v.type() == nPath) else if (v.type() == nPath) {
cachedValue = {root->db->setString(getKey(), v.path), string_t{v.path, {}}}; auto path = v.path().path;
cachedValue = {root->db->setString(getKey(), path.abs()), string_t{path.abs(), {}}};
}
else if (v.type() == nBool) else if (v.type() == nBool)
cachedValue = {root->db->setBool(getKey(), v.boolean), v.boolean}; cachedValue = {root->db->setBool(getKey(), v.boolean), v.boolean};
else if (v.type() == nInt) else if (v.type() == nInt)
@ -580,7 +582,7 @@ std::string AttrCursor::getString()
if (v.type() != nString && v.type() != nPath) if (v.type() != nString && v.type() != nPath)
root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>(); root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
return v.type() == nString ? v.string.s : v.path; return v.type() == nString ? v.string.s : v.path().to_string();
} }
string_t AttrCursor::getStringWithContext() string_t AttrCursor::getStringWithContext()
@ -624,7 +626,7 @@ string_t AttrCursor::getStringWithContext()
copyContext(v, context); copyContext(v, context);
return {v.string.s, std::move(context)}; return {v.string.s, std::move(context)};
} else if (v.type() == nPath) } else if (v.type() == nPath)
return {v.path, {}}; return {v.path().to_string(), {}};
else else
root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>(); root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
} }

View file

@ -111,7 +111,7 @@ void Value::print(const SymbolTable & symbols, std::ostream & str,
printLiteralString(str, string.s); printLiteralString(str, string.s);
break; break;
case tPath: case tPath:
str << path; // !!! escaping? str << path().to_string(); // !!! escaping?
break; break;
case tNull: case tNull:
str << "null"; str << "null";
@ -535,6 +535,7 @@ EvalState::EvalState(
, sOutputSpecified(symbols.create("outputSpecified")) , sOutputSpecified(symbols.create("outputSpecified"))
, repair(NoRepair) , repair(NoRepair)
, emptyBindings(0) , emptyBindings(0)
, derivationInternal(rootPath(CanonPath("/builtin/derivation.nix")))
, store(store) , store(store)
, buildStore(buildStore ? buildStore : store) , buildStore(buildStore ? buildStore : store)
, debugRepl(nullptr) , debugRepl(nullptr)
@ -612,11 +613,11 @@ void EvalState::allowAndSetStorePathString(const StorePath & storePath, Value &
mkStorePathString(storePath, v); mkStorePathString(storePath, v);
} }
Path EvalState::checkSourcePath(const Path & path_) SourcePath EvalState::checkSourcePath(const SourcePath & path_)
{ {
if (!allowedPaths) return path_; if (!allowedPaths) return path_;
auto i = resolvedPaths.find(path_); auto i = resolvedPaths.find(path_.path.abs());
if (i != resolvedPaths.end()) if (i != resolvedPaths.end())
return i->second; return i->second;
@ -626,9 +627,9 @@ Path EvalState::checkSourcePath(const Path & path_)
* attacker can't append ../../... to a path that would be in allowedPaths * attacker can't append ../../... to a path that would be in allowedPaths
* and thus leak symlink targets. * and thus leak symlink targets.
*/ */
Path abspath = canonPath(path_); Path abspath = canonPath(path_.path.abs());
if (hasPrefix(abspath, corepkgsPrefix)) return abspath; if (hasPrefix(abspath, corepkgsPrefix)) return CanonPath(abspath);
for (auto & i : *allowedPaths) { for (auto & i : *allowedPaths) {
if (isDirOrInDir(abspath, i)) { if (isDirOrInDir(abspath, i)) {
@ -646,11 +647,11 @@ Path EvalState::checkSourcePath(const Path & path_)
/* Resolve symlinks. */ /* Resolve symlinks. */
debug("checking access to '%s'", abspath); debug("checking access to '%s'", abspath);
Path path = canonPath(abspath, true); SourcePath path = CanonPath(canonPath(abspath, true));
for (auto & i : *allowedPaths) { for (auto & i : *allowedPaths) {
if (isDirOrInDir(path, i)) { if (isDirOrInDir(path.path.abs(), i)) {
resolvedPaths[path_] = path; resolvedPaths.insert_or_assign(path_.path.abs(), path);
return path; return path;
} }
} }
@ -678,12 +679,12 @@ void EvalState::checkURI(const std::string & uri)
/* If the URI is a path, then check it against allowedPaths as /* If the URI is a path, then check it against allowedPaths as
well. */ well. */
if (hasPrefix(uri, "/")) { if (hasPrefix(uri, "/")) {
checkSourcePath(uri); checkSourcePath(CanonPath(uri));
return; return;
} }
if (hasPrefix(uri, "file://")) { if (hasPrefix(uri, "file://")) {
checkSourcePath(std::string(uri, 7)); checkSourcePath(CanonPath(std::string(uri, 7)));
return; return;
} }
@ -968,9 +969,9 @@ void Value::mkStringMove(const char * s, const NixStringContext & context)
} }
void Value::mkPath(std::string_view s) void Value::mkPath(const SourcePath & path)
{ {
mkPath(makeImmutableString(s)); mkPath(makeImmutableString(path.path.abs()));
} }
@ -1026,9 +1027,9 @@ void EvalState::mkThunk_(Value & v, Expr * expr)
void EvalState::mkPos(Value & v, PosIdx p) void EvalState::mkPos(Value & v, PosIdx p)
{ {
auto pos = positions[p]; auto pos = positions[p];
if (auto path = std::get_if<Path>(&pos.origin)) { if (auto path = std::get_if<SourcePath>(&pos.origin)) {
auto attrs = buildBindings(3); auto attrs = buildBindings(3);
attrs.alloc(sFile).mkString(*path); attrs.alloc(sFile).mkString(path->path.abs());
attrs.alloc(sLine).mkInt(pos.line); attrs.alloc(sLine).mkInt(pos.line);
attrs.alloc(sColumn).mkInt(pos.column); attrs.alloc(sColumn).mkInt(pos.column);
v.mkAttrs(attrs); v.mkAttrs(attrs);
@ -1094,7 +1095,7 @@ Value * ExprPath::maybeThunk(EvalState & state, Env & env)
} }
void EvalState::evalFile(const Path & path_, Value & v, bool mustBeTrivial) void EvalState::evalFile(const SourcePath & path_, Value & v, bool mustBeTrivial)
{ {
auto path = checkSourcePath(path_); auto path = checkSourcePath(path_);
@ -1104,7 +1105,7 @@ void EvalState::evalFile(const Path & path_, Value & v, bool mustBeTrivial)
return; return;
} }
Path resolvedPath = resolveExprPath(path); auto resolvedPath = resolveExprPath(path);
if ((i = fileEvalCache.find(resolvedPath)) != fileEvalCache.end()) { if ((i = fileEvalCache.find(resolvedPath)) != fileEvalCache.end()) {
v = i->second; v = i->second;
return; return;
@ -1132,8 +1133,8 @@ void EvalState::resetFileCache()
void EvalState::cacheFile( void EvalState::cacheFile(
const Path & path, const SourcePath & path,
const Path & resolvedPath, const SourcePath & resolvedPath,
Expr * e, Expr * e,
Value & v, Value & v,
bool mustBeTrivial) bool mustBeTrivial)
@ -1147,7 +1148,7 @@ void EvalState::cacheFile(
*e, *e,
this->baseEnv, this->baseEnv,
e->getPos() ? static_cast<std::shared_ptr<AbstractPos>>(positions[e->getPos()]) : nullptr, e->getPos() ? static_cast<std::shared_ptr<AbstractPos>>(positions[e->getPos()]) : nullptr,
"while evaluating the file '%1%':", resolvedPath) "while evaluating the file '%1%':", resolvedPath.to_string())
: nullptr; : nullptr;
// Enforce that 'flake.nix' is a direct attrset, not a // Enforce that 'flake.nix' is a direct attrset, not a
@ -1157,7 +1158,7 @@ void EvalState::cacheFile(
error("file '%s' must be an attribute set", path).debugThrow<EvalError>(); error("file '%s' must be an attribute set", path).debugThrow<EvalError>();
eval(e, v); eval(e, v);
} catch (Error & e) { } catch (Error & e) {
addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath); addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath.to_string());
throw; throw;
} }
@ -1418,8 +1419,8 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
} catch (Error & e) { } catch (Error & e) {
if (pos2) { if (pos2) {
auto pos2r = state.positions[pos2]; auto pos2r = state.positions[pos2];
auto origin = std::get_if<Path>(&pos2r.origin); auto origin = std::get_if<SourcePath>(&pos2r.origin);
if (!(origin && *origin == state.derivationNixPath)) if (!(origin && *origin == state.derivationInternal))
state.addErrorTrace(e, pos2, "while evaluating the attribute '%1%'", state.addErrorTrace(e, pos2, "while evaluating the attribute '%1%'",
showAttrPath(state, env, attrPath)); showAttrPath(state, env, attrPath));
} }
@ -1992,7 +1993,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
else if (firstType == nPath) { else if (firstType == nPath) {
if (!context.empty()) if (!context.empty())
state.error("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow<EvalError>(); state.error("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow<EvalError>();
v.mkPath(canonPath(str())); v.mkPath(CanonPath(canonPath(str())));
} else } else
v.mkStringMove(c_str(), context); v.mkStringMove(c_str(), context);
} }
@ -2170,8 +2171,14 @@ std::optional<std::string> EvalState::tryAttrsToString(const PosIdx pos, Value &
return {}; return {};
} }
BackedStringView EvalState::coerceToString(const PosIdx pos, Value &v, NixStringContext &context, BackedStringView EvalState::coerceToString(
std::string_view errorCtx, bool coerceMore, bool copyToStore, bool canonicalizePath) const PosIdx pos,
Value & v,
NixStringContext & context,
std::string_view errorCtx,
bool coerceMore,
bool copyToStore,
bool canonicalizePath)
{ {
forceValue(v, pos); forceValue(v, pos);
@ -2181,12 +2188,14 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value &v, NixString
} }
if (v.type() == nPath) { if (v.type() == nPath) {
BackedStringView path(PathView(v.path)); return
if (canonicalizePath) !canonicalizePath && !copyToStore
path = canonPath(*path); ? // FIXME: hack to preserve path literals that end in a
if (copyToStore) // slash, as in /foo/${x}.
path = store->printStorePath(copyPathToStore(context, std::move(path).toOwned())); v._path
return path; : copyToStore
? store->printStorePath(copyPathToStore(context, v.path()))
: std::string(v.path().path.abs());
} }
if (v.type() == nAttrs) { if (v.type() == nAttrs) {
@ -2247,19 +2256,17 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value &v, NixString
} }
StorePath EvalState::copyPathToStore(NixStringContext & context, const Path & path) StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePath & path)
{ {
if (nix::isDerivation(path)) if (nix::isDerivation(path.path.abs()))
error("file names are not allowed to end in '%1%'", drvExtension).debugThrow<EvalError>(); error("file names are not allowed to end in '%1%'", drvExtension).debugThrow<EvalError>();
auto dstPath = [&]() -> StorePath
{
auto i = srcToStore.find(path); auto i = srcToStore.find(path);
if (i != srcToStore.end()) return i->second;
auto dstPath = settings.readOnlyMode auto dstPath = i != srcToStore.end()
? store->computeStorePathForPath(std::string(baseNameOf(path)), checkSourcePath(path)).first ? i->second
: store->addToStore(std::string(baseNameOf(path)), checkSourcePath(path), FileIngestionMethod::Recursive, htSHA256, defaultPathFilter, repair); : [&]() {
auto dstPath = path.fetchToStore(store, path.baseName(), nullptr, repair);
allowPath(dstPath); allowPath(dstPath);
srcToStore.insert_or_assign(path, dstPath); srcToStore.insert_or_assign(path, dstPath);
printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, store->printStorePath(dstPath)); printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, store->printStorePath(dstPath));
@ -2273,12 +2280,12 @@ StorePath EvalState::copyPathToStore(NixStringContext & context, const Path & pa
} }
Path EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext & context, std::string_view errorCtx) 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(); auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
if (path == "" || path[0] != '/') if (path == "" || path[0] != '/')
error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow<EvalError>(); error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
return path; return CanonPath(path);
} }
@ -2321,7 +2328,7 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
return strcmp(v1.string.s, v2.string.s) == 0; return strcmp(v1.string.s, v2.string.s) == 0;
case nPath: case nPath:
return strcmp(v1.path, v2.path) == 0; return strcmp(v1._path, v2._path) == 0;
case nNull: case nNull:
return true; return true;
@ -2448,8 +2455,8 @@ void EvalState::printStats()
else else
obj["name"] = nullptr; obj["name"] = nullptr;
if (auto pos = positions[fun->pos]) { if (auto pos = positions[fun->pos]) {
if (auto path = std::get_if<Path>(&pos.origin)) if (auto path = std::get_if<SourcePath>(&pos.origin))
obj["file"] = *path; obj["file"] = path->to_string();
obj["line"] = pos.line; obj["line"] = pos.line;
obj["column"] = pos.column; obj["column"] = pos.column;
} }
@ -2463,8 +2470,8 @@ void EvalState::printStats()
for (auto & i : attrSelects) { for (auto & i : attrSelects) {
json obj = json::object(); json obj = json::object();
if (auto pos = positions[i.first]) { if (auto pos = positions[i.first]) {
if (auto path = std::get_if<Path>(&pos.origin)) if (auto path = std::get_if<SourcePath>(&pos.origin))
obj["file"] = *path; obj["file"] = path->to_string();
obj["line"] = pos.line; obj["line"] = pos.line;
obj["column"] = pos.column; obj["column"] = pos.column;
} }

View file

@ -8,6 +8,7 @@
#include "symbol-table.hh" #include "symbol-table.hh"
#include "config.hh" #include "config.hh"
#include "experimental-features.hh" #include "experimental-features.hh"
#include "input-accessor.hh"
#include <map> #include <map>
#include <optional> #include <optional>
@ -59,17 +60,11 @@ std::unique_ptr<ValMap> mapStaticEnvBindings(const SymbolTable & st, const Stati
void copyContext(const Value & v, NixStringContext & context); void copyContext(const Value & v, NixStringContext & context);
/**
* Cache for calls to addToStore(); maps source paths to the store
* paths.
*/
typedef std::map<Path, StorePath> SrcToStore;
std::string printValue(const EvalState & state, const Value & v); std::string printValue(const EvalState & state, const Value & v);
std::ostream & operator << (std::ostream & os, const ValueType t); std::ostream & operator << (std::ostream & os, const ValueType t);
// FIXME: maybe change this to an std::variant<SourcePath, URL>.
typedef std::pair<std::string, std::string> SearchPathElem; typedef std::pair<std::string, std::string> SearchPathElem;
typedef std::list<SearchPathElem> SearchPath; typedef std::list<SearchPathElem> SearchPath;
@ -137,8 +132,6 @@ public:
SymbolTable symbols; SymbolTable symbols;
PosTable positions; PosTable positions;
static inline std::string derivationNixPath = "//builtin/derivation.nix";
const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue, const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue,
sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls, sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls,
sFile, sLine, sColumn, sFunctor, sToString, sFile, sLine, sColumn, sFunctor, sToString,
@ -149,7 +142,6 @@ public:
sDescription, sSelf, sEpsilon, sStartSet, sOperator, sKey, sPath, sDescription, sSelf, sEpsilon, sStartSet, sOperator, sKey, sPath,
sPrefix, sPrefix,
sOutputSpecified; sOutputSpecified;
Symbol sDerivationNix;
/** /**
* If set, force copying files to the Nix store even if they * If set, force copying files to the Nix store even if they
@ -165,6 +157,8 @@ public:
Bindings emptyBindings; Bindings emptyBindings;
const SourcePath derivationInternal;
/** /**
* Store used to materialise .drv files. * Store used to materialise .drv files.
*/ */
@ -234,15 +228,18 @@ public:
} }
private: private:
SrcToStore srcToStore;
/* Cache for calls to addToStore(); maps source paths to the store
paths. */
std::map<SourcePath, StorePath> srcToStore;
/** /**
* A cache from path names to parse trees. * A cache from path names to parse trees.
*/ */
#if HAVE_BOEHMGC #if HAVE_BOEHMGC
typedef std::map<Path, Expr *, std::less<Path>, traceable_allocator<std::pair<const Path, Expr *>>> FileParseCache; typedef std::map<SourcePath, Expr *, std::less<SourcePath>, traceable_allocator<std::pair<const SourcePath, Expr *>>> FileParseCache;
#else #else
typedef std::map<Path, Expr *> FileParseCache; typedef std::map<SourcePath, Expr *> FileParseCache;
#endif #endif
FileParseCache fileParseCache; FileParseCache fileParseCache;
@ -250,9 +247,9 @@ private:
* A cache from path names to values. * A cache from path names to values.
*/ */
#if HAVE_BOEHMGC #if HAVE_BOEHMGC
typedef std::map<Path, Value, std::less<Path>, traceable_allocator<std::pair<const Path, Value>>> FileEvalCache; typedef std::map<SourcePath, Value, std::less<SourcePath>, traceable_allocator<std::pair<const SourcePath, Value>>> FileEvalCache;
#else #else
typedef std::map<Path, Value> FileEvalCache; typedef std::map<SourcePath, Value> FileEvalCache;
#endif #endif
FileEvalCache fileEvalCache; FileEvalCache fileEvalCache;
@ -263,7 +260,7 @@ private:
/** /**
* Cache used by checkSourcePath(). * Cache used by checkSourcePath().
*/ */
std::unordered_map<Path, Path> resolvedPaths; std::unordered_map<Path, SourcePath> resolvedPaths;
/** /**
* Cache used by prim_match(). * Cache used by prim_match().
@ -294,6 +291,12 @@ public:
SearchPath getSearchPath() { return searchPath; } SearchPath getSearchPath() { return searchPath; }
/**
* Return a `SourcePath` that refers to `path` in the root
* filesystem.
*/
SourcePath rootPath(CanonPath path);
/** /**
* Allow access to a path. * Allow access to a path.
*/ */
@ -314,7 +317,7 @@ public:
* Check whether access to a path is allowed and throw an error if * Check whether access to a path is allowed and throw an error if
* not. Otherwise return the canonicalised path. * not. Otherwise return the canonicalised path.
*/ */
Path checkSourcePath(const Path & path); SourcePath checkSourcePath(const SourcePath & path);
void checkURI(const std::string & uri); void checkURI(const std::string & uri);
@ -332,14 +335,14 @@ public:
/** /**
* Parse a Nix expression from the specified file. * Parse a Nix expression from the specified file.
*/ */
Expr * parseExprFromFile(const Path & path); Expr * parseExprFromFile(const SourcePath & path);
Expr * parseExprFromFile(const Path & path, std::shared_ptr<StaticEnv> & staticEnv); Expr * parseExprFromFile(const SourcePath & path, std::shared_ptr<StaticEnv> & staticEnv);
/** /**
* Parse a Nix expression from the specified string. * Parse a Nix expression from the specified string.
*/ */
Expr * parseExprFromString(std::string s, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv); Expr * parseExprFromString(std::string s, const SourcePath & basePath, std::shared_ptr<StaticEnv> & staticEnv);
Expr * parseExprFromString(std::string s, const Path & basePath); Expr * parseExprFromString(std::string s, const SourcePath & basePath);
Expr * parseStdin(); Expr * parseStdin();
@ -348,14 +351,14 @@ public:
* form. Optionally enforce that the top-level expression is * form. Optionally enforce that the top-level expression is
* trivial (i.e. doesn't require arbitrary computation). * trivial (i.e. doesn't require arbitrary computation).
*/ */
void evalFile(const Path & path, Value & v, bool mustBeTrivial = false); void evalFile(const SourcePath & path, Value & v, bool mustBeTrivial = false);
/** /**
* Like `evalFile`, but with an already parsed expression. * Like `evalFile`, but with an already parsed expression.
*/ */
void cacheFile( void cacheFile(
const Path & path, const SourcePath & path,
const Path & resolvedPath, const SourcePath & resolvedPath,
Expr * e, Expr * e,
Value & v, Value & v,
bool mustBeTrivial = false); bool mustBeTrivial = false);
@ -365,8 +368,8 @@ public:
/** /**
* Look up a file in the search path. * Look up a file in the search path.
*/ */
Path findFile(const std::string_view path); SourcePath findFile(const std::string_view path);
Path findFile(SearchPath & searchPath, const std::string_view path, const PosIdx pos = noPos); SourcePath findFile(SearchPath & searchPath, const std::string_view path, const PosIdx pos = noPos);
/** /**
* If the specified search path element is a URI, download it. * If the specified search path element is a URI, download it.
@ -454,7 +457,7 @@ public:
bool coerceMore = false, bool copyToStore = true, bool coerceMore = false, bool copyToStore = true,
bool canonicalizePath = true); bool canonicalizePath = true);
StorePath copyPathToStore(NixStringContext & context, const Path & path); StorePath copyPathToStore(NixStringContext & context, const SourcePath & path);
/** /**
* Path coercion. * Path coercion.
@ -463,7 +466,7 @@ public:
* path. The result is guaranteed to be a canonicalised, absolute * path. The result is guaranteed to be a canonicalised, absolute
* path. Nothing is copied to the store. * path. Nothing is copied to the store.
*/ */
Path coerceToPath(const PosIdx pos, Value & v, NixStringContext & context, std::string_view errorCtx); SourcePath coerceToPath(const PosIdx pos, Value & v, NixStringContext & context, std::string_view errorCtx);
/** /**
* Like coerceToPath, but the result must be a store path. * Like coerceToPath, but the result must be a store path.
@ -525,7 +528,7 @@ private:
char * text, char * text,
size_t length, size_t length,
Pos::Origin origin, Pos::Origin origin,
Path basePath, const SourcePath & basePath,
std::shared_ptr<StaticEnv> & staticEnv); std::shared_ptr<StaticEnv> & staticEnv);
public: public:
@ -656,7 +659,7 @@ std::string showType(const Value & v);
/** /**
* If `path` refers to a directory, then append "/default.nix". * If `path` refers to a directory, then append "/default.nix".
*/ */
Path resolveExprPath(Path path); SourcePath resolveExprPath(const SourcePath & path);
struct InvalidPathError : EvalError struct InvalidPathError : EvalError
{ {

View file

@ -222,9 +222,9 @@ static Flake getFlake(
throw Error("source tree referenced by '%s' does not contain a '%s/flake.nix' file", lockedRef, lockedRef.subdir); throw Error("source tree referenced by '%s' does not contain a '%s/flake.nix' file", lockedRef, lockedRef.subdir);
Value vInfo; Value vInfo;
state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack state.evalFile(CanonPath(flakeFile), vInfo, true); // FIXME: symlink attack
expectType(state, nAttrs, vInfo, state.positions.add({flakeFile}, 1, 1)); expectType(state, nAttrs, vInfo, state.positions.add({CanonPath(flakeFile)}, 1, 1));
if (auto description = vInfo.attrs->get(state.sDescription)) { if (auto description = vInfo.attrs->get(state.sDescription)) {
expectType(state, nString, *description->value, description->pos); expectType(state, nString, *description->value, description->pos);
@ -745,7 +745,7 @@ void callFlake(EvalState & state,
state.vCallFlake = allocRootValue(state.allocValue()); state.vCallFlake = allocRootValue(state.allocValue());
state.eval(state.parseExprFromString( state.eval(state.parseExprFromString(
#include "call-flake.nix.gen.hh" #include "call-flake.nix.gen.hh"
, "/"), **state.vCallFlake); , CanonPath::root), **state.vCallFlake);
} }
state.callFunction(**state.vCallFlake, *vLocks, *vTmp1, noPos); state.callFunction(**state.vCallFlake, *vLocks, *vTmp1, noPos);

View file

@ -32,9 +32,9 @@ struct PosAdapter : AbstractPos
// Get rid of the null terminators added by the parser. // Get rid of the null terminators added by the parser.
return std::string(s.source->c_str()); return std::string(s.source->c_str());
}, },
[](const Path & path) -> std::optional<std::string> { [](const SourcePath & path) -> std::optional<std::string> {
try { try {
return readFile(path); return path.readFile();
} catch (Error &) { } catch (Error &) {
return std::nullopt; return std::nullopt;
} }
@ -48,7 +48,7 @@ struct PosAdapter : AbstractPos
[&](const Pos::none_tag &) { out << "«none»"; }, [&](const Pos::none_tag &) { out << "«none»"; },
[&](const Pos::Stdin &) { out << "«stdin»"; }, [&](const Pos::Stdin &) { out << "«stdin»"; },
[&](const Pos::String & s) { out << "«string»"; }, [&](const Pos::String & s) { out << "«string»"; },
[&](const Path & path) { out << path; } [&](const SourcePath & path) { out << path; }
}, origin); }, origin);
} }
}; };

View file

@ -34,7 +34,7 @@ struct Pos
struct Stdin { ref<std::string> source; }; struct Stdin { ref<std::string> source; };
struct String { ref<std::string> source; }; struct String { ref<std::string> source; };
typedef std::variant<none_tag, Stdin, String, Path> Origin; typedef std::variant<none_tag, Stdin, String, SourcePath> Origin;
Origin origin; Origin origin;

View file

@ -31,7 +31,7 @@ namespace nix {
EvalState & state; EvalState & state;
SymbolTable & symbols; SymbolTable & symbols;
Expr * result; Expr * result;
Path basePath; SourcePath basePath;
PosTable::Origin origin; PosTable::Origin origin;
std::optional<ErrorInfo> error; std::optional<ErrorInfo> error;
}; };
@ -509,7 +509,7 @@ string_parts_interpolated
path_start path_start
: PATH { : PATH {
Path path(absPath({$1.p, $1.l}, data->basePath)); Path path(absPath({$1.p, $1.l}, data->basePath.path.abs()));
/* add back in the trailing '/' to the first segment */ /* add back in the trailing '/' to the first segment */
if ($1.p[$1.l-1] == '/' && $1.l > 1) if ($1.p[$1.l-1] == '/' && $1.l > 1)
path += "/"; path += "/";
@ -651,7 +651,7 @@ Expr * EvalState::parse(
char * text, char * text,
size_t length, size_t length,
Pos::Origin origin, Pos::Origin origin,
Path basePath, const SourcePath & basePath,
std::shared_ptr<StaticEnv> & staticEnv) std::shared_ptr<StaticEnv> & staticEnv)
{ {
yyscan_t scanner; yyscan_t scanner;
@ -675,48 +675,36 @@ Expr * EvalState::parse(
} }
Path resolveExprPath(Path path) SourcePath resolveExprPath(const SourcePath & path)
{ {
assert(path[0] == '/');
unsigned int followCount = 0, maxFollow = 1024;
/* If `path' is a symlink, follow it. This is so that relative /* If `path' is a symlink, follow it. This is so that relative
path references work. */ path references work. */
struct stat st; auto path2 = path.resolveSymlinks();
while (true) {
// Basic cycle/depth limit to avoid infinite loops.
if (++followCount >= maxFollow)
throw Error("too many symbolic links encountered while traversing the path '%s'", path);
st = lstat(path);
if (!S_ISLNK(st.st_mode)) break;
path = absPath(readLink(path), dirOf(path));
}
/* If `path' refers to a directory, append `/default.nix'. */ /* If `path' refers to a directory, append `/default.nix'. */
if (S_ISDIR(st.st_mode)) if (path2.lstat().type == InputAccessor::tDirectory)
path = canonPath(path + "/default.nix"); return path2 + "default.nix";
return path; return path2;
} }
Expr * EvalState::parseExprFromFile(const Path & path) Expr * EvalState::parseExprFromFile(const SourcePath & path)
{ {
return parseExprFromFile(path, staticBaseEnv); return parseExprFromFile(path, staticBaseEnv);
} }
Expr * EvalState::parseExprFromFile(const Path & path, std::shared_ptr<StaticEnv> & staticEnv) Expr * EvalState::parseExprFromFile(const SourcePath & path, std::shared_ptr<StaticEnv> & staticEnv)
{ {
auto buffer = readFile(path); auto buffer = path.readFile();
// readFile should have left some extra space for terminators // readFile hopefully have left some extra space for terminators
buffer.append("\0\0", 2); buffer.append("\0\0", 2);
return parse(buffer.data(), buffer.size(), path, dirOf(path), staticEnv); return parse(buffer.data(), buffer.size(), Pos::Origin(path), path.parent(), staticEnv);
} }
Expr * EvalState::parseExprFromString(std::string s_, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv) Expr * EvalState::parseExprFromString(std::string s_, const SourcePath & basePath, std::shared_ptr<StaticEnv> & staticEnv)
{ {
auto s = make_ref<std::string>(std::move(s_)); auto s = make_ref<std::string>(std::move(s_));
s->append("\0\0", 2); s->append("\0\0", 2);
@ -724,7 +712,7 @@ Expr * EvalState::parseExprFromString(std::string s_, const Path & basePath, std
} }
Expr * EvalState::parseExprFromString(std::string s, const Path & basePath) Expr * EvalState::parseExprFromString(std::string s, const SourcePath & basePath)
{ {
return parseExprFromString(std::move(s), basePath, staticBaseEnv); return parseExprFromString(std::move(s), basePath, staticBaseEnv);
} }
@ -737,7 +725,7 @@ Expr * EvalState::parseStdin()
// drainFD should have left some extra space for terminators // drainFD should have left some extra space for terminators
buffer.append("\0\0", 2); buffer.append("\0\0", 2);
auto s = make_ref<std::string>(std::move(buffer)); auto s = make_ref<std::string>(std::move(buffer));
return parse(s->data(), s->size(), Pos::Stdin{.source = s}, absPath("."), staticBaseEnv); return parse(s->data(), s->size(), Pos::Stdin{.source = s}, rootPath(CanonPath::fromCwd()), staticBaseEnv);
} }
@ -757,13 +745,13 @@ void EvalState::addToSearchPath(const std::string & s)
} }
Path EvalState::findFile(const std::string_view path) SourcePath EvalState::findFile(const std::string_view path)
{ {
return findFile(searchPath, path); return findFile(searchPath, path);
} }
Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, const PosIdx pos) SourcePath EvalState::findFile(SearchPath & searchPath, const std::string_view path, const PosIdx pos)
{ {
for (auto & i : searchPath) { for (auto & i : searchPath) {
std::string suffix; std::string suffix;
@ -779,11 +767,11 @@ Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, c
auto r = resolveSearchPathElem(i); auto r = resolveSearchPathElem(i);
if (!r.first) continue; if (!r.first) continue;
Path res = r.second + suffix; Path res = r.second + suffix;
if (pathExists(res)) return canonPath(res); if (pathExists(res)) return CanonPath(canonPath(res));
} }
if (hasPrefix(path, "nix/")) if (hasPrefix(path, "nix/"))
return concatStrings(corepkgsPrefix, path.substr(4)); return CanonPath(concatStrings(corepkgsPrefix, path.substr(4)));
debugThrow(ThrownError({ debugThrow(ThrownError({
.msg = hintfmt(evalSettings.pureEval .msg = hintfmt(evalSettings.pureEval

10
src/libexpr/paths.cc Normal file
View file

@ -0,0 +1,10 @@
#include "eval.hh"
namespace nix {
SourcePath EvalState::rootPath(CanonPath path)
{
return std::move(path);
}
}

View file

@ -109,7 +109,7 @@ struct RealisePathFlags {
bool checkForPureEval = true; bool checkForPureEval = true;
}; };
static Path realisePath(EvalState & state, const PosIdx pos, Value & v, const RealisePathFlags flags = {}) static SourcePath realisePath(EvalState & state, const PosIdx pos, Value & v, const RealisePathFlags flags = {})
{ {
NixStringContext context; NixStringContext context;
@ -118,7 +118,7 @@ static Path realisePath(EvalState & state, const PosIdx pos, Value & v, const Re
try { try {
StringMap rewrites = state.realiseContext(context); StringMap rewrites = state.realiseContext(context);
auto realPath = state.toRealPath(rewriteStrings(path, rewrites), context); auto realPath = state.rootPath(CanonPath(state.toRealPath(rewriteStrings(path.path.abs(), rewrites), context)));
return flags.checkForPureEval return flags.checkForPureEval
? state.checkSourcePath(realPath) ? state.checkSourcePath(realPath)
@ -170,30 +170,30 @@ static void mkOutputString(
static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * vScope, Value & v) static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * vScope, Value & v)
{ {
auto path = realisePath(state, pos, vPath); auto path = realisePath(state, pos, vPath);
auto path2 = path.path.abs();
// FIXME // FIXME
auto isValidDerivationInStore = [&]() -> std::optional<StorePath> { auto isValidDerivationInStore = [&]() -> std::optional<StorePath> {
if (!state.store->isStorePath(path)) if (!state.store->isStorePath(path2))
return std::nullopt; return std::nullopt;
auto storePath = state.store->parseStorePath(path); auto storePath = state.store->parseStorePath(path2);
if (!(state.store->isValidPath(storePath) && isDerivation(path))) if (!(state.store->isValidPath(storePath) && isDerivation(path2)))
return std::nullopt; return std::nullopt;
return storePath; return storePath;
}; };
if (auto optStorePath = isValidDerivationInStore()) { if (auto storePath = isValidDerivationInStore()) {
auto storePath = *optStorePath; Derivation drv = state.store->readDerivation(*storePath);
Derivation drv = state.store->readDerivation(storePath);
auto attrs = state.buildBindings(3 + drv.outputs.size()); auto attrs = state.buildBindings(3 + drv.outputs.size());
attrs.alloc(state.sDrvPath).mkString(path, { attrs.alloc(state.sDrvPath).mkString(path2, {
NixStringContextElem::DrvDeep { .drvPath = storePath }, NixStringContextElem::DrvDeep { .drvPath = *storePath },
}); });
attrs.alloc(state.sName).mkString(drv.env["name"]); attrs.alloc(state.sName).mkString(drv.env["name"]);
auto & outputsVal = attrs.alloc(state.sOutputs); auto & outputsVal = attrs.alloc(state.sOutputs);
state.mkList(outputsVal, drv.outputs.size()); state.mkList(outputsVal, drv.outputs.size());
for (const auto & [i, o] : enumerate(drv.outputs)) { for (const auto & [i, o] : enumerate(drv.outputs)) {
mkOutputString(state, attrs, storePath, drv, o); mkOutputString(state, attrs, *storePath, drv, o);
(outputsVal.listElems()[i] = state.allocValue())->mkString(o.first); (outputsVal.listElems()[i] = state.allocValue())->mkString(o.first);
} }
@ -204,7 +204,7 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
state.vImportedDrvToDerivation = allocRootValue(state.allocValue()); state.vImportedDrvToDerivation = allocRootValue(state.allocValue());
state.eval(state.parseExprFromString( state.eval(state.parseExprFromString(
#include "imported-drv-to-derivation.nix.gen.hh" #include "imported-drv-to-derivation.nix.gen.hh"
, "/"), **state.vImportedDrvToDerivation); , CanonPath::root), **state.vImportedDrvToDerivation);
} }
state.forceFunction(**state.vImportedDrvToDerivation, pos, "while evaluating imported-drv-to-derivation.nix.gen.hh"); state.forceFunction(**state.vImportedDrvToDerivation, pos, "while evaluating imported-drv-to-derivation.nix.gen.hh");
@ -212,10 +212,10 @@ 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"); state.forceAttrs(v, pos, "while calling imported-drv-to-derivation.nix.gen.hh");
} }
else if (path == corepkgsPrefix + "fetchurl.nix") { else if (path2 == corepkgsPrefix + "fetchurl.nix") {
state.eval(state.parseExprFromString( state.eval(state.parseExprFromString(
#include "fetchurl.nix.gen.hh" #include "fetchurl.nix.gen.hh"
, "/"), v); , CanonPath::root), v);
} }
else { else {
@ -336,7 +336,7 @@ void prim_importNative(EvalState & state, const PosIdx pos, Value * * args, Valu
std::string sym(state.forceStringNoCtx(*args[1], pos, "while evaluating the second argument passed to builtins.importNative")); std::string sym(state.forceStringNoCtx(*args[1], pos, "while evaluating the second argument passed to builtins.importNative"));
void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL); void *handle = dlopen(path.path.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!handle) if (!handle)
state.debugThrowLastTrace(EvalError("could not open '%1%': %2%", path, dlerror())); state.debugThrowLastTrace(EvalError("could not open '%1%': %2%", path, dlerror()));
@ -384,7 +384,7 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
auto output = runProgram(program, true, commandArgs); auto output = runProgram(program, true, commandArgs);
Expr * parsed; Expr * parsed;
try { try {
parsed = state.parseExprFromString(std::move(output), "/"); parsed = state.parseExprFromString(std::move(output), state.rootPath(CanonPath::root));
} catch (Error & e) { } catch (Error & e) {
e.addTrace(state.positions[pos], "while parsing the output from '%1%'", program); e.addTrace(state.positions[pos], "while parsing the output from '%1%'", program);
throw; throw;
@ -594,7 +594,7 @@ struct CompareValues
case nString: case nString:
return strcmp(v1->string.s, v2->string.s) < 0; return strcmp(v1->string.s, v2->string.s) < 0;
case nPath: case nPath:
return strcmp(v1->path, v2->path) < 0; return strcmp(v1->_path, v2->_path) < 0;
case nList: case nList:
// Lexicographic comparison // Lexicographic comparison
for (size_t i = 0;; i++) { for (size_t i = 0;; i++) {
@ -1445,8 +1445,8 @@ static RegisterPrimOp primop_placeholder({
static void prim_toPath(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_toPath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
NixStringContext context; NixStringContext context;
Path path = state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.toPath"); auto path = state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.toPath");
v.mkString(canonPath(path), context); v.mkString(path.path.abs(), context);
} }
static RegisterPrimOp primop_toPath({ static RegisterPrimOp primop_toPath({
@ -1476,21 +1476,22 @@ static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args,
})); }));
NixStringContext context; NixStringContext context;
Path path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.storePath")); 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 /* Resolve symlinks in path, unless path itself is a symlink
directly in the store. The latter condition is necessary so directly in the store. The latter condition is necessary so
e.g. nix-push does the right thing. */ e.g. nix-push does the right thing. */
if (!state.store->isStorePath(path)) path = canonPath(path, true); if (!state.store->isStorePath(path.abs()))
if (!state.store->isInStore(path)) path = CanonPath(canonPath(path.abs(), true));
if (!state.store->isInStore(path.abs()))
state.debugThrowLastTrace(EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("path '%1%' is not in the Nix store", path), .msg = hintfmt("path '%1%' is not in the Nix store", path),
.errPos = state.positions[pos] .errPos = state.positions[pos]
})); }));
auto path2 = state.store->toStorePath(path).first; auto path2 = state.store->toStorePath(path.abs()).first;
if (!settings.readOnlyMode) if (!settings.readOnlyMode)
state.store->ensurePath(path2); state.store->ensurePath(path2);
context.insert(NixStringContextElem::Opaque { .path = path2 }); context.insert(NixStringContextElem::Opaque { .path = path2 });
v.mkString(path, context); v.mkString(path.abs(), context);
} }
static RegisterPrimOp primop_storePath({ static RegisterPrimOp primop_storePath({
@ -1521,7 +1522,7 @@ static void prim_pathExists(EvalState & state, const PosIdx pos, Value * * args,
auto path = realisePath(state, pos, *args[0], { .checkForPureEval = false }); auto path = realisePath(state, pos, *args[0], { .checkForPureEval = false });
try { try {
v.mkBool(pathExists(state.checkSourcePath(path))); v.mkBool(state.checkSourcePath(path).pathExists());
} catch (SysError & e) { } catch (SysError & e) {
/* Don't give away info from errors while canonicalising /* Don't give away info from errors while canonicalising
path in restricted mode. */ path in restricted mode. */
@ -1567,12 +1568,18 @@ static RegisterPrimOp primop_baseNameOf({
of the argument. */ of the argument. */
static void prim_dirOf(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_dirOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
state.forceValue(*args[0], pos);
if (args[0]->type() == nPath) {
auto path = args[0]->path();
v.mkPath(path.path.isRoot() ? path : path.parent());
} else {
NixStringContext context; NixStringContext context;
auto path = state.coerceToString(pos, *args[0], context, auto path = state.coerceToString(pos, *args[0], context,
"while evaluating the first argument passed to builtins.dirOf", "while evaluating the first argument passed to 'builtins.dirOf'",
false, false); false, false);
auto dir = dirOf(*path); auto dir = dirOf(*path);
if (args[0]->type() == nPath) v.mkPath(dir); else v.mkString(dir, context); v.mkString(dir, context);
}
} }
static RegisterPrimOp primop_dirOf({ static RegisterPrimOp primop_dirOf({
@ -1590,13 +1597,13 @@ static RegisterPrimOp primop_dirOf({
static void prim_readFile(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_readFile(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
auto path = realisePath(state, pos, *args[0]); auto path = realisePath(state, pos, *args[0]);
auto s = readFile(path); auto s = path.readFile();
if (s.find((char) 0) != std::string::npos) if (s.find((char) 0) != std::string::npos)
state.debugThrowLastTrace(Error("the contents of the file '%1%' cannot be represented as a Nix string", path)); state.debugThrowLastTrace(Error("the contents of the file '%1%' cannot be represented as a Nix string", path));
StorePathSet refs; StorePathSet refs;
if (state.store->isInStore(path)) { if (state.store->isInStore(path.path.abs())) {
try { try {
refs = state.store->queryPathInfo(state.store->toStorePath(path).first)->references; refs = state.store->queryPathInfo(state.store->toStorePath(path.path.abs()).first)->references;
} catch (Error &) { // FIXME: should be InvalidPathError } catch (Error &) { // FIXME: should be InvalidPathError
} }
// Re-scan references to filter down to just the ones that actually occur in the file. // Re-scan references to filter down to just the ones that actually occur in the file.
@ -1682,7 +1689,7 @@ static void prim_hashFile(EvalState & state, const PosIdx pos, Value * * args, V
auto path = realisePath(state, pos, *args[1]); auto path = realisePath(state, pos, *args[1]);
v.mkString(hashFile(*ht, path).to_string(Base16, false)); v.mkString(hashString(*ht, path.readFile()).to_string(Base16, false));
} }
static RegisterPrimOp primop_hashFile({ static RegisterPrimOp primop_hashFile({
@ -1696,26 +1703,20 @@ static RegisterPrimOp primop_hashFile({
.fun = prim_hashFile, .fun = prim_hashFile,
}); });
static std::string_view fileTypeToString(InputAccessor::Type type)
/* Stringize a directory entry enum. Used by `readFileType' and `readDir'. */
static const char * dirEntTypeToString(unsigned char dtType)
{ {
/* Enum DT_(DIR|LNK|REG|UNKNOWN) */ return
switch(dtType) { type == InputAccessor::Type::tRegular ? "regular" :
case DT_REG: return "regular"; break; type == InputAccessor::Type::tDirectory ? "directory" :
case DT_DIR: return "directory"; break; type == InputAccessor::Type::tSymlink ? "symlink" :
case DT_LNK: return "symlink"; break; "unknown";
default: return "unknown"; break;
}
return "unknown"; /* Unreachable */
} }
static void prim_readFileType(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_readFileType(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
auto path = realisePath(state, pos, *args[0]); auto path = realisePath(state, pos, *args[0]);
/* Retrieve the directory entry type and stringize it. */ /* Retrieve the directory entry type and stringize it. */
v.mkString(dirEntTypeToString(getFileType(path))); v.mkString(fileTypeToString(path.lstat().type));
} }
static RegisterPrimOp primop_readFileType({ static RegisterPrimOp primop_readFileType({
@ -1736,8 +1737,7 @@ static void prim_readDir(EvalState & state, const PosIdx pos, Value * * args, Va
// Retrieve directory entries for all nodes in a directory. // Retrieve directory entries for all nodes in a directory.
// This is similar to `getFileType` but is optimized to reduce system calls // This is similar to `getFileType` but is optimized to reduce system calls
// on many systems. // on many systems.
DirEntries entries = readDirectory(path); auto entries = path.readDirectory();
auto attrs = state.buildBindings(entries.size()); auto attrs = state.buildBindings(entries.size());
// If we hit unknown directory entry types we may need to fallback to // If we hit unknown directory entry types we may need to fallback to
@ -1746,22 +1746,21 @@ static void prim_readDir(EvalState & state, const PosIdx pos, Value * * args, Va
// `builtins.readFileType` application. // `builtins.readFileType` application.
Value * readFileType = nullptr; Value * readFileType = nullptr;
for (auto & ent : entries) { for (auto & [name, type] : entries) {
auto & attr = attrs.alloc(ent.name); auto & attr = attrs.alloc(name);
if (ent.type == DT_UNKNOWN) { if (!type) {
// Some filesystems or operating systems may not be able to return // Some filesystems or operating systems may not be able to return
// detailed node info quickly in this case we produce a thunk to // detailed node info quickly in this case we produce a thunk to
// query the file type lazily. // query the file type lazily.
auto epath = state.allocValue(); auto epath = state.allocValue();
Path path2 = path + "/" + ent.name; epath->mkPath(path + name);
epath->mkString(path2);
if (!readFileType) if (!readFileType)
readFileType = &state.getBuiltin("readFileType"); readFileType = &state.getBuiltin("readFileType");
attr.mkApp(readFileType, epath); attr.mkApp(readFileType, epath);
} else { } else {
// This branch of the conditional is much more likely. // This branch of the conditional is much more likely.
// Here we just stringize the directory entry type. // Here we just stringize the directory entry type.
attr.mkString(dirEntTypeToString(ent.type)); attr.mkString(fileTypeToString(*type));
} }
} }
@ -2068,7 +2067,7 @@ static RegisterPrimOp primop_toFile({
static void addPath( static void addPath(
EvalState & state, EvalState & state,
const PosIdx pos, const PosIdx pos,
const std::string & name, std::string_view name,
Path path, Path path,
Value * filterFun, Value * filterFun,
FileIngestionMethod method, FileIngestionMethod method,
@ -2096,7 +2095,7 @@ static void addPath(
path = evalSettings.pureEval && expectedHash path = evalSettings.pureEval && expectedHash
? path ? path
: state.checkSourcePath(path); : state.checkSourcePath(CanonPath(path)).path.abs();
PathFilter filter = filterFun ? ([&](const Path & path) { PathFilter filter = filterFun ? ([&](const Path & path) {
auto st = lstat(path); auto st = lstat(path);
@ -2149,9 +2148,10 @@ static void addPath(
static void prim_filterSource(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_filterSource(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
NixStringContext context; NixStringContext context;
Path path = state.coerceToPath(pos, *args[1], context, "while evaluating the second argument (the path to filter) passed to builtins.filterSource"); auto path = state.coerceToPath(pos, *args[1], context,
"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"); state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.filterSource");
addPath(state, pos, std::string(baseNameOf(path)), path, args[0], FileIngestionMethod::Recursive, std::nullopt, v, context); addPath(state, pos, path.baseName(), path.path.abs(), args[0], FileIngestionMethod::Recursive, std::nullopt, v, context);
} }
static RegisterPrimOp primop_filterSource({ static RegisterPrimOp primop_filterSource({
@ -2211,18 +2211,19 @@ static RegisterPrimOp primop_filterSource({
static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.path"); std::optional<SourcePath> path;
Path path;
std::string name; std::string name;
Value * filterFun = nullptr; Value * filterFun = nullptr;
auto method = FileIngestionMethod::Recursive; auto method = FileIngestionMethod::Recursive;
std::optional<Hash> expectedHash; std::optional<Hash> expectedHash;
NixStringContext context; NixStringContext context;
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to 'builtins.path'");
for (auto & attr : *args[0]->attrs) { for (auto & attr : *args[0]->attrs) {
auto n = state.symbols[attr.name]; auto n = state.symbols[attr.name];
if (n == "path") if (n == "path")
path = state.coerceToPath(attr.pos, *attr.value, context, "while evaluating the `path` attribute passed to builtins.path"); path.emplace(state.coerceToPath(attr.pos, *attr.value, context, "while evaluating the 'path' attribute passed to 'builtins.path'"));
else if (attr.name == state.sName) else if (attr.name == state.sName)
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.path"); name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.path");
else if (n == "filter") else if (n == "filter")
@ -2237,15 +2238,15 @@ static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value
.errPos = state.positions[attr.pos] .errPos = state.positions[attr.pos]
})); }));
} }
if (path.empty()) if (!path)
state.debugThrowLastTrace(EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("missing required 'path' attribute in the first argument to builtins.path"), .msg = hintfmt("missing required 'path' attribute in the first argument to builtins.path"),
.errPos = state.positions[pos] .errPos = state.positions[pos]
})); }));
if (name.empty()) if (name.empty())
name = baseNameOf(path); name = path->baseName();
addPath(state, pos, name, path, filterFun, method, expectedHash, v, context); addPath(state, pos, name, path->path.abs(), filterFun, method, expectedHash, v, context);
} }
static RegisterPrimOp primop_path({ static RegisterPrimOp primop_path({
@ -4163,7 +4164,6 @@ void EvalState::createBaseEnv()
/* Add a wrapper around the derivation primop that computes the /* Add a wrapper around the derivation primop that computes the
`drvPath' and `outPath' attributes lazily. */ `drvPath' and `outPath' attributes lazily. */
sDerivationNix = symbols.create(derivationNixPath);
auto vDerivation = allocValue(); auto vDerivation = allocValue();
addConstant("derivation", vDerivation); addConstant("derivation", vDerivation);
@ -4180,7 +4180,7 @@ void EvalState::createBaseEnv()
// the parser needs two NUL bytes as terminators; one of them // the parser needs two NUL bytes as terminators; one of them
// is implied by being a C string. // is implied by being a C string.
"\0"; "\0";
eval(parse(code, sizeof(code), derivationNixPath, "/", staticBaseEnv), *vDerivation); eval(parse(code, sizeof(code), derivationInternal, {CanonPath::root}, staticBaseEnv), *vDerivation);
} }

View file

@ -28,7 +28,7 @@ namespace nix {
} }
Value eval(std::string input, bool forceValue = true) { Value eval(std::string input, bool forceValue = true) {
Value v; Value v;
Expr * e = state.parseExprFromString(input, ""); Expr * e = state.parseExprFromString(input, state.rootPath(CanonPath::root));
assert(e); assert(e);
state.eval(e, v); state.eval(e, v);
if (forceValue) if (forceValue)

View file

@ -12,7 +12,7 @@ libexpr-tests_SOURCES := \
$(wildcard $(d)/*.cc) \ $(wildcard $(d)/*.cc) \
$(wildcard $(d)/value/*.cc) $(wildcard $(d)/value/*.cc)
libexpr-tests_CXXFLAGS += -I src/libexpr -I src/libutil -I src/libstore -I src/libexpr/tests libexpr-tests_CXXFLAGS += -I src/libexpr -I src/libutil -I src/libstore -I src/libexpr/tests -I src/libfetchers
libexpr-tests_LIBS = libstore-tests libutils-tests libexpr libutil libstore libfetchers libexpr-tests_LIBS = libstore-tests libutils-tests libexpr libutil libstore libfetchers

View file

@ -36,9 +36,10 @@ json printValueAsJSON(EvalState & state, bool strict,
case nPath: case nPath:
if (copyToStore) if (copyToStore)
out = state.store->printStorePath(state.copyPathToStore(context, v.path)); out = state.store->printStorePath(
state.copyPathToStore(context, v.path()));
else else
out = v.path; out = v.path().path.abs();
break; break;
case nNull: case nNull:

View file

@ -24,8 +24,8 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
static void posToXML(EvalState & state, XMLAttrs & xmlAttrs, const Pos & pos) static void posToXML(EvalState & state, XMLAttrs & xmlAttrs, const Pos & pos)
{ {
if (auto path = std::get_if<Path>(&pos.origin)) if (auto path = std::get_if<SourcePath>(&pos.origin))
xmlAttrs["path"] = *path; xmlAttrs["path"] = path->path.abs();
xmlAttrs["line"] = fmt("%1%", pos.line); xmlAttrs["line"] = fmt("%1%", pos.line);
xmlAttrs["column"] = fmt("%1%", pos.column); xmlAttrs["column"] = fmt("%1%", pos.column);
} }
@ -78,7 +78,7 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
break; break;
case nPath: case nPath:
doc.writeEmptyElement("path", singletonAttrs("value", v.path)); doc.writeEmptyElement("path", singletonAttrs("value", v.path().to_string()));
break; break;
case nNull: case nNull:

View file

@ -5,6 +5,7 @@
#include "symbol-table.hh" #include "symbol-table.hh"
#include "value/context.hh" #include "value/context.hh"
#include "input-accessor.hh"
#if HAVE_BOEHMGC #if HAVE_BOEHMGC
#include <gc/gc_allocator.h> #include <gc/gc_allocator.h>
@ -188,7 +189,7 @@ public:
const char * * context; // must be in sorted order const char * * context; // must be in sorted order
} string; } string;
const char * path; const char * _path;
Bindings * attrs; Bindings * attrs;
struct { struct {
size_t size; size_t size;
@ -272,15 +273,20 @@ public:
void mkStringMove(const char * s, const NixStringContext & context); void mkStringMove(const char * s, const NixStringContext & context);
inline void mkPath(const char * s) inline void mkString(const Symbol & s)
{
mkString(((const std::string &) s).c_str());
}
void mkPath(const SourcePath & path);
inline void mkPath(const char * path)
{ {
clearValue(); clearValue();
internalType = tPath; internalType = tPath;
path = s; _path = path;
} }
void mkPath(std::string_view s);
inline void mkNull() inline void mkNull()
{ {
clearValue(); clearValue();
@ -421,6 +427,18 @@ public:
auto begin = listElems(); auto begin = listElems();
return ConstListIterable { begin, begin + listSize() }; return ConstListIterable { begin, begin + listSize() };
} }
SourcePath path() const
{
assert(internalType == tPath);
return SourcePath{CanonPath(_path)};
}
std::string_view str() const
{
assert(internalType == tString);
return std::string_view(string.s);
}
}; };

View file

@ -0,0 +1,100 @@
#include "input-accessor.hh"
#include "store-api.hh"
namespace nix {
std::ostream & operator << (std::ostream & str, const SourcePath & path)
{
str << path.to_string();
return str;
}
std::string_view SourcePath::baseName() const
{
return path.baseName().value_or("source");
}
SourcePath SourcePath::parent() const
{
auto p = path.parent();
assert(p);
return std::move(*p);
}
InputAccessor::Stat SourcePath::lstat() const
{
auto st = nix::lstat(path.abs());
return InputAccessor::Stat {
.type =
S_ISREG(st.st_mode) ? InputAccessor::tRegular :
S_ISDIR(st.st_mode) ? InputAccessor::tDirectory :
S_ISLNK(st.st_mode) ? InputAccessor::tSymlink :
InputAccessor::tMisc,
.isExecutable = S_ISREG(st.st_mode) && st.st_mode & S_IXUSR
};
}
std::optional<InputAccessor::Stat> SourcePath::maybeLstat() const
{
// FIXME: merge these into one operation.
if (!pathExists())
return {};
return lstat();
}
InputAccessor::DirEntries SourcePath::readDirectory() const
{
InputAccessor::DirEntries res;
for (auto & entry : nix::readDirectory(path.abs())) {
std::optional<InputAccessor::Type> type;
switch (entry.type) {
case DT_REG: type = InputAccessor::Type::tRegular; break;
case DT_LNK: type = InputAccessor::Type::tSymlink; break;
case DT_DIR: type = InputAccessor::Type::tDirectory; break;
}
res.emplace(entry.name, type);
}
return res;
}
StorePath SourcePath::fetchToStore(
ref<Store> store,
std::string_view name,
PathFilter * filter,
RepairFlag repair) const
{
return
settings.readOnlyMode
? store->computeStorePathForPath(name, path.abs(), FileIngestionMethod::Recursive, htSHA256, filter ? *filter : defaultPathFilter).first
: store->addToStore(name, path.abs(), FileIngestionMethod::Recursive, htSHA256, filter ? *filter : defaultPathFilter, repair);
}
SourcePath SourcePath::resolveSymlinks() const
{
SourcePath res(CanonPath::root);
int linksAllowed = 1024;
for (auto & component : path) {
res.path.push(component);
while (true) {
if (auto st = res.maybeLstat()) {
if (!linksAllowed--)
throw Error("infinite symlink recursion in path '%s'", path);
if (st->type != InputAccessor::tSymlink) break;
auto target = res.readLink();
if (hasPrefix(target, "/"))
res = CanonPath(target);
else {
res.path.pop();
res.path.extend(CanonPath(target));
}
} else
break;
}
}
return res;
}
}

View file

@ -0,0 +1,167 @@
#pragma once
#include "ref.hh"
#include "types.hh"
#include "archive.hh"
#include "canon-path.hh"
#include "repair-flag.hh"
namespace nix {
class StorePath;
class Store;
struct InputAccessor
{
enum Type {
tRegular, tSymlink, tDirectory,
/**
Any other node types that may be encountered on the file system, such as device nodes, sockets, named pipe, and possibly even more exotic things.
Responsible for `"unknown"` from `builtins.readFileType "/dev/null"`.
Unlike `DT_UNKNOWN`, this must not be used for deferring the lookup of types.
*/
tMisc
};
struct Stat
{
Type type = tMisc;
//uint64_t fileSize = 0; // regular files only
bool isExecutable = false; // regular files only
};
typedef std::optional<Type> DirEntry;
typedef std::map<std::string, DirEntry> DirEntries;
};
/**
* An abstraction for accessing source files during
* evaluation. Currently, it's just a wrapper around `CanonPath` that
* accesses files in the regular filesystem, but in the future it will
* support fetching files in other ways.
*/
struct SourcePath
{
CanonPath path;
SourcePath(CanonPath path)
: path(std::move(path))
{ }
std::string_view baseName() const;
/**
* Construct the parent of this `SourcePath`. Aborts if `this`
* denotes the root.
*/
SourcePath parent() const;
/**
* If this `SourcePath` denotes a regular file (not a symlink),
* return its contents; otherwise throw an error.
*/
std::string readFile() const
{ return nix::readFile(path.abs()); }
/**
* Return whether this `SourcePath` denotes a file (of any type)
* that exists
*/
bool pathExists() const
{ return nix::pathExists(path.abs()); }
/**
* Return stats about this `SourcePath`, or throw an exception if
* it doesn't exist.
*/
InputAccessor::Stat lstat() const;
/**
* Return stats about this `SourcePath`, or std::nullopt if it
* doesn't exist.
*/
std::optional<InputAccessor::Stat> maybeLstat() const;
/**
* If this `SourcePath` denotes a directory (not a symlink),
* return its directory entries; otherwise throw an error.
*/
InputAccessor::DirEntries readDirectory() const;
/**
* If this `SourcePath` denotes a symlink, return its target;
* otherwise throw an error.
*/
std::string readLink() const
{ return nix::readLink(path.abs()); }
/**
* Dump this `SourcePath` to `sink` as a NAR archive.
*/
void dumpPath(
Sink & sink,
PathFilter & filter = defaultPathFilter) const
{ return nix::dumpPath(path.abs(), sink, filter); }
/**
* Copy this `SourcePath` to the Nix store.
*/
StorePath fetchToStore(
ref<Store> store,
std::string_view name = "source",
PathFilter * filter = nullptr,
RepairFlag repair = NoRepair) const;
/**
* Return the location of this path in the "real" filesystem, if
* it has a physical location.
*/
std::optional<CanonPath> getPhysicalPath() const
{ return path; }
std::string to_string() const
{ return path.abs(); }
/**
* Append a `CanonPath` to this path.
*/
SourcePath operator + (const CanonPath & x) const
{ return {path + x}; }
/**
* Append a single component `c` to this path. `c` must not
* contain a slash. A slash is implicitly added between this path
* and `c`.
*/
SourcePath operator + (std::string_view c) const
{ return {path + c}; }
bool operator == (const SourcePath & x) const
{
return path == x.path;
}
bool operator != (const SourcePath & x) const
{
return path != x.path;
}
bool operator < (const SourcePath & x) const
{
return path < x.path;
}
/**
* Resolve any symlinks in this `SourcePath` (including its
* parents). The result is a `SourcePath` in which no element is a
* symlink.
*/
SourcePath resolveSymlinks() const;
};
std::ostream & operator << (std::ostream & str, const SourcePath & path);
}

View file

@ -13,6 +13,11 @@ CanonPath::CanonPath(std::string_view raw, const CanonPath & root)
: path(absPath((Path) raw, root.abs())) : path(absPath((Path) raw, root.abs()))
{ } { }
CanonPath CanonPath::fromCwd(std::string_view path)
{
return CanonPath(unchecked_t(), absPath((Path) path));
}
std::optional<CanonPath> CanonPath::parent() const std::optional<CanonPath> CanonPath::parent() const
{ {
if (isRoot()) return std::nullopt; if (isRoot()) return std::nullopt;

View file

@ -46,6 +46,8 @@ public:
: path(std::move(path)) : path(std::move(path))
{ } { }
static CanonPath fromCwd(std::string_view path = ".");
static CanonPath root; static CanonPath root;
/** /**

View file

@ -289,7 +289,7 @@ static void main_nix_build(int argc, char * * argv)
else else
for (auto i : left) { for (auto i : left) {
if (fromArgs) if (fromArgs)
exprs.push_back(state->parseExprFromString(std::move(i), absPath("."))); exprs.push_back(state->parseExprFromString(std::move(i), state->rootPath(CanonPath::fromCwd())));
else { else {
auto absolute = i; auto absolute = i;
try { try {
@ -385,7 +385,9 @@ static void main_nix_build(int argc, char * * argv)
if (!shell) { if (!shell) {
try { try {
auto expr = state->parseExprFromString("(import <nixpkgs> {}).bashInteractive", absPath(".")); auto expr = state->parseExprFromString(
"(import <nixpkgs> {}).bashInteractive",
state->rootPath(CanonPath::fromCwd()));
Value v; Value v;
state->eval(expr, v); state->eval(expr, v);

View file

@ -44,7 +44,7 @@ typedef enum {
struct InstallSourceInfo struct InstallSourceInfo
{ {
InstallSourceType type; InstallSourceType type;
Path nixExprPath; /* for srcNixExprDrvs, srcNixExprs */ std::shared_ptr<SourcePath> nixExprPath; /* for srcNixExprDrvs, srcNixExprs */
Path profile; /* for srcProfile */ Path profile; /* for srcProfile */
std::string systemFilter; /* for srcNixExprDrvs */ std::string systemFilter; /* for srcNixExprDrvs */
Bindings * autoArgs; Bindings * autoArgs;
@ -92,9 +92,11 @@ static bool parseInstallSourceOptions(Globals & globals,
} }
static bool isNixExpr(const Path & path, struct stat & st) static bool isNixExpr(const SourcePath & path, struct InputAccessor::Stat & st)
{ {
return S_ISREG(st.st_mode) || (S_ISDIR(st.st_mode) && pathExists(path + "/default.nix")); return
st.type == InputAccessor::tRegular
|| (st.type == InputAccessor::tDirectory && (path + "default.nix").pathExists());
} }
@ -102,10 +104,10 @@ static constexpr size_t maxAttrs = 1024;
static void getAllExprs(EvalState & state, static void getAllExprs(EvalState & state,
const Path & path, StringSet & seen, BindingsBuilder & attrs) const SourcePath & path, StringSet & seen, BindingsBuilder & attrs)
{ {
StringSet namesSorted; StringSet namesSorted;
for (auto & i : readDirectory(path)) namesSorted.insert(i.name); for (auto & [name, _] : path.readDirectory()) namesSorted.insert(name);
for (auto & i : namesSorted) { for (auto & i : namesSorted) {
/* Ignore the manifest.nix used by profiles. This is /* Ignore the manifest.nix used by profiles. This is
@ -113,13 +115,16 @@ static void getAllExprs(EvalState & state,
are implemented using profiles). */ are implemented using profiles). */
if (i == "manifest.nix") continue; if (i == "manifest.nix") continue;
Path path2 = path + "/" + i; SourcePath path2 = path + i;
struct stat st; InputAccessor::Stat st;
if (stat(path2.c_str(), &st) == -1) try {
st = path2.resolveSymlinks().lstat();
} catch (Error &) {
continue; // ignore dangling symlinks in ~/.nix-defexpr continue; // ignore dangling symlinks in ~/.nix-defexpr
}
if (isNixExpr(path2, st) && (!S_ISREG(st.st_mode) || hasSuffix(path2, ".nix"))) { if (isNixExpr(path2, st) && (st.type != InputAccessor::tRegular || hasSuffix(path2.baseName(), ".nix"))) {
/* Strip off the `.nix' filename suffix (if applicable), /* Strip off the `.nix' filename suffix (if applicable),
otherwise the attribute cannot be selected with the otherwise the attribute cannot be selected with the
`-A' option. Useful if you want to stick a Nix `-A' option. Useful if you want to stick a Nix
@ -129,21 +134,20 @@ static void getAllExprs(EvalState & state,
attrName = std::string(attrName, 0, attrName.size() - 4); attrName = std::string(attrName, 0, attrName.size() - 4);
if (!seen.insert(attrName).second) { if (!seen.insert(attrName).second) {
std::string suggestionMessage = ""; std::string suggestionMessage = "";
if (path2.find("channels") != std::string::npos && path.find("channels") != std::string::npos) { if (path2.path.abs().find("channels") != std::string::npos && path.path.abs().find("channels") != std::string::npos)
suggestionMessage = fmt("\nsuggestion: remove '%s' from either the root channels or the user channels", attrName); suggestionMessage = fmt("\nsuggestion: remove '%s' from either the root channels or the user channels", attrName);
}
printError("warning: name collision in input Nix expressions, skipping '%1%'" printError("warning: name collision in input Nix expressions, skipping '%1%'"
"%2%", path2, suggestionMessage); "%2%", path2, suggestionMessage);
continue; continue;
} }
/* Load the expression on demand. */ /* Load the expression on demand. */
auto vArg = state.allocValue(); auto vArg = state.allocValue();
vArg->mkString(path2); vArg->mkString(path2.path.abs());
if (seen.size() == maxAttrs) if (seen.size() == maxAttrs)
throw Error("too many Nix expressions in directory '%1%'", path); throw Error("too many Nix expressions in directory '%1%'", path);
attrs.alloc(attrName).mkApp(&state.getBuiltin("import"), vArg); attrs.alloc(attrName).mkApp(&state.getBuiltin("import"), vArg);
} }
else if (S_ISDIR(st.st_mode)) else if (st.type == InputAccessor::tDirectory)
/* `path2' is a directory (with no default.nix in it); /* `path2' is a directory (with no default.nix in it);
recurse into it. */ recurse into it. */
getAllExprs(state, path2, seen, attrs); getAllExprs(state, path2, seen, attrs);
@ -152,11 +156,9 @@ static void getAllExprs(EvalState & state,
static void loadSourceExpr(EvalState & state, const Path & path, Value & v) static void loadSourceExpr(EvalState & state, const SourcePath & path, Value & v)
{ {
struct stat st; auto st = path.resolveSymlinks().lstat();
if (stat(path.c_str(), &st) == -1)
throw SysError("getting information about '%1%'", path);
if (isNixExpr(path, st)) if (isNixExpr(path, st))
state.evalFile(path, v); state.evalFile(path, v);
@ -167,7 +169,7 @@ static void loadSourceExpr(EvalState & state, const Path & path, Value & v)
set flat, not nested, to make it easier for a user to have a set flat, not nested, to make it easier for a user to have a
~/.nix-defexpr directory that includes some system-wide ~/.nix-defexpr directory that includes some system-wide
directory). */ directory). */
else if (S_ISDIR(st.st_mode)) { else if (st.type == InputAccessor::tDirectory) {
auto attrs = state.buildBindings(maxAttrs); auto attrs = state.buildBindings(maxAttrs);
attrs.alloc("_combineChannels").mkList(0); attrs.alloc("_combineChannels").mkList(0);
StringSet seen; StringSet seen;
@ -179,7 +181,7 @@ static void loadSourceExpr(EvalState & state, const Path & path, Value & v)
} }
static void loadDerivations(EvalState & state, Path nixExprPath, static void loadDerivations(EvalState & state, const SourcePath & nixExprPath,
std::string systemFilter, Bindings & autoArgs, std::string systemFilter, Bindings & autoArgs,
const std::string & pathPrefix, DrvInfos & elems) const std::string & pathPrefix, DrvInfos & elems)
{ {
@ -390,7 +392,7 @@ static void queryInstSources(EvalState & state,
/* Load the derivations from the (default or specified) /* Load the derivations from the (default or specified)
Nix expression. */ Nix expression. */
DrvInfos allElems; DrvInfos allElems;
loadDerivations(state, instSource.nixExprPath, loadDerivations(state, *instSource.nixExprPath,
instSource.systemFilter, *instSource.autoArgs, "", allElems); instSource.systemFilter, *instSource.autoArgs, "", allElems);
elems = filterBySelector(state, allElems, args, newestOnly); elems = filterBySelector(state, allElems, args, newestOnly);
@ -407,10 +409,10 @@ static void queryInstSources(EvalState & state,
case srcNixExprs: { case srcNixExprs: {
Value vArg; Value vArg;
loadSourceExpr(state, instSource.nixExprPath, vArg); loadSourceExpr(state, *instSource.nixExprPath, vArg);
for (auto & i : args) { for (auto & i : args) {
Expr * eFun = state.parseExprFromString(i, absPath(".")); Expr * eFun = state.parseExprFromString(i, state.rootPath(CanonPath::fromCwd()));
Value vFun, vTmp; Value vFun, vTmp;
state.eval(eFun, vFun); state.eval(eFun, vFun);
vTmp.mkApp(&vFun, &vArg); vTmp.mkApp(&vFun, &vArg);
@ -462,7 +464,7 @@ static void queryInstSources(EvalState & state,
case srcAttrPath: { case srcAttrPath: {
Value vRoot; Value vRoot;
loadSourceExpr(state, instSource.nixExprPath, vRoot); loadSourceExpr(state, *instSource.nixExprPath, vRoot);
for (auto & i : args) { for (auto & i : args) {
Value & v(*findAlongAttrPath(state, i, *instSource.autoArgs, vRoot).first); Value & v(*findAlongAttrPath(state, i, *instSource.autoArgs, vRoot).first);
getDerivations(state, v, "", *instSource.autoArgs, elems, true); getDerivations(state, v, "", *instSource.autoArgs, elems, true);
@ -1030,7 +1032,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
installedElems = queryInstalled(*globals.state, globals.profile); installedElems = queryInstalled(*globals.state, globals.profile);
if (source == sAvailable || compareVersions) if (source == sAvailable || compareVersions)
loadDerivations(*globals.state, globals.instSource.nixExprPath, loadDerivations(*globals.state, *globals.instSource.nixExprPath,
globals.instSource.systemFilter, *globals.instSource.autoArgs, globals.instSource.systemFilter, *globals.instSource.autoArgs,
attrPath, availElems); attrPath, availElems);
@ -1395,22 +1397,20 @@ static int main_nix_env(int argc, char * * argv)
Globals globals; Globals globals;
globals.instSource.type = srcUnknown; globals.instSource.type = srcUnknown;
{
Path nixExprPath = settings.useXDGBaseDirectories ? createNixStateDir() + "/defexpr" : getHome() + "/.nix-defexpr";
globals.instSource.nixExprPath = nixExprPath;
}
globals.instSource.systemFilter = "*"; globals.instSource.systemFilter = "*";
if (!pathExists(globals.instSource.nixExprPath)) { Path nixExprPath = settings.useXDGBaseDirectories ? createNixStateDir() + "/defexpr" : getHome() + "/.nix-defexpr";
if (!pathExists(nixExprPath)) {
try { try {
createDirs(globals.instSource.nixExprPath); createDirs(nixExprPath);
replaceSymlink( replaceSymlink(
defaultChannelsDir(), defaultChannelsDir(),
globals.instSource.nixExprPath + "/channels"); nixExprPath + "/channels");
if (getuid() != 0) if (getuid() != 0)
replaceSymlink( replaceSymlink(
rootChannelsDir(), rootChannelsDir(),
globals.instSource.nixExprPath + "/channels_root"); nixExprPath + "/channels_root");
} catch (Error &) { } } catch (Error &) { }
} }
@ -1517,8 +1517,10 @@ static int main_nix_env(int argc, char * * argv)
globals.state = std::shared_ptr<EvalState>(new EvalState(myArgs.searchPath, store)); globals.state = std::shared_ptr<EvalState>(new EvalState(myArgs.searchPath, store));
globals.state->repair = repair; globals.state->repair = repair;
if (file != "") globals.instSource.nixExprPath = std::make_shared<SourcePath>(
globals.instSource.nixExprPath = lookupFileArg(*globals.state, file); file != ""
? lookupFileArg(*globals.state, file)
: globals.state->rootPath(CanonPath(nixExprPath)));
globals.instSource.autoArgs = myArgs.getAutoArgs(*globals.state); globals.instSource.autoArgs = myArgs.getAutoArgs(*globals.state);

View file

@ -19,10 +19,10 @@ DrvInfos queryInstalled(EvalState & state, const Path & userEnv)
DrvInfos elems; DrvInfos elems;
if (pathExists(userEnv + "/manifest.json")) if (pathExists(userEnv + "/manifest.json"))
throw Error("profile '%s' is incompatible with 'nix-env'; please use 'nix profile' instead", userEnv); throw Error("profile '%s' is incompatible with 'nix-env'; please use 'nix profile' instead", userEnv);
Path manifestFile = userEnv + "/manifest.nix"; auto manifestFile = userEnv + "/manifest.nix";
if (pathExists(manifestFile)) { if (pathExists(manifestFile)) {
Value v; Value v;
state.evalFile(manifestFile, v); state.evalFile(state.rootPath(CanonPath(manifestFile)), v);
Bindings & bindings(*state.allocBindings(0)); Bindings & bindings(*state.allocBindings(0));
getDerivations(state, v, "", bindings, elems, false); getDerivations(state, v, "", bindings, elems, false);
} }
@ -114,7 +114,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
Value envBuilder; Value envBuilder;
state.eval(state.parseExprFromString( state.eval(state.parseExprFromString(
#include "buildenv.nix.gen.hh" #include "buildenv.nix.gen.hh"
, "/"), envBuilder); , state.rootPath(CanonPath::root)), envBuilder);
/* Construct a Nix expression that calls the user environment /* Construct a Nix expression that calls the user environment
builder with the manifest as argument. */ builder with the manifest as argument. */

View file

@ -168,9 +168,11 @@ static int main_nix_instantiate(int argc, char * * argv)
if (findFile) { if (findFile) {
for (auto & i : files) { for (auto & i : files) {
Path p = state->findFile(i); auto p = state->findFile(i);
if (p == "") throw Error("unable to find '%1%'", i); if (auto fn = p.getPhysicalPath())
std::cout << p << std::endl; std::cout << fn->abs() << std::endl;
else
throw Error("'%s' has no physical path", p);
} }
return 0; return 0;
} }
@ -184,7 +186,7 @@ static int main_nix_instantiate(int argc, char * * argv)
for (auto & i : files) { for (auto & i : files) {
Expr * e = fromArgs Expr * e = fromArgs
? state->parseExprFromString(i, absPath(".")) ? state->parseExprFromString(i, state->rootPath(CanonPath::fromCwd()))
: state->parseExprFromFile(resolveExprPath(state->checkSourcePath(lookupFileArg(*state, i)))); : state->parseExprFromFile(resolveExprPath(state->checkSourcePath(lookupFileArg(*state, i))));
processExpr(*state, attrPaths, parseOnly, strict, autoArgs, processExpr(*state, attrPaths, parseOnly, strict, autoArgs,
evalOnly, outputKind, xmlOutputSourceLocation, e); evalOnly, outputKind, xmlOutputSourceLocation, e);

View file

@ -66,7 +66,7 @@ struct CmdEval : MixJSON, InstallableValueCommand, MixReadOnlyOption
if (apply) { if (apply) {
auto vApply = state->allocValue(); auto vApply = state->allocValue();
state->eval(state->parseExprFromString(*apply, absPath(".")), *vApply); state->eval(state->parseExprFromString(*apply, state->rootPath(CanonPath::fromCwd())), *vApply);
auto vRes = state->allocValue(); auto vRes = state->allocValue();
state->callFunction(*vApply, *v, *vRes, noPos); state->callFunction(*vApply, *v, *vRes, noPos);
v = vRes; v = vRes;

View file

@ -440,8 +440,8 @@ struct CmdFlakeCheck : FlakeCommand
if (attr->name == state->symbols.create("path")) { if (attr->name == state->symbols.create("path")) {
NixStringContext context; NixStringContext context;
auto path = state->coerceToPath(attr->pos, *attr->value, context, ""); auto path = state->coerceToPath(attr->pos, *attr->value, context, "");
if (!store->isInStore(path)) if (!path.pathExists())
throw Error("template '%s' has a bad 'path' attribute"); throw Error("template '%s' refers to a non-existent path '%s'", attrPath, path);
// TODO: recursively check the flake in 'path'. // TODO: recursively check the flake in 'path'.
} }
} else } else

View file

@ -201,14 +201,14 @@ static void showHelp(std::vector<std::string> subcommand, NixArgs & toplevel)
auto vGenerateManpage = state.allocValue(); auto vGenerateManpage = state.allocValue();
state.eval(state.parseExprFromString( state.eval(state.parseExprFromString(
#include "generate-manpage.nix.gen.hh" #include "generate-manpage.nix.gen.hh"
, "/"), *vGenerateManpage); , CanonPath::root), *vGenerateManpage);
auto vUtils = state.allocValue(); auto vUtils = state.allocValue();
state.cacheFile( state.cacheFile(
"/utils.nix", "/utils.nix", CanonPath("/utils.nix"), CanonPath("/utils.nix"),
state.parseExprFromString( state.parseExprFromString(
#include "utils.nix.gen.hh" #include "utils.nix.gen.hh"
, "/"), , CanonPath::root),
*vUtils); *vUtils);
auto vDump = state.allocValue(); auto vDump = state.allocValue();

View file

@ -27,7 +27,10 @@ std::string resolveMirrorUrl(EvalState & state, const std::string & url)
Value vMirrors; Value vMirrors;
// FIXME: use nixpkgs flake // FIXME: use nixpkgs flake
state.eval(state.parseExprFromString("import <nixpkgs/pkgs/build-support/fetchurl/mirrors.nix>", "."), vMirrors); state.eval(state.parseExprFromString(
"import <nixpkgs/pkgs/build-support/fetchurl/mirrors.nix>",
state.rootPath(CanonPath::root)),
vMirrors);
state.forceAttrs(vMirrors, noPos, "while evaluating the set of all mirrors"); state.forceAttrs(vMirrors, noPos, "while evaluating the set of all mirrors");
auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName)); auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName));
@ -198,9 +201,11 @@ static int main_nix_prefetch_url(int argc, char * * argv)
throw UsageError("you must specify a URL"); throw UsageError("you must specify a URL");
url = args[0]; url = args[0];
} else { } else {
Path path = resolveExprPath(lookupFileArg(*state, args.empty() ? "." : args[0]));
Value vRoot; Value vRoot;
state->evalFile(path, vRoot); state->evalFile(
resolveExprPath(
lookupFileArg(*state, args.empty() ? "." : args[0])),
vRoot);
Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first); Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first);
state->forceAttrs(v, noPos, "while evaluating the source attribute to prefetch"); state->forceAttrs(v, noPos, "while evaluating the source attribute to prefetch");

View file

@ -148,7 +148,7 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
auto state = std::make_unique<EvalState>(Strings(), store); auto state = std::make_unique<EvalState>(Strings(), store);
auto v = state->allocValue(); auto v = state->allocValue();
state->eval(state->parseExprFromString(res.data, "/no-such-path"), *v); state->eval(state->parseExprFromString(res.data, state->rootPath(CanonPath("/no-such-path"))), *v);
Bindings & bindings(*state->allocBindings(0)); Bindings & bindings(*state->allocBindings(0));
auto v2 = findAlongAttrPath(*state, settings.thisSystem, bindings, *v).first; auto v2 = findAlongAttrPath(*state, settings.thisSystem, bindings, *v).first;

View file

@ -8,4 +8,4 @@ libplugintest_ALLOW_UNDEFINED := 1
libplugintest_EXCLUDE_FROM_LIBRARY_LIST := 1 libplugintest_EXCLUDE_FROM_LIBRARY_LIST := 1
libplugintest_CXXFLAGS := -I src/libutil -I src/libstore -I src/libexpr libplugintest_CXXFLAGS := -I src/libutil -I src/libstore -I src/libexpr -I src/libfetchers