aboutsummaryrefslogtreecommitdiff
path: root/src/libexpr
diff options
context:
space:
mode:
authorJohn Ericson <John.Ericson@Obsidian.Systems>2021-09-30 23:47:53 +0000
committerJohn Ericson <John.Ericson@Obsidian.Systems>2021-09-30 23:47:53 +0000
commit1ef88da3503b88f289a15786dfd48821cf5b6502 (patch)
treeaef8833d734cf1f892b27a54ae969641a20c9d7b /src/libexpr
parent8499f32fb2e7fdf09e97d0beb1fe78bef5900d93 (diff)
parent6a8d6246f603a372d557ab026670ae42bad558b0 (diff)
Merge remote-tracking branch 'upstream/master' into indexed-store-path-outputs
Diffstat (limited to 'src/libexpr')
-rw-r--r--src/libexpr/attr-path.cc8
-rw-r--r--src/libexpr/attr-path.hh2
-rw-r--r--src/libexpr/attr-set.hh7
-rw-r--r--src/libexpr/common-eval-args.cc8
-rw-r--r--src/libexpr/common-eval-args.hh3
-rw-r--r--src/libexpr/eval-cache.cc4
-rw-r--r--src/libexpr/eval-cache.hh2
-rw-r--r--src/libexpr/eval.cc176
-rw-r--r--src/libexpr/eval.hh37
-rw-r--r--src/libexpr/flake/config.cc6
-rw-r--r--src/libexpr/flake/flake.cc149
-rw-r--r--src/libexpr/flake/flake.hh18
-rw-r--r--src/libexpr/flake/flakeref.cc6
-rw-r--r--src/libexpr/flake/lockfile.cc21
-rw-r--r--src/libexpr/lexer.l100
-rw-r--r--src/libexpr/local.mk6
-rw-r--r--src/libexpr/nixexpr.hh4
-rw-r--r--src/libexpr/parser.y27
-rw-r--r--src/libexpr/primops.cc266
-rw-r--r--src/libexpr/primops.hh4
-rw-r--r--src/libexpr/primops/fetchMercurial.cc1
-rw-r--r--src/libexpr/primops/fetchTree.cc95
-rw-r--r--src/libexpr/value-to-xml.cc2
-rw-r--r--src/libexpr/value.hh2
24 files changed, 677 insertions, 277 deletions
diff --git a/src/libexpr/attr-path.cc b/src/libexpr/attr-path.cc
index 9dd557205..c50c6d92b 100644
--- a/src/libexpr/attr-path.cc
+++ b/src/libexpr/attr-path.cc
@@ -19,7 +19,7 @@ static Strings parseAttrPath(std::string_view s)
++i;
while (1) {
if (i == s.end())
- throw Error("missing closing quote in selection path '%1%'", s);
+ throw ParseError("missing closing quote in selection path '%1%'", s);
if (*i == '"') break;
cur.push_back(*i++);
}
@@ -100,7 +100,7 @@ std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const string & attr
}
-Pos findDerivationFilename(EvalState & state, Value & v, std::string what)
+Pos findPackageFilename(EvalState & state, Value & v, std::string what)
{
Value * v2;
try {
@@ -116,14 +116,14 @@ Pos findDerivationFilename(EvalState & state, Value & v, std::string what)
auto colon = pos.rfind(':');
if (colon == std::string::npos)
- throw Error("cannot parse meta.position attribute '%s'", pos);
+ throw ParseError("cannot parse meta.position attribute '%s'", pos);
std::string filename(pos, 0, colon);
unsigned int lineno;
try {
lineno = std::stoi(std::string(pos, colon + 1));
} catch (std::invalid_argument & e) {
- throw Error("cannot parse line number '%s'", pos);
+ throw ParseError("cannot parse line number '%s'", pos);
}
Symbol file = state.symbols.create(filename);
diff --git a/src/libexpr/attr-path.hh b/src/libexpr/attr-path.hh
index d9d74ab2d..2ee3ea089 100644
--- a/src/libexpr/attr-path.hh
+++ b/src/libexpr/attr-path.hh
@@ -14,7 +14,7 @@ std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const string & attr
Bindings & autoArgs, Value & vIn);
/* Heuristic to find the filename and lineno or a nix value. */
-Pos findDerivationFilename(EvalState & state, Value & v, std::string what);
+Pos findPackageFilename(EvalState & state, Value & v, std::string what);
std::vector<Symbol> parseAttrPath(EvalState & state, std::string_view s);
diff --git a/src/libexpr/attr-set.hh b/src/libexpr/attr-set.hh
index 6d68e5df3..7d6ffc9f3 100644
--- a/src/libexpr/attr-set.hh
+++ b/src/libexpr/attr-set.hh
@@ -17,8 +17,8 @@ struct Attr
{
Symbol name;
Value * value;
- Pos * pos;
- Attr(Symbol name, Value * value, Pos * pos = &noPos)
+ ptr<Pos> pos;
+ Attr(Symbol name, Value * value, ptr<Pos> pos = ptr(&noPos))
: name(name), value(value), pos(pos) { };
Attr() : pos(&noPos) { };
bool operator < (const Attr & a) const
@@ -35,12 +35,13 @@ class Bindings
{
public:
typedef uint32_t size_t;
+ ptr<Pos> pos;
private:
size_t size_, capacity_;
Attr attrs[0];
- Bindings(size_t capacity) : size_(0), capacity_(capacity) { }
+ Bindings(size_t capacity) : pos(&noPos), size_(0), capacity_(capacity) { }
Bindings(const Bindings & bindings) = delete;
public:
diff --git a/src/libexpr/common-eval-args.cc b/src/libexpr/common-eval-args.cc
index aa14bf79b..fb0932c00 100644
--- a/src/libexpr/common-eval-args.cc
+++ b/src/libexpr/common-eval-args.cc
@@ -61,6 +61,14 @@ MixEvalArgs::MixEvalArgs()
fetchers::overrideRegistry(from.input, to.input, extraAttrs);
}}
});
+
+ addFlag({
+ .longName = "eval-store",
+ .description = "The Nix store to use for evaluations.",
+ .category = category,
+ .labels = {"store-url"},
+ .handler = {&evalStoreUrl},
+ });
}
Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
diff --git a/src/libexpr/common-eval-args.hh b/src/libexpr/common-eval-args.hh
index be7fda783..0e113fff1 100644
--- a/src/libexpr/common-eval-args.hh
+++ b/src/libexpr/common-eval-args.hh
@@ -16,8 +16,9 @@ struct MixEvalArgs : virtual Args
Strings searchPath;
-private:
+ std::optional<std::string> evalStoreUrl;
+private:
std::map<std::string, std::string> autoArgs;
};
diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc
index 98d91c905..d7e21783d 100644
--- a/src/libexpr/eval-cache.cc
+++ b/src/libexpr/eval-cache.cc
@@ -486,11 +486,11 @@ std::shared_ptr<AttrCursor> AttrCursor::getAttr(std::string_view name)
return getAttr(root->state.symbols.create(name));
}
-std::shared_ptr<AttrCursor> AttrCursor::findAlongAttrPath(const std::vector<Symbol> & attrPath)
+std::shared_ptr<AttrCursor> AttrCursor::findAlongAttrPath(const std::vector<Symbol> & attrPath, bool force)
{
auto res = shared_from_this();
for (auto & attr : attrPath) {
- res = res->maybeGetAttr(attr);
+ res = res->maybeGetAttr(attr, force);
if (!res) return {};
}
return res;
diff --git a/src/libexpr/eval-cache.hh b/src/libexpr/eval-cache.hh
index e23e45c94..43b34ebcb 100644
--- a/src/libexpr/eval-cache.hh
+++ b/src/libexpr/eval-cache.hh
@@ -102,7 +102,7 @@ public:
std::shared_ptr<AttrCursor> getAttr(std::string_view name);
- std::shared_ptr<AttrCursor> findAlongAttrPath(const std::vector<Symbol> & attrPath);
+ std::shared_ptr<AttrCursor> findAlongAttrPath(const std::vector<Symbol> & attrPath, bool force = false);
std::string getString();
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 3afe2e47b..800839a8d 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -64,7 +64,11 @@ static char * dupStringWithLen(const char * s, size_t size)
RootValue allocRootValue(Value * v)
{
+#if HAVE_BOEHMGC
return std::allocate_shared<Value *>(traceable_allocator<Value *>(), v);
+#else
+ return std::make_shared<Value *>(v);
+#endif
}
@@ -201,6 +205,15 @@ string showType(const Value & v)
}
}
+Pos Value::determinePos(const Pos &pos) const
+{
+ switch (internalType) {
+ case tAttrs: return *attrs->pos;
+ case tLambda: return lambda.fun->pos;
+ case tApp: return app.left->determinePos(pos);
+ default: return pos;
+ }
+}
bool Value::isTrivial() const
{
@@ -224,22 +237,34 @@ static void * oomHandler(size_t requested)
}
class BoehmGCStackAllocator : public StackAllocator {
- boost::coroutines2::protected_fixedsize_stack stack {
- // We allocate 8 MB, the default max stack size on NixOS.
- // A smaller stack might be quicker to allocate but reduces the stack
- // depth available for source filter expressions etc.
- std::max(boost::context::stack_traits::default_size(), static_cast<std::size_t>(8 * 1024 * 1024))
+ boost::coroutines2::protected_fixedsize_stack stack {
+ // We allocate 8 MB, the default max stack size on NixOS.
+ // A smaller stack might be quicker to allocate but reduces the stack
+ // depth available for source filter expressions etc.
+ std::max(boost::context::stack_traits::default_size(), static_cast<std::size_t>(8 * 1024 * 1024))
};
+ // This is specific to boost::coroutines2::protected_fixedsize_stack.
+ // The stack protection page is included in sctx.size, so we have to
+ // subtract one page size from the stack size.
+ std::size_t pfss_usable_stack_size(boost::context::stack_context &sctx) {
+ return sctx.size - boost::context::stack_traits::page_size();
+ }
+
public:
boost::context::stack_context allocate() override {
auto sctx = stack.allocate();
- GC_add_roots(static_cast<char *>(sctx.sp) - sctx.size, sctx.sp);
+
+ // Stacks generally start at a high address and grow to lower addresses.
+ // Architectures that do the opposite are rare; in fact so rare that
+ // boost_routine does not implement it.
+ // So we subtract the stack size.
+ GC_add_roots(static_cast<char *>(sctx.sp) - pfss_usable_stack_size(sctx), sctx.sp);
return sctx;
}
void deallocate(boost::context::stack_context sctx) override {
- GC_remove_roots(static_cast<char *>(sctx.sp) - sctx.size, sctx.sp);
+ GC_remove_roots(static_cast<char *>(sctx.sp) - pfss_usable_stack_size(sctx), sctx.sp);
stack.deallocate(sctx);
}
@@ -353,7 +378,10 @@ static Strings parseNixPath(const string & s)
}
-EvalState::EvalState(const Strings & _searchPath, ref<Store> store)
+EvalState::EvalState(
+ const Strings & _searchPath,
+ ref<Store> store,
+ std::shared_ptr<Store> buildStore)
: sWith(symbols.create("<with>"))
, sOutPath(symbols.create("outPath"))
, sDrvPath(symbols.create("drvPath"))
@@ -386,6 +414,7 @@ EvalState::EvalState(const Strings & _searchPath, ref<Store> store)
, sEpsilon(symbols.create(""))
, repair(NoRepair)
, store(store)
+ , buildStore(buildStore ? buildStore : store)
, regexCache(makeRegexCache())
, baseEnv(allocEnv(128))
, staticBaseEnv(false, 0)
@@ -436,6 +465,23 @@ EvalState::~EvalState()
}
+void EvalState::requireExperimentalFeatureOnEvaluation(
+ const std::string & feature,
+ const std::string_view fName,
+ const Pos & pos)
+{
+ if (!settings.isExperimentalFeatureEnabled(feature)) {
+ throw EvalError({
+ .msg = hintfmt(
+ "Cannot call '%2%' because experimental Nix feature '%1%' is disabled. You can enable it via '--extra-experimental-features %1%'.",
+ feature,
+ fName
+ ),
+ .errPos = pos
+ });
+ }
+}
+
Path EvalState::checkSourcePath(const Path & path_)
{
if (!allowedPaths) return path_;
@@ -741,7 +787,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
}
Bindings::iterator j = env->values[0]->attrs->find(var.name);
if (j != env->values[0]->attrs->end()) {
- if (countCalls && j->pos) attrSelects[*j->pos]++;
+ if (countCalls) attrSelects[*j->pos]++;
return j->value;
}
if (!env->prevWith)
@@ -751,18 +797,10 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
}
-std::atomic<uint64_t> nrValuesFreed{0};
-
-void finalizeValue(void * obj, void * data)
-{
- nrValuesFreed++;
-}
-
Value * EvalState::allocValue()
{
nrValues++;
auto v = (Value *) allocBytes(sizeof(Value));
- //GC_register_finalizer_no_order(v, finalizeValue, nullptr, nullptr, nullptr);
return v;
}
@@ -804,9 +842,9 @@ void EvalState::mkThunk_(Value & v, Expr * expr)
}
-void EvalState::mkPos(Value & v, Pos * pos)
+void EvalState::mkPos(Value & v, ptr<Pos> pos)
{
- if (pos && pos->file.set()) {
+ if (pos->file.set()) {
mkAttrs(v, 3);
mkString(*allocAttr(v, sFile), pos->file);
mkInt(*allocAttr(v, sLine), pos->line);
@@ -829,39 +867,37 @@ Value * Expr::maybeThunk(EvalState & state, Env & env)
}
-unsigned long nrAvoided = 0;
-
Value * ExprVar::maybeThunk(EvalState & state, Env & env)
{
Value * v = state.lookupVar(&env, *this, true);
/* The value might not be initialised in the environment yet.
In that case, ignore it. */
- if (v) { nrAvoided++; return v; }
+ if (v) { state.nrAvoided++; return v; }
return Expr::maybeThunk(state, env);
}
Value * ExprString::maybeThunk(EvalState & state, Env & env)
{
- nrAvoided++;
+ state.nrAvoided++;
return &v;
}
Value * ExprInt::maybeThunk(EvalState & state, Env & env)
{
- nrAvoided++;
+ state.nrAvoided++;
return &v;
}
Value * ExprFloat::maybeThunk(EvalState & state, Env & env)
{
- nrAvoided++;
+ state.nrAvoided++;
return &v;
}
Value * ExprPath::maybeThunk(EvalState & state, Env & env)
{
- nrAvoided++;
+ state.nrAvoided++;
return &v;
}
@@ -876,45 +912,56 @@ void EvalState::evalFile(const Path & path_, Value & v, bool mustBeTrivial)
return;
}
- Path path2 = resolveExprPath(path);
- if ((i = fileEvalCache.find(path2)) != fileEvalCache.end()) {
+ Path resolvedPath = resolveExprPath(path);
+ if ((i = fileEvalCache.find(resolvedPath)) != fileEvalCache.end()) {
v = i->second;
return;
}
- printTalkative("evaluating file '%1%'", path2);
+ printTalkative("evaluating file '%1%'", resolvedPath);
Expr * e = nullptr;
- auto j = fileParseCache.find(path2);
+ auto j = fileParseCache.find(resolvedPath);
if (j != fileParseCache.end())
e = j->second;
if (!e)
- e = parseExprFromFile(checkSourcePath(path2));
+ e = parseExprFromFile(checkSourcePath(resolvedPath));
- fileParseCache[path2] = e;
+ cacheFile(path, resolvedPath, e, v, mustBeTrivial);
+}
+
+
+void EvalState::resetFileCache()
+{
+ fileEvalCache.clear();
+ fileParseCache.clear();
+}
+
+
+void EvalState::cacheFile(
+ const Path & path,
+ const Path & resolvedPath,
+ Expr * e,
+ Value & v,
+ bool mustBeTrivial)
+{
+ fileParseCache[resolvedPath] = e;
try {
// Enforce that 'flake.nix' is a direct attrset, not a
// computation.
if (mustBeTrivial &&
!(dynamic_cast<ExprAttrs *>(e)))
- throw Error("file '%s' must be an attribute set", path);
+ throw EvalError("file '%s' must be an attribute set", path);
eval(e, v);
} catch (Error & e) {
- addErrorTrace(e, "while evaluating the file '%1%':", path2);
+ addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath);
throw;
}
- fileEvalCache[path2] = v;
- if (path != path2) fileEvalCache[path] = v;
-}
-
-
-void EvalState::resetFileCache()
-{
- fileEvalCache.clear();
- fileParseCache.clear();
+ fileEvalCache[resolvedPath] = v;
+ if (path != resolvedPath) fileEvalCache[path] = v;
}
@@ -1008,7 +1055,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
} else
vAttr = i.second.e->maybeThunk(state, i.second.inherited ? env : env2);
env2.values[displ++] = vAttr;
- v.attrs->push_back(Attr(i.first, vAttr, &i.second.pos));
+ v.attrs->push_back(Attr(i.first, vAttr, ptr(&i.second.pos)));
}
/* If the rec contains an attribute called `__overrides', then
@@ -1040,7 +1087,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
else
for (auto & i : attrs)
- v.attrs->push_back(Attr(i.first, i.second.e->maybeThunk(state, env), &i.second.pos));
+ v.attrs->push_back(Attr(i.first, i.second.e->maybeThunk(state, env), ptr(&i.second.pos)));
/* Dynamic attrs apply *after* rec and __overrides. */
for (auto & i : dynamicAttrs) {
@@ -1057,9 +1104,11 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
i.valueExpr->setName(nameSym);
/* Keep sorted order so find can catch duplicates */
- v.attrs->push_back(Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), &i.pos));
+ v.attrs->push_back(Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), ptr(&i.pos)));
v.attrs->sort(); // FIXME: inefficient
}
+
+ v.attrs->pos = ptr(&pos);
}
@@ -1114,12 +1163,10 @@ static string showAttrPath(EvalState & state, Env & env, const AttrPath & attrPa
}
-unsigned long nrLookups = 0;
-
void ExprSelect::eval(EvalState & state, Env & env, Value & v)
{
Value vTmp;
- Pos * pos2 = 0;
+ ptr<Pos> pos2(&noPos);
Value * vAttrs = &vTmp;
e->eval(state, env, vTmp);
@@ -1127,7 +1174,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
try {
for (auto & i : attrPath) {
- nrLookups++;
+ state.nrLookups++;
Bindings::iterator j;
Symbol name = getName(i, state, env);
if (def) {
@@ -1145,13 +1192,13 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
}
vAttrs = j->value;
pos2 = j->pos;
- if (state.countCalls && pos2) state.attrSelects[*pos2]++;
+ if (state.countCalls) state.attrSelects[*pos2]++;
}
- state.forceValue(*vAttrs, ( pos2 != NULL ? *pos2 : this->pos ) );
+ state.forceValue(*vAttrs, (*pos2 != noPos ? *pos2 : this->pos ) );
} catch (Error & e) {
- if (pos2 && pos2->file != state.sDerivationNix)
+ if (*pos2 != noPos && pos2->file != state.sDerivationNix)
addErrorTrace(e, *pos2, "while evaluating the attribute '%1%'",
showAttrPath(state, env, attrPath));
throw;
@@ -1557,7 +1604,6 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
and none of the strings are allowed to have contexts. */
if (first) {
firstType = vTmp.type();
- first = false;
}
if (firstType == nInt) {
@@ -1578,7 +1624,12 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
} else
throwEvalError(pos, "cannot add %1% to a float", showType(vTmp));
} else
- s << state.coerceToString(pos, vTmp, context, false, firstType == nString);
+ /* skip canonization of first path, which would only be not
+ canonized in the first place if it's coming from a ./${foo} type
+ path */
+ s << state.coerceToString(pos, vTmp, context, false, firstType == nString, !first);
+
+ first = false;
}
if (firstType == nInt)
@@ -1597,7 +1648,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
void ExprPos::eval(EvalState & state, Env & env, Value & v)
{
- state.mkPos(v, &pos);
+ state.mkPos(v, ptr(&pos));
}
@@ -1767,7 +1818,7 @@ std::optional<string> EvalState::tryAttrsToString(const Pos & pos, Value & v,
}
string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context,
- bool coerceMore, bool copyToStore)
+ bool coerceMore, bool copyToStore, bool canonicalizePath)
{
forceValue(v, pos);
@@ -1779,7 +1830,7 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context,
}
if (v.type() == nPath) {
- Path path(canonPath(v.path));
+ Path path(canonicalizePath ? canonPath(v.path) : v.path);
return copyToStore ? copyPathToStore(context, path) : path;
}
@@ -2091,9 +2142,12 @@ Strings EvalSettings::getDefaultNixPath()
}
};
- add(getHome() + "/.nix-defexpr/channels");
- add(settings.nixStateDir + "/profiles/per-user/root/channels/nixpkgs", "nixpkgs");
- add(settings.nixStateDir + "/profiles/per-user/root/channels");
+ if (!evalSettings.restrictEval && !evalSettings.pureEval) {
+ add(getHome() + "/.nix-defexpr/channels");
+ add(settings.nixStateDir + "/profiles/per-user/root/channels/nixpkgs", "nixpkgs");
+ add(settings.nixStateDir + "/profiles/per-user/root/channels");
+ }
+
return res;
}
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index e3eaed6d3..9df6150c6 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -94,8 +94,14 @@ public:
Value vEmptySet;
+ /* Store used to materialise .drv files. */
const ref<Store> store;
+ /* Store used to build stuff. */
+ const ref<Store> buildStore;
+
+ RootValue vCallFlake = nullptr;
+ RootValue vImportedDrvToDerivation = nullptr;
private:
SrcToStore srcToStore;
@@ -128,9 +134,18 @@ private:
public:
- EvalState(const Strings & _searchPath, ref<Store> store);
+ EvalState(
+ const Strings & _searchPath,
+ ref<Store> store,
+ std::shared_ptr<Store> buildStore = nullptr);
~EvalState();
+ void requireExperimentalFeatureOnEvaluation(
+ const std::string & feature,
+ const std::string_view fName,
+ const Pos & pos
+ );
+
void addToSearchPath(const string & s);
SearchPath getSearchPath() { return searchPath; }
@@ -163,6 +178,14 @@ public:
trivial (i.e. doesn't require arbitrary computation). */
void evalFile(const Path & path, Value & v, bool mustBeTrivial = false);
+ /* Like `cacheFile`, but with an already parsed expression. */
+ void cacheFile(
+ const Path & path,
+ const Path & resolvedPath,
+ Expr * e,
+ Value & v,
+ bool mustBeTrivial = false);
+
void resetFileCache();
/* Look up a file in the search path. */
@@ -217,7 +240,8 @@ public:
booleans and lists to a string. If `copyToStore' is set,
referenced paths are copied to the Nix store as a side effect. */
string coerceToString(const Pos & pos, Value & v, PathSet & context,
- bool coerceMore = false, bool copyToStore = true);
+ bool coerceMore = false, bool copyToStore = true,
+ bool canonicalizePath = true);
string copyPathToStore(PathSet & context, const Path & path);
@@ -301,7 +325,7 @@ public:
void mkList(Value & v, size_t length);
void mkAttrs(Value & v, size_t capacity);
void mkThunk_(Value & v, Expr * expr);
- void mkPos(Value & v, Pos * pos);
+ void mkPos(Value & v, ptr<Pos> pos);
void concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos);
@@ -316,8 +340,10 @@ private:
unsigned long nrValuesInEnvs = 0;
unsigned long nrValues = 0;
unsigned long nrListElems = 0;
+ unsigned long nrLookups = 0;
unsigned long nrAttrsets = 0;
unsigned long nrAttrsInAttrsets = 0;
+ unsigned long nrAvoided = 0;
unsigned long nrOpUpdates = 0;
unsigned long nrOpUpdateValuesCopied = 0;
unsigned long nrListConcats = 0;
@@ -339,6 +365,11 @@ private:
friend struct ExprOpUpdate;
friend struct ExprOpConcatLists;
+ friend struct ExprVar;
+ friend struct ExprString;
+ friend struct ExprInt;
+ friend struct ExprFloat;
+ friend struct ExprPath;
friend struct ExprSelect;
friend void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v);
friend void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v);
diff --git a/src/libexpr/flake/config.cc b/src/libexpr/flake/config.cc
index 63566131e..41b6f78ed 100644
--- a/src/libexpr/flake/config.cc
+++ b/src/libexpr/flake/config.cc
@@ -22,12 +22,14 @@ static TrustedList readTrustedList()
static void writeTrustedList(const TrustedList & trustedList)
{
- writeFile(trustedListPath(), nlohmann::json(trustedList).dump());
+ auto path = trustedListPath();
+ createDirs(dirOf(path));
+ writeFile(path, nlohmann::json(trustedList).dump());
}
void ConfigFile::apply()
{
- std::set<std::string> whitelist{"bash-prompt", "bash-prompt-suffix"};
+ std::set<std::string> whitelist{"bash-prompt", "bash-prompt-suffix", "flake-registry"};
for (auto & [name, value] : settings) {
diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc
index 2e94490d4..1a1fa6938 100644
--- a/src/libexpr/flake/flake.cc
+++ b/src/libexpr/flake/flake.cc
@@ -1,4 +1,5 @@
#include "flake.hh"
+#include "eval.hh"
#include "lockfile.hh"
#include "primops.hh"
#include "eval-inline.hh"
@@ -88,10 +89,12 @@ static void expectType(EvalState & state, ValueType type,
}
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
- EvalState & state, Value * value, const Pos & pos);
+ EvalState & state, Value * value, const Pos & pos,
+ const std::optional<Path> & baseDir);
static FlakeInput parseFlakeInput(EvalState & state,
- const std::string & inputName, Value * value, const Pos & pos)
+ const std::string & inputName, Value * value, const Pos & pos,
+ const std::optional<Path> & baseDir)
{
expectType(state, nAttrs, *value, pos);
@@ -115,7 +118,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
expectType(state, nBool, *attr.value, *attr.pos);
input.isFlake = attr.value->boolean;
} else if (attr.name == sInputs) {
- input.overrides = parseFlakeInputs(state, attr.value, *attr.pos);
+ input.overrides = parseFlakeInputs(state, attr.value, *attr.pos, baseDir);
} else if (attr.name == sFollows) {
expectType(state, nString, *attr.value, *attr.pos);
input.follows = parseInputPath(attr.value->string.s);
@@ -153,7 +156,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
if (!attrs.empty())
throw Error("unexpected flake input attribute '%s', at %s", attrs.begin()->first, pos);
if (url)
- input.ref = parseFlakeRef(*url, {}, true);
+ input.ref = parseFlakeRef(*url, baseDir, true);
}
if (!input.follows && !input.ref)
@@ -163,7 +166,8 @@ static FlakeInput parseFlakeInput(EvalState & state,
}
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
- EvalState & state, Value * value, const Pos & pos)
+ EvalState & state, Value * value, const Pos & pos,
+ const std::optional<Path> & baseDir)
{
std::map<FlakeId, FlakeInput> inputs;
@@ -174,7 +178,8 @@ static std::map<FlakeId, FlakeInput> parseFlakeInputs(
parseFlakeInput(state,
inputAttr.name,
inputAttr.value,
- *inputAttr.pos));
+ *inputAttr.pos,
+ baseDir));
}
return inputs;
@@ -190,7 +195,8 @@ static Flake getFlake(
state, originalRef, allowLookup, flakeCache);
// Guard against symlink attacks.
- auto flakeFile = canonPath(sourceInfo.actualPath + "/" + lockedRef.subdir + "/flake.nix");
+ auto flakeDir = canonPath(sourceInfo.actualPath + "/" + lockedRef.subdir);
+ auto flakeFile = canonPath(flakeDir + "/flake.nix");
if (!isInDir(flakeFile, sourceInfo.actualPath))
throw Error("'flake.nix' file of flake '%s' escapes from '%s'",
lockedRef, state.store->printStorePath(sourceInfo.storePath));
@@ -218,7 +224,7 @@ static Flake getFlake(
auto sInputs = state.symbols.create("inputs");
if (auto inputs = vInfo.attrs->get(sInputs))
- flake.inputs = parseFlakeInputs(state, inputs->value, *inputs->pos);
+ flake.inputs = parseFlakeInputs(state, inputs->value, *inputs->pos, flakeDir);
auto sOutputs = state.symbols.create("outputs");
@@ -296,7 +302,14 @@ LockedFlake lockFlake(
FlakeCache flakeCache;
- auto flake = getFlake(state, topRef, lockFlags.useRegistries, flakeCache);
+ auto useRegistries = lockFlags.useRegistries.value_or(settings.useRegistries);
+
+ auto flake = getFlake(state, topRef, useRegistries, flakeCache);
+
+ if (lockFlags.applyNixConfig) {
+ flake.config.apply();
+ // FIXME: send new config to the daemon.
+ }
try {
@@ -317,25 +330,38 @@ LockedFlake lockFlake(
std::vector<FlakeRef> parents;
+ struct LockParent {
+ /* The path to this parent. */
+ InputPath path;
+
+ /* Whether we are currently inside a top-level lockfile
+ (inputs absolute) or subordinate lockfile (inputs
+ relative). */
+ bool absolute;
+ };
+
std::function<void(
const FlakeInputs & flakeInputs,
std::shared_ptr<Node> node,
const InputPath & inputPathPrefix,
- std::shared_ptr<const Node> oldNode)>
+ std::shared_ptr<const Node> oldNode,
+ const LockParent & parent,
+ const Path & parentPath)>
computeLocks;
computeLocks = [&](
const FlakeInputs & flakeInputs,
std::shared_ptr<Node> node,
const InputPath & inputPathPrefix,
- std::shared_ptr<const Node> oldNode)
+ std::shared_ptr<const Node> oldNode,
+ const LockParent & parent,
+ const Path & parentPath)
{
debug("computing lock file node '%s'", printInputPath(inputPathPrefix));
/* Get the overrides (i.e. attributes of the form
'inputs.nixops.inputs.nixpkgs.url = ...'). */
- // FIXME: check this
- for (auto & [id, input] : flake.inputs) {
+ for (auto & [id, input] : flakeInputs) {
for (auto & [idOverride, inputOverride] : input.overrides) {
auto inputPath(inputPathPrefix);
inputPath.push_back(id);
@@ -359,22 +385,31 @@ LockedFlake lockFlake(
ancestors? */
auto i = overrides.find(inputPath);
bool hasOverride = i != overrides.end();
- if (hasOverride) overridesUsed.insert(inputPath);
+ if (hasOverride) {
+ overridesUsed.insert(inputPath);
+ // Respect the “flakeness” of the input even if we
+ // override it
+ i->second.isFlake = input2.isFlake;
+ }
auto & input = hasOverride ? i->second : input2;
/* Resolve 'follows' later (since it may refer to an input
path we haven't processed yet. */
if (input.follows) {
InputPath target;
- if (hasOverride || input.absolute)
- /* 'follows' from an override is relative to the
- root of the graph. */
+
+ if (parent.absolute && !hasOverride) {
target = *input.follows;
- else {
- /* Otherwise, it's relative to the current flake. */
- target = inputPathPrefix;
+ } else {
+ if (hasOverride) {
+ target = inputPathPrefix;
+ target.pop_back();
+ } else
+ target = parent.path;
+
for (auto & i : *input.follows) target.push_back(i);
}
+
debug("input '%s' follows '%s'", inputPathS, printInputPath(target));
node->inputs.insert_or_assign(id, target);
continue;
@@ -420,7 +455,7 @@ LockedFlake lockFlake(
if (hasChildUpdate) {
auto inputFlake = getFlake(
state, oldLock->lockedRef, false, flakeCache);
- computeLocks(inputFlake.inputs, childNode, inputPath, oldLock);
+ computeLocks(inputFlake.inputs, childNode, inputPath, oldLock, parent, parentPath);
} else {
/* No need to fetch this flake, we can be
lazy. However there may be new overrides on the
@@ -437,12 +472,11 @@ LockedFlake lockFlake(
} else if (auto follows = std::get_if<1>(&i.second)) {
fakeInputs.emplace(i.first, FlakeInput {
.follows = *follows,
- .absolute = true
});
}
}
- computeLocks(fakeInputs, childNode, inputPath, oldLock);
+ computeLocks(fakeInputs, childNode, inputPath, oldLock, parent, parentPath);
}
} else {
@@ -454,7 +488,15 @@ LockedFlake lockFlake(
throw Error("cannot update flake input '%s' in pure mode", inputPathS);
if (input.isFlake) {
- auto inputFlake = getFlake(state, *input.ref, lockFlags.useRegistries, flakeCache);
+ Path localPath = parentPath;
+ FlakeRef localRef = *input.ref;
+
+ // If this input is a path, recurse it down.
+ // This allows us to resolve path inputs relative to the current flake.
+ if (localRef.input.getType() == "path")
+ localPath = absPath(*input.ref->input.getSourcePath(), parentPath);
+
+ auto inputFlake = getFlake(state, localRef, useRegistries, flakeCache);
/* Note: in case of an --override-input, we use
the *original* ref (input2.ref) for the
@@ -475,6 +517,13 @@ LockedFlake lockFlake(
parents.push_back(*input.ref);
Finally cleanup([&]() { parents.pop_back(); });
+ // Follows paths from existing inputs in the top-level lockfile are absolute,
+ // whereas paths in subordinate lockfiles are relative to those lockfiles.
+ LockParent newParent {
+ .path = inputPath,
+ .absolute = oldLock ? true : false
+ };
+
/* Recursively process the inputs of this
flake. Also, unless we already have this flake
in the top-level lock file, use this flake's
@@ -484,12 +533,13 @@ LockedFlake lockFlake(
oldLock
? std::dynamic_pointer_cast<const Node>(oldLock)
: LockFile::read(
- inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root);
+ inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root,
+ newParent, localPath);
}
else {
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
- state, *input.ref, lockFlags.useRegistries, flakeCache);
+ state, *input.ref, useRegistries, flakeCache);
node->inputs.insert_or_assign(id,
std::make_shared<LockedNode>(lockedRef, *input.ref, false));
}
@@ -502,9 +552,17 @@ LockedFlake lockFlake(
}
};
+ LockParent parent {
+ .path = {},
+ .absolute = true
+ };
+
+ // Bring in the current ref for relative path resolution if we have it
+ auto parentPath = canonPath(flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir);
+
computeLocks(
flake.inputs, newLockFile.root, {},
- lockFlags.recreateLockFile ? nullptr : oldLockFile.root);
+ lockFlags.recreateLockFile ? nullptr : oldLockFile.root, parent, parentPath);
for (auto & i : lockFlags.inputOverrides)
if (!overridesUsed.count(i.first))
@@ -554,8 +612,8 @@ LockedFlake lockFlake(
topRef.input.markChangedFile(
(topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock",
lockFlags.commitLockFile
- ? std::optional<std::string>(fmt("%s: %s\n\nFlake input changes:\n\n%s",
- relPath, lockFileExists ? "Update" : "Add", diff))
+ ? std::optional<std::string>(fmt("%s: %s\n\nFlake lock file changes:\n\n%s",
+ relPath, lockFileExists ? "Update" : "Add", filterANSIEscapes(diff, true)))
: std::nullopt);
/* Rewriting the lockfile changed the top-level
@@ -563,7 +621,7 @@ LockedFlake lockFlake(
also just clear the 'rev' field... */
auto prevLockedRef = flake.lockedRef;
FlakeCache dummyCache;
- flake = getFlake(state, topRef, lockFlags.useRegistries, dummyCache);
+ flake = getFlake(state, topRef, useRegistries, dummyCache);
if (lockFlags.commitLockFile &&
flake.lockedRef.input.getRev() &&
@@ -580,8 +638,10 @@ LockedFlake lockFlake(
}
} else
throw Error("cannot write modified lock file of flake '%s' (use '--no-write-lock-file' to ignore)", topRef);
- } else
+ } else {
warn("not writing modified lock file of flake '%s':\n%s", topRef, chomp(diff));
+ flake.forceDirty = true;
+ }
}
return LockedFlake { .flake = std::move(flake), .lockFile = std::move(newLockFile) };
@@ -604,26 +664,32 @@ void callFlake(EvalState & state,
mkString(*vLocks, lockedFlake.lockFile.to_string());
- emitTreeAttrs(state, *lockedFlake.flake.sourceInfo, lockedFlake.flake.lockedRef.input, *vRootSrc);
+ emitTreeAttrs(
+ state,
+ *lockedFlake.flake.sourceInfo,
+ lockedFlake.flake.lockedRef.input,
+ *vRootSrc,
+ false,
+ lockedFlake.flake.forceDirty);
mkString(*vRootSubdir, lockedFlake.flake.lockedRef.subdir);
- static RootValue vCallFlake = nullptr;
-
- if (!vCallFlake) {
- vCallFlake = allocRootValue(state.allocValue());
+ if (!state.vCallFlake) {
+ state.vCallFlake = allocRootValue(state.allocValue());
state.eval(state.parseExprFromString(
#include "call-flake.nix.gen.hh"
- , "/"), **vCallFlake);
+ , "/"), **state.vCallFlake);
}
- state.callFunction(**vCallFlake, *vLocks, *vTmp1, noPos);
+ state.callFunction(**state.vCallFlake, *vLocks, *vTmp1, noPos);
state.callFunction(*vTmp1, *vRootSrc, *vTmp2, noPos);
state.callFunction(*vTmp2, *vRootSubdir, vRes, noPos);
}
static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
+ state.requireExperimentalFeatureOnEvaluation("flakes", "builtins.getFlake", pos);
+
auto flakeRefS = state.forceStringNoCtx(*args[0], pos);
auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
if (evalSettings.pureEval && !flakeRef.input.isImmutable())
@@ -633,13 +699,13 @@ static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Va
lockFlake(state, flakeRef,
LockFlags {
.updateLockFile = false,
- .useRegistries = !evalSettings.pureEval,
+ .useRegistries = !evalSettings.pureEval && settings.useRegistries,
.allowMutable = !evalSettings.pureEval,
}),
v);
}
-static RegisterPrimOp r2("__getFlake", 1, prim_getFlake, "flakes");
+static RegisterPrimOp r2("__getFlake", 1, prim_getFlake);
}
@@ -649,8 +715,9 @@ Fingerprint LockedFlake::getFingerprint() const
// and we haven't changed it, then it's sufficient to use
// flake.sourceInfo.storePath for the fingerprint.
return hashString(htSHA256,
- fmt("%s;%d;%d;%s",
+ fmt("%s;%s;%d;%d;%s",
flake.sourceInfo->storePath.to_string(),
+ flake.lockedRef.subdir,
flake.lockedRef.input.getRevCount().value_or(0),
flake.lockedRef.input.getLastModified().value_or(0),
lockFile));
diff --git a/src/libexpr/flake/flake.hh b/src/libexpr/flake/flake.hh
index d17d5e183..524b18af1 100644
--- a/src/libexpr/flake/flake.hh
+++ b/src/libexpr/flake/flake.hh
@@ -43,7 +43,6 @@ struct FlakeInput
std::optional<FlakeRef> ref;
bool isFlake = true; // true = process flake to get outputs, false = (fetched) static source path
std::optional<InputPath> follows;
- bool absolute = false; // whether 'follows' is relative to the flake root
FlakeInputs overrides;
};
@@ -59,9 +58,10 @@ struct ConfigFile
/* The contents of a flake.nix file. */
struct Flake
{
- FlakeRef originalRef; // the original flake specification (by the user)
- FlakeRef resolvedRef; // registry references and caching resolved to the specific underlying flake
- FlakeRef lockedRef; // the specific local store result of invoking the fetcher
+ FlakeRef originalRef; // the original flake specification (by the user)
+ FlakeRef resolvedRef; // registry references and caching resolved to the specific underlying flake
+ FlakeRef lockedRef; // the specific local store result of invoking the fetcher
+ bool forceDirty = false; // pretend that 'lockedRef' is dirty
std::optional<std::string> description;
std::shared_ptr<const fetchers::Tree> sourceInfo;
FlakeInputs inputs;
@@ -102,7 +102,11 @@ struct LockFlags
/* Whether to use the registries to lookup indirect flake
references like 'nixpkgs'. */
- bool useRegistries = true;
+ std::optional<bool> useRegistries = std::nullopt;
+
+ /* Whether to apply flake's nixConfig attribute to the configuration */
+
+ bool applyNixConfig = false;
/* Whether mutable flake references (i.e. those without a Git
revision or similar) without a corresponding lock are
@@ -137,6 +141,8 @@ void emitTreeAttrs(
EvalState & state,
const fetchers::Tree & tree,
const fetchers::Input & input,
- Value & v, bool emptyRevFallback = false);
+ Value & v,
+ bool emptyRevFallback = false,
+ bool forceDirty = false);
}
diff --git a/src/libexpr/flake/flakeref.cc b/src/libexpr/flake/flakeref.cc
index 833e8a776..29128d789 100644
--- a/src/libexpr/flake/flakeref.cc
+++ b/src/libexpr/flake/flakeref.cc
@@ -172,8 +172,12 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
auto parsedURL = parseURL(url);
std::string fragment;
std::swap(fragment, parsedURL.fragment);
+
+ auto input = Input::fromURL(parsedURL);
+ input.parent = baseDir;
+
return std::make_pair(
- FlakeRef(Input::fromURL(parsedURL), get(parsedURL.query, "dir").value_or("")),
+ FlakeRef(std::move(input), get(parsedURL.query, "dir").value_or("")),
fragment);
}
}
diff --git a/src/libexpr/flake/lockfile.cc b/src/libexpr/flake/lockfile.cc
index 6089d1363..fda340789 100644
--- a/src/libexpr/flake/lockfile.cc
+++ b/src/libexpr/flake/lockfile.cc
@@ -2,6 +2,8 @@
#include "store-api.hh"
#include "url-parts.hh"
+#include <iomanip>
+
#include <nlohmann/json.hpp>
namespace nix::flake {
@@ -268,10 +270,20 @@ std::map<InputPath, Node::Edge> LockFile::getAllInputs() const
return res;
}
+static std::string describe(const FlakeRef & flakeRef)
+{
+ auto s = fmt("'%s'", flakeRef.to_string());
+
+ if (auto lastModified = flakeRef.input.getLastModified())
+ s += fmt(" (%s)", std::put_time(std::gmtime(&*lastModified), "%Y-%m-%d"));
+
+ return s;
+}
+
std::ostream & operator <<(std::ostream & stream, const Node::Edge & edge)
{
if (auto node = std::get_if<0>(&edge))
- stream << "'" << (*node)->lockedRef << "'";
+ stream << describe((*node)->lockedRef);
else if (auto follows = std::get_if<1>(&edge))
stream << fmt("follows '%s'", printInputPath(*follows));
return stream;
@@ -299,14 +311,15 @@ std::string LockFile::diff(const LockFile & oldLocks, const LockFile & newLocks)
while (i != oldFlat.end() || j != newFlat.end()) {
if (j != newFlat.end() && (i == oldFlat.end() || i->first > j->first)) {
- res += fmt("* Added '%s': %s\n", printInputPath(j->first), j->second);
+ res += fmt("• " ANSI_GREEN "Added input '%s':" ANSI_NORMAL "\n %s\n",
+ printInputPath(j->first), j->second);
++j;
} else if (i != oldFlat.end() && (j == newFlat.end() || i->first < j->first)) {
- res += fmt("* Removed '%s'\n", printInputPath(i->first));
+ res += fmt("• " ANSI_RED "Removed input '%s'" ANSI_NORMAL "\n", printInputPath(i->first));
++i;
} else {
if (!equals(i->second, j->second)) {
- res += fmt("* Updated '%s': %s -> %s\n",
+ res += fmt("• " ANSI_BOLD "Updated input '%s':" ANSI_NORMAL "\n %s\n → %s\n",
printInputPath(i->first),
i->second,
j->second);
diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l
index 7298419d9..51593eccd 100644
--- a/src/libexpr/lexer.l
+++ b/src/libexpr/lexer.l
@@ -9,6 +9,9 @@
%s DEFAULT
%x STRING
%x IND_STRING
+%x INPATH
+%x INPATH_SLASH
+%x PATH_START
%{
@@ -25,6 +28,8 @@ using namespace nix;
namespace nix {
+// backup to recover from yyless(0)
+YYLTYPE prev_yylloc;
static void initLoc(YYLTYPE * loc)
{
@@ -35,14 +40,18 @@ static void initLoc(YYLTYPE * loc)
static void adjustLoc(YYLTYPE * loc, const char * s, size_t len)
{
+ prev_yylloc = *loc;
+
loc->first_line = loc->last_line;
loc->first_column = loc->last_column;
- while (len--) {
+ for (size_t i = 0; i < len; i++) {
switch (*s++) {
case '\r':
- if (*s == '\n') /* cr/lf */
+ if (*s == '\n') { /* cr/lf */
+ i++;
s++;
+ }
/* fall through */
case '\n':
++loc->last_line;
@@ -95,9 +104,12 @@ ANY .|\n
ID [a-zA-Z\_][a-zA-Z0-9\_\'\-]*
INT [0-9]+
FLOAT (([1-9][0-9]*\.[0-9]*)|(0?\.[0-9]+))([Ee][+-]?[0-9]+)?
-PATH [a-zA-Z0-9\.\_\-\+]*(\/[a-zA-Z0-9\.\_\-\+]+)+\/?
-HPATH \~(\/[a-zA-Z0-9\.\_\-\+]+)+\/?
-SPATH \<[a-zA-Z0-9\.\_\-\+]+(\/[a-zA-Z0-9\.\_\-\+]+)*\>
+PATH_CHAR [a-zA-Z0-9\.\_\-\+]
+PATH {PATH_CHAR}*(\/{PATH_CHAR}+)+\/?
+PATH_SEG {PATH_CHAR}*\/
+HPATH \~(\/{PATH_CHAR}+)+\/?
+HPATH_START \~\/
+SPATH \<{PATH_CHAR}+(\/{PATH_CHAR}+)*\>
URI [a-zA-Z][a-zA-Z0-9\+\-\.]*\:[a-zA-Z0-9\%\/\?\:\@\&\=\+\$\,\-\_\.\!\~\*\']+
@@ -198,17 +210,75 @@ or { return OR_KW; }
return IND_STR;
}
+{PATH_SEG}\$\{ |
+{HPATH_START}\$\{ {
+ PUSH_STATE(PATH_START);
+ yyless(0);
+ *yylloc = prev_yylloc;
+}
+
+<PATH_START>{PATH_SEG} {
+ POP_STATE();
+ PUSH_STATE(INPATH_SLASH);
+ yylval->path = strdup(yytext);
+ return PATH;
+}
+
+<PATH_START>{HPATH_START} {
+ POP_STATE();
+ PUSH_STATE(INPATH_SLASH);
+ yylval->path = strdup(yytext);
+ return HPATH;
+}
+
+{PATH} {
+ if (yytext[yyleng-1] == '/')
+ PUSH_STATE(INPATH_SLASH);
+ else
+ PUSH_STATE(INPATH);
+ yylval->path = strdup(yytext);
+ return PATH;
+}
+{HPATH} {
+ if (yytext[yyleng-1] == '/')
+ PUSH_STATE(INPATH_SLASH);
+ else
+ PUSH_STATE(INPATH);
+ yylval->path = strdup(yytext);
+ return HPATH;
+}
+
+<INPATH,INPATH_SLASH>\$\{ {
+ POP_STATE();
+ PUSH_STATE(INPATH);
+ PUSH_STATE(DEFAULT);
+ return DOLLAR_CURLY;
+}
+<INPATH,INPATH_SLASH>{PATH}|{PATH_SEG}|{PATH_CHAR}+ {
+ POP_STATE();
+ if (yytext[yyleng-1] == '/')
+ PUSH_STATE(INPATH_SLASH);
+ else
+ PUSH_STATE(INPATH);
+ yylval->e = new ExprString(data->symbols.create(string(yytext)));
+ return STR;
+}
+<INPATH>{ANY} |
+<INPATH><<EOF>> {
+ /* if we encounter a non-path character we inform the parser that the path has
+ ended with a PATH_END token and re-parse this character in the default
+ context (it may be ')', ';', or something of that sort) */
+ POP_STATE();
+ yyless(0);
+ *yylloc = prev_yylloc;
+ return PATH_END;
+}
+
+<INPATH_SLASH>{ANY} |
+<INPATH_SLASH><<EOF>> {
+ throw ParseError("path has a trailing slash");
+}
-{PATH} { if (yytext[yyleng-1] == '/')
- throw ParseError("path '%s' has a trailing slash", yytext);
- yylval->path = strdup(yytext);
- return PATH;
- }
-{HPATH} { if (yytext[yyleng-1] == '/')
- throw ParseError("path '%s' has a trailing slash", yytext);
- yylval->path = strdup(yytext);
- return HPATH;
- }
{SPATH} { yylval->path = strdup(yytext); return SPATH; }
{URI} { yylval->uri = strdup(yytext); return URI; }
diff --git a/src/libexpr/local.mk b/src/libexpr/local.mk
index 26c53d301..016631647 100644
--- a/src/libexpr/local.mk
+++ b/src/libexpr/local.mk
@@ -15,8 +15,8 @@ libexpr_CXXFLAGS += -I src/libutil -I src/libstore -I src/libfetchers -I src/lib
libexpr_LIBS = libutil libstore libfetchers
-libexpr_LDFLAGS = -lboost_context
-ifneq ($(OS), FreeBSD)
+libexpr_LDFLAGS += -lboost_context -pthread
+ifdef HOST_LINUX
libexpr_LDFLAGS += -ldl
endif
@@ -35,7 +35,7 @@ $(d)/lexer-tab.cc $(d)/lexer-tab.hh: $(d)/lexer.l
clean-files += $(d)/parser-tab.cc $(d)/parser-tab.hh $(d)/lexer-tab.cc $(d)/lexer-tab.hh
-$(eval $(call install-file-in, $(d)/nix-expr.pc, $(prefix)/lib/pkgconfig, 0644))
+$(eval $(call install-file-in, $(d)/nix-expr.pc, $(libdir)/pkgconfig, 0644))
$(foreach i, $(wildcard src/libexpr/flake/*.hh), \
$(eval $(call install-file-in, $(i), $(includedir)/nix/flake, 0644)))
diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh
index 8df8055b3..51a14cd59 100644
--- a/src/libexpr/nixexpr.hh
+++ b/src/libexpr/nixexpr.hh
@@ -180,6 +180,7 @@ struct ExprOpHasAttr : Expr
struct ExprAttrs : Expr
{
bool recursive;
+ Pos pos;
struct AttrDef {
bool inherited;
Expr * e;
@@ -199,7 +200,8 @@ struct ExprAttrs : Expr
};
typedef std::vector<DynamicAttrDef> DynamicAttrDefs;
DynamicAttrDefs dynamicAttrs;
- ExprAttrs() : recursive(false) { };
+ ExprAttrs(const Pos &pos) : recursive(false), pos(pos) { };
+ ExprAttrs() : recursive(false), pos(noPos) { };
COMMON_METHODS
};
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index 49d995bb9..e3749783a 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -290,13 +290,13 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
%type <formal> formal
%type <attrNames> attrs attrpath
%type <string_parts> string_parts_interpolated ind_string_parts
-%type <e> string_parts string_attr
+%type <e> path_start string_parts string_attr
%type <id> attr
%token <id> ID ATTRPATH
%token <e> STR IND_STR
%token <n> INT
%token <nf> FLOAT
-%token <path> PATH HPATH SPATH
+%token <path> PATH HPATH SPATH PATH_END
%token <uri> URI
%token IF THEN ELSE ASSERT WITH LET IN REC INHERIT EQ NEQ AND OR IMPL OR_KW
%token DOLLAR_CURLY /* == ${ */
@@ -405,8 +405,11 @@ expr_simple
| IND_STRING_OPEN ind_string_parts IND_STRING_CLOSE {
$$ = stripIndentation(CUR_POS, data->symbols, *$2);
}
- | PATH { $$ = new ExprPath(absPath($1, data->basePath)); }
- | HPATH { $$ = new ExprPath(getHome() + string{$1 + 1}); }
+ | path_start PATH_END { $$ = $1; }
+ | path_start string_parts_interpolated PATH_END {
+ $2->insert($2->begin(), $1);
+ $$ = new ExprConcatStrings(CUR_POS, false, $2);
+ }
| SPATH {
string path($1 + 1, strlen($1) - 2);
$$ = new ExprApp(CUR_POS,
@@ -452,6 +455,20 @@ string_parts_interpolated
}
;
+path_start
+ : PATH {
+ Path path(absPath($1, data->basePath));
+ /* add back in the trailing '/' to the first segment */
+ if ($1[strlen($1)-1] == '/' && strlen($1) > 1)
+ path += "/";
+ $$ = new ExprPath(path);
+ }
+ | HPATH {
+ Path path(getHome() + string($1 + 1));
+ $$ = new ExprPath(path);
+ }
+ ;
+
ind_string_parts
: ind_string_parts IND_STR { $$ = $1; $1->push_back($2); }
| ind_string_parts DOLLAR_CURLY expr '}' { $$ = $1; $1->push_back($3); }
@@ -478,7 +495,7 @@ binds
$$->attrs[i.symbol] = ExprAttrs::AttrDef(new ExprSelect(CUR_POS, $4, i.symbol), makeCurPos(@6, data));
}
}
- | { $$ = new ExprAttrs; }
+ | { $$ = new ExprAttrs(makeCurPos(@0, data)); }
;
attrs
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 428adf4c2..0422102a9 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -21,6 +21,8 @@
#include <regex>
#include <dlfcn.h>
+#include <cmath>
+
namespace nix {
@@ -50,7 +52,8 @@ void EvalState::realiseContext(const PathSet & context)
if (drvs.empty()) return;
if (!evalSettings.enableImportFromDerivation)
- throw EvalError("attempted to realize '%1%' during evaluation but 'allow-import-from-derivation' is false",
+ throw Error(
+ "cannot build '%1%' during evaluation because the option 'allow-import-from-derivation' is disabled",
store->printStorePath(drvs.begin()->drvPath));
/* For performance, prefetch all substitute info. */
@@ -122,7 +125,7 @@ static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vS
});
} catch (Error & e) {
e.addTrace(pos, "while importing '%s'", path);
- throw e;
+ throw;
}
Path realPath = state.checkSourcePath(state.toRealPath(path, context));
@@ -158,16 +161,15 @@ static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vS
}
w.attrs->sort();
- static RootValue fun;
- if (!fun) {
- fun = allocRootValue(state.allocValue());
+ if (!state.vImportedDrvToDerivation) {
+ state.vImportedDrvToDerivation = allocRootValue(state.allocValue());
state.eval(state.parseExprFromString(
#include "imported-drv-to-derivation.nix.gen.hh"
- , "/"), **fun);
+ , "/"), **state.vImportedDrvToDerivation);
}
- state.forceFunction(**fun, pos);
- mkApp(v, **fun, w);
+ state.forceFunction(**state.vImportedDrvToDerivation, pos);
+ mkApp(v, **state.vImportedDrvToDerivation, w);
state.forceAttrs(v, pos);
}
@@ -547,18 +549,56 @@ typedef list<Value *> ValueList;
#endif
+static Bindings::iterator getAttr(
+ EvalState & state,
+ string funcName,
+ string attrName,
+ Bindings * attrSet,
+ const Pos & pos)
+{
+ Bindings::iterator value = attrSet->find(state.symbols.create(attrName));
+ if (value == attrSet->end()) {
+ hintformat errorMsg = hintfmt(
+ "attribute '%s' missing for call to '%s'",
+ attrName,
+ funcName
+ );
+
+ Pos aPos = *attrSet->pos;
+ if (aPos == noPos) {
+ throw TypeError({
+ .msg = errorMsg,
+ .errPos = pos,
+ });
+ } else {
+ auto e = TypeError({
+ .msg = errorMsg,
+ .errPos = aPos,
+ });
+
+ // Adding another trace for the function name to make it clear
+ // which call received wrong arguments.
+ e.addTrace(pos, hintfmt("while invoking '%s'", funcName));
+ throw;
+ }
+ }
+
+ return value;
+}
+
static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
state.forceAttrs(*args[0], pos);
/* Get the start set. */
- Bindings::iterator startSet =
- args[0]->attrs->find(state.symbols.create("startSet"));
- if (startSet == args[0]->attrs->end())
- throw EvalError({
- .msg = hintfmt("attribute 'startSet' required"),
- .errPos = pos
- });
+ Bindings::iterator startSet = getAttr(
+ state,
+ "genericClosure",
+ "startSet",
+ args[0]->attrs,
+ pos
+ );
+
state.forceList(*startSet->value, pos);
ValueList workSet;
@@ -566,13 +606,14 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar
workSet.push_back(startSet->value->listElems()[n]);
/* Get the operator. */
- Bindings::iterator op =
- args[0]->attrs->find(state.symbols.create("operator"));
- if (op == args[0]->attrs->end())
- throw EvalError({
- .msg = hintfmt("attribute 'operator' required"),
- .errPos = pos
- });
+ Bindings::iterator op = getAttr(
+ state,
+ "genericClosure",
+ "operator",
+ args[0]->attrs,
+ pos
+ );
+
state.forceValue(*op->value, pos);
/* Construct the closure by applying the operator to element of
@@ -675,6 +716,44 @@ static RegisterPrimOp primop_addErrorContext(RegisterPrimOp::Info {
.fun = prim_addErrorContext,
});
+static void prim_ceil(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+ auto value = state.forceFloat(*args[0], args[0]->determinePos(pos));
+ mkInt(v, ceil(value));
+}
+
+static RegisterPrimOp primop_ceil({
+ .name = "__ceil",
+ .args = {"double"},
+ .doc = R"(
+ Converts an IEEE-754 double-precision floating-point number (*double*) to
+ the next higher integer.
+
+ If the datatype is neither an integer nor a "float", an evaluation error will be
+ thrown.
+ )",
+ .fun = prim_ceil,
+});
+
+static void prim_floor(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+ auto value = state.forceFloat(*args[0], args[0]->determinePos(pos));
+ mkInt(v, floor(value));
+}
+
+static RegisterPrimOp primop_floor({
+ .name = "__floor",
+ .args = {"double"},
+ .doc = R"(
+ Converts an IEEE-754 double-precision floating-point number (*double*) to
+ the next lower integer.
+
+ If the datatype is neither an integer nor a "float", an evaluation error will be
+ thrown.
+ )",
+ .fun = prim_floor,
+});
+
/* Try evaluating the argument. Success => {success=true; value=something;},
* else => {success=false; value=false;} */
static void prim_tryEval(EvalState & state, const Pos & pos, Value * * args, Value & v)
@@ -816,12 +895,14 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
state.forceAttrs(*args[0], pos);
/* Figure out the name first (for stack backtraces). */
- Bindings::iterator attr = args[0]->attrs->find(state.sName);
- if (attr == args[0]->attrs->end())
- throw EvalError({
- .msg = hintfmt("required attribute 'name' missing"),
- .errPos = pos
- });
+ Bindings::iterator attr = getAttr(
+ state,
+ "derivationStrict",
+ state.sName,
+ args[0]->attrs,
+ pos
+ );
+
string drvName;
Pos & posDrvName(*attr->pos);
try {
@@ -953,7 +1034,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
}
} else {
- auto s = state.coerceToString(posDrvName, *i->value, context, true);
+ auto s = state.coerceToString(*i->pos, *i->value, context, true);
drv.env.emplace(key, s);
if (i->name == state.sBuilder) drv.builder = s;
else if (i->name == state.sSystem) drv.platform = s;
@@ -1210,7 +1291,10 @@ static RegisterPrimOp primop_toPath({
static void prim_storePath(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
if (evalSettings.pureEval)
- throw EvalError("builtins.storePath' is not allowed in pure evaluation mode");
+ throw EvalError({
+ .msg = hintfmt("'%s' is not allowed in pure evaluation mode", "builtins.storePath"),
+ .errPos = pos
+ });
PathSet context;
Path path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context));
@@ -1369,12 +1453,13 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va
if (i != v2.attrs->end())
prefix = state.forceStringNoCtx(*i->value, pos);
- i = v2.attrs->find(state.symbols.create("path"));
- if (i == v2.attrs->end())
- throw EvalError({
- .msg = hintfmt("attribute 'path' missing"),
- .errPos = pos
- });
+ i = getAttr(
+ state,
+ "findFile",
+ "path",
+ v2.attrs,
+ pos
+ );
PathSet context;
string path = state.coerceToString(pos, *i->value, context, false, false);
@@ -1408,15 +1493,20 @@ static void prim_hashFile(EvalState & state, const Pos & pos, Value * * args, Va
string type = state.forceStringNoCtx(*args[0], pos);
std::optional<HashType> ht = parseHashType(type);
if (!ht)
- throw Error({
- .msg = hintfmt("unknown hash type '%1%'", type),
- .errPos = pos
- });
+ throw Error({
+ .msg = hintfmt("unknown hash type '%1%'", type),
+ .errPos = pos
+ });
- PathSet context; // discarded
- Path p = state.coerceToPath(pos, *args[1], context);
+ PathSet context;
+ Path path = state.coerceToPath(pos, *args[1], context);
+ try {
+ state.realiseContext(context);
+ } catch (InvalidPathError & e) {
+ throw EvalError("cannot read '%s' since path '%s' is not valid, at %s", path, e.path, pos);
+ }
- mkString(v, hashFile(*ht, state.checkSourcePath(p)).to_string(Base16, false), context);
+ mkString(v, hashFile(*ht, state.checkSourcePath(state.toRealPath(path, context))).to_string(Base16, false));
}
static RegisterPrimOp primop_hashFile({
@@ -1627,7 +1717,7 @@ static void prim_fromJSON(EvalState & state, const Pos & pos, Value * * args, Va
parseJSON(state, s, v);
} catch (JSONParseError &e) {
e.addTrace(pos, "while decoding a JSON string");
- throw e;
+ throw;
}
}
@@ -1920,26 +2010,26 @@ static RegisterPrimOp primop_path({
An enrichment of the built-in path type, based on the attributes
present in *args*. All are optional except `path`:
- - path
+ - path\
The underlying path.
- - name
+ - name\
The name of the path when added to the store. This can used to
reference paths that have nix-illegal characters in their names,
like `@`.
- - filter
+ - filter\
A function of the type expected by `builtins.filterSource`,
with the same semantics.
- - recursive
+ - recursive\
When `false`, when `path` is added to the store it is with a
flat hash, rather than a hash of the NAR serialization of the
file. Thus, `path` must refer to a regular file, not a
directory. This allows similar behavior to `fetchurl`. Defaults
to `true`.
- - sha256
+ - sha256\
When provided, this is the expected hash of the file at the
path. Evaluation will fail if the hash is incorrect, and
providing a hash allows `builtins.path` to be used even when the
@@ -2016,14 +2106,15 @@ void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v)
string attr = state.forceStringNoCtx(*args[0], pos);
state.forceAttrs(*args[1], pos);
// !!! Should we create a symbol here or just do a lookup?
- Bindings::iterator i = args[1]->attrs->find(state.symbols.create(attr));
- if (i == args[1]->attrs->end())
- throw EvalError({
- .msg = hintfmt("attribute '%1%' missing", attr),
- .errPos = pos
- });
+ Bindings::iterator i = getAttr(
+ state,
+ "getAttr",
+ attr,
+ args[1]->attrs,
+ pos
+ );
// !!! add to stack trace?
- if (state.countCalls && i->pos) state.attrSelects[*i->pos]++;
+ if (state.countCalls && *i->pos != noPos) state.attrSelects[*i->pos]++;
state.forceValue(*i->value, pos);
v = *i->value;
}
@@ -2148,22 +2239,25 @@ static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args,
Value & v2(*args[0]->listElems()[i]);
state.forceAttrs(v2, pos);
- Bindings::iterator j = v2.attrs->find(state.sName);
- if (j == v2.attrs->end())
- throw TypeError({
- .msg = hintfmt("'name' attribute missing in a call to 'listToAttrs'"),
- .errPos = pos
- });
- string name = state.forceStringNoCtx(*j->value, pos);
+ Bindings::iterator j = getAttr(
+ state,
+ "listToAttrs",
+ state.sName,
+ v2.attrs,
+ pos
+ );
+
+ string name = state.forceStringNoCtx(*j->value, *j->pos);
Symbol sym = state.symbols.create(name);
if (seen.insert(sym).second) {
- Bindings::iterator j2 = v2.attrs->find(state.symbols.create(state.sValue));
- if (j2 == v2.attrs->end())
- throw TypeError({
- .msg = hintfmt("'value' attribute missing in a call to 'listToAttrs'"),
- .errPos = pos
- });
+ Bindings::iterator j2 = getAttr(
+ state,
+ "listToAttrs",
+ state.sValue,
+ v2.attrs,
+ pos
+ );
v.attrs->push_back(Attr(sym, j2->value, j2->pos));
}
}
@@ -2280,7 +2374,7 @@ static void prim_functionArgs(EvalState & state, const Pos & pos, Value * * args
for (auto & i : args[0]->lambda.fun->formals->formals) {
// !!! should optimise booleans (allocate only once)
Value * value = state.allocValue();
- v.attrs->push_back(Attr(i.name, value, &i.pos));
+ v.attrs->push_back(Attr(i.name, value, ptr(&i.pos)));
mkBool(*value, i.def);
}
v.attrs->sort();
@@ -2804,7 +2898,12 @@ static void prim_concatMap(EvalState & state, const Pos & pos, Value * * args, V
for (unsigned int n = 0; n < nrLists; ++n) {
Value * vElem = args[1]->listElems()[n];
state.callFunction(*args[0], *vElem, lists[n], pos);
- state.forceList(lists[n], pos);
+ try {
+ state.forceList(lists[n], lists[n].determinePos(args[0]->determinePos(pos)));
+ } catch (TypeError &e) {
+ e.addTrace(pos, hintfmt("while invoking '%s'", "concatMap"));
+ throw;
+ }
len += lists[n].listSize();
}
@@ -3015,7 +3114,7 @@ static RegisterPrimOp primop_toString({
- A path (e.g., `toString /foo/bar` yields `"/foo/bar"`.
- - A set containing `{ __toString = self: ...; }`.
+ - A set containing `{ __toString = self: ...; }` or `{ outPath = ...; }`.
- An integer.
@@ -3100,7 +3199,7 @@ static void prim_hashString(EvalState & state, const Pos & pos, Value * * args,
PathSet context; // discarded
string s = state.forceString(*args[1], context, pos);
- mkString(v, hashString(*ht, s).to_string(Base16, false), context);
+ mkString(v, hashString(*ht, s).to_string(Base16, false));
}
static RegisterPrimOp primop_hashString({
@@ -3507,15 +3606,13 @@ static RegisterPrimOp primop_splitVersion({
RegisterPrimOp::PrimOps * RegisterPrimOp::primOps;
-RegisterPrimOp::RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun,
- std::optional<std::string> requiredFeature)
+RegisterPrimOp::RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun)
{
if (!primOps) primOps = new PrimOps;
primOps->push_back({
.name = name,
.args = {},
.arity = arity,
- .requiredFeature = std::move(requiredFeature),
.fun = fun
});
}
@@ -3551,9 +3648,7 @@ void EvalState::createBaseEnv()
if (!evalSettings.pureEval) {
mkInt(v, time(0));
addConstant("__currentTime", v);
- }
- if (!evalSettings.pureEval) {
mkString(v, settings.thisSystem.get());
addConstant("__currentSystem", v);
}
@@ -3591,14 +3686,13 @@ void EvalState::createBaseEnv()
if (RegisterPrimOp::primOps)
for (auto & primOp : *RegisterPrimOp::primOps)
- if (!primOp.requiredFeature || settings.isExperimentalFeatureEnabled(*primOp.requiredFeature))
- addPrimOp({
- .fun = primOp.fun,
- .arity = std::max(primOp.args.size(), primOp.arity),
- .name = symbols.create(primOp.name),
- .args = std::move(primOp.args),
- .doc = primOp.doc,
- });
+ addPrimOp({
+ .fun = primOp.fun,
+ .arity = std::max(primOp.args.size(), primOp.arity),
+ .name = symbols.create(primOp.name),
+ .args = std::move(primOp.args),
+ .doc = primOp.doc,
+ });
/* Add a wrapper around the derivation primop that computes the
`drvPath' and `outPath' attributes lazily. */
diff --git a/src/libexpr/primops.hh b/src/libexpr/primops.hh
index 9d42d6539..5b16e075f 100644
--- a/src/libexpr/primops.hh
+++ b/src/libexpr/primops.hh
@@ -15,7 +15,6 @@ struct RegisterPrimOp
std::vector<std::string> args;
size_t arity = 0;
const char * doc;
- std::optional<std::string> requiredFeature;
PrimOpFun fun;
};
@@ -28,8 +27,7 @@ struct RegisterPrimOp
RegisterPrimOp(
std::string name,
size_t arity,
- PrimOpFun fun,
- std::optional<std::string> requiredFeature = {});
+ PrimOpFun fun);
RegisterPrimOp(Info && info);
};
diff --git a/src/libexpr/primops/fetchMercurial.cc b/src/libexpr/primops/fetchMercurial.cc
index 4830ebec3..3f88ccb91 100644
--- a/src/libexpr/primops/fetchMercurial.cc
+++ b/src/libexpr/primops/fetchMercurial.cc
@@ -62,6 +62,7 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar
fetchers::Attrs attrs;
attrs.insert_or_assign("type", "hg");
attrs.insert_or_assign("url", url.find("://") != std::string::npos ? url : "file://" + url);
+ attrs.insert_or_assign("name", name);
if (ref) attrs.insert_or_assign("ref", *ref);
if (rev) attrs.insert_or_assign("rev", rev->gitRev());
auto input = fetchers::Input::fromAttrs(std::move(attrs));
diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc
index 27d8ddf35..06bdec003 100644
--- a/src/libexpr/primops/fetchTree.cc
+++ b/src/libexpr/primops/fetchTree.cc
@@ -7,6 +7,7 @@
#include <ctime>
#include <iomanip>
+#include <regex>
namespace nix {
@@ -15,7 +16,8 @@ void emitTreeAttrs(
const fetchers::Tree & tree,
const fetchers::Input & input,
Value & v,
- bool emptyRevFallback)
+ bool emptyRevFallback,
+ bool forceDirty)
{
assert(input.isImmutable());
@@ -32,24 +34,28 @@ void emitTreeAttrs(
mkString(*state.allocAttr(v, state.symbols.create("narHash")),
narHash->to_string(SRI, true));
- if (auto rev = input.getRev()) {
- mkString(*state.allocAttr(v, state.symbols.create("rev")), rev->gitRev());
- mkString(*state.allocAttr(v, state.symbols.create("shortRev")), rev->gitShortRev());
- } else if (emptyRevFallback) {
- // Backwards compat for `builtins.fetchGit`: dirty repos return an empty sha1 as rev
- auto emptyHash = Hash(htSHA1);
- mkString(*state.allocAttr(v, state.symbols.create("rev")), emptyHash.gitRev());
- mkString(*state.allocAttr(v, state.symbols.create("shortRev")), emptyHash.gitShortRev());
- }
-
if (input.getType() == "git")
mkBool(*state.allocAttr(v, state.symbols.create("submodules")),
fetchers::maybeGetBoolAttr(input.attrs, "submodules").value_or(false));
- if (auto revCount = input.getRevCount())
- mkInt(*state.allocAttr(v, state.symbols.create("revCount")), *revCount);
- else if (emptyRevFallback)
- mkInt(*state.allocAttr(v, state.symbols.create("revCount")), 0);
+ if (!forceDirty) {
+
+ if (auto rev = input.getRev()) {
+ mkString(*state.allocAttr(v, state.symbols.create("rev")), rev->gitRev());
+ mkString(*state.allocAttr(v, state.symbols.create("shortRev")), rev->gitShortRev());
+ } else if (emptyRevFallback) {
+ // Backwards compat for `builtins.fetchGit`: dirty repos return an empty sha1 as rev
+ auto emptyHash = Hash(htSHA1);
+ mkString(*state.allocAttr(v, state.symbols.create("rev")), emptyHash.gitRev());
+ mkString(*state.allocAttr(v, state.symbols.create("shortRev")), emptyHash.gitShortRev());
+ }
+
+ if (auto revCount = input.getRevCount())
+ mkInt(*state.allocAttr(v, state.symbols.create("revCount")), *revCount);
+ else if (emptyRevFallback)
+ mkInt(*state.allocAttr(v, state.symbols.create("revCount")), 0);
+
+ }
if (auto lastModified = input.getLastModified()) {
mkInt(*state.allocAttr(v, state.symbols.create("lastModified")), *lastModified);
@@ -60,10 +66,19 @@ void emitTreeAttrs(
v.attrs->sort();
}
-std::string fixURI(std::string uri, EvalState &state)
+std::string fixURI(std::string uri, EvalState &state, const std::string & defaultScheme = "file")
{
state.checkURI(uri);
- return uri.find("://") != std::string::npos ? uri : "file://" + uri;
+ return uri.find("://") != std::string::npos ? uri : defaultScheme + "://" + uri;
+}
+
+std::string fixURIForGit(std::string uri, EvalState & state)
+{
+ static std::regex scp_uri("([^/].*)@(.*):(.*)");
+ if (uri[0] != '/' && std::regex_match(uri, scp_uri))
+ return fixURI(std::regex_replace(uri, scp_uri, "$1@$2/$3"), state, "ssh");
+ else
+ return fixURI(uri, state);
}
void addURI(EvalState &state, fetchers::Attrs &attrs, Symbol name, std::string v)
@@ -72,13 +87,18 @@ void addURI(EvalState &state, fetchers::Attrs &attrs, Symbol name, std::string v
attrs.emplace(name, n == "url" ? fixURI(v, state) : v);
}
+struct FetchTreeParams {
+ bool emptyRevFallback = false;
+ bool allowNameArgument = false;
+};
+
static void fetchTree(
EvalState &state,
const Pos &pos,
Value **args,
Value &v,
const std::optional<std::string> type,
- bool emptyRevFallback = false
+ const FetchTreeParams & params = FetchTreeParams{}
) {
fetchers::Input input;
PathSet context;
@@ -119,17 +139,25 @@ static void fetchTree(
.errPos = pos
});
+ if (!params.allowNameArgument)
+ if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
+ throw Error({
+ .msg = hintfmt("attribute 'name' isn’t supported in call to 'fetchTree'"),
+ .errPos = pos
+ });
+
+
input = fetchers::Input::fromAttrs(std::move(attrs));
} else {
- auto url = fixURI(state.coerceToString(pos, *args[0], context, false, false), state);
+ auto url = state.coerceToString(pos, *args[0], context, false, false);
if (type == "git") {
fetchers::Attrs attrs;
attrs.emplace("type", "git");
- attrs.emplace("url", url);
+ attrs.emplace("url", fixURIForGit(url, state));
input = fetchers::Input::fromAttrs(std::move(attrs));
} else {
- input = fetchers::Input::fromURL(url);
+ input = fetchers::Input::fromURL(fixURI(url, state));
}
}
@@ -144,13 +172,13 @@ static void fetchTree(
if (state.allowedPaths)
state.allowedPaths->insert(tree.actualPath);
- emitTreeAttrs(state, tree, input2, v, emptyRevFallback);
+ emitTreeAttrs(state, tree, input2, v, params.emptyRevFallback, false);
}
static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
settings.requireExperimentalFeature("flakes");
- fetchTree(state, pos, args, v, std::nullopt);
+ fetchTree(state, pos, args, v, std::nullopt, FetchTreeParams { .allowNameArgument = false });
}
// FIXME: document
@@ -206,20 +234,21 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
? fetchers::downloadTarball(state.store, *url, name, (bool) expectedHash).first.storePath
: fetchers::downloadFile(state.store, *url, name, (bool) expectedHash).storePath;
- auto path = state.store->toRealPath(storePath);
+ auto realPath = state.store->toRealPath(storePath);
if (expectedHash) {
auto hash = unpack
? state.store->queryPathInfo(storePath)->narHash
- : hashFile(htSHA256, path);
+ : hashFile(htSHA256, realPath);
if (hash != *expectedHash)
throw Error((unsigned int) 102, "hash mismatch in file downloaded from '%s':\n specified: %s\n got: %s",
*url, expectedHash->to_string(Base32, true), hash.to_string(Base32, true));
}
if (state.allowedPaths)
- state.allowedPaths->insert(path);
+ state.allowedPaths->insert(realPath);
+ auto path = state.store->printStorePath(storePath);
mkString(v, path, PathSet({path}));
}
@@ -292,7 +321,7 @@ static RegisterPrimOp primop_fetchTarball({
static void prim_fetchGit(EvalState &state, const Pos &pos, Value **args, Value &v)
{
- fetchTree(state, pos, args, v, "git", true);
+ fetchTree(state, pos, args, v, "git", FetchTreeParams { .emptyRevFallback = true, .allowNameArgument = true });
}
static RegisterPrimOp primop_fetchGit({
@@ -303,17 +332,17 @@ static RegisterPrimOp primop_fetchGit({
of the repo at that URL is fetched. Otherwise, it can be an
attribute with the following attributes (all except `url` optional):
- - url
+ - url\
The URL of the repo.
- - name
+ - name\
The name of the directory the repo should be exported to in the
store. Defaults to the basename of the URL.
- - rev
+ - rev\
The git revision to fetch. Defaults to the tip of `ref`.
- - ref
+ - ref\
The git ref to look for the requested revision under. This is
often a branch or tag name. Defaults to `HEAD`.
@@ -321,11 +350,11 @@ static RegisterPrimOp primop_fetchGit({
of Nix 2.3.0 Nix will not prefix `refs/heads/` if `ref` starts
with `refs/`.
- - submodules
+ - submodules\
A Boolean parameter that specifies whether submodules should be
checked out. Defaults to `false`.
- - allRefs
+ - allRefs\
Whether to fetch all refs of the repository. With this argument being
true, it's possible to load a `rev` from *any* `ref` (by default only
`rev`s from the specified `ref` are supported).
diff --git a/src/libexpr/value-to-xml.cc b/src/libexpr/value-to-xml.cc
index 7464455d8..2ddc5f751 100644
--- a/src/libexpr/value-to-xml.cc
+++ b/src/libexpr/value-to-xml.cc
@@ -42,7 +42,7 @@ static void showAttrs(EvalState & state, bool strict, bool location,
XMLAttrs xmlAttrs;
xmlAttrs["name"] = i;
- if (location && a.pos != &noPos) posToXML(xmlAttrs, *a.pos);
+ if (location && a.pos != ptr(&noPos)) posToXML(xmlAttrs, *a.pos);
XMLOpenElement _(doc, "attr", xmlAttrs);
printValueAsXML(state, strict, location,
diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh
index b317c1898..a1f131f9e 100644
--- a/src/libexpr/value.hh
+++ b/src/libexpr/value.hh
@@ -341,6 +341,8 @@ public:
return internalType == tList1 ? 1 : internalType == tList2 ? 2 : bigList.size;
}
+ Pos determinePos(const Pos &pos) const;
+
/* Check whether forcing this value requires a trivial amount of
computation. In particular, function applications are
non-trivial. */