1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-15 15:02:42 +01:00

refactor(libutil/topo-sort): return variant instead of throwing

The variant has on the left-hand side the topologically sorted vector
and the right-hand side is a pair showing the path and its parent that
represent a cycle in the graph making the sort impossible.

This change prepares for enhanced cycle error messages that can provide
more context about the cycle. The variant approach allows callers to
handle cycles more flexibly, enabling better error reporting that shows
the full cycle path and which files are involved.

Adapted from Lix commit f7871fcb5.

Change-Id: I70a987f470437df8beb3b1cc203ff88701d0aa1b
Co-Authored-By: Maximilian Bosch <maximilian@mbosch.me>
This commit is contained in:
Bernardo Meurer Costa 2025-11-03 22:04:12 +00:00 committed by John Ericson
parent 7a60f1429f
commit 182ae393d1
6 changed files with 427 additions and 77 deletions

View file

@ -2,39 +2,61 @@
///@file
#include "nix/util/error.hh"
#include <variant>
namespace nix {
template<typename T>
struct Cycle
{
T path;
T parent;
};
template<typename T>
using TopoSortResult = std::variant<std::vector<T>, Cycle<T>>;
template<typename T, typename Compare>
std::vector<T> topoSort(
std::set<T, Compare> items,
std::function<std::set<T, Compare>(const T &)> getChildren,
std::function<Error(const T &, const T &)> makeCycleError)
TopoSortResult<T> topoSort(std::set<T, Compare> items, std::function<std::set<T, Compare>(const T &)> getChildren)
{
std::vector<T> sorted;
decltype(items) visited, parents;
auto dfsVisit = [&](this auto & dfsVisit, const T & path, const T * parent) {
if (parents.count(path))
throw makeCycleError(path, *parent);
std::function<std::optional<Cycle<T>>(const T & path, const T * parent)> dfsVisit;
if (!visited.insert(path).second)
return;
dfsVisit = [&](const T & path, const T * parent) -> std::optional<Cycle<T>> {
if (parents.count(path)) {
return Cycle{path, *parent};
}
if (!visited.insert(path).second) {
return std::nullopt;
}
parents.insert(path);
auto references = getChildren(path);
for (auto & i : references)
/* Don't traverse into items that don't exist in our starting set. */
if (i != path && items.count(i))
dfsVisit(i, &path);
if (i != path && items.count(i)) {
auto result = dfsVisit(i, &path);
if (result.has_value()) {
return result;
}
}
sorted.push_back(path);
parents.erase(path);
return std::nullopt;
};
for (auto & i : items)
dfsVisit(i, nullptr);
for (auto & i : items) {
auto cycle = dfsVisit(i, nullptr);
if (cycle.has_value()) {
return *cycle;
}
}
std::reverse(sorted.begin(), sorted.end());