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