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