aboutsummaryrefslogtreecommitdiff
path: root/src/libexpr/flake
diff options
context:
space:
mode:
authorJohn Ericson <John.Ericson@Obsidian.Systems>2021-02-25 21:58:41 +0000
committerJohn Ericson <John.Ericson@Obsidian.Systems>2021-02-25 21:58:41 +0000
commit90d76fa399de4e207ea14ec4c0dd65434f60c152 (patch)
tree3c52e982cba5bcf7b91c99d1b63ba967b4ea8b92 /src/libexpr/flake
parent4636cc9a1f6de70947abbfb17a0ad91981d1cad7 (diff)
parentca0994819d68aee26a2906c37a47ae609ac46c4c (diff)
Merge remote-tracking branch 'obsidian/path-info' into ca-drv-exotic
Diffstat (limited to 'src/libexpr/flake')
-rw-r--r--src/libexpr/flake/config.cc81
-rw-r--r--src/libexpr/flake/flake.cc637
-rw-r--r--src/libexpr/flake/flake.hh40
-rw-r--r--src/libexpr/flake/flakeref.hh23
-rw-r--r--src/libexpr/flake/lockfile.cc26
-rw-r--r--src/libexpr/flake/lockfile.hh2
6 files changed, 501 insertions, 308 deletions
diff --git a/src/libexpr/flake/config.cc b/src/libexpr/flake/config.cc
new file mode 100644
index 000000000..63566131e
--- /dev/null
+++ b/src/libexpr/flake/config.cc
@@ -0,0 +1,81 @@
+#include "flake.hh"
+
+#include <nlohmann/json.hpp>
+
+namespace nix::flake {
+
+// setting name -> setting value -> allow or ignore.
+typedef std::map<std::string, std::map<std::string, bool>> TrustedList;
+
+Path trustedListPath()
+{
+ return getDataDir() + "/nix/trusted-settings.json";
+}
+
+static TrustedList readTrustedList()
+{
+ auto path = trustedListPath();
+ if (!pathExists(path)) return {};
+ auto json = nlohmann::json::parse(readFile(path));
+ return json;
+}
+
+static void writeTrustedList(const TrustedList & trustedList)
+{
+ writeFile(trustedListPath(), nlohmann::json(trustedList).dump());
+}
+
+void ConfigFile::apply()
+{
+ std::set<std::string> whitelist{"bash-prompt", "bash-prompt-suffix"};
+
+ for (auto & [name, value] : settings) {
+
+ auto baseName = hasPrefix(name, "extra-") ? std::string(name, 6) : name;
+
+ // FIXME: Move into libutil/config.cc.
+ std::string valueS;
+ 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))
+ valueS = b->t ? "true" : "false";
+ else if (auto ss = std::get_if<std::vector<std::string>>(&value))
+ valueS = concatStringsSep(" ", *ss); // FIXME: evil
+ else
+ assert(false);
+
+ if (!whitelist.count(baseName)) {
+ auto trustedList = readTrustedList();
+
+ bool trusted = false;
+
+ if (auto saved = get(get(trustedList, name).value_or(std::map<std::string, bool>()), valueS)) {
+ trusted = *saved;
+ } 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 (!trusted) {
+ warn("ignoring untrusted flake configuration setting '%s'", name);
+ continue;
+ }
+ }
+
+ globalConfig.set(name, valueS);
+ }
+}
+
+}
diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc
index bae4d65e5..2e94490d4 100644
--- a/src/libexpr/flake/flake.cc
+++ b/src/libexpr/flake/flake.cc
@@ -71,14 +71,20 @@ static std::tuple<fetchers::Tree, FlakeRef, FlakeRef> fetchOrSubstituteTree(
return {std::move(tree), resolvedRef, lockedRef};
}
+static void forceTrivialValue(EvalState & state, Value & value, const Pos & pos)
+{
+ if (value.isThunk() && value.isTrivial())
+ state.forceValue(value, pos);
+}
+
+
static void expectType(EvalState & state, ValueType type,
Value & value, const Pos & pos)
{
- if (value.type == tThunk && value.isTrivial())
- state.forceValue(value, pos);
- if (value.type != type)
+ forceTrivialValue(state, value, pos);
+ if (value.type() != type)
throw Error("expected %s but got %s at %s",
- showType(type), showType(value.type), pos);
+ showType(type), showType(value.type()), pos);
}
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
@@ -87,7 +93,7 @@ static std::map<FlakeId, FlakeInput> parseFlakeInputs(
static FlakeInput parseFlakeInput(EvalState & state,
const std::string & inputName, Value * value, const Pos & pos)
{
- expectType(state, tAttrs, *value, pos);
+ expectType(state, nAttrs, *value, pos);
FlakeInput input;
@@ -102,24 +108,32 @@ static FlakeInput parseFlakeInput(EvalState & state,
for (nix::Attr attr : *(value->attrs)) {
try {
if (attr.name == sUrl) {
- expectType(state, tString, *attr.value, *attr.pos);
+ expectType(state, nString, *attr.value, *attr.pos);
url = attr.value->string.s;
attrs.emplace("url", *url);
} else if (attr.name == sFlake) {
- expectType(state, tBool, *attr.value, *attr.pos);
+ 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);
} else if (attr.name == sFollows) {
- expectType(state, tString, *attr.value, *attr.pos);
+ expectType(state, nString, *attr.value, *attr.pos);
input.follows = parseInputPath(attr.value->string.s);
} else {
- state.forceValue(*attr.value);
- if (attr.value->type == tString)
- attrs.emplace(attr.name, attr.value->string.s);
- else
- throw TypeError("flake input attribute '%s' is %s while a string is expected",
- attr.name, showType(*attr.value));
+ switch (attr.value->type()) {
+ case nString:
+ attrs.emplace(attr.name, attr.value->string.s);
+ break;
+ case nBool:
+ attrs.emplace(attr.name, Explicit<bool> { attr.value->boolean });
+ break;
+ case nInt:
+ attrs.emplace(attr.name, (long unsigned int)attr.value->integer);
+ break;
+ default:
+ throw TypeError("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
+ attr.name, showType(*attr.value));
+ }
}
} catch (Error & e) {
e.addTrace(*attr.pos, hintfmt("in flake attribute '%s'", attr.name));
@@ -153,7 +167,7 @@ static std::map<FlakeId, FlakeInput> parseFlakeInputs(
{
std::map<FlakeId, FlakeInput> inputs;
- expectType(state, tAttrs, *value, pos);
+ expectType(state, nAttrs, *value, pos);
for (nix::Attr & inputAttr : *(*value).attrs) {
inputs.emplace(inputAttr.name,
@@ -194,15 +208,10 @@ static Flake getFlake(
Value vInfo;
state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack
- expectType(state, tAttrs, vInfo, Pos(foFile, state.symbols.create(flakeFile), 0, 0));
-
- auto sEdition = state.symbols.create("edition"); // FIXME: remove soon
-
- if (vInfo.attrs->get(sEdition))
- warn("flake '%s' has deprecated attribute 'edition'", lockedRef);
+ expectType(state, nAttrs, vInfo, Pos(foFile, state.symbols.create(flakeFile), 0, 0));
if (auto description = vInfo.attrs->get(state.sDescription)) {
- expectType(state, tString, *description->value, *description->pos);
+ expectType(state, nString, *description->value, *description->pos);
flake.description = description->value->string.s;
}
@@ -214,9 +223,9 @@ static Flake getFlake(
auto sOutputs = state.symbols.create("outputs");
if (auto outputs = vInfo.attrs->get(sOutputs)) {
- expectType(state, tLambda, *outputs->value, *outputs->pos);
+ expectType(state, nFunction, *outputs->value, *outputs->pos);
- if (outputs->value->lambda.fun->matchAttrs) {
+ if (outputs->value->isLambda() && outputs->value->lambda.fun->matchAttrs) {
for (auto & formal : outputs->value->lambda.fun->formals->formals) {
if (formal.name != state.sSelf)
flake.inputs.emplace(formal.name, FlakeInput {
@@ -228,11 +237,41 @@ static Flake getFlake(
} else
throw Error("flake '%s' lacks attribute 'outputs'", lockedRef);
+ auto sNixConfig = state.symbols.create("nixConfig");
+
+ if (auto nixConfig = vInfo.attrs->get(sNixConfig)) {
+ expectType(state, nAttrs, *nixConfig->value, *nixConfig->pos);
+
+ 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)});
+ 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)});
+ 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];
+ 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));
+ }
+ flake.config.settings.insert({setting.name, ss});
+ }
+ else
+ throw TypeError("flake configuration setting '%s' is %s",
+ setting.name, showType(*setting.value));
+ }
+ }
+
for (auto & attr : *vInfo.attrs) {
- if (attr.name != sEdition &&
- attr.name != state.sDescription &&
+ if (attr.name != state.sDescription &&
attr.name != sInputs &&
- attr.name != sOutputs)
+ attr.name != sOutputs &&
+ attr.name != sNixConfig)
throw Error("flake '%s' has an unsupported attribute '%s', at %s",
lockedRef, attr.name, *attr.pos);
}
@@ -259,284 +298,298 @@ LockedFlake lockFlake(
auto flake = getFlake(state, topRef, lockFlags.useRegistries, flakeCache);
- // FIXME: symlink attack
- auto oldLockFile = LockFile::read(
- flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir + "/flake.lock");
-
- debug("old lock file: %s", oldLockFile);
-
- // FIXME: check whether all overrides are used.
- std::map<InputPath, FlakeInput> overrides;
- std::set<InputPath> overridesUsed, updatesUsed;
-
- for (auto & i : lockFlags.inputOverrides)
- overrides.insert_or_assign(i.first, FlakeInput { .ref = i.second });
-
- LockFile newLockFile;
-
- std::vector<FlakeRef> parents;
-
- std::function<void(
- const FlakeInputs & flakeInputs,
- std::shared_ptr<Node> node,
- const InputPath & inputPathPrefix,
- std::shared_ptr<const Node> oldNode)>
- computeLocks;
-
- computeLocks = [&](
- const FlakeInputs & flakeInputs,
- std::shared_ptr<Node> node,
- const InputPath & inputPathPrefix,
- std::shared_ptr<const Node> oldNode)
- {
- 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 & [idOverride, inputOverride] : input.overrides) {
- auto inputPath(inputPathPrefix);
- inputPath.push_back(id);
- inputPath.push_back(idOverride);
- overrides.insert_or_assign(inputPath, inputOverride);
- }
- }
-
- /* Go over the flake inputs, resolve/fetch them if
- necessary (i.e. if they're new or the flakeref changed
- from what's in the lock file). */
- for (auto & [id, input2] : flakeInputs) {
- auto inputPath(inputPathPrefix);
- inputPath.push_back(id);
- auto inputPathS = printInputPath(inputPath);
- debug("computing input '%s'", inputPathS);
-
- /* Do we have an override for this input from one of the
- ancestors? */
- auto i = overrides.find(inputPath);
- bool hasOverride = i != overrides.end();
- if (hasOverride) overridesUsed.insert(inputPath);
- 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. */
- target = *input.follows;
- else {
- /* Otherwise, it's relative to the current flake. */
- target = inputPathPrefix;
- for (auto & i : *input.follows) target.push_back(i);
+ try {
+
+ // FIXME: symlink attack
+ auto oldLockFile = LockFile::read(
+ flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir + "/flake.lock");
+
+ debug("old lock file: %s", oldLockFile);
+
+ // FIXME: check whether all overrides are used.
+ std::map<InputPath, FlakeInput> overrides;
+ std::set<InputPath> overridesUsed, updatesUsed;
+
+ for (auto & i : lockFlags.inputOverrides)
+ overrides.insert_or_assign(i.first, FlakeInput { .ref = i.second });
+
+ LockFile newLockFile;
+
+ std::vector<FlakeRef> parents;
+
+ std::function<void(
+ const FlakeInputs & flakeInputs,
+ std::shared_ptr<Node> node,
+ const InputPath & inputPathPrefix,
+ std::shared_ptr<const Node> oldNode)>
+ computeLocks;
+
+ computeLocks = [&](
+ const FlakeInputs & flakeInputs,
+ std::shared_ptr<Node> node,
+ const InputPath & inputPathPrefix,
+ std::shared_ptr<const Node> oldNode)
+ {
+ 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 & [idOverride, inputOverride] : input.overrides) {
+ auto inputPath(inputPathPrefix);
+ inputPath.push_back(id);
+ inputPath.push_back(idOverride);
+ overrides.insert_or_assign(inputPath, inputOverride);
}
- debug("input '%s' follows '%s'", inputPathS, printInputPath(target));
- node->inputs.insert_or_assign(id, target);
- continue;
}
- assert(input.ref);
-
- /* Do we have an entry in the existing lock file? And we
- don't have a --update-input flag for this input? */
- std::shared_ptr<LockedNode> oldLock;
-
- updatesUsed.insert(inputPath);
-
- if (oldNode && !lockFlags.inputUpdates.count(inputPath))
- if (auto oldLock2 = get(oldNode->inputs, id))
- if (auto oldLock3 = std::get_if<0>(&*oldLock2))
- oldLock = *oldLock3;
-
- if (oldLock
- && oldLock->originalRef == *input.ref
- && !hasOverride)
- {
- debug("keeping existing input '%s'", inputPathS);
-
- /* Copy the input from the old lock since its flakeref
- didn't change and there is no override from a
- higher level flake. */
- auto childNode = std::make_shared<LockedNode>(
- oldLock->lockedRef, oldLock->originalRef, oldLock->isFlake);
-
- node->inputs.insert_or_assign(id, childNode);
-
- /* If we have an --update-input flag for an input
- of this input, then we must fetch the flake to
- update it. */
- auto lb = lockFlags.inputUpdates.lower_bound(inputPath);
-
- auto hasChildUpdate =
- 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);
- } else {
- /* 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 {
- .ref = (*lockedNode)->originalRef,
- .isFlake = (*lockedNode)->isFlake,
- });
- } else if (auto follows = std::get_if<1>(&i.second)) {
- fakeInputs.emplace(i.first, FlakeInput {
- .follows = *follows,
- .absolute = true
- });
+ /* Go over the flake inputs, resolve/fetch them if
+ necessary (i.e. if they're new or the flakeref changed
+ from what's in the lock file). */
+ for (auto & [id, input2] : flakeInputs) {
+ auto inputPath(inputPathPrefix);
+ inputPath.push_back(id);
+ auto inputPathS = printInputPath(inputPath);
+ debug("computing input '%s'", inputPathS);
+
+ try {
+
+ /* Do we have an override for this input from one of the
+ ancestors? */
+ auto i = overrides.find(inputPath);
+ bool hasOverride = i != overrides.end();
+ if (hasOverride) overridesUsed.insert(inputPath);
+ 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. */
+ target = *input.follows;
+ else {
+ /* Otherwise, it's relative to the current flake. */
+ target = inputPathPrefix;
+ 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;
}
- computeLocks(fakeInputs, childNode, inputPath, oldLock);
- }
+ assert(input.ref);
+
+ /* Do we have an entry in the existing lock file? And we
+ don't have a --update-input flag for this input? */
+ std::shared_ptr<LockedNode> oldLock;
+
+ updatesUsed.insert(inputPath);
+
+ if (oldNode && !lockFlags.inputUpdates.count(inputPath))
+ if (auto oldLock2 = get(oldNode->inputs, id))
+ if (auto oldLock3 = std::get_if<0>(&*oldLock2))
+ oldLock = *oldLock3;
+
+ if (oldLock
+ && oldLock->originalRef == *input.ref
+ && !hasOverride)
+ {
+ debug("keeping existing input '%s'", inputPathS);
+
+ /* Copy the input from the old lock since its flakeref
+ didn't change and there is no override from a
+ higher level flake. */
+ auto childNode = std::make_shared<LockedNode>(
+ oldLock->lockedRef, oldLock->originalRef, oldLock->isFlake);
+
+ node->inputs.insert_or_assign(id, childNode);
+
+ /* If we have an --update-input flag for an input
+ of this input, then we must fetch the flake to
+ update it. */
+ auto lb = lockFlags.inputUpdates.lower_bound(inputPath);
+
+ auto hasChildUpdate =
+ 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);
+ } else {
+ /* 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 {
+ .ref = (*lockedNode)->originalRef,
+ .isFlake = (*lockedNode)->isFlake,
+ });
+ } else if (auto follows = std::get_if<1>(&i.second)) {
+ fakeInputs.emplace(i.first, FlakeInput {
+ .follows = *follows,
+ .absolute = true
+ });
+ }
+ }
+
+ computeLocks(fakeInputs, childNode, inputPath, oldLock);
+ }
- } 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())
- throw Error("cannot update flake input '%s' in pure mode", inputPathS);
-
- if (input.isFlake) {
- auto inputFlake = getFlake(state, *input.ref, lockFlags.useRegistries, flakeCache);
-
- /* Note: in case of an --override-input, we use
- the *original* ref (input2.ref) for the
- "original" field, rather than the
- override. This ensures that the override isn't
- nuked the next time we update the lock
- file. That is, overrides are sticky unless you
- use --no-write-lock-file. */
- auto childNode = std::make_shared<LockedNode>(
- inputFlake.lockedRef, input2.ref ? *input2.ref : *input.ref);
-
- node->inputs.insert_or_assign(id, childNode);
-
- /* Guard against circular flake imports. */
- for (auto & parent : parents)
- if (parent == *input.ref)
- throw Error("found circular import of flake '%s'", parent);
- parents.push_back(*input.ref);
- Finally cleanup([&]() { parents.pop_back(); });
-
- /* Recursively process the inputs of this
- flake. Also, unless we already have this flake
- in the top-level lock file, use this flake's
- own lock file. */
- computeLocks(
- inputFlake.inputs, childNode, inputPath,
- oldLock
- ? std::dynamic_pointer_cast<const Node>(oldLock)
- : LockFile::read(
- inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root);
- }
+ } 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())
+ throw Error("cannot update flake input '%s' in pure mode", inputPathS);
+
+ if (input.isFlake) {
+ auto inputFlake = getFlake(state, *input.ref, lockFlags.useRegistries, flakeCache);
+
+ /* Note: in case of an --override-input, we use
+ the *original* ref (input2.ref) for the
+ "original" field, rather than the
+ override. This ensures that the override isn't
+ nuked the next time we update the lock
+ file. That is, overrides are sticky unless you
+ use --no-write-lock-file. */
+ auto childNode = std::make_shared<LockedNode>(
+ inputFlake.lockedRef, input2.ref ? *input2.ref : *input.ref);
+
+ node->inputs.insert_or_assign(id, childNode);
+
+ /* Guard against circular flake imports. */
+ for (auto & parent : parents)
+ if (parent == *input.ref)
+ throw Error("found circular import of flake '%s'", parent);
+ parents.push_back(*input.ref);
+ Finally cleanup([&]() { parents.pop_back(); });
+
+ /* Recursively process the inputs of this
+ flake. Also, unless we already have this flake
+ in the top-level lock file, use this flake's
+ own lock file. */
+ computeLocks(
+ inputFlake.inputs, childNode, inputPath,
+ oldLock
+ ? std::dynamic_pointer_cast<const Node>(oldLock)
+ : LockFile::read(
+ inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root);
+ }
+
+ else {
+ auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
+ state, *input.ref, lockFlags.useRegistries, flakeCache);
+ node->inputs.insert_or_assign(id,
+ std::make_shared<LockedNode>(lockedRef, *input.ref, false));
+ }
+ }
- else {
- auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
- state, *input.ref, lockFlags.useRegistries, flakeCache);
- node->inputs.insert_or_assign(id,
- std::make_shared<LockedNode>(lockedRef, *input.ref, false));
+ } catch (Error & e) {
+ e.addTrace({}, "while updating the flake input '%s'", inputPathS);
+ throw;
}
}
+ };
+
+ computeLocks(
+ flake.inputs, newLockFile.root, {},
+ lockFlags.recreateLockFile ? nullptr : oldLockFile.root);
+
+ for (auto & i : lockFlags.inputOverrides)
+ if (!overridesUsed.count(i.first))
+ warn("the flag '--override-input %s %s' does not match any input",
+ printInputPath(i.first), i.second);
+
+ for (auto & i : lockFlags.inputUpdates)
+ if (!updatesUsed.count(i))
+ warn("the flag '--update-input %s' does not match any input", printInputPath(i));
+
+ /* Check 'follows' inputs. */
+ newLockFile.check();
+
+ debug("new lock file: %s", newLockFile);
+
+ /* Check whether we need to / can write the new lock file. */
+ if (!(newLockFile == oldLockFile)) {
+
+ auto diff = LockFile::diff(oldLockFile, newLockFile);
+
+ if (lockFlags.writeLockFile) {
+ if (auto sourcePath = topRef.input.getSourcePath()) {
+ if (!newLockFile.isImmutable()) {
+ if (settings.warnDirty)
+ warn("will not write lock file of flake '%s' because it has a mutable input", topRef);
+ } else {
+ if (!lockFlags.updateLockFile)
+ throw Error("flake '%s' requires lock file changes but they're not allowed due to '--no-update-lock-file'", topRef);
+
+ auto relPath = (topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock";
+
+ auto path = *sourcePath + "/" + relPath;
+
+ bool lockFileExists = pathExists(path);
+
+ if (lockFileExists) {
+ auto s = chomp(diff);
+ if (s.empty())
+ warn("updating lock file '%s'", path);
+ else
+ warn("updating lock file '%s':\n%s", path, s);
+ } else
+ warn("creating lock file '%s'", path);
+
+ newLockFile.write(path);
+
+ 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::nullopt);
+
+ /* Rewriting the lockfile changed the top-level
+ repo, so we should re-read it. FIXME: we could
+ also just clear the 'rev' field... */
+ auto prevLockedRef = flake.lockedRef;
+ FlakeCache dummyCache;
+ flake = getFlake(state, topRef, lockFlags.useRegistries, dummyCache);
+
+ if (lockFlags.commitLockFile &&
+ flake.lockedRef.input.getRev() &&
+ prevLockedRef.input.getRev() != flake.lockedRef.input.getRev())
+ warn("committed new revision '%s'", flake.lockedRef.input.getRev()->gitRev());
+
+ /* Make sure that we picked up the change,
+ i.e. the tree should usually be dirty
+ now. Corner case: we could have reverted from a
+ dirty to a clean tree! */
+ if (flake.lockedRef.input == prevLockedRef.input
+ && !flake.lockedRef.input.isImmutable())
+ throw Error("'%s' did not change after I updated its 'flake.lock' file; is 'flake.lock' under version control?", flake.originalRef);
+ }
+ } else
+ throw Error("cannot write modified lock file of flake '%s' (use '--no-write-lock-file' to ignore)", topRef);
+ } else
+ warn("not writing modified lock file of flake '%s':\n%s", topRef, chomp(diff));
}
- };
- computeLocks(
- flake.inputs, newLockFile.root, {},
- lockFlags.recreateLockFile ? nullptr : oldLockFile.root);
-
- for (auto & i : lockFlags.inputOverrides)
- if (!overridesUsed.count(i.first))
- warn("the flag '--override-input %s %s' does not match any input",
- printInputPath(i.first), i.second);
-
- for (auto & i : lockFlags.inputUpdates)
- if (!updatesUsed.count(i))
- warn("the flag '--update-input %s' does not match any input", printInputPath(i));
-
- /* Check 'follows' inputs. */
- newLockFile.check();
-
- debug("new lock file: %s", newLockFile);
-
- /* Check whether we need to / can write the new lock file. */
- if (!(newLockFile == oldLockFile)) {
-
- auto diff = LockFile::diff(oldLockFile, newLockFile);
-
- if (lockFlags.writeLockFile) {
- if (auto sourcePath = topRef.input.getSourcePath()) {
- if (!newLockFile.isImmutable()) {
- if (settings.warnDirty)
- warn("will not write lock file of flake '%s' because it has a mutable input", topRef);
- } else {
- if (!lockFlags.updateLockFile)
- throw Error("flake '%s' requires lock file changes but they're not allowed due to '--no-update-lock-file'", topRef);
-
- auto relPath = (topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock";
-
- auto path = *sourcePath + "/" + relPath;
-
- bool lockFileExists = pathExists(path);
-
- if (lockFileExists) {
- auto s = chomp(diff);
- if (s.empty())
- warn("updating lock file '%s'", path);
- else
- warn("updating lock file '%s':\n%s", path, s);
- } else
- warn("creating lock file '%s'", path);
-
- newLockFile.write(path);
-
- 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::nullopt);
-
- /* Rewriting the lockfile changed the top-level
- repo, so we should re-read it. FIXME: we could
- also just clear the 'rev' field... */
- auto prevLockedRef = flake.lockedRef;
- FlakeCache dummyCache;
- flake = getFlake(state, topRef, lockFlags.useRegistries, dummyCache);
-
- if (lockFlags.commitLockFile &&
- flake.lockedRef.input.getRev() &&
- prevLockedRef.input.getRev() != flake.lockedRef.input.getRev())
- warn("committed new revision '%s'", flake.lockedRef.input.getRev()->gitRev());
-
- /* Make sure that we picked up the change,
- i.e. the tree should usually be dirty
- now. Corner case: we could have reverted from a
- dirty to a clean tree! */
- if (flake.lockedRef.input == prevLockedRef.input
- && !flake.lockedRef.input.isImmutable())
- throw Error("'%s' did not change after I updated its 'flake.lock' file; is 'flake.lock' under version control?", flake.originalRef);
- }
- } else
- throw Error("cannot write modified lock file of flake '%s' (use '--no-write-lock-file' to ignore)", topRef);
- } else
- warn("not writing modified lock file of flake '%s':\n%s", topRef, chomp(diff));
- }
+ return LockedFlake { .flake = std::move(flake), .lockFile = std::move(newLockFile) };
- return LockedFlake { .flake = std::move(flake), .lockFile = std::move(newLockFile) };
+ } catch (Error & e) {
+ e.addTrace({}, "while updating the lock file of flake '%s'", flake.lockedRef.to_string());
+ throw;
+ }
}
void callFlake(EvalState & state,
diff --git a/src/libexpr/flake/flake.hh b/src/libexpr/flake/flake.hh
index 69c779af8..65ed1ad0a 100644
--- a/src/libexpr/flake/flake.hh
+++ b/src/libexpr/flake/flake.hh
@@ -17,23 +17,55 @@ struct FlakeInput;
typedef std::map<FlakeId, FlakeInput> FlakeInputs;
+/* FlakeInput is the 'Flake'-level parsed form of the "input" entries
+ * in the flake file.
+ *
+ * A FlakeInput is normally constructed by the 'parseFlakeInput'
+ * function which parses the input specification in the '.flake' file
+ * to create a 'FlakeRef' (a fetcher, the fetcher-specific
+ * representation of the input specification, and possibly the fetched
+ * local store path result) and then creating this FlakeInput to hold
+ * that FlakeRef, along with anything that might override that
+ * FlakeRef (like command-line overrides or "follows" specifications).
+ *
+ * A FlakeInput is also sometimes constructed directly from a FlakeRef
+ * instead of starting at the flake-file input specification
+ * (e.g. overrides, follows, and implicit inputs).
+ *
+ * A FlakeInput will usually have one of either "ref" or "follows"
+ * set. If not otherwise specified, a "ref" will be generated to a
+ * 'type="indirect"' flake, which is treated as simply the name of a
+ * flake to be resolved in the registry.
+ */
+
struct FlakeInput
{
std::optional<FlakeRef> ref;
- bool isFlake = true;
+ 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;
};
+struct ConfigFile
+{
+ using ConfigValue = std::variant<std::string, int64_t, Explicit<bool>, std::vector<std::string>>;
+
+ std::map<std::string, ConfigValue> settings;
+
+ void apply();
+};
+
+/* The contents of a flake.nix file. */
struct Flake
{
- FlakeRef originalRef;
- FlakeRef resolvedRef;
- FlakeRef lockedRef;
+ 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
std::optional<std::string> description;
std::shared_ptr<const fetchers::Tree> sourceInfo;
FlakeInputs inputs;
+ ConfigFile config; // 'nixConfig' attribute
~Flake();
};
diff --git a/src/libexpr/flake/flakeref.hh b/src/libexpr/flake/flakeref.hh
index f4eb825a6..0292eb210 100644
--- a/src/libexpr/flake/flakeref.hh
+++ b/src/libexpr/flake/flakeref.hh
@@ -12,10 +12,33 @@ class Store;
typedef std::string FlakeId;
+/* A flake reference specifies how to fetch a flake or raw source
+ * (e.g. from a Git repository). It is created from a URL-like syntax
+ * (e.g. 'github:NixOS/patchelf'), an attrset representation (e.g. '{
+ * type="github"; owner = "NixOS"; repo = "patchelf"; }'), or a local
+ * path.
+ *
+ * Each flake will have a number of FlakeRef objects: one for each
+ * input to the flake.
+ *
+ * The normal method of constructing a FlakeRef is by starting with an
+ * input description (usually the attrs or a url from the flake file),
+ * locating a fetcher for that input, and then capturing the Input
+ * object that fetcher generates (usually via
+ * FlakeRef::fromAttrs(attrs) or parseFlakeRef(url) calls).
+ *
+ * The actual fetch not have been performed yet (i.e. a FlakeRef may
+ * be lazy), but the fetcher can be invoked at any time via the
+ * FlakeRef to ensure the store is populated with this input.
+ */
+
struct FlakeRef
{
+ /* fetcher-specific representation of the input, sufficient to
+ perform the fetch operation. */
fetchers::Input input;
+ /* sub-path within the fetched input that represents this input */
Path subdir;
bool operator==(const FlakeRef & other) const;
diff --git a/src/libexpr/flake/lockfile.cc b/src/libexpr/flake/lockfile.cc
index bb46e1bb4..6089d1363 100644
--- a/src/libexpr/flake/lockfile.cc
+++ b/src/libexpr/flake/lockfile.cc
@@ -34,7 +34,8 @@ LockedNode::LockedNode(const nlohmann::json & json)
, isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true)
{
if (!lockedRef.input.isImmutable())
- throw Error("lockfile contains mutable lock '%s'", attrsToJson(lockedRef.input.toAttrs()));
+ throw Error("lockfile contains mutable lock '%s'",
+ fetchers::attrsToJSON(lockedRef.input.toAttrs()));
}
StorePath LockedNode::computeStorePath(Store & store) const
@@ -77,7 +78,7 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path)
{
if (jsonNode.find("inputs") == jsonNode.end()) return;
for (auto & i : jsonNode["inputs"].items()) {
- if (i.value().is_array()) {
+ if (i.value().is_array()) { // FIXME: remove, obsolete
InputPath path;
for (auto & j : i.value())
path.push_back(j);
@@ -86,10 +87,13 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path)
std::string inputKey = i.value();
auto k = nodeMap.find(inputKey);
if (k == nodeMap.end()) {
- auto jsonNode2 = json["nodes"][inputKey];
- auto input = std::make_shared<LockedNode>(jsonNode2);
+ auto nodes = json["nodes"];
+ auto jsonNode2 = nodes.find(inputKey);
+ if (jsonNode2 == nodes.end())
+ throw Error("lock file references missing node '%s'", inputKey);
+ auto input = std::make_shared<LockedNode>(*jsonNode2);
k = nodeMap.insert_or_assign(inputKey, input).first;
- getInputs(*input, jsonNode2);
+ getInputs(*input, *jsonNode2);
}
if (auto child = std::dynamic_pointer_cast<LockedNode>(k->second))
node.inputs.insert_or_assign(i.key(), child);
@@ -110,7 +114,7 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path)
// a bit since we don't need to worry about cycles.
}
-nlohmann::json LockFile::toJson() const
+nlohmann::json LockFile::toJSON() const
{
nlohmann::json nodes;
std::unordered_map<std::shared_ptr<const Node>, std::string> nodeKeys;
@@ -154,8 +158,8 @@ nlohmann::json LockFile::toJson() const
}
if (auto lockedNode = std::dynamic_pointer_cast<const LockedNode>(node)) {
- n["original"] = fetchers::attrsToJson(lockedNode->originalRef.toAttrs());
- n["locked"] = fetchers::attrsToJson(lockedNode->lockedRef.toAttrs());
+ n["original"] = fetchers::attrsToJSON(lockedNode->originalRef.toAttrs());
+ n["locked"] = fetchers::attrsToJSON(lockedNode->lockedRef.toAttrs());
if (!lockedNode->isFlake) n["flake"] = false;
}
@@ -174,7 +178,7 @@ nlohmann::json LockFile::toJson() const
std::string LockFile::to_string() const
{
- return toJson().dump(2);
+ return toJSON().dump(2);
}
LockFile LockFile::read(const Path & path)
@@ -185,7 +189,7 @@ LockFile LockFile::read(const Path & path)
std::ostream & operator <<(std::ostream & stream, const LockFile & lockFile)
{
- stream << lockFile.toJson().dump(2);
+ stream << lockFile.toJSON().dump(2);
return stream;
}
@@ -223,7 +227,7 @@ bool LockFile::isImmutable() const
bool LockFile::operator ==(const LockFile & other) const
{
// FIXME: slow
- return toJson() == other.toJson();
+ return toJSON() == other.toJSON();
}
InputPath parseInputPath(std::string_view s)
diff --git a/src/libexpr/flake/lockfile.hh b/src/libexpr/flake/lockfile.hh
index 627794d8c..96f1edc76 100644
--- a/src/libexpr/flake/lockfile.hh
+++ b/src/libexpr/flake/lockfile.hh
@@ -52,7 +52,7 @@ struct LockFile
LockFile() {};
LockFile(const nlohmann::json & json, const Path & path);
- nlohmann::json toJson() const;
+ nlohmann::json toJSON() const;
std::string to_string() const;