Expand description
ยงFerris Wheel - Detect Dependency Cycles in Rust Monorepos
Ferris Wheel is a tool for detecting circular dependencies in Rust workspaces. It analyzes Cargo workspace structures and identifies dependency cycles that could lead to compilation issues or architectural problems.
ยงMain Components
- Analyzer: Discovers and analyzes Rust workspaces and their dependencies
- Detector: Implements cycle detection algorithms (Tarjanโs SCC)
- Graph: Builds and manages the dependency graph representation
- Reports: Generates human-readable and machine-readable reports
ยงUsage
ยงReal-World Example: Analyzing a Rust Monorepo
use std::path::PathBuf;
use cargo_ferris_wheel::analyzer::WorkspaceAnalyzer;
use cargo_ferris_wheel::detector::CycleDetector;
use cargo_ferris_wheel::graph::DependencyGraphBuilder;
use cargo_ferris_wheel::reports::{HumanReportGenerator, JsonReportGenerator, ReportGenerator};
use miette::IntoDiagnostic;
// Step 1: Discover all workspaces in your monorepo
let mut analyzer = WorkspaceAnalyzer::new();
let repo_root = PathBuf::from("/path/to/your/monorepo");
analyzer.discover_workspaces(&[repo_root], None)?;
println!("Found {} workspaces", analyzer.workspaces().len());
// Step 2: Build the dependency graph
let mut graph_builder = DependencyGraphBuilder::new(
false, // include dev dependencies
false, // include build dependencies
false, // include target-specific dependencies
);
graph_builder.build_cross_workspace_graph(
analyzer.workspaces(),
analyzer.crate_to_workspace(),
analyzer.crate_path_to_workspace(),
analyzer.crate_to_paths(),
None, // no progress reporter
)?;
// Step 3: Detect circular dependencies
let mut detector = CycleDetector::new();
detector.detect_cycles(graph_builder.graph())?;
// Step 4: Generate reports
if detector.has_cycles() {
println!(
"โ ๏ธ Found {} circular dependencies!",
detector.cycle_count()
);
// Human-readable report for console output
let human_report = HumanReportGenerator::new(Some(5)); // show max 5 cycles
println!("{}", human_report.generate_report(&detector)?);
// JSON report for programmatic processing
let json_report = JsonReportGenerator::new();
let json_output = json_report.generate_report(&detector)?;
std::fs::write("cycles.json", json_output).into_diagnostic()?;
} else {
println!("โ
No circular dependencies found!");
}ยงExample: Visualizing the Dependency Graph
use std::io::Write;
use cargo_ferris_wheel::graph::GraphRenderer;
use miette::IntoDiagnostic;
// Create a visual representation of your dependency graph
let renderer = GraphRenderer::new(
true, // highlight cycles
false, // don't show individual crate details
);
// Generate a Mermaid diagram (great for documentation)
let mut mermaid_output = Vec::new();
renderer.render_mermaid(
graph_builder.graph(),
detector.cycles(),
&mut mermaid_output,
)?;
std::fs::write("dependencies.mmd", mermaid_output).into_diagnostic()?;
// Or generate a DOT file for Graphviz
let mut dot_output = Vec::new();
renderer.render_dot(graph_builder.graph(), detector.cycles(), &mut dot_output)?;
std::fs::write("dependencies.dot", dot_output).into_diagnostic()?;ยงExample: Filtering Dependencies
// Check only production dependencies (exclude dev and build deps)
let mut graph_builder = DependencyGraphBuilder::new(
true, // exclude dev dependencies
true, // exclude build dependencies
false, // include target-specific dependencies
);
graph_builder.build_cross_workspace_graph(
analyzer.workspaces(),
analyzer.crate_to_workspace(),
analyzer.crate_path_to_workspace(),
analyzer.crate_to_paths(),
None,
)?;
let mut detector = CycleDetector::new();
detector.detect_cycles(graph_builder.graph())?;
println!("Production dependency cycles: {}", detector.cycle_count());ยงExample: Analyzing Specific Workspaces
// Find cycles involving a specific workspace
let target_workspace = "backend-core";
let cycles_with_target: Vec<&WorkspaceCycle> = detector
.cycles()
.iter()
.filter(|cycle| {
cycle
.workspace_names()
.contains(&target_workspace.to_string())
})
.collect();
println!(
"Found {} cycles involving {}",
cycles_with_target.len(),
target_workspace
);
for (i, cycle) in cycles_with_target.iter().enumerate() {
println!("\nCycle #{}", i + 1);
println!("Workspaces: {}", cycle.workspace_names().join(" โ "));
// Show specific crate-level dependencies
for edge in cycle.edges() {
println!(
" {} โ {} ({:?} dependency)",
edge.from_crate(),
edge.to_crate(),
edge.dependency_type()
);
}
}Modulesยง
- analyzer
- Workspace Analysis Module
- cli
- commands
- Command implementations for ferris-wheel CLI
- common
- Common functionality shared across commands
- config
- Configuration Module
- core
- Core data types and structures
- detector
- Cycle Detection Module
- error
- executors
- Command executors that handle the actual logic for each command
- graph
- Graph Construction and Rendering Module
- reports
- Report generation modules for different output formats
Macrosยง
- impl_
try_ from_ command - Macro to implement
TryFrom<Commands>usingFromCommandtrait