@@ -42,6 +42,12 @@ enum Commands {
4242 #[ arg( long, short) ]
4343 verbose : bool ,
4444 } ,
45+ /// Run clippy on all projects
46+ Clippy {
47+ /// Show verbose output
48+ #[ arg( long, short) ]
49+ verbose : bool ,
50+ } ,
4551 /// Run all maintenance tasks: format, update (compatible only), and build
4652 All {
4753 /// Continue with other tasks even if one fails
@@ -131,6 +137,7 @@ async fn main() -> Result<()> {
131137 verbose,
132138 } => update_dependencies ( & projects, dry_run, incompatible, verbose) . await ,
133139 Commands :: Format { verbose } => format_all_projects ( & projects, verbose) . await ,
140+ Commands :: Clippy { verbose } => clippy_all_projects ( & projects, verbose) . await ,
134141 Commands :: All {
135142 keep_going,
136143 verbose,
@@ -237,8 +244,13 @@ async fn build_all_projects(
237244 }
238245 } else {
239246 println ! ( "❌ Build failed: {}" , project. name) ;
240- if verbose || !keep_going {
241- println ! ( " Error: {}" , result. message) ;
247+ // Always show error details for failures, regardless of verbose flag
248+ println ! ( " Error details:" ) ;
249+ for line in result. message . lines ( ) . take ( 20 ) {
250+ println ! ( " {}" , line) ;
251+ }
252+ if result. message . lines ( ) . count ( ) > 20 {
253+ println ! ( " ... (truncated, use --verbose for full output)" ) ;
242254 }
243255 if !keep_going {
244256 return Err ( anyhow:: anyhow!( "Build failed for {}" , project. name) ) ;
@@ -516,6 +528,122 @@ async fn format_project(project: &ProjectInfo, verbose: bool) -> Result<TaskResu
516528 } )
517529}
518530
531+ async fn clippy_all_projects ( projects : & [ ProjectInfo ] , verbose : bool ) -> Result < ( ) > {
532+ println ! ( "\n 🔍 Running clippy on all ESP32 projects (--release mode)" ) ;
533+ println ! ( "════════════════════════════════════════════════════" ) ;
534+
535+ let mut summary = TaskSummary :: new ( ) ;
536+
537+ for project in projects. iter ( ) . filter ( |p| p. has_cargo_toml ) {
538+ println ! ( "\n 🔍 Clippy: {}" , project. name) ;
539+
540+ let result = clippy_project ( project, verbose) . await ?;
541+
542+ if result. success {
543+ println ! ( "✅ Clippy passed: {}" , project. name) ;
544+ if !result. warnings . is_empty ( ) {
545+ println ! ( "⚠️ {} clippy warnings found:" , result. warnings. len( ) ) ;
546+ for warning in & result. warnings [ ..std:: cmp:: min ( 3 , result. warnings . len ( ) ) ] {
547+ println ! ( " {}" , warning) ;
548+ }
549+ if result. warnings . len ( ) > 3 {
550+ println ! ( " ... and {} more warnings" , result. warnings. len( ) - 3 ) ;
551+ }
552+ }
553+ } else {
554+ println ! ( "❌ Clippy failed: {}" , project. name) ;
555+ // Always show error details for failures, regardless of verbose flag
556+ println ! ( " Error details:" ) ;
557+ for line in result. message . lines ( ) . take ( 20 ) {
558+ println ! ( " {}" , line) ;
559+ }
560+ if result. message . lines ( ) . count ( ) > 20 {
561+ println ! ( " ... (truncated, use --verbose for full output)" ) ;
562+ }
563+ }
564+
565+ summary. add_result ( & result) ;
566+ }
567+
568+ println ! ( "\n ═══════════════════════════════════════" ) ;
569+ println ! ( "📊 Clippy Summary:" ) ;
570+ println ! ( "✅ Clippy passed: {} projects" , summary. success) ;
571+ if summary. failed > 0 {
572+ println ! ( "❌ Clippy failed: {} projects" , summary. failed) ;
573+ }
574+ if summary. warnings > 0 {
575+ println ! ( "⚠️ Total clippy warnings: {}" , summary. warnings) ;
576+ }
577+
578+ Ok ( ( ) )
579+ }
580+
581+ async fn clippy_project ( project : & ProjectInfo , verbose : bool ) -> Result < TaskResult > {
582+ // Read the rust-toolchain.toml to determine which toolchain to use
583+ let toolchain_path = project. path . join ( "rust-toolchain.toml" ) ;
584+ let toolchain = if toolchain_path. exists ( ) {
585+ let content =
586+ fs:: read_to_string ( & toolchain_path) . context ( "Failed to read rust-toolchain.toml" ) ?;
587+
588+ // Parse the toolchain channel from TOML
589+ if content. contains ( "channel = \" esp\" " ) {
590+ "esp"
591+ } else if content. contains ( "channel = \" stable\" " ) {
592+ "stable"
593+ } else {
594+ "stable" // fallback
595+ }
596+ } else {
597+ "stable" // fallback if no toolchain file
598+ } ;
599+
600+ let output = TokioCommand :: new ( "rustup" )
601+ . arg ( "run" )
602+ . arg ( toolchain)
603+ . arg ( "cargo" )
604+ . arg ( "clippy" )
605+ . arg ( "--release" )
606+ . arg ( "--all-features" )
607+ . arg ( "--workspace" )
608+ . arg ( "--" )
609+ . arg ( "-D" )
610+ . arg ( "warnings" )
611+ . current_dir ( & project. path )
612+ . output ( )
613+ . await
614+ . context ( "Failed to run cargo clippy" ) ?;
615+
616+ let success = output. status . success ( ) ;
617+ let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
618+ let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
619+
620+ let mut warnings = Vec :: new ( ) ;
621+ let combined_output = format ! ( "{}{}" , stdout, stderr) ;
622+
623+ // Extract warnings from output
624+ for line in combined_output. lines ( ) {
625+ if verbose {
626+ println ! ( " {}" , line) ;
627+ }
628+ if line. contains ( "warning:" ) || line. contains ( "help:" ) {
629+ warnings. push ( line. to_string ( ) ) ;
630+ }
631+ }
632+
633+ let message = if success {
634+ "Clippy passed" . to_string ( )
635+ } else {
636+ format ! ( "{}{}" , stdout, stderr)
637+ } ;
638+
639+ Ok ( TaskResult {
640+ project : project. name . clone ( ) ,
641+ success,
642+ message,
643+ warnings,
644+ } )
645+ }
646+
519647async fn run_all_tasks ( projects : & [ ProjectInfo ] , keep_going : bool , verbose : bool ) -> Result < ( ) > {
520648 println ! ( "\n 🚀 Running ALL maintenance tasks" ) ;
521649 println ! ( "═══════════════════════════════" ) ;
0 commit comments