cargo_ferris_wheel/executors/
check.rs1use console::style;
4use miette::{IntoDiagnostic, Result, WrapErr};
5
6use crate::analyzer::WorkspaceAnalyzer;
7use crate::cli::OutputFormat;
8use crate::config::CheckCyclesConfig;
9use crate::detector::CycleDetector;
10use crate::executors::CommandExecutor;
11use crate::graph::DependencyGraphBuilder;
12use crate::progress::ProgressReporter;
13use crate::reports::{
14 GitHubReportGenerator, HumanReportGenerator, JsonReportGenerator, JunitReportGenerator,
15 ReportGenerator,
16};
17
18pub struct CheckExecutor;
19
20impl CommandExecutor for CheckExecutor {
21 type Config = CheckCyclesConfig;
22
23 fn execute(config: Self::Config) -> Result<()> {
24 if config.intra_workspace {
25 eprintln!(
26 "{} Checking for intra-workspace dependency cycles...\n",
27 style("🎡").cyan()
28 );
29 } else {
30 eprintln!(
31 "{} Checking for inter-workspace dependency cycles...\n",
32 style("🎡").cyan()
33 );
34 }
35
36 let mut progress = if console::Term::stderr().is_term() {
38 Some(ProgressReporter::new())
39 } else {
40 None
41 };
42
43 let mut analyzer = WorkspaceAnalyzer::new();
45 analyzer
46 .discover_workspaces(&config.paths, progress.as_mut())
47 .wrap_err("Failed to discover and analyze workspaces")?;
48
49 if analyzer.workspaces().is_empty() {
50 eprintln!("{} No workspaces found to analyze", style("ℹ").blue());
51 return Ok(());
52 }
53
54 eprintln!("\n{} Building dependency graph...", style("🔨").blue());
56 eprintln!(
57 " {} Exclude dev dependencies: {}",
58 style("→").dim(),
59 if config.exclude_dev {
60 style("yes").red()
61 } else {
62 style("no").green()
63 }
64 );
65 eprintln!(
66 " {} Exclude build dependencies: {}",
67 style("→").dim(),
68 if config.exclude_build {
69 style("yes").red()
70 } else {
71 style("no").green()
72 }
73 );
74 eprintln!(
75 " {} Exclude target dependencies: {}",
76 style("→").dim(),
77 if config.exclude_target {
78 style("yes").red()
79 } else {
80 style("no").green()
81 }
82 );
83
84 let mut graph_builder = DependencyGraphBuilder::new(
85 config.exclude_dev,
86 config.exclude_build,
87 config.exclude_target,
88 );
89
90 if config.intra_workspace {
91 graph_builder
92 .build_intra_workspace_graph(analyzer.workspaces(), progress.as_ref())
93 .wrap_err("Failed to build intra-workspace dependency graph")?;
94 } else {
95 graph_builder
96 .build_cross_workspace_graph(
97 analyzer.workspaces(),
98 analyzer.crate_to_workspace(),
99 analyzer.crate_path_to_workspace(),
100 analyzer.crate_to_paths(),
101 progress.as_ref(),
102 )
103 .wrap_err("Failed to build cross-workspace dependency graph")?;
104 }
105
106 if let Some(p) = progress.as_mut() {
108 p.start_cycle_detection();
109 }
110
111 let mut detector = CycleDetector::new();
112 detector
113 .detect_cycles(graph_builder.graph())
114 .wrap_err("Failed to detect dependency cycles")?;
115
116 if let Some(p) = progress.as_ref() {
117 p.finish_cycle_detection(detector.cycle_count());
118 }
119
120 let report_result = match config.format {
122 OutputFormat::Human => {
123 let generator = HumanReportGenerator::new(config.max_cycles);
124 generator.generate_report(&detector)
125 }
126 OutputFormat::Json => {
127 let generator = JsonReportGenerator::new();
128 generator.generate_report(&detector)
129 }
130 OutputFormat::Junit => {
131 let generator = JunitReportGenerator::new();
132 generator.generate_report(&detector)
133 }
134 OutputFormat::GitHub => {
135 let generator = GitHubReportGenerator::new();
136 generator.generate_report(&detector)
137 }
138 };
139
140 match report_result {
141 Ok(report) => print!("{report}"),
142 Err(e) => {
143 return Err(e)
144 .into_diagnostic()
145 .wrap_err("Failed to generate report");
146 }
147 }
148
149 if config.error_on_cycles && detector.has_cycles() {
151 std::process::exit(1);
152 }
153
154 Ok(())
155 }
156}