cargo_e/e_cargocommand_ext.rs
1use crate::e_command_builder::{CargoCommandBuilder, TerminalError};
2use crate::e_eventdispatcher::EventDispatcher;
3#[allow(unused_imports)]
4use cargo_metadata::Message;
5use nu_ansi_term::{Color, Style};
6#[cfg(feature = "uses_serde")]
7use serde_json;
8use std::collections::VecDeque;
9use std::io::{BufRead, BufReader, Read};
10use std::process::ExitStatus;
11use std::process::{Child, Command, Stdio};
12#[allow(unused_imports)]
13use std::sync::atomic::{AtomicUsize, Ordering};
14use std::sync::{Arc, Mutex};
15use std::time::{Duration, SystemTime};
16use std::{fmt, thread};
17// enum CaptureMode {
18// Filtering(DispatcherSet),
19// Passthrough { stdout: std::io::Stdout, stderr: std::io::Stderr },
20// }
21// struct DispatcherSet {
22// stdout: Option<Arc<EventDispatcher>>,
23// stderr: Option<Arc<EventDispatcher>>,
24// progress: Option<Arc<EventDispatcher>>,
25// stage: Option<Arc<EventDispatcher>>,
26// }
27
28/// CargoStats tracks counts for different cargo events and also stores the first occurrence times.
29#[derive(Debug, Default, Clone)]
30pub struct CargoStats {
31 pub is_comiler_target: bool,
32 pub start_time: Option<SystemTime>,
33 pub compiler_message_count: usize,
34 pub compiler_artifact_count: usize,
35 pub build_script_executed_count: usize,
36 pub build_finished_count: usize,
37 // Record the first occurrence of each stage.
38 pub compiler_message_time: Option<SystemTime>,
39 pub compiler_artifact_time: Option<SystemTime>,
40 pub build_script_executed_time: Option<SystemTime>,
41 pub build_finished_time: Option<SystemTime>,
42}
43
44#[derive(Clone)]
45pub struct CargoDiagnostic {
46 pub lineref: String,
47 pub level: String,
48 pub message: String,
49 pub error_code: Option<String>,
50 pub suggestion: Option<String>,
51 pub note: Option<String>,
52 pub help: Option<String>,
53 pub uses_color: bool,
54 pub diag_number: Option<usize>,
55 pub diag_num_padding: Option<usize>,
56}
57
58impl CargoDiagnostic {
59 pub fn print_short(&self) {
60 // Render the full Debug output
61 let full = format!("{:?}", self);
62 // Grab only the first line (or an empty string if somehow there isn't one)
63 let first = full.lines().next().unwrap_or("");
64 println!("{}", first);
65 }
66}
67impl CargoDiagnostic {
68 #[allow(clippy::too_many_arguments)]
69 pub fn new(
70 lineref: String,
71 level: String,
72 message: String,
73 error_code: Option<String>,
74 suggestion: Option<String>,
75 note: Option<String>,
76 help: Option<String>,
77 uses_color: bool,
78 diag_number: Option<usize>,
79 diag_num_padding: Option<usize>,
80 ) -> Self {
81 CargoDiagnostic {
82 lineref,
83 level,
84 message,
85 error_code,
86 suggestion,
87 note,
88 help,
89 uses_color,
90 diag_number,
91 diag_num_padding,
92 }
93 }
94
95 fn update_suggestion_with_lineno(
96 &self,
97 suggestion: &str,
98 file: String,
99 line_number: usize,
100 ) -> String {
101 // Regex to match line number in the suggestion (e.g., "79 | fn clean<S: AsRef<str>>(s: S) -> String {")
102 let suggestion_regex = regex::Regex::new(r"(?P<line>\d+)\s*\|\s*(.*)").unwrap();
103
104 // Iterate through suggestion lines and check line numbers
105 suggestion
106 .lines()
107 .filter_map(|line| {
108 let binding = line.replace(['|', '^'], "");
109 let cleaned_line = binding.trim();
110
111 // If the line becomes empty after removing | and ^, skip it
112 if cleaned_line.is_empty() {
113 return None; // Skip empty lines
114 }
115 if let Some(caps) = suggestion_regex.captures(line.trim()) {
116 let suggestion_line: usize = caps["line"].parse().unwrap_or(line_number); // If parsing fails, use the default line number
117 // Replace the line number if it doesn't match the diagnostic's line number
118 if suggestion_line != line_number {
119 return Some(format!(
120 "{}:{} | {}",
121 file,
122 suggestion_line, // Replace with the actual diagnostic line number
123 caps.get(2).map_or("", |m| m.as_str())
124 ));
125 } else {
126 // If the line number matches, return the original suggestion line without line number
127 return Some(
128 caps.get(2)
129 .map_or("".to_owned(), |m| m.as_str().to_string()),
130 );
131 }
132 }
133 Some(line.to_string())
134 })
135 .collect::<Vec<String>>()
136 .join("\n")
137 }
138}
139
140impl fmt::Debug for CargoDiagnostic {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 // Capitalize the first letter of the level
143 // let struct_name = self
144 // .level
145 // .chars()
146 // .next()
147 // .unwrap_or(' ')
148 // .to_uppercase()
149 // .to_string();
150
151 // Extract the file and line number from lineref (e.g., "cargo-e\src\e_command_builder.rs:79:8")
152 let lineref_regex = regex::Regex::new(r"(?P<file>.*):(?P<line>\d+):(?P<col>\d+)").unwrap();
153 let lineref_caps = lineref_regex.captures(&self.lineref);
154
155 let file = lineref_caps
156 .as_ref()
157 .and_then(|caps| caps.name("file").map(|m| m.as_str().to_string()))
158 .unwrap_or_else(|| "unknown file".to_string());
159 let line_number: usize = lineref_caps
160 .as_ref()
161 .and_then(|caps| caps.name("line").map(|m| m.as_str().parse().unwrap_or(0)))
162 .unwrap_or(0);
163
164 // Print the diagnostic number and level (e.g., W01: or E001:)
165 let diag_number = if let Some(dn) = &self.diag_number {
166 format!("{:0width$}", dn, width = self.diag_num_padding.unwrap_or(0))
167 // Apply padding to the number
168 } else {
169 String::new()
170 };
171
172 // Color the diagnostic number based on the level
173 let diag_number = match self.level.as_str() {
174 "warning" => format!("W{}:", diag_number),
175 "error" => format!("E{}:", diag_number),
176 "help" => format!("H{}:", diag_number),
177 _ => format!("N{}:", diag_number), // Default to green for notes
178 };
179 let diag_number_colored = match self.level.as_str() {
180 "warning" => Color::Yellow.paint(format!("W{}:", diag_number)),
181 "error" => Color::Red.paint(format!("E{}:", diag_number)),
182 "help" => Color::Purple.paint(format!("H{}:", diag_number)),
183 _ => Color::Green.paint(format!("N{}:", diag_number)), // Default to green for notes
184 };
185
186 // Print the struct name (capitalized level) and lineref
187 // Print the struct name (capitalized level) and lineref
188 // write!(f, "{}: ", struct_name)?;
189
190 // Always show lineref with underline if uses_color is true
191 if self.uses_color {
192 write!(f, "{} ", diag_number_colored)?;
193 let underlined_text = Style::new().underline().paint(&self.lineref).to_string();
194 write!(f, "\x1b[4m{}\x1b[0m ", underlined_text)?; // Apply underline using ANSI codes
195 let message = match self.level.as_str() {
196 "warning" => Color::Yellow.paint(&self.message).to_string(),
197 "error" => Color::Red.paint(&self.message).to_string(),
198 _ => Color::Green.paint(&self.message).to_string(),
199 };
200 write!(f, "{}: ", message)?;
201 } else {
202 write!(f, "{} ", diag_number)?;
203 write!(f, "{} ", self.lineref)?; // Plain lineref without color
204 write!(f, "{}: ", self.message)?;
205 }
206
207 // Print the message with color if necessary
208 // Print the suggestion if present, with color if uses_color is true
209 if let Some(suggestion) = &self.suggestion {
210 let suggestion = self.update_suggestion_with_lineno(suggestion, file, line_number);
211
212 let suggestion_text = if self.uses_color {
213 Color::Green.paint(suggestion).to_string()
214 } else {
215 suggestion.clone()
216 };
217 write!(f, "{} ", suggestion_text)?;
218 }
219
220 // Print the note if present, with color if uses_color is true
221 if let Some(note) = &self.note {
222 let note_text = if self.uses_color {
223 Color::Blue.paint(note).to_string()
224 } else {
225 note.clone()
226 };
227 write!(f, "\n{}", note_text)?;
228 }
229
230 // Print the help if present, with color if uses_color is true
231 if let Some(help) = &self.help {
232 let help_text = if self.uses_color {
233 Color::LightYellow.paint(help).to_string()
234 } else {
235 help.clone()
236 };
237 write!(f, "\n{} ", help_text)?;
238 }
239
240 // Finish the debug formatting
241 write!(f, "") // No further fields are needed
242 }
243}
244
245/// CargoProcessResult is returned when the cargo process completes.
246#[derive(Debug, Default, Clone)]
247pub struct CargoProcessResult {
248 pub target_name: String,
249 pub cmd: String,
250 pub args: Vec<String>,
251 pub pid: u32,
252 pub terminal_error: Option<TerminalError>,
253 pub exit_status: Option<ExitStatus>,
254 pub start_time: Option<SystemTime>,
255 pub build_finished_time: Option<SystemTime>,
256 pub end_time: Option<SystemTime>,
257 pub build_elapsed: Option<Duration>,
258 pub runtime_elapsed: Option<Duration>,
259 pub elapsed_time: Option<Duration>,
260 pub stats: CargoStats,
261 pub build_output_size: usize,
262 pub runtime_output_size: usize,
263 pub diagnostics: Vec<CargoDiagnostic>,
264 pub is_filter: bool,
265}
266
267impl CargoProcessResult {
268 /// Print every diagnostic in full detail.
269 pub fn print_exact(&self) {
270 if self.diagnostics.is_empty() {
271 return;
272 }
273 println!(
274 "--- Full Diagnostics for PID {} --- {}",
275 self.pid,
276 self.diagnostics.len()
277 );
278 for diag in &self.diagnostics {
279 println!("{:?}", diag);
280 }
281 }
282
283 /// Print warnings first, then errors, one‐line summary.
284 pub fn print_short(&self) {
285 if self.diagnostics.is_empty() {
286 return;
287 }
288 // let warnings: Vec<_> = self.diagnostics.iter()
289 // .filter(|d| d.level.eq("warning"))
290 // .collect();
291 let errors: Vec<_> = self
292 .diagnostics
293 .iter()
294 .filter(|d| d.level.eq("error"))
295 .collect();
296
297 // println!("--- Warnings ({} total) ---", warnings.len());
298 // for d in warnings {
299 // d.print_short();
300 // }
301
302 println!("--- Errors ({} total) ---", errors.len());
303 for d in errors {
304 d.print_short();
305 }
306 }
307
308 /// Print a compact, zero‑padded, numbered list of *all* diagnostics.
309 pub fn print_compact(&self) {
310 if self.diagnostics.is_empty() {
311 return;
312 }
313 let total = self.diagnostics.len();
314 println!("--- All Diagnostics ({} total) ---", total);
315 for diag in self.diagnostics.iter() {
316 println!(
317 "{} {}: {} {}",
318 diag.level,
319 diag.diag_number.unwrap_or_default(),
320 diag.lineref,
321 diag.message.trim()
322 );
323 }
324 }
325}
326
327/// CargoProcessHandle holds the cargo process and related state.
328#[derive(Debug)]
329pub struct CargoProcessHandle {
330 pub child: Child,
331 pub result: CargoProcessResult,
332 pub pid: u32,
333 pub requested_exit: bool,
334 pub stdout_handle: thread::JoinHandle<()>,
335 pub stderr_handle: thread::JoinHandle<()>,
336 pub start_time: SystemTime,
337 pub stats: Arc<Mutex<CargoStats>>,
338 pub stdout_dispatcher: Option<Arc<EventDispatcher>>,
339 pub stderr_dispatcher: Option<Arc<EventDispatcher>>,
340 pub progress_dispatcher: Option<Arc<EventDispatcher>>,
341 pub stage_dispatcher: Option<Arc<EventDispatcher>>,
342 pub estimate_bytes: Option<usize>,
343 // Separate progress counters for build and runtime output.
344 pub build_progress_counter: Arc<AtomicUsize>,
345 pub runtime_progress_counter: Arc<AtomicUsize>,
346 pub terminal_error_flag: Arc<Mutex<TerminalError>>,
347 pub diagnostics: Arc<Mutex<Vec<CargoDiagnostic>>>,
348 pub is_filter: bool,
349}
350
351impl CargoProcessHandle {
352 pub fn print_results(result: &CargoProcessResult) {
353 let start_time = result.start_time.unwrap_or(SystemTime::now());
354 println!("-------------------------------------------------");
355 println!("Process started at: {:?}", result.start_time);
356 if let Some(build_time) = result.build_finished_time {
357 println!("Build phase ended at: {:?}", build_time);
358 println!(
359 "Build phase elapsed: {}",
360 crate::e_fmt::format_duration(
361 build_time
362 .duration_since(start_time)
363 .unwrap_or_else(|_| Duration::new(0, 0))
364 )
365 );
366 } else {
367 println!("No BuildFinished timestamp recorded.");
368 }
369 println!("Process ended at: {:?}", result.end_time);
370 if let Some(runtime_dur) = result.runtime_elapsed {
371 println!(
372 "Runtime phase elapsed: {}",
373 crate::e_fmt::format_duration(runtime_dur)
374 );
375 }
376 if let Some(build_dur) = result.build_elapsed {
377 println!(
378 "Build phase elapsed: {}",
379 crate::e_fmt::format_duration(build_dur)
380 );
381 }
382 if let Some(total_elapsed) = result
383 .end_time
384 .and_then(|end| end.duration_since(start_time).ok())
385 {
386 println!(
387 "Total elapsed time: {}",
388 crate::e_fmt::format_duration(total_elapsed)
389 );
390 } else {
391 println!("No total elapsed time available.");
392 }
393 println!(
394 "Build output size: {} ({} bytes)",
395 crate::e_fmt::format_bytes(result.build_output_size),
396 result.build_output_size
397 );
398 println!(
399 "Runtime output size: {} ({} bytes)",
400 crate::e_fmt::format_bytes(result.runtime_output_size),
401 result.runtime_output_size
402 );
403 println!("-------------------------------------------------");
404 }
405
406 /// Kill the cargo process if needed.
407 pub fn kill(&mut self) -> std::io::Result<()> {
408 self.child.kill()
409 }
410 pub fn pid(&self) -> u32 {
411 self.pid
412 }
413
414 // pub fn wait(&mut self) -> std::io::Result<CargoProcessResult> {
415 // // Lock the instance since `self` is an `Arc`
416 // // let mut cargo_process_handle = self.lock().unwrap(); // `lock()` returns a mutable reference
417
418 // // Call wait on the child process
419 // let status = self.child.wait()?; // Call wait on the child process
420
421 // println!("Cargo process finished with status: {:?}", status);
422
423 // let end_time = SystemTime::now();
424
425 // // Retrieve the statistics from the process handle
426 // let stats = Arc::try_unwrap(self.stats.clone())
427 // .map(|mutex| mutex.into_inner().unwrap())
428 // .unwrap_or_else(|arc| (*arc.lock().unwrap()).clone());
429
430 // let build_out = self.build_progress_counter.load(Ordering::Relaxed);
431 // let runtime_out = self.runtime_progress_counter.load(Ordering::Relaxed);
432
433 // // Calculate phase durations if build_finished_time is recorded
434 // let (build_elapsed, runtime_elapsed) = if let Some(build_finished) = stats.build_finished_time {
435 // let build_dur = build_finished.duration_since(self.start_time)
436 // .unwrap_or_else(|_| Duration::new(0, 0));
437 // let runtime_dur = end_time.duration_since(build_finished)
438 // .unwrap_or_else(|_| Duration::new(0, 0));
439 // (Some(build_dur), Some(runtime_dur))
440 // } else {
441 // (None, None)
442 // };
443
444 // self.result.exit_status = Some(status);
445 // self.result.end_time = Some(end_time);
446 // self.result.build_output_size = self.build_progress_counter.load(Ordering::Relaxed);
447 // self.result.runtime_output_size = self.runtime_progress_counter.load(Ordering::Relaxed);
448
449 // Ok(self.result.clone())
450 // // Return the final process result
451 // // Ok(CargoProcessResult {
452 // // pid: self.pid,
453 // // exit_status: Some(status),
454 // // start_time: Some(self.start_time),
455 // // build_finished_time: stats.build_finished_time,
456 // // end_time: Some(end_time),
457 // // build_elapsed,
458 // // runtime_elapsed,
459 // // stats,
460 // // build_output_size: build_out,
461 // // runtime_output_size: runtime_out,
462 // // })
463 // }
464
465 // pub fn wait(self: Arc<Self>) -> std::io::Result<CargoProcessResult> {
466 // let mut global = GLOBAL_CHILDREN.lock().unwrap();
467
468 // // Lock and access the CargoProcessHandle inside the Mutex
469 // if let Some(cargo_process_handle) = global.iter_mut().find(|handle| {
470 // handle.lock().unwrap().pid == self.pid // Compare the pid to find the correct handle
471 // }) {
472 // let mut cargo_process_handle = cargo_process_handle.lock().unwrap(); // Mutably borrow the process handle
473
474 // let status = cargo_process_handle.child.wait()?; // Call wait on the child process
475
476 // println!("Cargo process finished with status: {:?}", status);
477
478 // let end_time = SystemTime::now();
479
480 // // Retrieve the statistics from the process handle
481 // let stats = Arc::try_unwrap(cargo_process_handle.stats.clone())
482 // .map(|mutex| mutex.into_inner().unwrap())
483 // .unwrap_or_else(|arc| (*arc.lock().unwrap()).clone());
484
485 // let build_out = cargo_process_handle.build_progress_counter.load(Ordering::Relaxed);
486 // let runtime_out = cargo_process_handle.runtime_progress_counter.load(Ordering::Relaxed);
487
488 // // Calculate phase durations if build_finished_time is recorded
489 // let (build_elapsed, runtime_elapsed) = if let Some(build_finished) = stats.build_finished_time {
490 // let build_dur = build_finished.duration_since(cargo_process_handle.start_time)
491 // .unwrap_or_else(|_| Duration::new(0, 0));
492 // let runtime_dur = end_time.duration_since(build_finished)
493 // .unwrap_or_else(|_| Duration::new(0, 0));
494 // (Some(build_dur), Some(runtime_dur))
495 // } else {
496 // (None, None)
497 // };
498
499 // // Return the final process result
500 // Ok(CargoProcessResult {
501 // exit_status: status,
502 // start_time: cargo_process_handle.start_time,
503 // build_finished_time: stats.build_finished_time,
504 // end_time,
505 // build_elapsed,
506 // runtime_elapsed,
507 // stats,
508 // build_output_size: build_out,
509 // runtime_output_size: runtime_out,
510 // })
511 // } else {
512 // Err(std::io::Error::new(std::io::ErrorKind::NotFound, "Process handle not found").into())
513 // }
514 // }
515
516 // Wait for the process and output threads to finish.
517 // Computes elapsed times for the build phase and runtime phase, and returns a CargoProcessResult.
518 // pub fn wait(mut self) -> std::io::Result<CargoProcessResult> {
519 // let status = self.child.wait()?;
520 // println!("Cargo process finished with status: {:?}", status);
521
522 // self.stdout_handle.join().expect("stdout thread panicked");
523 // self.stderr_handle.join().expect("stderr thread panicked");
524
525 // let end_time = SystemTime::now();
526
527 // // Retrieve the statistics.
528 // let stats = Arc::try_unwrap(self.stats)
529 // .map(|mutex| mutex.into_inner().unwrap())
530 // .unwrap_or_else(|arc| (*arc.lock().unwrap()).clone());
531
532 // let build_out = self.build_progress_counter.load(Ordering::Relaxed);
533 // let runtime_out = self.runtime_progress_counter.load(Ordering::Relaxed);
534
535 // // Calculate phase durations if build_finished_time is recorded.
536 // let (build_elapsed, runtime_elapsed) = if let Some(build_finished) = stats.build_finished_time {
537 // let build_dur = build_finished.duration_since(self.start_time).unwrap_or_else(|_| Duration::new(0, 0));
538 // let runtime_dur = end_time.duration_since(build_finished).unwrap_or_else(|_| Duration::new(0, 0));
539 // (Some(build_dur), Some(runtime_dur))
540 // } else {
541 // (None, None)
542 // };
543
544 // Ok(CargoProcessResult {
545 // exit_status: status,
546 // start_time: self.start_time,
547 // build_finished_time: stats.build_finished_time,
548 // end_time,
549 // build_elapsed,
550 // runtime_elapsed,
551 // stats,
552 // build_output_size: build_out,
553 // runtime_output_size: runtime_out,
554 // })
555 // }
556
557 /// Returns a formatted status string.
558 /// If `system` is provided, CPU/memory and runtime info is displayed on the right.
559 /// Otherwise, only the start time is shown.
560 pub fn format_status(&self, process: Option<&sysinfo::Process>) -> String {
561 // Ensure the start time is available.
562 let start_time = self
563 .result
564 .start_time
565 .expect("start_time should be initialized");
566 let start_dt: chrono::DateTime<chrono::Local> = start_time.into();
567 let start_str = start_dt.format("%H:%M:%S").to_string();
568 // Use ANSI coloring for the left display.
569 let colored_start = nu_ansi_term::Color::Green.paint(&start_str).to_string();
570
571 if let Some(process) = process {
572 // if let Some(process) = system.process((self.pid as usize).into()) {
573 let cpu_usage = process.cpu_usage();
574 let mem_kb = process.memory();
575 let mem_human = if mem_kb >= 1024 {
576 format!("{:.2} MB", mem_kb as f64 / 1024.0)
577 } else {
578 format!("{} KB", mem_kb)
579 };
580
581 let now = SystemTime::now();
582 let runtime_duration = now.duration_since(start_time).unwrap();
583 let runtime_str = crate::e_fmt::format_duration(runtime_duration);
584
585 let left_display = format!(
586 "{} | CPU: {:.2}% | Mem: {}",
587 colored_start, cpu_usage, mem_human
588 );
589 // Use plain text for length calculations.
590 let left_plain = format!(
591 "{} | CPU: {:.2}% | Mem: {}",
592 start_str, cpu_usage, mem_human
593 );
594
595 // Get terminal width.
596 #[cfg(feature = "tui")]
597 let (cols, _) = crossterm::terminal::size().unwrap_or((80, 20));
598 #[cfg(not(feature = "tui"))]
599 let (cols, _) = (80, 20);
600 let total_width = cols as usize;
601
602 // Format the runtime info with underlining.
603 let right_display = nu_ansi_term::Style::new()
604 .reset_before_style()
605 .underline()
606 .paint(&runtime_str)
607 .to_string();
608 let left_len = left_plain.len();
609 let right_len = runtime_str.len();
610 let padding = if total_width > left_len + right_len {
611 total_width - left_len - right_len
612 } else {
613 1
614 };
615
616 let ret = format!("{}{}{}", left_display, " ".repeat(padding), right_display);
617 if ret.trim().is_empty() {
618 String::from("No output available")
619 } else {
620 ret
621 }
622 } else {
623 // return format!("Process {} not found",(self.pid as usize));
624 String::new()
625 }
626 // } else {
627 // // If system monitoring is disabled, just return the start time.
628 // colored_start
629 // }
630 }
631}
632
633/// Extension trait to add cargo-specific capture capabilities to Command.
634pub trait CargoCommandExt {
635 fn spawn_cargo_capture(
636 &mut self,
637 builder: Arc<CargoCommandBuilder>,
638 stdout_dispatcher: Option<Arc<EventDispatcher>>,
639 stderr_dispatcher: Option<Arc<EventDispatcher>>,
640 progress_dispatcher: Option<Arc<EventDispatcher>>,
641 stage_dispatcher: Option<Arc<EventDispatcher>>,
642 estimate_bytes: Option<usize>,
643 ) -> CargoProcessHandle;
644 fn spawn_cargo_passthrough(&mut self, builder: Arc<CargoCommandBuilder>) -> CargoProcessHandle;
645}
646
647impl CargoCommandExt for Command {
648 fn spawn_cargo_passthrough(&mut self, builder: Arc<CargoCommandBuilder>) -> CargoProcessHandle {
649 // Spawn the child process without redirecting stdout and stderr
650 let child = self.spawn().unwrap_or_else(|_| {
651 panic!(
652 "Failed to spawn cargo process {:?} {:?}",
653 &builder.alternate_cmd, builder.args
654 )
655 });
656
657 let pid = child.id();
658 let start_time = SystemTime::now();
659 let diagnostics = Arc::clone(&builder.diagnostics);
660 let s = CargoStats {
661 is_comiler_target: builder.is_compiler_target(), // Ensure this field is now valid
662 start_time: Some(start_time),
663 build_finished_time: Some(start_time),
664 ..Default::default()
665 };
666 let stats = Arc::new(Mutex::new(s.clone()));
667 // Try to take ownership of the Vec<CargoDiagnostic> out of the Arc.
668
669 let (cmd, args) = builder.injected_args();
670 // Create the CargoProcessHandle
671 let result = CargoProcessResult {
672 target_name: builder.target_name.clone(),
673 cmd: cmd,
674 args: args,
675 pid,
676 terminal_error: None,
677 exit_status: None,
678 start_time: Some(start_time),
679 build_finished_time: None,
680 end_time: None,
681 elapsed_time: None,
682 build_elapsed: None,
683 runtime_elapsed: None,
684 stats: s, // CargoStats::default(),
685 build_output_size: 0,
686 runtime_output_size: 0,
687 diagnostics: Vec::new(),
688 is_filter: builder.is_filter,
689 };
690
691 // Return the CargoProcessHandle that owns the child process
692 CargoProcessHandle {
693 child, // The child process is now owned by the handle
694 result, // The result contains information about the process
695 pid, // The PID of the process
696 stdout_handle: thread::spawn(move || {
697 // This thread is now unnecessary if we are not capturing anything
698 // We can leave it empty or remove it altogether
699 }),
700 stderr_handle: thread::spawn(move || {
701 // This thread is also unnecessary if we are not capturing anything
702 }),
703 start_time,
704 stats,
705 stdout_dispatcher: None, // No dispatcher is needed
706 stderr_dispatcher: None, // No dispatcher is needed
707 progress_dispatcher: None, // No dispatcher is needed
708 stage_dispatcher: None, // No dispatcher is needed
709 estimate_bytes: None,
710 build_progress_counter: Arc::new(AtomicUsize::new(0)),
711 runtime_progress_counter: Arc::new(AtomicUsize::new(0)),
712 requested_exit: false,
713 terminal_error_flag: Arc::new(Mutex::new(TerminalError::NoError)),
714 diagnostics,
715 is_filter: builder.is_filter,
716 }
717 }
718
719 fn spawn_cargo_capture(
720 &mut self,
721 builder: Arc<CargoCommandBuilder>,
722 stdout_dispatcher: Option<Arc<EventDispatcher>>,
723 stderr_dispatcher: Option<Arc<EventDispatcher>>,
724 progress_dispatcher: Option<Arc<EventDispatcher>>,
725 stage_dispatcher: Option<Arc<EventDispatcher>>,
726 estimate_bytes: Option<usize>,
727 ) -> CargoProcessHandle {
728 self.stdout(Stdio::piped()).stderr(Stdio::piped());
729 // println!("Spawning cargo process with capture {:?}",self);
730 let mut child = self.spawn().expect("Failed to spawn cargo process");
731 let pid = child.id();
732 let start_time = SystemTime::now();
733 let diagnostics = Arc::new(Mutex::new(Vec::<CargoDiagnostic>::new()));
734 let s = CargoStats {
735 is_comiler_target: builder.is_compiler_target(),
736 start_time: Some(start_time),
737 ..Default::default()
738 };
739 let stats = Arc::new(Mutex::new(s));
740
741 // Two separate counters: one for build output and one for runtime output.
742 let stderr_compiler_msg = Arc::new(Mutex::new(VecDeque::<String>::new()));
743 let build_progress_counter = Arc::new(AtomicUsize::new(0));
744 let runtime_progress_counter = Arc::new(AtomicUsize::new(0));
745
746 // Clone dispatchers and counters for use in threads.
747 let _stdout_disp_clone = stdout_dispatcher.clone();
748 let progress_disp_clone_stdout = progress_dispatcher.clone();
749 let stage_disp_clone = stage_dispatcher.clone();
750
751 let stats_stdout_clone = Arc::clone(&stats);
752 let stats_stderr_clone = Arc::clone(&stats);
753 let _build_counter_stdout = Arc::clone(&build_progress_counter);
754 let _runtime_counter_stdout = Arc::clone(&runtime_progress_counter);
755
756 // Spawn a thread to process stdout.
757 let _stderr_compiler_msg_clone = Arc::clone(&stderr_compiler_msg);
758 let stdout = child.stdout.take().expect("Failed to capture stdout");
759 // println!("{}: Capturing stdout", pid);
760 let stdout_handle = thread::spawn(move || {
761 let stdout_reader = BufReader::new(stdout);
762 // This flag marks whether we are still in the build phase.
763 #[allow(unused_mut)]
764 let mut _in_build_phase = true;
765 let stdout_buffer = Arc::new(Mutex::new(Vec::<String>::new()));
766 let buf = Arc::clone(&stdout_buffer);
767 {
768 for line in stdout_reader.lines().map(|line| line) {
769 if let Ok(line) = line {
770 // println!("{}: {}", pid, line);
771 // Try to parse the line as a JSON cargo message.
772
773 #[cfg(not(feature = "uses_serde"))]
774 println!("{}", line);
775 #[cfg(feature = "uses_serde")]
776 match serde_json::from_str::<Message>(&line) {
777 Ok(msg) => {
778 // let msg_str = format!("{:?}", msg);
779 // if let Some(ref disp) = stdout_disp_clone {
780 // disp.dispatch(&msg_str);
781 // }
782 // Add message length to the appropriate counter.
783 // if in_build_phase {
784 // build_counter_stdout.fetch_add(msg_str.len(), Ordering::Relaxed);
785 // } else {
786 // runtime_counter_stdout.fetch_add(msg_str.len(), Ordering::Relaxed);
787 // }
788 if let Some(total) = estimate_bytes {
789 let current = if _in_build_phase {
790 _build_counter_stdout.load(Ordering::Relaxed)
791 } else {
792 _runtime_counter_stdout.load(Ordering::Relaxed)
793 };
794 let progress = (current as f64 / total as f64) * 100.0;
795 if let Some(ref pd) = progress_disp_clone_stdout {
796 pd.dispatch(
797 &format!("Progress: {:.2}%", progress),
798 stats_stdout_clone.clone(),
799 );
800 }
801 }
802
803 let now = SystemTime::now();
804 // Process known cargo message variants.
805 match msg {
806 Message::BuildFinished(_) => {
807 // Mark the end of the build phase.
808 if _in_build_phase {
809 _in_build_phase = false;
810 let mut s = stats_stdout_clone.lock().unwrap();
811 s.build_finished_count += 1;
812 s.build_finished_time.get_or_insert(now);
813 drop(s);
814 // self.result.build_finished_time = Some(now);
815 if let Some(ref sd) = stage_disp_clone {
816 sd.dispatch(
817 &format!(
818 "Stage: BuildFinished occurred at {:?}",
819 now
820 ),
821 stats_stdout_clone.clone(),
822 );
823 }
824 if let Some(ref sd) = stage_disp_clone {
825 sd.dispatch(
826 "Stage: Switching to runtime passthrough",
827 stats_stdout_clone.clone(),
828 );
829 }
830 }
831 }
832 Message::CompilerMessage(msg) => {
833 // println!("parsed{}: {:?}", pid, msg);
834 let mut s = stats_stdout_clone.lock().unwrap();
835 s.compiler_message_count += 1;
836 if s.compiler_message_time.is_none() {
837 s.compiler_message_time = Some(now);
838 drop(s);
839 if let Some(ref sd) = stage_disp_clone {
840 sd.dispatch(
841 &format!(
842 "Stage: CompilerMessage occurred at {:?}",
843 now
844 ),
845 stats_stdout_clone.clone(),
846 );
847 }
848 }
849 let mut msg_vec =
850 _stderr_compiler_msg_clone.lock().unwrap();
851 msg_vec.push_back(format!(
852 "{}\n\n",
853 msg.message.rendered.unwrap_or_default()
854 ));
855 // let mut diags = diagnostics.lock().unwrap();
856 // let diag = crate::e_eventdispatcher::convert_message_to_diagnostic(msg, &msg_str);
857 // diags.push(diag.clone());
858 // if let Some(ref sd) = stage_disp_clone {
859 // sd.dispatch(&format!("Stage: Diagnostic occurred at {:?}", now));
860 // }
861 }
862 Message::CompilerArtifact(_) => {
863 let mut s = stats_stdout_clone.lock().unwrap();
864 s.compiler_artifact_count += 1;
865 if s.compiler_artifact_time.is_none() {
866 s.compiler_artifact_time = Some(now);
867 drop(s);
868 if let Some(ref sd) = stage_disp_clone {
869 sd.dispatch(
870 &format!(
871 "Stage: CompilerArtifact occurred at {:?}",
872 now
873 ),
874 stats_stdout_clone.clone(),
875 );
876 }
877 }
878 }
879 Message::BuildScriptExecuted(_) => {
880 let mut s = stats_stdout_clone.lock().unwrap();
881 s.build_script_executed_count += 1;
882 if s.build_script_executed_time.is_none() {
883 s.build_script_executed_time = Some(now);
884 drop(s);
885 if let Some(ref sd) = stage_disp_clone {
886 sd.dispatch(
887 &format!(
888 "Stage: BuildScriptExecuted occurred at {:?}",
889 now
890 ),
891 stats_stdout_clone.clone(),
892 );
893 }
894 }
895 }
896 _ => {}
897 }
898 }
899 Err(_err) => {
900 // println!("ERROR {} {}: {}",_err, pid, line);
901 // If JSON parsing fails, assume this is plain runtime output.
902 // If still in build phase, we assume the build phase has ended.
903 if _in_build_phase {
904 _in_build_phase = false;
905 let now = SystemTime::now();
906 let mut s = stats_stdout_clone.lock().unwrap();
907 s.build_finished_count += 1;
908 s.build_finished_time.get_or_insert(now);
909 drop(s);
910 if let Some(ref sd) = stage_disp_clone {
911 sd.dispatch(
912 &format!(
913 "Stage: BuildFinished (assumed) occurred at {:?}",
914 now
915 ),
916 stats_stdout_clone.clone(),
917 );
918 }
919 buf.lock().unwrap().push(line.to_string());
920 } else {
921 // build is done: first flush anything we buffered
922 let mut b = buf.lock().unwrap();
923 for l in b.drain(..) {
924 println!("{}", l);
925 }
926 // then print live
927 println!("{}", line);
928 }
929 if let Some(ref disp) = _stdout_disp_clone {
930 disp.dispatch(&line, stats_stdout_clone.clone());
931 }
932 // Print the runtime output.
933 // println!("{}: {}", pid, line);
934 if line.contains("not a terminal") {
935 println!(
936 "{}NOT A TERMINAL - MARK AND RUN AGAIN: {}",
937 pid, line
938 );
939 }
940 _runtime_counter_stdout.fetch_add(line.len(), Ordering::Relaxed);
941 if let Some(total) = estimate_bytes {
942 let current = _runtime_counter_stdout.load(Ordering::Relaxed);
943 let progress = (current as f64 / total as f64) * 100.0;
944 if let Some(ref pd) = progress_disp_clone_stdout {
945 pd.dispatch(
946 &format!("Progress: {:.2}%", progress),
947 stats_stdout_clone.clone(),
948 );
949 }
950 }
951 }
952 }
953 }
954 }
955 }
956 }); // End of stdout thread
957
958 let tflag = TerminalError::NoError;
959 // Create a flag to indicate if the process is a terminal process.
960 let terminal_flag = Arc::new(Mutex::new(TerminalError::NoError));
961 // Spawn a thread to capture stderr.
962 let stderr = child.stderr.take().expect("Failed to capture stderr");
963 let stderr_disp_clone = stderr_dispatcher.clone();
964 // let terminal_flag_clone = Arc::clone(&terminal_flag);
965 // let build_counter_stderr = Arc::clone(&build_progress_counter);
966 // let runtime_counter_stderr = Arc::clone(&runtime_progress_counter);
967 // let progress_disp_clone_stderr = progress_dispatcher.clone();
968 let escape_sequence = "\u{1b}[1m\u{1b}[32m";
969 // let diagnostics_clone = Arc::clone(&diagnostics);
970 let stderr_compiler_msg_clone = Arc::clone(&stderr_compiler_msg);
971 // println!("{}: Capturing stderr", pid);
972 let mut stderr_reader = BufReader::new(stderr);
973 let stderr_handle = thread::spawn(move || {
974 // let mut msg_vec = stderr_compiler_msg_clone.lock().unwrap();
975 loop {
976 // println!("looping stderr thread {}", pid);
977 // Lock the deque and pop all messages available in a while loop
978 while let Some(message) = {
979 let mut guard = match stderr_compiler_msg_clone.lock() {
980 Ok(guard) => guard,
981 Err(err) => {
982 eprintln!("Failed to lock stderr_compiler_msg_clone: {}", err);
983 return; // Exit the function or loop in case of an error
984 }
985 };
986 guard.pop_front()
987 } {
988 for line in message.lines().map(|line| line) {
989 if let Some(ref disp) = stderr_disp_clone {
990 // Dispatch the line and receive the Vec<Option<CallbackResponse>>.
991 let responses = disp.dispatch(line, stats_stderr_clone.clone());
992
993 // Iterate over the responses.
994 for ret in responses {
995 if let Some(response) = ret {
996 if response.terminal_status == Some(TerminalError::NoTerminal) {
997 // If the response indicates a terminal error, set the flag.
998 println!("{} IS A TERMINAL PROCESS - {}", pid, line);
999 } else if response.terminal_status
1000 == Some(TerminalError::NoError)
1001 {
1002 // If the response indicates no terminal error, set the flag to NoError.
1003 } else if response.terminal_status
1004 == Some(TerminalError::NoTerminal)
1005 {
1006 // If the response indicates not a terminal, set the flag to NoTerminal.
1007 println!("{} IS A TERMINAL PROCESS - {}", pid, line);
1008 }
1009 // if let Some(ref msg) = response.message {
1010 // println!("DISPATCH RESULT {} {}", pid, msg);
1011 // // }
1012 // let diag = crate::e_eventdispatcher::convert_response_to_diagnostic(response, &line);
1013 // // let mut diags = diagnostics_clone.lock().unwrap();
1014
1015 // let in_multiline = disp.callbacks
1016 // .lock().unwrap()
1017 // .iter()
1018 // .any(|cb| cb.is_reading_multiline.load(Ordering::Relaxed));
1019
1020 // if !in_multiline {
1021 // // start of a new diagnostic
1022 // diags.push(diag);
1023 // } else {
1024 // // continuation → child of the last diagnostic
1025 // if let Some(parent) = diags.last_mut() {
1026 // parent.children.push(diag);
1027 // } else {
1028 // // no parent yet (unlikely), just push
1029 // diags.push(diag);
1030 // }
1031 // }
1032 }
1033 }
1034 }
1035 }
1036 }
1037 // Sleep briefly if no messages are available to avoid busy waiting
1038 thread::sleep(Duration::from_millis(100));
1039 // If still in build phase, add to the build counter.
1040 // break;
1041
1042 // println!("{}: dave stderr", pid);
1043 // let mut flag = terminal_flag_clone.lock().unwrap();
1044 for line in stderr_reader.by_ref().lines() {
1045 // println!("SPAWN{}: {:?}", pid, line);
1046 if let Ok(line) = line {
1047 // if line.contains("IO(Custom { kind: NotConnected") {
1048 // println!("{} IS A TERMINAL PROCESS - {}", pid,line);
1049 // continue;
1050 // }
1051 let line = if line.starts_with(escape_sequence) {
1052 // If the line starts with the escape sequence, preserve it and remove leading spaces
1053 let rest_of_line = &line[escape_sequence.len()..]; // Get the part of the line after the escape sequence
1054 format!("{}{}", escape_sequence, rest_of_line.trim_start())
1055 // Reassemble the escape sequence and the trimmed text
1056 } else {
1057 line // If it doesn't start with the escape sequence, leave it unchanged
1058 };
1059 if let Some(ref disp) = stderr_disp_clone {
1060 // Dispatch the line and receive the Vec<Option<CallbackResponse>>.
1061 let responses = disp.dispatch(&line, stats_stderr_clone.clone());
1062 let mut has_match = false;
1063 // Iterate over the responses.
1064 for ret in responses {
1065 if let Some(_response) = ret {
1066 has_match = true;
1067 // if response.terminal_status == Some(TerminalError::NoTerminal) {
1068 // // If the response indicates a terminal error, set the flag.
1069 // *flag = TerminalError::NoTerminal;
1070 // println!("{} IS A TERMINAL PROCESS - {}", pid, line);
1071 // } else if response.terminal_status == Some(TerminalError::NoError) {
1072 // // If the response indicates no terminal error, set the flag to NoError.
1073 // *flag = TerminalError::NoError;
1074 // } else if response.terminal_status == Some(TerminalError::NoTerminal) {
1075 // // If the response indicates not a terminal, set the flag to NoTerminal.
1076 // *flag = TerminalError::NoTerminal;
1077 // println!("{} IS A TERMINAL PROCESS - {}", pid, line);
1078 // }
1079 // if let Some(ref msg) = response.message {
1080 // println!("DISPATCH RESULT {} {}", pid, msg);
1081 // }
1082 // let diag = crate::e_eventdispatcher::convert_response_to_diagnostic(response, &line);
1083 // // let mut diags = diagnostics_clone.lock().unwrap();
1084
1085 // let in_multiline = disp.callbacks
1086 // .lock().unwrap()
1087 // .iter()
1088 // .any(|cb| cb.is_reading_multiline.load(Ordering::Relaxed));
1089
1090 // if !in_multiline {
1091 // // start of a new diagnostic
1092 // diags.push(diag);
1093 // } else {
1094 // // continuation → child of the last diagnostic
1095 // if let Some(parent) = diags.last_mut() {
1096 // parent.children.push(diag);
1097 // } else {
1098 // // no parent yet (unlikely), just push
1099 // diags.push(diag);
1100 // }
1101 // }
1102 }
1103 }
1104 if !has_match && !line.trim().is_empty() && !line.eq("...") {
1105 // If the line doesn't match any pattern, print it as is.
1106 println!("{}", line);
1107 }
1108 } else {
1109 println!("ALLLINES {}", line.trim()); //all lines
1110 }
1111 // if let Some(ref disp) = stderr_disp_clone {
1112 // if let Some(ret) = disp.dispatch(&line) {
1113 // if let Some(ref msg) = ret.message {
1114 // println!("DISPATCH RESULT {} {}", pid, msg);
1115 // }
1116 // }
1117 // }
1118 // // Here, we assume stderr is less structured. We add its length to runtime counter.
1119 // runtime_counter_stderr.fetch_add(line.len(), Ordering::Relaxed);
1120 // if let Some(total) = estimate_bytes {
1121 // let current = runtime_counter_stderr.load(Ordering::Relaxed);
1122 // let progress = (current as f64 / total as f64) * 100.0;
1123 // if let Some(ref pd) = progress_disp_clone_stderr {
1124 // pd.dispatch(&format!("Progress: {:.2}%", progress));
1125 // }
1126 // }
1127 }
1128 }
1129 // println!("{}: dave stderr end", pid);
1130 } //loop
1131 }); // End of stderr thread
1132
1133 let final_diagnostics = {
1134 let diag_lock = diagnostics.lock().unwrap();
1135 diag_lock.clone()
1136 };
1137 let pid = child.id();
1138 let (cmd, args) = builder.injected_args();
1139 let result = CargoProcessResult {
1140 target_name: builder.target_name.clone(),
1141 cmd: cmd,
1142 args: args,
1143 pid,
1144 exit_status: None,
1145 start_time: Some(start_time),
1146 build_finished_time: None,
1147 end_time: None,
1148 elapsed_time: None,
1149 build_elapsed: None,
1150 runtime_elapsed: None,
1151 stats: stats.lock().unwrap().clone(),
1152 build_output_size: 0,
1153 runtime_output_size: 0,
1154 terminal_error: Some(tflag),
1155 diagnostics: final_diagnostics,
1156 is_filter: builder.is_filter,
1157 };
1158 CargoProcessHandle {
1159 child,
1160 result,
1161 pid,
1162 stdout_handle,
1163 stderr_handle,
1164 start_time,
1165 stats,
1166 stdout_dispatcher,
1167 stderr_dispatcher,
1168 progress_dispatcher,
1169 stage_dispatcher,
1170 estimate_bytes,
1171 build_progress_counter,
1172 runtime_progress_counter,
1173 requested_exit: false,
1174 terminal_error_flag: terminal_flag,
1175 diagnostics,
1176 is_filter: builder.is_filter,
1177 }
1178 }
1179}