aboutsummaryrefslogtreecommitdiff
path: root/src/libexpr/flake
diff options
context:
space:
mode:
Diffstat (limited to 'src/libexpr/flake')
-rw-r--r--src/libexpr/flake/config.cc32
-rw-r--r--src/libexpr/flake/flake.cc194
-rw-r--r--src/libexpr/flake/flakeref.cc105
-rw-r--r--src/libexpr/flake/flakeref.hh10
-rw-r--r--src/libexpr/flake/lockfile.cc4
5 files changed, 200 insertions, 145 deletions
diff --git a/src/libexpr/flake/config.cc b/src/libexpr/flake/config.cc
index 41b6f78ed..a811e59a1 100644
--- a/src/libexpr/flake/config.cc
+++ b/src/libexpr/flake/config.cc
@@ -1,4 +1,6 @@
#include "flake.hh"
+#include "globals.hh"
+#include "fetch-settings.hh"
#include <nlohmann/json.hpp>
@@ -37,11 +39,11 @@ void ConfigFile::apply()
// FIXME: Move into libutil/config.cc.
std::string valueS;
- if (auto s = std::get_if<std::string>(&value))
+ if (auto* s = std::get_if<std::string>(&value))
valueS = *s;
- else if (auto n = std::get_if<int64_t>(&value))
- valueS = fmt("%d", n);
- else if (auto b = std::get_if<Explicit<bool>>(&value))
+ else if (auto* n = std::get_if<int64_t>(&value))
+ valueS = fmt("%d", *n);
+ else if (auto* b = std::get_if<Explicit<bool>>(&value))
valueS = b->t ? "true" : "false";
else if (auto ss = std::get_if<std::vector<std::string>>(&value))
valueS = concatStringsSep(" ", *ss); // FIXME: evil
@@ -52,21 +54,19 @@ void ConfigFile::apply()
auto trustedList = readTrustedList();
bool trusted = false;
-
- if (auto saved = get(get(trustedList, name).value_or(std::map<std::string, bool>()), valueS)) {
+ if (nix::fetchSettings.acceptFlakeConfig){
+ trusted = true;
+ } else if (auto saved = get(get(trustedList, name).value_or(std::map<std::string, bool>()), valueS)) {
trusted = *saved;
+ warn("Using saved setting for '%s = %s' from ~/.local/share/nix/trusted-settings.json.", name,valueS);
} else {
// FIXME: filter ANSI escapes, newlines, \r, etc.
- if (std::tolower(logger->ask(fmt("do you want to allow configuration setting '%s' to be set to '" ANSI_RED "%s" ANSI_NORMAL "' (y/N)?", name, valueS)).value_or('n')) != 'y') {
- if (std::tolower(logger->ask("do you want to permanently mark this value as untrusted (y/N)?").value_or('n')) == 'y') {
- trustedList[name][valueS] = false;
- writeTrustedList(trustedList);
- }
- } else {
- if (std::tolower(logger->ask("do you want to permanently mark this value as trusted (y/N)?").value_or('n')) == 'y') {
- trustedList[name][valueS] = trusted = true;
- writeTrustedList(trustedList);
- }
+ if (std::tolower(logger->ask(fmt("do you want to allow configuration setting '%s' to be set to '" ANSI_RED "%s" ANSI_NORMAL "' (y/N)?", name, valueS)).value_or('n')) == 'y') {
+ trusted = true;
+ }
+ if (std::tolower(logger->ask(fmt("do you want to permanently mark this value as %s (y/N)?", trusted ? "trusted": "untrusted" )).value_or('n')) == 'y') {
+ trustedList[name][valueS] = trusted;
+ writeTrustedList(trustedList);
}
}
diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc
index 1a1fa6938..6a1aca40d 100644
--- a/src/libexpr/flake/flake.cc
+++ b/src/libexpr/flake/flake.cc
@@ -6,6 +6,7 @@
#include "store-api.hh"
#include "fetchers.hh"
#include "finally.hh"
+#include "fetch-settings.hh"
namespace nix {
@@ -64,8 +65,7 @@ static std::tuple<fetchers::Tree, FlakeRef, FlakeRef> fetchOrSubstituteTree(
debug("got tree '%s' from '%s'",
state.store->printStorePath(tree.storePath), lockedRef);
- if (state.allowedPaths)
- state.allowedPaths->insert(tree.actualPath);
+ state.allowPath(tree.storePath);
assert(!originalRef.input.getNarHash() || tree.storePath == originalRef.input.computeStorePath(*state.store));
@@ -90,11 +90,11 @@ static void expectType(EvalState & state, ValueType type,
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
EvalState & state, Value * value, const Pos & pos,
- const std::optional<Path> & baseDir);
+ const std::optional<Path> & baseDir, InputPath lockRootPath);
static FlakeInput parseFlakeInput(EvalState & state,
const std::string & inputName, Value * value, const Pos & pos,
- const std::optional<Path> & baseDir)
+ const std::optional<Path> & baseDir, InputPath lockRootPath)
{
expectType(state, nAttrs, *value, pos);
@@ -118,10 +118,12 @@ 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, baseDir);
+ input.overrides = parseFlakeInputs(state, attr.value, *attr.pos, baseDir, lockRootPath);
} else if (attr.name == sFollows) {
expectType(state, nString, *attr.value, *attr.pos);
- input.follows = parseInputPath(attr.value->string.s);
+ auto follows(parseInputPath(attr.value->string.s));
+ follows.insert(follows.begin(), lockRootPath.begin(), lockRootPath.end());
+ input.follows = follows;
} else {
switch (attr.value->type()) {
case nString:
@@ -156,7 +158,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, baseDir, true);
+ input.ref = parseFlakeRef(*url, baseDir, true, input.isFlake);
}
if (!input.follows && !input.ref)
@@ -167,7 +169,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
EvalState & state, Value * value, const Pos & pos,
- const std::optional<Path> & baseDir)
+ const std::optional<Path> & baseDir, InputPath lockRootPath)
{
std::map<FlakeId, FlakeInput> inputs;
@@ -179,7 +181,8 @@ static std::map<FlakeId, FlakeInput> parseFlakeInputs(
inputAttr.name,
inputAttr.value,
*inputAttr.pos,
- baseDir));
+ baseDir,
+ lockRootPath));
}
return inputs;
@@ -189,14 +192,15 @@ static Flake getFlake(
EvalState & state,
const FlakeRef & originalRef,
bool allowLookup,
- FlakeCache & flakeCache)
+ FlakeCache & flakeCache,
+ InputPath lockRootPath)
{
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
state, originalRef, allowLookup, flakeCache);
// Guard against symlink attacks.
- auto flakeDir = canonPath(sourceInfo.actualPath + "/" + lockedRef.subdir);
- auto flakeFile = canonPath(flakeDir + "/flake.nix");
+ auto flakeDir = canonPath(sourceInfo.actualPath + "/" + lockedRef.subdir, true);
+ auto flakeFile = canonPath(flakeDir + "/flake.nix", true);
if (!isInDir(flakeFile, sourceInfo.actualPath))
throw Error("'flake.nix' file of flake '%s' escapes from '%s'",
lockedRef, state.store->printStorePath(sourceInfo.storePath));
@@ -224,14 +228,14 @@ static Flake getFlake(
auto sInputs = state.symbols.create("inputs");
if (auto inputs = vInfo.attrs->get(sInputs))
- flake.inputs = parseFlakeInputs(state, inputs->value, *inputs->pos, flakeDir);
+ flake.inputs = parseFlakeInputs(state, inputs->value, *inputs->pos, flakeDir, lockRootPath);
auto sOutputs = state.symbols.create("outputs");
if (auto outputs = vInfo.attrs->get(sOutputs)) {
expectType(state, nFunction, *outputs->value, *outputs->pos);
- if (outputs->value->isLambda() && outputs->value->lambda.fun->matchAttrs) {
+ if (outputs->value->isLambda() && outputs->value->lambda.fun->hasFormals()) {
for (auto & formal : outputs->value->lambda.fun->formals->formals) {
if (formal.name != state.sSelf)
flake.inputs.emplace(formal.name, FlakeInput {
@@ -251,19 +255,24 @@ static Flake getFlake(
for (auto & setting : *nixConfig->value->attrs) {
forceTrivialValue(state, *setting.value, *setting.pos);
if (setting.value->type() == nString)
- flake.config.settings.insert({setting.name, state.forceStringNoCtx(*setting.value, *setting.pos)});
+ flake.config.settings.insert({setting.name, std::string(state.forceStringNoCtx(*setting.value, *setting.pos))});
+ else if (setting.value->type() == nPath) {
+ PathSet emptyContext = {};
+ flake.config.settings.emplace(
+ setting.name,
+ state.coerceToString(*setting.pos, *setting.value, emptyContext, false, true, true) .toOwned());
+ }
else if (setting.value->type() == nInt)
flake.config.settings.insert({setting.name, state.forceInt(*setting.value, *setting.pos)});
else if (setting.value->type() == nBool)
- flake.config.settings.insert({setting.name, state.forceBool(*setting.value, *setting.pos)});
+ flake.config.settings.insert({setting.name, Explicit<bool> { state.forceBool(*setting.value, *setting.pos) }});
else if (setting.value->type() == nList) {
std::vector<std::string> ss;
- for (unsigned int n = 0; n < setting.value->listSize(); ++n) {
- auto elem = setting.value->listElems()[n];
+ for (auto elem : setting.value->listItems()) {
if (elem->type() != nString)
throw TypeError("list element in flake configuration setting '%s' is %s while a string is expected",
setting.name, showType(*setting.value));
- ss.push_back(state.forceStringNoCtx(*elem, *setting.pos));
+ ss.emplace_back(state.forceStringNoCtx(*elem, *setting.pos));
}
flake.config.settings.insert({setting.name, ss});
}
@@ -285,6 +294,11 @@ static Flake getFlake(
return flake;
}
+Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup, FlakeCache & flakeCache)
+{
+ return getFlake(state, originalRef, allowLookup, flakeCache, {});
+}
+
Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup)
{
FlakeCache flakeCache;
@@ -298,17 +312,17 @@ LockedFlake lockFlake(
const FlakeRef & topRef,
const LockFlags & lockFlags)
{
- settings.requireExperimentalFeature("flakes");
+ settings.requireExperimentalFeature(Xp::Flakes);
FlakeCache flakeCache;
- auto useRegistries = lockFlags.useRegistries.value_or(settings.useRegistries);
+ auto useRegistries = lockFlags.useRegistries.value_or(fetchSettings.useRegistries);
auto flake = getFlake(state, topRef, useRegistries, flakeCache);
if (lockFlags.applyNixConfig) {
flake.config.apply();
- // FIXME: send new config to the daemon.
+ state.store->setOptions();
}
try {
@@ -330,23 +344,14 @@ 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,
- const LockParent & parent,
- const Path & parentPath)>
+ const InputPath & lockRootPath,
+ const Path & parentPath,
+ bool trustLock)>
computeLocks;
computeLocks = [&](
@@ -354,8 +359,9 @@ LockedFlake lockFlake(
std::shared_ptr<Node> node,
const InputPath & inputPathPrefix,
std::shared_ptr<const Node> oldNode,
- const LockParent & parent,
- const Path & parentPath)
+ const InputPath & lockRootPath,
+ const Path & parentPath,
+ bool trustLock)
{
debug("computing lock file node '%s'", printInputPath(inputPathPrefix));
@@ -398,17 +404,7 @@ LockedFlake lockFlake(
if (input.follows) {
InputPath target;
- if (parent.absolute && !hasOverride) {
- target = *input.follows;
- } else {
- if (hasOverride) {
- target = inputPathPrefix;
- target.pop_back();
- } else
- target = parent.path;
-
- for (auto & i : *input.follows) target.push_back(i);
- }
+ target.insert(target.end(), input.follows->begin(), input.follows->end());
debug("input '%s' follows '%s'", inputPathS, printInputPath(target));
node->inputs.insert_or_assign(id, target);
@@ -447,22 +443,18 @@ LockedFlake lockFlake(
update it. */
auto lb = lockFlags.inputUpdates.lower_bound(inputPath);
- auto hasChildUpdate =
+ auto mustRefetch =
lb != lockFlags.inputUpdates.end()
&& lb->size() > inputPath.size()
&& std::equal(inputPath.begin(), inputPath.end(), lb->begin());
- if (hasChildUpdate) {
- auto inputFlake = getFlake(
- state, oldLock->lockedRef, false, flakeCache);
- computeLocks(inputFlake.inputs, childNode, inputPath, oldLock, parent, parentPath);
- } else {
+ FlakeInputs fakeInputs;
+
+ if (!mustRefetch) {
/* No need to fetch this flake, we can be
lazy. However there may be new overrides on the
inputs of this flake, so we need to check
those. */
- FlakeInputs fakeInputs;
-
for (auto & i : oldLock->inputs) {
if (auto lockedNode = std::get_if<0>(&i.second)) {
fakeInputs.emplace(i.first, FlakeInput {
@@ -470,21 +462,47 @@ LockedFlake lockFlake(
.isFlake = (*lockedNode)->isFlake,
});
} else if (auto follows = std::get_if<1>(&i.second)) {
+ if (! trustLock) {
+ // It is possible that the flake has changed,
+ // so we must confirm all the follows that are in the lockfile are also in the flake.
+ auto overridePath(inputPath);
+ overridePath.push_back(i.first);
+ auto o = overrides.find(overridePath);
+ // If the override disappeared, we have to refetch the flake,
+ // since some of the inputs may not be present in the lockfile.
+ if (o == overrides.end()) {
+ mustRefetch = true;
+ // There's no point populating the rest of the fake inputs,
+ // since we'll refetch the flake anyways.
+ break;
+ }
+ }
+ auto absoluteFollows(lockRootPath);
+ absoluteFollows.insert(absoluteFollows.end(), follows->begin(), follows->end());
fakeInputs.emplace(i.first, FlakeInput {
- .follows = *follows,
+ .follows = absoluteFollows,
});
}
}
-
- computeLocks(fakeInputs, childNode, inputPath, oldLock, parent, parentPath);
}
+ auto localPath(parentPath);
+ // If this input is a path, recurse it down.
+ // This allows us to resolve path inputs relative to the current flake.
+ if ((*input.ref).input.getType() == "path")
+ localPath = absPath(*input.ref->input.getSourcePath(), parentPath);
+ computeLocks(
+ mustRefetch
+ ? getFlake(state, oldLock->lockedRef, false, flakeCache, inputPath).inputs
+ : fakeInputs,
+ childNode, inputPath, oldLock, lockRootPath, parentPath, !mustRefetch);
+
} else {
/* We need to create a new lock file entry. So fetch
this input. */
debug("creating new input '%s'", inputPathS);
- if (!lockFlags.allowMutable && !input.ref->input.isImmutable())
+ if (!lockFlags.allowMutable && !input.ref->input.isLocked())
throw Error("cannot update flake input '%s' in pure mode", inputPathS);
if (input.isFlake) {
@@ -496,7 +514,7 @@ LockedFlake lockFlake(
if (localRef.input.getType() == "path")
localPath = absPath(*input.ref->input.getSourcePath(), parentPath);
- auto inputFlake = getFlake(state, localRef, useRegistries, flakeCache);
+ auto inputFlake = getFlake(state, localRef, useRegistries, flakeCache, inputPath);
/* Note: in case of an --override-input, we use
the *original* ref (input2.ref) for the
@@ -517,13 +535,6 @@ 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
@@ -534,7 +545,7 @@ LockedFlake lockFlake(
? std::dynamic_pointer_cast<const Node>(oldLock)
: LockFile::read(
inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root,
- newParent, localPath);
+ oldLock ? lockRootPath : inputPath, localPath, false);
}
else {
@@ -552,17 +563,12 @@ 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);
+ auto parentPath = canonPath(flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir, true);
computeLocks(
flake.inputs, newLockFile.root, {},
- lockFlags.recreateLockFile ? nullptr : oldLockFile.root, parent, parentPath);
+ lockFlags.recreateLockFile ? nullptr : oldLockFile.root, {}, parentPath, false);
for (auto & i : lockFlags.inputOverrides)
if (!overridesUsed.count(i.first))
@@ -586,7 +592,7 @@ LockedFlake lockFlake(
if (lockFlags.writeLockFile) {
if (auto sourcePath = topRef.input.getSourcePath()) {
if (!newLockFile.isImmutable()) {
- if (settings.warnDirty)
+ if (fetchSettings.warnDirty)
warn("will not write lock file of flake '%s' because it has a mutable input", topRef);
} else {
if (!lockFlags.updateLockFile)
@@ -609,12 +615,24 @@ LockedFlake lockFlake(
newLockFile.write(path);
+ std::optional<std::string> commitMessage = std::nullopt;
+ if (lockFlags.commitLockFile) {
+ std::string cm;
+
+ cm = fetchSettings.commitLockFileSummary.get();
+
+ if (cm == "") {
+ cm = fmt("%s: %s", relPath, lockFileExists ? "Update" : "Add");
+ }
+
+ cm += "\n\nFlake lock file updates:\n\n";
+ cm += filterANSIEscapes(diff, true);
+ commitMessage = cm;
+ }
+
topRef.input.markChangedFile(
(topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock",
- lockFlags.commitLockFile
- ? std::optional<std::string>(fmt("%s: %s\n\nFlake lock file changes:\n\n%s",
- relPath, lockFileExists ? "Update" : "Add", filterANSIEscapes(diff, true)))
- : std::nullopt);
+ commitMessage);
/* Rewriting the lockfile changed the top-level
repo, so we should re-read it. FIXME: we could
@@ -633,7 +651,7 @@ LockedFlake lockFlake(
now. Corner case: we could have reverted from a
dirty to a clean tree! */
if (flake.lockedRef.input == prevLockedRef.input
- && !flake.lockedRef.input.isImmutable())
+ && !flake.lockedRef.input.isLocked())
throw Error("'%s' did not change after I updated its 'flake.lock' file; is 'flake.lock' under version control?", flake.originalRef);
}
} else
@@ -662,7 +680,7 @@ void callFlake(EvalState & state,
auto vTmp1 = state.allocValue();
auto vTmp2 = state.allocValue();
- mkString(*vLocks, lockedFlake.lockFile.to_string());
+ vLocks->mkString(lockedFlake.lockFile.to_string());
emitTreeAttrs(
state,
@@ -672,7 +690,7 @@ void callFlake(EvalState & state,
false,
lockedFlake.flake.forceDirty);
- mkString(*vRootSubdir, lockedFlake.flake.lockedRef.subdir);
+ vRootSubdir->mkString(lockedFlake.flake.lockedRef.subdir);
if (!state.vCallFlake) {
state.vCallFlake = allocRootValue(state.allocValue());
@@ -688,18 +706,18 @@ void callFlake(EvalState & state,
static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
- state.requireExperimentalFeatureOnEvaluation("flakes", "builtins.getFlake", pos);
+ state.requireExperimentalFeatureOnEvaluation(Xp::Flakes, "builtins.getFlake", pos);
- auto flakeRefS = state.forceStringNoCtx(*args[0], pos);
+ std::string flakeRefS(state.forceStringNoCtx(*args[0], pos));
auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
- if (evalSettings.pureEval && !flakeRef.input.isImmutable())
- throw Error("cannot call 'getFlake' on mutable flake reference '%s', at %s (use --impure to override)", flakeRefS, pos);
+ if (evalSettings.pureEval && !flakeRef.input.isLocked())
+ throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, pos);
callFlake(state,
lockFlake(state, flakeRef,
LockFlags {
.updateLockFile = false,
- .useRegistries = !evalSettings.pureEval && settings.useRegistries,
+ .useRegistries = !evalSettings.pureEval && fetchSettings.useRegistries,
.allowMutable = !evalSettings.pureEval,
}),
v);
diff --git a/src/libexpr/flake/flakeref.cc b/src/libexpr/flake/flakeref.cc
index 29128d789..c1eae413f 100644
--- a/src/libexpr/flake/flakeref.cc
+++ b/src/libexpr/flake/flakeref.cc
@@ -48,9 +48,12 @@ FlakeRef FlakeRef::resolve(ref<Store> store) const
}
FlakeRef parseFlakeRef(
- const std::string & url, const std::optional<Path> & baseDir, bool allowMissing)
+ const std::string & url,
+ const std::optional<Path> & baseDir,
+ bool allowMissing,
+ bool isFlake)
{
- auto [flakeRef, fragment] = parseFlakeRefWithFragment(url, baseDir, allowMissing);
+ auto [flakeRef, fragment] = parseFlakeRefWithFragment(url, baseDir, allowMissing, isFlake);
if (fragment != "")
throw Error("unexpected fragment '%s' in flake reference '%s'", fragment, url);
return flakeRef;
@@ -67,7 +70,10 @@ std::optional<FlakeRef> maybeParseFlakeRef(
}
std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
- const std::string & url, const std::optional<Path> & baseDir, bool allowMissing)
+ const std::string & url,
+ const std::optional<Path> & baseDir,
+ bool allowMissing,
+ bool isFlake)
{
using namespace fetchers;
@@ -92,7 +98,7 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
if (std::regex_match(url, match, flakeRegex)) {
auto parsedURL = ParsedURL{
.url = url,
- .base = "flake:" + std::string(match[1]),
+ .base = "flake:" + match.str(1),
.scheme = "flake",
.authority = "",
.path = match[1],
@@ -100,58 +106,83 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
return std::make_pair(
FlakeRef(Input::fromURL(parsedURL), ""),
- percentDecode(std::string(match[6])));
+ percentDecode(match.str(6)));
}
else if (std::regex_match(url, match, pathUrlRegex)) {
std::string path = match[1];
- std::string fragment = percentDecode(std::string(match[3]));
+ std::string fragment = percentDecode(match.str(3));
if (baseDir) {
/* Check if 'url' is a path (either absolute or relative
to 'baseDir'). If so, search upward to the root of the
repo (i.e. the directory containing .git). */
- path = absPath(path, baseDir, true);
+ path = absPath(path, baseDir);
+
+ if (isFlake) {
+
+ if (!allowMissing && !pathExists(path + "/flake.nix")){
+ notice("path '%s' does not contain a 'flake.nix', searching up",path);
+
+ // Save device to detect filesystem boundary
+ dev_t device = lstat(path).st_dev;
+ bool found = false;
+ while (path != "/") {
+ if (pathExists(path + "/flake.nix")) {
+ found = true;
+ break;
+ } else if (pathExists(path + "/.git"))
+ throw Error("path '%s' is not part of a flake (neither it nor its parent directories contain a 'flake.nix' file)", path);
+ else {
+ if (lstat(path).st_dev != device)
+ throw Error("unable to find a flake before encountering filesystem boundary at '%s'", path);
+ }
+ path = dirOf(path);
+ }
+ if (!found)
+ throw BadURL("could not find a flake.nix file");
+ }
- if (!S_ISDIR(lstat(path).st_mode))
- throw BadURL("path '%s' is not a flake (because it's not a directory)", path);
+ if (!S_ISDIR(lstat(path).st_mode))
+ throw BadURL("path '%s' is not a flake (because it's not a directory)", path);
- if (!allowMissing && !pathExists(path + "/flake.nix"))
- throw BadURL("path '%s' is not a flake (because it doesn't contain a 'flake.nix' file)", path);
+ if (!allowMissing && !pathExists(path + "/flake.nix"))
+ throw BadURL("path '%s' is not a flake (because it doesn't contain a 'flake.nix' file)", path);
- auto flakeRoot = path;
- std::string subdir;
+ auto flakeRoot = path;
+ std::string subdir;
- while (flakeRoot != "/") {
- if (pathExists(flakeRoot + "/.git")) {
- auto base = std::string("git+file://") + flakeRoot;
+ while (flakeRoot != "/") {
+ if (pathExists(flakeRoot + "/.git")) {
+ auto base = std::string("git+file://") + flakeRoot;
- auto parsedURL = ParsedURL{
- .url = base, // FIXME
- .base = base,
- .scheme = "git+file",
- .authority = "",
- .path = flakeRoot,
- .query = decodeQuery(match[2]),
- };
+ auto parsedURL = ParsedURL{
+ .url = base, // FIXME
+ .base = base,
+ .scheme = "git+file",
+ .authority = "",
+ .path = flakeRoot,
+ .query = decodeQuery(match[2]),
+ };
- if (subdir != "") {
- if (parsedURL.query.count("dir"))
- throw Error("flake URL '%s' has an inconsistent 'dir' parameter", url);
- parsedURL.query.insert_or_assign("dir", subdir);
- }
+ if (subdir != "") {
+ if (parsedURL.query.count("dir"))
+ throw Error("flake URL '%s' has an inconsistent 'dir' parameter", url);
+ parsedURL.query.insert_or_assign("dir", subdir);
+ }
- if (pathExists(flakeRoot + "/.git/shallow"))
- parsedURL.query.insert_or_assign("shallow", "1");
+ if (pathExists(flakeRoot + "/.git/shallow"))
+ parsedURL.query.insert_or_assign("shallow", "1");
- return std::make_pair(
- FlakeRef(Input::fromURL(parsedURL), get(parsedURL.query, "dir").value_or("")),
- fragment);
- }
+ return std::make_pair(
+ FlakeRef(Input::fromURL(parsedURL), get(parsedURL.query, "dir").value_or("")),
+ fragment);
+ }
- subdir = std::string(baseNameOf(flakeRoot)) + (subdir.empty() ? "" : "/" + subdir);
- flakeRoot = dirOf(flakeRoot);
+ subdir = std::string(baseNameOf(flakeRoot)) + (subdir.empty() ? "" : "/" + subdir);
+ flakeRoot = dirOf(flakeRoot);
+ }
}
} else {
diff --git a/src/libexpr/flake/flakeref.hh b/src/libexpr/flake/flakeref.hh
index 0292eb210..1fddfd9a0 100644
--- a/src/libexpr/flake/flakeref.hh
+++ b/src/libexpr/flake/flakeref.hh
@@ -62,13 +62,19 @@ struct FlakeRef
std::ostream & operator << (std::ostream & str, const FlakeRef & flakeRef);
FlakeRef parseFlakeRef(
- const std::string & url, const std::optional<Path> & baseDir = {}, bool allowMissing = false);
+ const std::string & url,
+ const std::optional<Path> & baseDir = {},
+ bool allowMissing = false,
+ bool isFlake = true);
std::optional<FlakeRef> maybeParseFlake(
const std::string & url, const std::optional<Path> & baseDir = {});
std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
- const std::string & url, const std::optional<Path> & baseDir = {}, bool allowMissing = false);
+ const std::string & url,
+ const std::optional<Path> & baseDir = {},
+ bool allowMissing = false,
+ bool isFlake = true);
std::optional<std::pair<FlakeRef, std::string>> maybeParseFlakeRefWithFragment(
const std::string & url, const std::optional<Path> & baseDir = {});
diff --git a/src/libexpr/flake/lockfile.cc b/src/libexpr/flake/lockfile.cc
index fda340789..60b52d578 100644
--- a/src/libexpr/flake/lockfile.cc
+++ b/src/libexpr/flake/lockfile.cc
@@ -35,7 +35,7 @@ LockedNode::LockedNode(const nlohmann::json & json)
, originalRef(getFlakeRef(json, "original", nullptr))
, isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true)
{
- if (!lockedRef.input.isImmutable())
+ if (!lockedRef.input.isLocked())
throw Error("lockfile contains mutable lock '%s'",
fetchers::attrsToJSON(lockedRef.input.toAttrs()));
}
@@ -220,7 +220,7 @@ bool LockFile::isImmutable() const
for (auto & i : nodes) {
if (i == root) continue;
auto lockedNode = std::dynamic_pointer_cast<const LockedNode>(i);
- if (lockedNode && !lockedNode->lockedRef.input.isImmutable()) return false;
+ if (lockedNode && !lockedNode->lockedRef.input.isLocked()) return false;
}
return true;