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