forked from lix-project/lix
Represent 'follows' inputs explicitly in the lock file
This fixes an issue where lockfile generation was not idempotent: after updating a lockfile, a "follows" node would end up pointing to a new copy of the node, rather than to the original node.
This commit is contained in:
parent
195ed43b60
commit
0c62b4ad0f
|
@ -8,14 +8,41 @@ let
|
||||||
builtins.mapAttrs
|
builtins.mapAttrs
|
||||||
(key: node:
|
(key: node:
|
||||||
let
|
let
|
||||||
|
|
||||||
sourceInfo =
|
sourceInfo =
|
||||||
if key == lockFile.root
|
if key == lockFile.root
|
||||||
then rootSrc
|
then rootSrc
|
||||||
else fetchTree (node.info or {} // removeAttrs node.locked ["dir"]);
|
else fetchTree (node.info or {} // removeAttrs node.locked ["dir"]);
|
||||||
|
|
||||||
subdir = if key == lockFile.root then rootSubdir else node.locked.dir or "";
|
subdir = if key == lockFile.root then rootSubdir else node.locked.dir or "";
|
||||||
|
|
||||||
flake = import (sourceInfo + (if subdir != "" then "/" else "") + subdir + "/flake.nix");
|
flake = import (sourceInfo + (if subdir != "" then "/" else "") + subdir + "/flake.nix");
|
||||||
inputs = builtins.mapAttrs (inputName: key: allNodes.${key}) (node.inputs or {});
|
|
||||||
|
inputs = builtins.mapAttrs
|
||||||
|
(inputName: inputSpec: allNodes.${resolveInput inputSpec})
|
||||||
|
(node.inputs or {});
|
||||||
|
|
||||||
|
# Resolve a input spec into a node name. An input spec is
|
||||||
|
# either a node name, or a 'follows' path from the root
|
||||||
|
# node.
|
||||||
|
resolveInput = inputSpec:
|
||||||
|
if builtins.isList inputSpec
|
||||||
|
then getInputByPath lockFile.root inputSpec
|
||||||
|
else inputSpec;
|
||||||
|
|
||||||
|
# Follow an input path (e.g. ["dwarffs" "nixpkgs"]) from the
|
||||||
|
# root node, returning the final node.
|
||||||
|
getInputByPath = nodeName: path:
|
||||||
|
if path == []
|
||||||
|
then nodeName
|
||||||
|
else
|
||||||
|
getInputByPath
|
||||||
|
# Since this could be a 'follows' input, call resolveInput.
|
||||||
|
(resolveInput lockFile.nodes.${nodeName}.inputs.${builtins.head path})
|
||||||
|
(builtins.tail path);
|
||||||
|
|
||||||
outputs = flake.outputs (inputs // { self = result; });
|
outputs = flake.outputs (inputs // { self = result; });
|
||||||
|
|
||||||
result = outputs // sourceInfo // { inherit inputs; inherit outputs; inherit sourceInfo; };
|
result = outputs // sourceInfo // { inherit inputs; inherit outputs; inherit sourceInfo; };
|
||||||
in
|
in
|
||||||
if node.flake or true then
|
if node.flake or true then
|
||||||
|
|
|
@ -90,9 +90,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
|
||||||
{
|
{
|
||||||
expectType(state, tAttrs, *value, pos);
|
expectType(state, tAttrs, *value, pos);
|
||||||
|
|
||||||
FlakeInput input {
|
FlakeInput input;
|
||||||
.ref = FlakeRef::fromAttrs({{"type", "indirect"}, {"id", inputName}})
|
|
||||||
};
|
|
||||||
|
|
||||||
auto sInputs = state.symbols.create("inputs");
|
auto sInputs = state.symbols.create("inputs");
|
||||||
auto sUrl = state.symbols.create("url");
|
auto sUrl = state.symbols.create("url");
|
||||||
|
@ -145,6 +143,9 @@ static FlakeInput parseFlakeInput(EvalState & state,
|
||||||
input.ref = parseFlakeRef(*url, {}, true);
|
input.ref = parseFlakeRef(*url, {}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!input.follows && !input.ref)
|
||||||
|
input.ref = FlakeRef::fromAttrs({{"type", "indirect"}, {"id", inputName}});
|
||||||
|
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,7 +277,6 @@ LockedFlake lockFlake(
|
||||||
LockFile newLockFile;
|
LockFile newLockFile;
|
||||||
|
|
||||||
std::vector<FlakeRef> parents;
|
std::vector<FlakeRef> parents;
|
||||||
std::map<InputPath, InputPath> follows;
|
|
||||||
|
|
||||||
std::function<void(
|
std::function<void(
|
||||||
const FlakeInputs & flakeInputs,
|
const FlakeInputs & flakeInputs,
|
||||||
|
@ -324,34 +324,36 @@ LockedFlake lockFlake(
|
||||||
/* Resolve 'follows' later (since it may refer to an input
|
/* Resolve 'follows' later (since it may refer to an input
|
||||||
path we haven't processed yet. */
|
path we haven't processed yet. */
|
||||||
if (input.follows) {
|
if (input.follows) {
|
||||||
if (hasOverride)
|
InputPath target;
|
||||||
|
if (hasOverride || input.absolute)
|
||||||
/* 'follows' from an override is relative to the
|
/* 'follows' from an override is relative to the
|
||||||
root of the graph. */
|
root of the graph. */
|
||||||
follows.insert_or_assign(inputPath, *input.follows);
|
target = *input.follows;
|
||||||
else {
|
else {
|
||||||
/* Otherwise, it's relative to the current flake. */
|
/* Otherwise, it's relative to the current flake. */
|
||||||
InputPath path(inputPathPrefix);
|
target = inputPathPrefix;
|
||||||
for (auto & i : *input.follows) path.push_back(i);
|
for (auto & i : *input.follows) target.push_back(i);
|
||||||
debug("input '%s' follows '%s'", inputPathS, printInputPath(path));
|
|
||||||
follows.insert_or_assign(inputPath, path);
|
|
||||||
}
|
}
|
||||||
|
debug("input '%s' follows '%s'", inputPathS, printInputPath(target));
|
||||||
|
node->inputs.insert_or_assign(id, target);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(input.ref);
|
||||||
|
|
||||||
/* Do we have an entry in the existing lock file? And we
|
/* Do we have an entry in the existing lock file? And we
|
||||||
don't have a --update-input flag for this input? */
|
don't have a --update-input flag for this input? */
|
||||||
std::shared_ptr<const LockedNode> oldLock;
|
std::shared_ptr<LockedNode> oldLock;
|
||||||
|
|
||||||
updatesUsed.insert(inputPath);
|
updatesUsed.insert(inputPath);
|
||||||
|
|
||||||
if (oldNode && !lockFlags.inputUpdates.count(inputPath)) {
|
if (oldNode && !lockFlags.inputUpdates.count(inputPath))
|
||||||
auto oldLockIt = oldNode->inputs.find(id);
|
if (auto oldLock2 = get(oldNode->inputs, id))
|
||||||
if (oldLockIt != oldNode->inputs.end())
|
if (auto oldLock3 = std::get_if<0>(&*oldLock2))
|
||||||
oldLock = std::dynamic_pointer_cast<const LockedNode>(oldLockIt->second);
|
oldLock = *oldLock3;
|
||||||
}
|
|
||||||
|
|
||||||
if (oldLock
|
if (oldLock
|
||||||
&& oldLock->originalRef == input.ref
|
&& oldLock->originalRef == *input.ref
|
||||||
&& !hasOverride)
|
&& !hasOverride)
|
||||||
{
|
{
|
||||||
debug("keeping existing input '%s'", inputPathS);
|
debug("keeping existing input '%s'", inputPathS);
|
||||||
|
@ -386,18 +388,16 @@ LockedFlake lockFlake(
|
||||||
FlakeInputs fakeInputs;
|
FlakeInputs fakeInputs;
|
||||||
|
|
||||||
for (auto & i : oldLock->inputs) {
|
for (auto & i : oldLock->inputs) {
|
||||||
auto lockedNode = std::dynamic_pointer_cast<LockedNode>(i.second);
|
if (auto lockedNode = std::get_if<0>(&i.second)) {
|
||||||
// Note: this node is not locked in case
|
|
||||||
// of a circular reference back to the root.
|
|
||||||
if (lockedNode)
|
|
||||||
fakeInputs.emplace(i.first, FlakeInput {
|
fakeInputs.emplace(i.first, FlakeInput {
|
||||||
.ref = lockedNode->originalRef,
|
.ref = (*lockedNode)->originalRef,
|
||||||
.isFlake = lockedNode->isFlake,
|
.isFlake = (*lockedNode)->isFlake,
|
||||||
|
});
|
||||||
|
} else if (auto follows = std::get_if<1>(&i.second)) {
|
||||||
|
fakeInputs.emplace(i.first, FlakeInput {
|
||||||
|
.follows = *follows,
|
||||||
|
.absolute = true
|
||||||
});
|
});
|
||||||
else {
|
|
||||||
InputPath path(inputPath);
|
|
||||||
path.push_back(i.first);
|
|
||||||
follows.insert_or_assign(path, InputPath());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -409,11 +409,11 @@ LockedFlake lockFlake(
|
||||||
this input. */
|
this input. */
|
||||||
debug("creating new input '%s'", inputPathS);
|
debug("creating new input '%s'", inputPathS);
|
||||||
|
|
||||||
if (!lockFlags.allowMutable && !input.ref.input.isImmutable())
|
if (!lockFlags.allowMutable && !input.ref->input.isImmutable())
|
||||||
throw Error("cannot update flake input '%s' in pure mode", inputPathS);
|
throw Error("cannot update flake input '%s' in pure mode", inputPathS);
|
||||||
|
|
||||||
if (input.isFlake) {
|
if (input.isFlake) {
|
||||||
auto inputFlake = getFlake(state, input.ref, lockFlags.useRegistries, flakeCache);
|
auto inputFlake = getFlake(state, *input.ref, lockFlags.useRegistries, flakeCache);
|
||||||
|
|
||||||
/* Note: in case of an --override-input, we use
|
/* Note: in case of an --override-input, we use
|
||||||
the *original* ref (input2.ref) for the
|
the *original* ref (input2.ref) for the
|
||||||
|
@ -423,15 +423,15 @@ LockedFlake lockFlake(
|
||||||
file. That is, overrides are sticky unless you
|
file. That is, overrides are sticky unless you
|
||||||
use --no-write-lock-file. */
|
use --no-write-lock-file. */
|
||||||
auto childNode = std::make_shared<LockedNode>(
|
auto childNode = std::make_shared<LockedNode>(
|
||||||
inputFlake.lockedRef, input2.ref);
|
inputFlake.lockedRef, input2.ref ? *input2.ref : *input.ref);
|
||||||
|
|
||||||
node->inputs.insert_or_assign(id, childNode);
|
node->inputs.insert_or_assign(id, childNode);
|
||||||
|
|
||||||
/* Guard against circular flake imports. */
|
/* Guard against circular flake imports. */
|
||||||
for (auto & parent : parents)
|
for (auto & parent : parents)
|
||||||
if (parent == input.ref)
|
if (parent == *input.ref)
|
||||||
throw Error("found circular import of flake '%s'", parent);
|
throw Error("found circular import of flake '%s'", parent);
|
||||||
parents.push_back(input.ref);
|
parents.push_back(*input.ref);
|
||||||
Finally cleanup([&]() { parents.pop_back(); });
|
Finally cleanup([&]() { parents.pop_back(); });
|
||||||
|
|
||||||
/* Recursively process the inputs of this
|
/* Recursively process the inputs of this
|
||||||
|
@ -448,9 +448,9 @@ LockedFlake lockFlake(
|
||||||
|
|
||||||
else {
|
else {
|
||||||
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
|
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
|
||||||
state, input.ref, lockFlags.useRegistries, flakeCache);
|
state, *input.ref, lockFlags.useRegistries, flakeCache);
|
||||||
node->inputs.insert_or_assign(id,
|
node->inputs.insert_or_assign(id,
|
||||||
std::make_shared<LockedNode>(lockedRef, input.ref, false));
|
std::make_shared<LockedNode>(lockedRef, *input.ref, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -460,29 +460,6 @@ LockedFlake lockFlake(
|
||||||
flake.inputs, newLockFile.root, {},
|
flake.inputs, newLockFile.root, {},
|
||||||
lockFlags.recreateLockFile ? nullptr : oldLockFile.root);
|
lockFlags.recreateLockFile ? nullptr : oldLockFile.root);
|
||||||
|
|
||||||
/* Insert edges for 'follows' overrides. */
|
|
||||||
for (auto & [from, to] : follows) {
|
|
||||||
debug("adding 'follows' node from '%s' to '%s'",
|
|
||||||
printInputPath(from),
|
|
||||||
printInputPath(to));
|
|
||||||
|
|
||||||
assert(!from.empty());
|
|
||||||
|
|
||||||
InputPath fromParent(from);
|
|
||||||
fromParent.pop_back();
|
|
||||||
|
|
||||||
auto fromParentNode = newLockFile.root->findInput(fromParent);
|
|
||||||
assert(fromParentNode);
|
|
||||||
|
|
||||||
auto toNode = newLockFile.root->findInput(to);
|
|
||||||
if (!toNode)
|
|
||||||
throw Error("flake input '%s' follows non-existent flake input '%s'",
|
|
||||||
printInputPath(from),
|
|
||||||
printInputPath(to));
|
|
||||||
|
|
||||||
fromParentNode->inputs.insert_or_assign(from.back(), toNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto & i : lockFlags.inputOverrides)
|
for (auto & i : lockFlags.inputOverrides)
|
||||||
if (!overridesUsed.count(i.first))
|
if (!overridesUsed.count(i.first))
|
||||||
warn("the flag '--override-input %s %s' does not match any input",
|
warn("the flag '--override-input %s %s' does not match any input",
|
||||||
|
@ -514,9 +491,13 @@ LockedFlake lockFlake(
|
||||||
|
|
||||||
bool lockFileExists = pathExists(path);
|
bool lockFileExists = pathExists(path);
|
||||||
|
|
||||||
if (lockFileExists)
|
if (lockFileExists) {
|
||||||
warn("updating lock file '%s':\n%s", path, chomp(diff));
|
auto s = chomp(diff);
|
||||||
else
|
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);
|
warn("creating lock file '%s'", path);
|
||||||
|
|
||||||
newLockFile.write(path);
|
newLockFile.write(path);
|
||||||
|
|
|
@ -19,9 +19,10 @@ typedef std::map<FlakeId, FlakeInput> FlakeInputs;
|
||||||
|
|
||||||
struct FlakeInput
|
struct FlakeInput
|
||||||
{
|
{
|
||||||
FlakeRef ref;
|
std::optional<FlakeRef> ref;
|
||||||
bool isFlake = true;
|
bool isFlake = true;
|
||||||
std::optional<InputPath> follows;
|
std::optional<InputPath> follows;
|
||||||
|
bool absolute = false; // whether 'follows' is relative to the flake root
|
||||||
FlakeInputs overrides;
|
FlakeInputs overrides;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -41,15 +41,22 @@ StorePath LockedNode::computeStorePath(Store & store) const
|
||||||
return lockedRef.input.computeStorePath(store);
|
return lockedRef.input.computeStorePath(store);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Node> Node::findInput(const InputPath & path)
|
std::shared_ptr<Node> LockFile::findInput(const InputPath & path)
|
||||||
{
|
{
|
||||||
auto pos = shared_from_this();
|
auto pos = root;
|
||||||
|
|
||||||
|
if (!pos) return {};
|
||||||
|
|
||||||
for (auto & elem : path) {
|
for (auto & elem : path) {
|
||||||
auto i = pos->inputs.find(elem);
|
if (auto i = get(pos->inputs, elem)) {
|
||||||
if (i == pos->inputs.end())
|
if (auto node = std::get_if<0>(&*i))
|
||||||
|
pos = *node;
|
||||||
|
else if (auto follows = std::get_if<1>(&*i)) {
|
||||||
|
pos = findInput(*follows);
|
||||||
|
if (!pos) return {};
|
||||||
|
}
|
||||||
|
} else
|
||||||
return {};
|
return {};
|
||||||
pos = i->second;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return pos;
|
return pos;
|
||||||
|
@ -58,7 +65,7 @@ std::shared_ptr<Node> Node::findInput(const InputPath & path)
|
||||||
LockFile::LockFile(const nlohmann::json & json, const Path & path)
|
LockFile::LockFile(const nlohmann::json & json, const Path & path)
|
||||||
{
|
{
|
||||||
auto version = json.value("version", 0);
|
auto version = json.value("version", 0);
|
||||||
if (version < 5 || version > 6)
|
if (version < 5 || version > 7)
|
||||||
throw Error("lock file '%s' has unsupported version %d", path, version);
|
throw Error("lock file '%s' has unsupported version %d", path, version);
|
||||||
|
|
||||||
std::unordered_map<std::string, std::shared_ptr<Node>> nodeMap;
|
std::unordered_map<std::string, std::shared_ptr<Node>> nodeMap;
|
||||||
|
@ -69,21 +76,37 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path)
|
||||||
{
|
{
|
||||||
if (jsonNode.find("inputs") == jsonNode.end()) return;
|
if (jsonNode.find("inputs") == jsonNode.end()) return;
|
||||||
for (auto & i : jsonNode["inputs"].items()) {
|
for (auto & i : jsonNode["inputs"].items()) {
|
||||||
std::string inputKey = i.value();
|
if (i.value().is_array()) {
|
||||||
auto k = nodeMap.find(inputKey);
|
InputPath path;
|
||||||
if (k == nodeMap.end()) {
|
for (auto & j : i.value())
|
||||||
auto jsonNode2 = json["nodes"][inputKey];
|
path.push_back(j);
|
||||||
auto input = std::make_shared<LockedNode>(jsonNode2);
|
node.inputs.insert_or_assign(i.key(), path);
|
||||||
k = nodeMap.insert_or_assign(inputKey, input).first;
|
} else {
|
||||||
getInputs(*input, jsonNode2);
|
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);
|
||||||
|
k = nodeMap.insert_or_assign(inputKey, input).first;
|
||||||
|
getInputs(*input, jsonNode2);
|
||||||
|
}
|
||||||
|
if (auto child = std::dynamic_pointer_cast<LockedNode>(k->second))
|
||||||
|
node.inputs.insert_or_assign(i.key(), child);
|
||||||
|
else
|
||||||
|
// FIXME: replace by follows node
|
||||||
|
throw Error("lock file contains cycle to root node");
|
||||||
}
|
}
|
||||||
node.inputs.insert_or_assign(i.key(), k->second);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string rootKey = json["root"];
|
std::string rootKey = json["root"];
|
||||||
nodeMap.insert_or_assign(rootKey, root);
|
nodeMap.insert_or_assign(rootKey, root);
|
||||||
getInputs(*root, json["nodes"][rootKey]);
|
getInputs(*root, json["nodes"][rootKey]);
|
||||||
|
|
||||||
|
// FIXME: check that there are no cycles in version >= 7. Cycles
|
||||||
|
// between inputs are only possible using 'follows' indirections.
|
||||||
|
// Once we drop support for version <= 6, we can simplify the code
|
||||||
|
// a bit since we don't need to worry about cycles.
|
||||||
}
|
}
|
||||||
|
|
||||||
nlohmann::json LockFile::toJson() const
|
nlohmann::json LockFile::toJson() const
|
||||||
|
@ -116,8 +139,16 @@ nlohmann::json LockFile::toJson() const
|
||||||
|
|
||||||
if (!node->inputs.empty()) {
|
if (!node->inputs.empty()) {
|
||||||
auto inputs = nlohmann::json::object();
|
auto inputs = nlohmann::json::object();
|
||||||
for (auto & i : node->inputs)
|
for (auto & i : node->inputs) {
|
||||||
inputs[i.first] = dumpNode(i.first, i.second);
|
if (auto child = std::get_if<0>(&i.second)) {
|
||||||
|
inputs[i.first] = dumpNode(i.first, *child);
|
||||||
|
} else if (auto follows = std::get_if<1>(&i.second)) {
|
||||||
|
auto arr = nlohmann::json::array();
|
||||||
|
for (auto & x : *follows)
|
||||||
|
arr.push_back(x);
|
||||||
|
inputs[i.first] = std::move(arr);
|
||||||
|
}
|
||||||
|
}
|
||||||
n["inputs"] = std::move(inputs);
|
n["inputs"] = std::move(inputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +164,7 @@ nlohmann::json LockFile::toJson() const
|
||||||
};
|
};
|
||||||
|
|
||||||
nlohmann::json json;
|
nlohmann::json json;
|
||||||
json["version"] = 6;
|
json["version"] = 7;
|
||||||
json["root"] = dumpNode("root", root);
|
json["root"] = dumpNode("root", root);
|
||||||
json["nodes"] = std::move(nodes);
|
json["nodes"] = std::move(nodes);
|
||||||
|
|
||||||
|
@ -172,7 +203,9 @@ bool LockFile::isImmutable() const
|
||||||
visit = [&](std::shared_ptr<const Node> node)
|
visit = [&](std::shared_ptr<const Node> node)
|
||||||
{
|
{
|
||||||
if (!nodes.insert(node).second) return;
|
if (!nodes.insert(node).second) return;
|
||||||
for (auto & i : node->inputs) visit(i.second);
|
for (auto & i : node->inputs)
|
||||||
|
if (auto child = std::get_if<0>(&i.second))
|
||||||
|
visit(*child);
|
||||||
};
|
};
|
||||||
|
|
||||||
visit(root);
|
visit(root);
|
||||||
|
@ -216,9 +249,11 @@ static void flattenLockFile(
|
||||||
for (auto &[id, input] : node->inputs) {
|
for (auto &[id, input] : node->inputs) {
|
||||||
auto inputPath(prefix);
|
auto inputPath(prefix);
|
||||||
inputPath.push_back(id);
|
inputPath.push_back(id);
|
||||||
if (auto lockedInput = std::dynamic_pointer_cast<const LockedNode>(input))
|
if (auto child = std::get_if<0>(&input)) {
|
||||||
res.emplace(inputPath, lockedInput);
|
if (auto lockedInput = std::dynamic_pointer_cast<const LockedNode>(*child))
|
||||||
flattenLockFile(input, inputPath, done, res);
|
res.emplace(inputPath, lockedInput);
|
||||||
|
flattenLockFile(*child, inputPath, done, res);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,16 +15,18 @@ using namespace fetchers;
|
||||||
|
|
||||||
typedef std::vector<FlakeId> InputPath;
|
typedef std::vector<FlakeId> InputPath;
|
||||||
|
|
||||||
|
struct LockedNode;
|
||||||
|
|
||||||
/* A node in the lock file. It has outgoing edges to other nodes (its
|
/* A node in the lock file. It has outgoing edges to other nodes (its
|
||||||
inputs). Only the root node has this type; all other nodes have
|
inputs). Only the root node has this type; all other nodes have
|
||||||
type LockedNode. */
|
type LockedNode. */
|
||||||
struct Node : std::enable_shared_from_this<Node>
|
struct Node : std::enable_shared_from_this<Node>
|
||||||
{
|
{
|
||||||
std::map<FlakeId, std::shared_ptr<Node>> inputs;
|
typedef std::variant<std::shared_ptr<LockedNode>, InputPath> Edge;
|
||||||
|
|
||||||
|
std::map<FlakeId, Edge> inputs;
|
||||||
|
|
||||||
virtual ~Node() { }
|
virtual ~Node() { }
|
||||||
|
|
||||||
std::shared_ptr<Node> findInput(const InputPath & path);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* A non-root node in the lock file. */
|
/* A non-root node in the lock file. */
|
||||||
|
@ -63,6 +65,8 @@ struct LockFile
|
||||||
bool isImmutable() const;
|
bool isImmutable() const;
|
||||||
|
|
||||||
bool operator ==(const LockFile & other) const;
|
bool operator ==(const LockFile & other) const;
|
||||||
|
|
||||||
|
std::shared_ptr<Node> findInput(const InputPath & path);
|
||||||
};
|
};
|
||||||
|
|
||||||
std::ostream & operator <<(std::ostream & stream, const LockFile & lockFile);
|
std::ostream & operator <<(std::ostream & stream, const LockFile & lockFile);
|
||||||
|
|
|
@ -169,15 +169,21 @@ struct CmdFlakeListInputs : FlakeCommand, MixJSON
|
||||||
recurse = [&](const Node & node, const std::string & prefix)
|
recurse = [&](const Node & node, const std::string & prefix)
|
||||||
{
|
{
|
||||||
for (const auto & [i, input] : enumerate(node.inputs)) {
|
for (const auto & [i, input] : enumerate(node.inputs)) {
|
||||||
bool firstVisit = visited.insert(input.second).second;
|
|
||||||
bool last = i + 1 == node.inputs.size();
|
bool last = i + 1 == node.inputs.size();
|
||||||
auto lockedNode = std::dynamic_pointer_cast<const LockedNode>(input.second);
|
|
||||||
|
|
||||||
logger->stdout("%s" ANSI_BOLD "%s" ANSI_NORMAL ": %s",
|
if (auto lockedNode = std::get_if<0>(&input.second)) {
|
||||||
prefix + (last ? treeLast : treeConn), input.first,
|
logger->stdout("%s" ANSI_BOLD "%s" ANSI_NORMAL ": %s",
|
||||||
lockedNode ? lockedNode->lockedRef : flake.flake.lockedRef);
|
prefix + (last ? treeLast : treeConn), input.first,
|
||||||
|
*lockedNode ? (*lockedNode)->lockedRef : flake.flake.lockedRef);
|
||||||
|
|
||||||
if (firstVisit) recurse(*input.second, prefix + (last ? treeNull : treeLine));
|
bool firstVisit = visited.insert(*lockedNode).second;
|
||||||
|
|
||||||
|
if (firstVisit) recurse(**lockedNode, prefix + (last ? treeNull : treeLine));
|
||||||
|
} else if (auto follows = std::get_if<1>(&input.second)) {
|
||||||
|
logger->stdout("%s" ANSI_BOLD "%s" ANSI_NORMAL " follows input '%s'",
|
||||||
|
prefix + (last ? treeLast : treeConn), input.first,
|
||||||
|
printInputPath(*follows));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -723,18 +729,18 @@ struct CmdFlakeArchive : FlakeCommand, MixJSON, MixDryRun
|
||||||
traverse = [&](const Node & node, std::optional<JSONObject> & jsonObj)
|
traverse = [&](const Node & node, std::optional<JSONObject> & jsonObj)
|
||||||
{
|
{
|
||||||
auto jsonObj2 = jsonObj ? jsonObj->object("inputs") : std::optional<JSONObject>();
|
auto jsonObj2 = jsonObj ? jsonObj->object("inputs") : std::optional<JSONObject>();
|
||||||
for (auto & input : node.inputs) {
|
for (auto & [inputName, input] : node.inputs) {
|
||||||
auto lockedInput = std::dynamic_pointer_cast<const LockedNode>(input.second);
|
if (auto inputNode = std::get_if<0>(&input)) {
|
||||||
assert(lockedInput);
|
auto jsonObj3 = jsonObj2 ? jsonObj2->object(inputName) : std::optional<JSONObject>();
|
||||||
auto jsonObj3 = jsonObj2 ? jsonObj2->object(input.first) : std::optional<JSONObject>();
|
auto storePath =
|
||||||
auto storePath =
|
dryRun
|
||||||
dryRun
|
? (*inputNode)->lockedRef.input.computeStorePath(*store)
|
||||||
? lockedInput->lockedRef.input.computeStorePath(*store)
|
: (*inputNode)->lockedRef.input.fetch(store).first.storePath;
|
||||||
: lockedInput->lockedRef.input.fetch(store).first.storePath;
|
if (jsonObj3)
|
||||||
if (jsonObj3)
|
jsonObj3->attr("path", store->printStorePath(storePath));
|
||||||
jsonObj3->attr("path", store->printStorePath(storePath));
|
sources.insert(std::move(storePath));
|
||||||
sources.insert(std::move(storePath));
|
traverse(**inputNode, jsonObj3);
|
||||||
traverse(*lockedInput, jsonObj3);
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -538,9 +538,8 @@ FlakeRef InstallableFlake::nixpkgsFlakeRef() const
|
||||||
{
|
{
|
||||||
auto lockedFlake = getLockedFlake();
|
auto lockedFlake = getLockedFlake();
|
||||||
|
|
||||||
auto nixpkgsInput = lockedFlake->lockFile.root->inputs.find("nixpkgs");
|
if (auto nixpkgsInput = lockedFlake->lockFile.findInput({"nixpkgs"})) {
|
||||||
if (nixpkgsInput != lockedFlake->lockFile.root->inputs.end()) {
|
if (auto lockedNode = std::dynamic_pointer_cast<const flake::LockedNode>(nixpkgsInput)) {
|
||||||
if (auto lockedNode = std::dynamic_pointer_cast<const flake::LockedNode>(nixpkgsInput->second)) {
|
|
||||||
debug("using nixpkgs flake '%s'", lockedNode->lockedRef);
|
debug("using nixpkgs flake '%s'", lockedNode->lockedRef);
|
||||||
return std::move(lockedNode->lockedRef);
|
return std::move(lockedNode->lockedRef);
|
||||||
}
|
}
|
||||||
|
|
|
@ -551,7 +551,7 @@ cat > $flake3Dir/flake.nix <<EOF
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
nix flake update $flake3Dir
|
nix flake update $flake3Dir
|
||||||
[[ $(jq .nodes.root.inputs.foo $flake3Dir/flake.lock) = $(jq .nodes.root.inputs.bar $flake3Dir/flake.lock) ]]
|
[[ $(jq -c .nodes.root.inputs.bar $flake3Dir/flake.lock) = '["foo"]' ]]
|
||||||
|
|
||||||
cat > $flake3Dir/flake.nix <<EOF
|
cat > $flake3Dir/flake.nix <<EOF
|
||||||
{
|
{
|
||||||
|
@ -563,7 +563,7 @@ cat > $flake3Dir/flake.nix <<EOF
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
nix flake update $flake3Dir
|
nix flake update $flake3Dir
|
||||||
[[ $(jq .nodes.bar.locked.url $flake3Dir/flake.lock) =~ flake1 ]]
|
[[ $(jq -c .nodes.root.inputs.bar $flake3Dir/flake.lock) = '["flake2","flake1"]' ]]
|
||||||
|
|
||||||
cat > $flake3Dir/flake.nix <<EOF
|
cat > $flake3Dir/flake.nix <<EOF
|
||||||
{
|
{
|
||||||
|
@ -575,7 +575,7 @@ cat > $flake3Dir/flake.nix <<EOF
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
nix flake update $flake3Dir
|
nix flake update $flake3Dir
|
||||||
[[ $(jq .nodes.bar.locked.url $flake3Dir/flake.lock) =~ flake2 ]]
|
[[ $(jq -c .nodes.root.inputs.bar $flake3Dir/flake.lock) = '["flake2"]' ]]
|
||||||
|
|
||||||
# Test overriding inputs of inputs.
|
# Test overriding inputs of inputs.
|
||||||
cat > $flake3Dir/flake.nix <<EOF
|
cat > $flake3Dir/flake.nix <<EOF
|
||||||
|
@ -604,7 +604,8 @@ cat > $flake3Dir/flake.nix <<EOF
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
nix flake update $flake3Dir --recreate-lock-file
|
nix flake update $flake3Dir --recreate-lock-file
|
||||||
[[ $(jq .nodes.flake1.locked.url $flake3Dir/flake.lock) =~ flake7 ]]
|
[[ $(jq -c .nodes.flake2.inputs.flake1 $flake3Dir/flake.lock) =~ '["foo"]' ]]
|
||||||
|
[[ $(jq .nodes.foo.locked.url $flake3Dir/flake.lock) =~ flake7 ]]
|
||||||
|
|
||||||
# Test Mercurial flakes.
|
# Test Mercurial flakes.
|
||||||
rm -rf $flake5Dir
|
rm -rf $flake5Dir
|
||||||
|
|
Loading…
Reference in a new issue