Crate cargo_ferris_wheel

Crate cargo_ferris_wheel 

Source
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> using FromCommand trait

Functionsยง

run