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