Merge pull request #9169 from vkryachko/follow_cycle

Detect cycles in flake follows.
This commit is contained in:
Théophane Hufschmitt 2023-10-18 07:34:03 +02:00 committed by GitHub
commit c1a1766c46
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 52 additions and 4 deletions

View file

@ -2,8 +2,10 @@
#include "store-api.hh" #include "store-api.hh"
#include "url-parts.hh" #include "url-parts.hh"
#include <algorithm>
#include <iomanip> #include <iomanip>
#include <iterator>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
namespace nix::flake { namespace nix::flake {
@ -45,16 +47,26 @@ StorePath LockedNode::computeStorePath(Store & store) const
return lockedRef.input.computeStorePath(store); return lockedRef.input.computeStorePath(store);
} }
std::shared_ptr<Node> LockFile::findInput(const InputPath & path)
{ static std::shared_ptr<Node> doFind(const ref<Node>& root, const InputPath & path, std::vector<InputPath>& visited) {
auto pos = root; auto pos = root;
auto found = std::find(visited.cbegin(), visited.cend(), path);
if(found != visited.end()) {
std::vector<std::string> cycle;
std::transform(found, visited.cend(), std::back_inserter(cycle), printInputPath);
cycle.push_back(printInputPath(path));
throw Error("follow cycle detected: [%s]", concatStringsSep(" -> ", cycle));
}
visited.push_back(path);
for (auto & elem : path) { for (auto & elem : path) {
if (auto i = get(pos->inputs, elem)) { if (auto i = get(pos->inputs, elem)) {
if (auto node = std::get_if<0>(&*i)) if (auto node = std::get_if<0>(&*i))
pos = *node; pos = *node;
else if (auto follows = std::get_if<1>(&*i)) { else if (auto follows = std::get_if<1>(&*i)) {
if (auto p = findInput(*follows)) if (auto p = doFind(root, *follows, visited))
pos = ref(p); pos = ref(p);
else else
return {}; return {};
@ -66,6 +78,12 @@ std::shared_ptr<Node> LockFile::findInput(const InputPath & path)
return pos; return pos;
} }
std::shared_ptr<Node> LockFile::findInput(const InputPath & path)
{
std::vector<InputPath> visited;
return doFind(root, path, visited);
}
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);

View file

@ -167,7 +167,7 @@ nix flake lock "$flakeFollowsA" 2>&1 | grep "warning: input 'B' has an override
# #
# The message was # The message was
# error: input 'B/D' follows a non-existent input 'B/C/D' # error: input 'B/D' follows a non-existent input 'B/C/D'
# #
# Note that for `B` to resolve its follow for `D`, it needs `C/D`, for which it needs to resolve the follow on `C` first. # Note that for `B` to resolve its follow for `D`, it needs `C/D`, for which it needs to resolve the follow on `C` first.
flakeFollowsOverloadA="$TEST_ROOT/follows/overload/flakeA" flakeFollowsOverloadA="$TEST_ROOT/follows/overload/flakeA"
flakeFollowsOverloadB="$TEST_ROOT/follows/overload/flakeA/flakeB" flakeFollowsOverloadB="$TEST_ROOT/follows/overload/flakeA/flakeB"
@ -230,3 +230,33 @@ git -C "$flakeFollowsOverloadA" add flake.nix flakeB/flake.nix \
nix flake metadata "$flakeFollowsOverloadA" nix flake metadata "$flakeFollowsOverloadA"
nix flake update "$flakeFollowsOverloadA" nix flake update "$flakeFollowsOverloadA"
nix flake lock "$flakeFollowsOverloadA" nix flake lock "$flakeFollowsOverloadA"
# Now test follow cycle detection
# We construct the following follows graph:
#
# foo
# / ^
# / \
# v \
# bar -> baz
# The message was
# error: follow cycle detected: [baz -> foo -> bar -> baz]
flakeFollowCycle="$TEST_ROOT/follows/followCycle"
# Test following path flakerefs.
mkdir -p "$flakeFollowCycle"
cat > $flakeFollowCycle/flake.nix <<EOF
{
description = "Flake A";
inputs = {
foo.follows = "bar";
bar.follows = "baz";
baz.follows = "foo";
};
outputs = { ... }: {};
}
EOF
checkRes=$(nix flake lock "$flakeFollowCycle" 2>&1 && fail "nix flake lock should have failed." || true)
echo $checkRes | grep -F "error: follow cycle detected: [baz -> foo -> bar -> baz]"