forked from lix-project/lix
Backport SourcePath from the lazy-trees branch
This introduces the SourcePath type from lazy-trees as an abstraction for accessing files from inputs that may not be materialized in the real filesystem (e.g. Git repositories). Currently, however, it's just a wrapper around CanonPath, so it shouldn't change any behaviour. (On lazy-trees, SourcePath is a <InputAccessor, CanonPath> tuple.)
This commit is contained in:
parent
5256ba6d87
commit
94812cca98
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,12 @@
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
|
#include "input-accessor.hh"
|
||||||
|
|
||||||
namespace nix {
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,8 +96,7 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
|
||||||
auto v = attr->forceValue();
|
auto v = attr->forceValue();
|
||||||
|
|
||||||
if (v.type() == nPath) {
|
if (v.type() == nPath) {
|
||||||
PathSet 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),
|
||||||
|
|
|
@ -427,7 +427,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,8 +54,6 @@ struct NixRepl
|
||||||
, gc
|
, gc
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
std::string curDir;
|
|
||||||
|
|
||||||
size_t debugTraceIndex;
|
size_t debugTraceIndex;
|
||||||
|
|
||||||
Strings loadedFiles;
|
Strings loadedFiles;
|
||||||
|
@ -113,7 +111,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(".");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -590,7 +587,7 @@ 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) {
|
||||||
PathSet context;
|
PathSet 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");
|
||||||
|
@ -598,7 +595,7 @@ bool NixRepl::processLine(std::string line)
|
||||||
} 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<Path>(&pos.origin))
|
||||||
return {*path, pos.line};
|
return {SourcePath(CanonPath(*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);
|
||||||
} else {
|
} else {
|
||||||
|
@ -872,8 +869,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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -930,7 +926,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:
|
||||||
|
|
|
@ -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");
|
PathSet 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 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ std::pair<Value *, PosIdx> findAlongAttrPath(
|
||||||
Value & vIn);
|
Value & vIn);
|
||||||
|
|
||||||
/* 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);
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
@ -622,7 +624,7 @@ string_t AttrCursor::getStringWithContext()
|
||||||
if (v.type() == nString)
|
if (v.type() == nString)
|
||||||
return {v.string.s, v.getContext(*root->state.store)};
|
return {v.string.s, v.getContext(*root->state.store)};
|
||||||
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>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,7 +118,7 @@ void Value::print(const SymbolTable & symbols, std::ostream & str,
|
||||||
str << "\"";
|
str << "\"";
|
||||||
break;
|
break;
|
||||||
case tPath:
|
case tPath:
|
||||||
str << path; // !!! escaping?
|
str << path().to_string(); // !!! escaping?
|
||||||
break;
|
break;
|
||||||
case tNull:
|
case tNull:
|
||||||
str << "null";
|
str << "null";
|
||||||
|
@ -577,11 +577,11 @@ void EvalState::allowAndSetStorePathString(const StorePath & storePath, Value &
|
||||||
v.mkString(path, PathSet({path}));
|
v.mkString(path, PathSet({path}));
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
|
@ -591,9 +591,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)) {
|
||||||
|
@ -611,11 +611,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -643,12 +643,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -933,9 +933,9 @@ void Value::mkStringMove(const char * s, const PathSet & context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Value::mkPath(std::string_view s)
|
void Value::mkPath(const SourcePath & path)
|
||||||
{
|
{
|
||||||
mkPath(makeImmutableString(s));
|
mkPath(makeImmutableString(path.path.abs()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1049,7 +1049,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_);
|
||||||
|
|
||||||
|
@ -1059,7 +1059,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;
|
||||||
|
@ -1087,8 +1087,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)
|
||||||
|
@ -1102,7 +1102,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
|
||||||
|
@ -1112,7 +1112,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1947,7 +1947,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);
|
||||||
}
|
}
|
||||||
|
@ -2136,8 +2136,14 @@ std::optional<std::string> EvalState::tryAttrsToString(const PosIdx pos, Value &
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
BackedStringView EvalState::coerceToString(const PosIdx pos, Value &v, PathSet &context,
|
BackedStringView EvalState::coerceToString(
|
||||||
std::string_view errorCtx, bool coerceMore, bool copyToStore, bool canonicalizePath)
|
const PosIdx pos,
|
||||||
|
Value & v,
|
||||||
|
PathSet & context,
|
||||||
|
std::string_view errorCtx,
|
||||||
|
bool coerceMore,
|
||||||
|
bool copyToStore,
|
||||||
|
bool canonicalizePath)
|
||||||
{
|
{
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
|
|
||||||
|
@ -2147,12 +2153,14 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value &v, PathSet &
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
@ -2213,19 +2221,17 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value &v, PathSet &
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
StorePath EvalState::copyPathToStore(PathSet & context, const Path & path)
|
StorePath EvalState::copyPathToStore(PathSet & 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));
|
||||||
|
@ -2237,12 +2243,12 @@ StorePath EvalState::copyPathToStore(PathSet & context, const Path & path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx)
|
SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2285,7 +2291,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;
|
||||||
|
|
|
@ -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>
|
||||||
|
@ -56,15 +57,11 @@ std::unique_ptr<ValMap> mapStaticEnvBindings(const SymbolTable & st, const Stati
|
||||||
void copyContext(const Value & v, PathSet & context);
|
void copyContext(const Value & v, PathSet & 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;
|
||||||
|
|
||||||
|
@ -217,21 +214,24 @@ 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;
|
||||||
|
|
||||||
/* 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;
|
||||||
|
|
||||||
|
@ -240,7 +240,7 @@ private:
|
||||||
std::map<std::string, std::pair<bool, std::string>> searchPathResolved;
|
std::map<std::string, std::pair<bool, std::string>> searchPathResolved;
|
||||||
|
|
||||||
/* 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(). */
|
||||||
std::shared_ptr<RegexCache> regexCache;
|
std::shared_ptr<RegexCache> regexCache;
|
||||||
|
@ -265,6 +265,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. */
|
||||||
void allowPath(const Path & path);
|
void allowPath(const Path & path);
|
||||||
|
|
||||||
|
@ -277,7 +283,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);
|
||||||
|
|
||||||
|
@ -291,24 +297,24 @@ public:
|
||||||
Path toRealPath(const Path & path, const PathSet & context);
|
Path toRealPath(const Path & path, const PathSet & context);
|
||||||
|
|
||||||
/* 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();
|
||||||
|
|
||||||
/* Evaluate an expression read from the given file to normal
|
/* Evaluate an expression read from the given file to normal
|
||||||
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);
|
||||||
|
@ -316,8 +322,8 @@ public:
|
||||||
void resetFileCache();
|
void resetFileCache();
|
||||||
|
|
||||||
/* 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. */
|
||||||
std::pair<bool, std::string> resolveSearchPathElem(const SearchPathElem & elem);
|
std::pair<bool, std::string> resolveSearchPathElem(const SearchPathElem & elem);
|
||||||
|
@ -383,12 +389,12 @@ public:
|
||||||
bool coerceMore = false, bool copyToStore = true,
|
bool coerceMore = false, bool copyToStore = true,
|
||||||
bool canonicalizePath = true);
|
bool canonicalizePath = true);
|
||||||
|
|
||||||
StorePath copyPathToStore(PathSet & context, const Path & path);
|
StorePath copyPathToStore(PathSet & context, const SourcePath & path);
|
||||||
|
|
||||||
/* Path coercion. Converts strings, paths and derivations to a
|
/* Path coercion. Converts strings, paths and derivations to a
|
||||||
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, PathSet & context, std::string_view errorCtx);
|
SourcePath coerceToPath(const PosIdx pos, Value & v, PathSet & 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. */
|
||||||
StorePath coerceToStorePath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx);
|
StorePath coerceToStorePath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx);
|
||||||
|
@ -444,7 +450,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:
|
||||||
|
@ -556,7 +562,7 @@ std::string_view showType(ValueType type);
|
||||||
std::string showType(const Value & v);
|
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
|
||||||
{
|
{
|
||||||
|
|
|
@ -218,7 +218,7 @@ 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({flakeFile}, 1, 1));
|
||||||
|
|
||||||
|
@ -731,7 +731,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);
|
||||||
|
|
|
@ -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(), path.path.abs(), 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
10
src/libexpr/paths.cc
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#include "eval.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
SourcePath EvalState::rootPath(CanonPath path)
|
||||||
|
{
|
||||||
|
return std::move(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -110,7 +110,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 = {})
|
||||||
{
|
{
|
||||||
PathSet context;
|
PathSet context;
|
||||||
|
|
||||||
|
@ -119,7 +119,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)
|
||||||
|
@ -166,13 +166,14 @@ 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;
|
||||||
};
|
};
|
||||||
|
@ -181,7 +182,7 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
|
||||||
auto storePath = *optStorePath;
|
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, {"=" + path});
|
attrs.alloc(state.sDrvPath).mkString(path2, {"=" + path2});
|
||||||
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());
|
||||||
|
@ -198,7 +199,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");
|
||||||
|
@ -206,10 +207,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 {
|
||||||
|
@ -330,7 +331,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()));
|
||||||
|
|
||||||
|
@ -378,7 +379,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;
|
||||||
|
@ -585,7 +586,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++) {
|
||||||
|
@ -1428,8 +1429,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)
|
||||||
{
|
{
|
||||||
PathSet context;
|
PathSet 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({
|
||||||
|
@ -1459,21 +1460,22 @@ static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
PathSet context;
|
PathSet 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(state.store->printStorePath(path2));
|
context.insert(state.store->printStorePath(path2));
|
||||||
v.mkString(path, context);
|
v.mkString(path.abs(), context);
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_storePath({
|
static RegisterPrimOp primop_storePath({
|
||||||
|
@ -1504,7 +1506,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. */
|
||||||
|
@ -1551,11 +1553,17 @@ static RegisterPrimOp primop_baseNameOf({
|
||||||
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)
|
||||||
{
|
{
|
||||||
PathSet context;
|
PathSet context;
|
||||||
|
state.forceValue(*args[0], pos);
|
||||||
|
if (args[0]->type() == nPath) {
|
||||||
|
auto path = args[0]->path();
|
||||||
|
v.mkPath(path.path.isRoot() ? path : path.parent());
|
||||||
|
} else {
|
||||||
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({
|
||||||
|
@ -1573,13 +1581,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.
|
||||||
|
@ -1660,7 +1668,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({
|
||||||
|
@ -1674,26 +1682,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({
|
||||||
|
@ -1714,8 +1716,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
|
||||||
|
@ -1724,22 +1725,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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2045,7 +2045,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,
|
||||||
|
@ -2073,7 +2073,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);
|
||||||
|
@ -2120,9 +2120,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)
|
||||||
{
|
{
|
||||||
PathSet context;
|
PathSet 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({
|
||||||
|
@ -2182,18 +2183,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;
|
||||||
PathSet context;
|
PathSet 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")
|
||||||
|
@ -2208,15 +2210,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({
|
||||||
|
@ -4151,7 +4153,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), derivationNixPath, {CanonPath::root}, staticBaseEnv), *vDerivation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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>
|
||||||
|
@ -171,7 +172,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;
|
||||||
|
@ -251,15 +252,20 @@ public:
|
||||||
|
|
||||||
void mkStringMove(const char * s, const PathSet & context);
|
void mkStringMove(const char * s, const PathSet & 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();
|
||||||
|
@ -400,6 +406,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);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
100
src/libfetchers/input-accessor.cc
Normal file
100
src/libfetchers/input-accessor.cc
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
149
src/libfetchers/input-accessor.hh
Normal file
149
src/libfetchers/input-accessor.hh
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
#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, 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(); }
|
||||||
|
|
||||||
|
SourcePath operator + (const CanonPath & x) const
|
||||||
|
{ return {path + x}; }
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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. */
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -440,8 +440,8 @@ struct CmdFlakeCheck : FlakeCommand
|
||||||
if (attr->name == state->symbols.create("path")) {
|
if (attr->name == state->symbols.create("path")) {
|
||||||
PathSet context;
|
PathSet 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
|
||||||
|
|
|
@ -197,14 +197,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();
|
||||||
|
|
|
@ -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));
|
||||||
|
@ -192,9 +195,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");
|
||||||
|
|
||||||
|
|
|
@ -140,7 +140,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;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue