Skip to content

Commit 4c04a5e

Browse files
committed
feat(libstore): add findCycles() to DependencyGraph
Adds cycle detection to DependencyGraph using DFS with back-edge detection. This will be used by the cycle detection feature for build errors. Each cycle is represented as a path that starts and ends at the same node, e.g., [A, B, C, A].
1 parent 60857de commit 4c04a5e

File tree

2 files changed

+63
-0
lines changed

2 files changed

+63
-0
lines changed

src/libstore/include/nix/store/dependency-graph-impl.hh

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "nix/util/error.hh"
1818

1919
#include <boost/graph/graph_traits.hpp>
20+
#include <boost/graph/depth_first_search.hpp>
2021
#include <boost/graph/reverse_graph.hpp>
2122
#include <boost/graph/properties.hpp>
2223

@@ -222,6 +223,61 @@ DependencyGraph<NodeId, EdgeProperty>::getEdgeProperty(const NodeId & from, cons
222223
return graph[edge];
223224
}
224225

226+
template<GraphNodeId NodeId, typename EdgeProperty>
227+
std::vector<std::vector<NodeId>> DependencyGraph<NodeId, EdgeProperty>::findCycles() const
228+
{
229+
using vertex_descriptor = typename boost::graph_traits<Graph>::vertex_descriptor;
230+
using edge_descriptor = typename boost::graph_traits<Graph>::edge_descriptor;
231+
232+
std::vector<std::vector<vertex_descriptor>> cycleDescriptors;
233+
std::vector<vertex_descriptor> dfsPath;
234+
235+
// Custom DFS visitor to detect back edges and extract cycles
236+
class CycleFinder : public boost::default_dfs_visitor
237+
{
238+
public:
239+
std::vector<std::vector<vertex_descriptor>> & cycles;
240+
std::vector<vertex_descriptor> & dfsPath;
241+
242+
CycleFinder(std::vector<std::vector<vertex_descriptor>> & cycles, std::vector<vertex_descriptor> & dfsPath)
243+
: cycles(cycles)
244+
, dfsPath(dfsPath)
245+
{
246+
}
247+
248+
void discover_vertex(vertex_descriptor v, const Graph & g)
249+
{
250+
dfsPath.push_back(v);
251+
}
252+
253+
void finish_vertex(vertex_descriptor v, const Graph & g)
254+
{
255+
if (!dfsPath.empty() && dfsPath.back() == v) {
256+
dfsPath.pop_back();
257+
}
258+
}
259+
260+
void back_edge(edge_descriptor e, const Graph & g)
261+
{
262+
auto target = boost::target(e, g);
263+
auto cycleStart = std::ranges::find(dfsPath, target);
264+
std::vector<vertex_descriptor> cycle(cycleStart, dfsPath.end());
265+
cycle.push_back(target);
266+
cycles.push_back(std::move(cycle));
267+
}
268+
};
269+
270+
CycleFinder visitor(cycleDescriptors, dfsPath);
271+
boost::depth_first_search(graph, boost::visitor(visitor));
272+
273+
// Convert vertex_descriptors to NodeIds using ranges
274+
return cycleDescriptors | std::views::transform([&](const auto & cycleVerts) {
275+
return cycleVerts | std::views::transform([&](auto v) { return getNodeId(v); })
276+
| std::ranges::to<std::vector<NodeId>>();
277+
})
278+
| std::ranges::to<std::vector>();
279+
}
280+
225281
template<GraphNodeId NodeId, typename EdgeProperty>
226282
std::vector<NodeId> DependencyGraph<NodeId, EdgeProperty>::getAllNodes() const
227283
{

src/libstore/include/nix/store/dependency-graph.hh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "nix/util/canon-path.hh"
66

77
#include <boost/graph/adjacency_list.hpp>
8+
#include <boost/graph/depth_first_search.hpp>
89
#include <boost/graph/dijkstra_shortest_paths.hpp>
910
#include <boost/graph/reverse_graph.hpp>
1011

@@ -137,6 +138,12 @@ public:
137138

138139
std::vector<NodeId> getAllNodes() const;
139140

141+
/**
142+
* Find all cycles in the graph using DFS.
143+
* Returns vector of cycles, each represented as a path that starts and ends at the same node.
144+
*/
145+
std::vector<std::vector<NodeId>> findCycles() const;
146+
140147
size_t numVertices() const
141148
{
142149
return boost::num_vertices(graph);

0 commit comments

Comments
 (0)