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