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

Merge pull request #14479 from lovesegfault/topo-sort-handle-cycles

refactor(libutil/topo-sort): return variant instead of throwing
This commit is contained in:
John Ericson 2025-11-10 20:50:17 +00:00 committed by GitHub
commit 750306234d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
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());