cargo_e/e_processmanager.rs
1// src/e_process_manager.rs
2
3use crate::e_cargocommand_ext::{CargoProcessHandle, CargoProcessResult};
4use crate::e_command_builder::TerminalError;
5use crate::e_target::CargoTarget;
6use crate::Cli;
7use chrono::Local;
8use std::collections::HashMap;
9use std::process::ExitStatus;
10use std::sync::atomic::AtomicUsize;
11use std::sync::mpsc::{self, Receiver, Sender};
12use std::sync::{Arc, Mutex};
13use std::thread::{self, sleep};
14use std::time::{Duration, SystemTime};
15use sysinfo::System;
16// use std::io::Write;
17#[cfg(feature = "tui")]
18use crossterm::{
19 cursor, execute,
20 terminal::{Clear, ClearType},
21};
22use std::io::{self, Write};
23use std::sync::atomic::Ordering;
24#[cfg(unix)]
25use {
26 nix::sys::signal::{kill as nix_kill, Signal},
27 nix::unistd::Pid,
28 std::os::unix::process::ExitStatusExt,
29};
30impl ProcessObserver for ProcessManager {
31 fn on_spawn(&self, pid: u32, handle: CargoProcessHandle) {
32 self.processes
33 .lock()
34 .unwrap()
35 .insert(pid, Arc::new(Mutex::new(handle)));
36 }
37 // let pid = handle.lock().unwrap().pid;
38 // self.processes.lock().unwrap().insert(pid, handle);
39 // Ok(())
40}
41
42// #[cfg(feature = "uses_async")]
43// use tokio::sync::Notify;
44
45// pub static PROCESS_MANAGER: Lazy<ProcessManager> = Lazy::new(ProcessManager::new);
46
47pub trait ProcessObserver: Send + Sync + 'static {
48 fn on_spawn(&self, pid: u32, handle: CargoProcessHandle);
49}
50
51pub struct ProcessManager {
52 signalled_count: AtomicUsize,
53 signal_tx: Sender<()>,
54 processes: Mutex<HashMap<u32, Arc<Mutex<CargoProcessHandle>>>>,
55 // #[cfg(feature = "uses_async")]
56 // notifier: Notify,
57 results: Mutex<Vec<CargoProcessResult>>,
58}
59
60impl ProcessManager {
61 pub fn new(_cli: &Cli) -> Arc<Self> {
62 let (tx, rx) = mpsc::channel();
63 let manager = Arc::new(Self {
64 signalled_count: AtomicUsize::new(0),
65 signal_tx: tx.clone(),
66 processes: Mutex::new(HashMap::new()),
67 results: Mutex::new(Vec::new()),
68 });
69 ProcessManager::install_handler(Arc::clone(&manager), rx);
70 manager
71 }
72
73 pub fn reset_signalled(&self) {
74 self.signalled_count.store(0, Ordering::SeqCst);
75 }
76 pub fn has_signalled(&self) -> usize {
77 self.signalled_count.load(Ordering::SeqCst)
78 }
79
80 fn install_handler(self_: Arc<Self>, rx: Receiver<()>) {
81 match ctrlc::set_handler({
82 let tx = self_.signal_tx.clone();
83 move || {
84 let _ = tx.send(());
85 }
86 }) {
87 Ok(_) => {
88 thread::spawn(move || {
89 while rx.recv().is_ok() {
90 self_.signalled_count.fetch_add(1, Ordering::SeqCst);
91 println!(
92 "ctrlc> signal received. {}",
93 self_.signalled_count.load(Ordering::SeqCst)
94 );
95 self_.handle_signal();
96 }
97 });
98 }
99 Err(e) => {
100 eprintln!("ctrlc> Failed to install Ctrl+C handler: {}", e);
101 return;
102 }
103 }
104 }
105
106 fn handle_signal(&self) {
107 println!("ctrlc>");
108 let mut processes = self.processes.lock().unwrap();
109 for (pid, handle) in processes.iter() {
110 println!("ctrlc> Terminating process with PID: {}", pid);
111 if let Ok(mut h) = handle.lock() {
112 let _ = h.kill();
113 let final_diagnostics = {
114 let diag_lock = h.diagnostics.lock().unwrap();
115 diag_lock.clone()
116 };
117 h.result.diagnostics = final_diagnostics.clone();
118
119 // Ensure `es` is properly defined or assigned
120 if let Some(exit_status) = h.child.try_wait().ok().flatten() {
121 h.result.exit_status = Some(exit_status);
122 }
123
124 h.result.end_time = Some(SystemTime::now());
125 if let (Some(start), Some(end)) = (h.result.start_time, h.result.end_time) {
126 h.result.elapsed_time = Some(end.duration_since(start).unwrap_or_default());
127 }
128 self.record_result(h.result.clone());
129 }
130 }
131 processes.clear();
132 }
133
134 /// Updates the status line in the terminal.
135 /// When `raw_mode` is true, it uses crossterm commands to clear the current line.
136 /// Otherwise, it uses the carriage return (`\r`) trick.
137 pub fn update_status_line(output: &str, raw_mode: bool) -> io::Result<()> {
138 let mut stdout = io::stdout();
139 if raw_mode {
140 // Move cursor to beginning and clear the current line.
141 #[cfg(feature = "tui")]
142 {
143 execute!(
144 stdout,
145 cursor::MoveToColumn(0),
146 Clear(ClearType::CurrentLine)
147 )?;
148 print!("{}", output);
149 }
150 #[cfg(not(feature = "tui"))]
151 print!("\r{}", output);
152 } else {
153 // In non-raw mode, the \r trick can work.
154 print!("\r{}", output);
155 }
156 stdout.flush()
157 }
158
159 pub fn register(&self, handle: CargoProcessHandle) -> u32 {
160 let pid = handle.pid;
161 self.processes
162 .lock()
163 .unwrap()
164 .insert(pid, Arc::new(Mutex::new(handle)));
165
166 // #[cfg(feature = "uses_async")]
167 // self.notifier.notify_waiters();
168
169 pid
170 }
171
172 pub fn take(&self, pid: u32) -> Option<Arc<Mutex<CargoProcessHandle>>> {
173 self.processes.lock().unwrap().remove(&pid)
174 }
175
176 pub fn remove(&self, pid: u32) {
177 if let Some(handle_arc) = self.processes.lock().unwrap().remove(&pid) {
178 let mut h = handle_arc.lock().unwrap();
179 let final_diagnostics = {
180 let diag_lock = h.diagnostics.lock().unwrap();
181 diag_lock.clone()
182 };
183 h.result.diagnostics = final_diagnostics.clone();
184
185 // Ensure `es` is properly defined or assigned
186 if let Some(exit_status) = h.child.try_wait().ok().flatten() {
187 h.result.exit_status = Some(exit_status);
188 }
189
190 h.result.end_time = Some(SystemTime::now());
191 if let (Some(start), Some(end)) = (h.result.start_time, h.result.end_time) {
192 h.result.elapsed_time = Some(end.duration_since(start).unwrap_or_default());
193 }
194 self.record_result(h.result.clone());
195 drop(h);
196 // This was the only Arc reference, so dropping it here will run CargoProcessHandle::drop()
197 drop(handle_arc);
198 }
199 }
200
201 pub fn try_wait(&self, pid: u32) -> anyhow::Result<Option<ExitStatus>> {
202 // 1. Lock the processes map just long enough to clone the Arc.
203 let handle_arc = {
204 let processes = self.processes.lock().unwrap();
205 // Clone the Arc to keep the handle in the map while getting your own reference.
206 processes
207 .get(&pid)
208 .ok_or_else(|| anyhow::anyhow!("Process handle with PID {} not found", pid))?
209 .clone()
210 };
211
212 // 2. Lock the individual process handle to perform try_wait.
213 let mut handle = handle_arc.lock().unwrap();
214 // Here, try_wait returns a Result<Option<ExitStatus>, std::io::Error>.
215 // The '?' operator will convert any std::io::Error to anyhow::Error automatically.
216 let status = handle.child.try_wait()?;
217
218 // Return the exit status (or None) wrapped in Ok.
219 Ok(status)
220 }
221
222 pub fn get(&self, pid: u32) -> Option<Arc<Mutex<CargoProcessHandle>>> {
223 self.processes.lock().unwrap().get(&pid).cloned()
224 }
225
226 pub fn list(&self) -> Vec<u32> {
227 self.processes.lock().unwrap().keys().cloned().collect()
228 }
229
230 pub fn status(&self) {
231 let processes = self.processes.lock().unwrap();
232 if processes.is_empty() {
233 println!("No active cargo processes.");
234 } else {
235 println!("Active processes:");
236 for pid in processes.keys() {
237 println!(" - PID: {}", pid);
238 }
239 }
240 }
241
242 // /// Attempts to kill the process corresponding to the provided PID.
243 // /// Returns Ok(true) if the process was found and successfully killed (or had already exited),
244 // /// Ok(false) if the process was not found or did not exit within the maximum attempts,
245 // /// or an error if something went wrong.
246 // pub fn kill_by_pid(&self, pid: u32) -> anyhow::Result<bool> {
247 // // Retrieve a clone of the handle (Arc) for the given PID without removing it.
248 // let handle = {
249 // let processes = self.processes.lock().unwrap();
250 // processes.get(&pid).cloned()
251 // };
252
253 // if let Some(handle) = handle {
254 // eprintln!("Attempting to kill PID: {}", pid);
255
256 // let max_attempts = 3;
257 // let mut attempts = 0;
258 // let mut process_exited = false;
259
260 // loop {
261 // // Lock the process handle for this iteration.
262 // if let Ok(mut h) = handle.lock() {
263 // // Check if the process has already exited.
264 // match h.child.try_wait() {
265 // Ok(Some(status)) => {
266 // eprintln!("Process {} already exited with status: {:?}", pid, status);
267 // process_exited = true;
268 // break;
269 // }
270 // Ok(None) => {
271 // // Process is still running.
272 // if attempts == 0 {
273 // #[cfg(not(target_os = "windows"))] {
274 // eprintln!("Sending initial Ctrl+C signal to PID: {}", pid);
275 // crate::e_runall::send_ctrl_c(&mut h.child)?;
276 // }
277 // #[cfg(target_os = "windows")] {
278 // eprintln!("Sending initial kill signal to PID: {}", pid);
279 // }
280 // } else {
281 // eprintln!("Attempt {}: Forcing kill for PID: {}", attempts, pid);
282 // }
283 // // Attempt to kill the process.
284 // if let Err(e) = h.kill() {
285 // eprintln!("Failed to send kill signal to PID {}: {}", pid, e);
286 // }
287 // // Mark that an exit was requested.
288 // h.requested_exit = true;
289 // }
290 // Err(e) => {
291 // eprintln!("Error checking exit status for PID {}: {}", pid, e);
292 // break;
293 // }
294 // }
295 // } else {
296 // eprintln!("Could not lock process handle for PID {}", pid);
297 // break;
298 // }
299
300 // attempts += 1;
301 // // Allow some time for the process to exit.
302 // sleep(Duration::from_millis(2000));
303
304 // // Re-check if the process has exited.
305 // if let Ok(mut h) = handle.lock() {
306 // match h.child.try_wait() {
307 // Ok(Some(status)) => {
308 // eprintln!("Process {} exited with status: {:?}", pid, status);
309 // process_exited = true;
310 // break;
311 // }
312 // Ok(None) => {
313 // eprintln!("Process {} still running after attempt {}.", pid, attempts);
314 // }
315 // Err(e) => {
316 // eprintln!("Error rechecking process {}: {}", pid, e);
317 // break;
318 // }
319 // }
320 // }
321
322 // if attempts >= max_attempts {
323 // eprintln!("Maximum kill attempts reached for PID {}.", pid);
324 // break;
325 // }
326 // }
327
328 // // If the process exited, remove it from the map.
329 // if process_exited {
330 // let mut processes = self.processes.lock().unwrap();
331 // processes.remove(&pid);
332 // eprintln!("Process {} removed from map after exit.", pid);
333 // } else {
334 // eprintln!(
335 // "Process {} did not exit after {} attempts; it remains in the map for future handling.",
336 // pid, attempts
337 // );
338 // }
339 // Ok(process_exited)
340 // } else {
341 // eprintln!("Process handle with PID {} not found.", pid);
342 // Ok(false)
343 // }
344 // }
345
346 /// Attempts to kill the process corresponding to the provided PID.
347 /// Returns Ok(true) if the process was found and exited (even via signal),
348 /// Ok(false) if the process wasn’t found or didn’t exit after trying
349 /// all signals (in which case we drop the handle), or Err if something went wrong.
350 pub fn kill_by_pid(&self, pid: u32) -> anyhow::Result<bool> {
351 // Grab the handle, if any.
352 let handle_opt = {
353 let procs = self.processes.lock().unwrap();
354 procs.get(&pid).cloned()
355 };
356
357 if let Some(handle) = handle_opt {
358 eprintln!("Attempting to kill PID: {}", pid);
359 #[cfg(unix)]
360 let signals = [
361 Signal::SIGHUP,
362 Signal::SIGINT,
363 Signal::SIGQUIT,
364 Signal::SIGABRT,
365 Signal::SIGKILL,
366 Signal::SIGALRM,
367 Signal::SIGTERM,
368 ];
369 #[cfg(unix)]
370 let max_attempts = signals.len();
371
372 #[cfg(windows)]
373 let max_attempts = 3; // arbitrary, since Child::kill() is always SIGKILL
374
375 let mut attempts = 0;
376 let mut did_exit = false;
377
378 while attempts < max_attempts {
379 // 1) Check status
380 if let Ok(mut h) = handle.lock() {
381 match h.child.try_wait() {
382 Ok(Some(status)) => {
383 // Child has exited—report how.
384 #[cfg(unix)]
385 {
386 if let Some(sig) = status.signal() {
387 eprintln!("Process {} terminated by signal {}", pid, sig);
388 } else if let Some(code) = status.code() {
389 eprintln!("Process {} exited with code {}", pid, code);
390 } else {
391 eprintln!(
392 "Process {} exited with unknown status: {:?}",
393 pid, status
394 );
395 }
396 }
397 #[cfg(not(unix))]
398 {
399 if let Some(code) = status.code() {
400 eprintln!("Process {} exited with code {}", pid, code);
401 } else {
402 eprintln!(
403 "Process {} exited with unknown status: {:?}",
404 pid, status
405 );
406 }
407 }
408 did_exit = true;
409 break;
410 }
411 Ok(None) => {
412 // Still running → send the next signal
413 #[cfg(unix)]
414 {
415 let sig = signals[attempts];
416 eprintln!(
417 "Attempt {}: sending {:?} to PID {}",
418 attempts + 1,
419 sig,
420 pid
421 );
422 nix_kill(Pid::from_raw(pid as i32), sig)?;
423 }
424 #[cfg(windows)]
425 {
426 // // Remove the handle so it drops (and on Windows that will kill if still alive)
427 // {
428 // let mut procs = self.processes.lock().unwrap();
429 // procs.remove(&pid);
430 // }
431
432 eprintln!("Attempt {}: killing PID {}", attempts + 1, pid);
433 if let Err(e) = h.child.kill() {
434 eprintln!("Failed to kill PID {}: {}", pid, e);
435 }
436 _ = std::process::Command::new("taskkill")
437 .args(["/F", "/PID", &pid.to_string()])
438 .spawn();
439 }
440 h.requested_exit = true;
441 }
442 Err(e) => {
443 eprintln!("Error checking status for PID {}: {}", pid, e);
444 break;
445 }
446 }
447 } else {
448 eprintln!("Could not lock handle for PID {}", pid);
449 break;
450 }
451
452 attempts += 1;
453 if did_exit {
454 break;
455 }
456
457 // Give it a moment before retrying
458 thread::sleep(Duration::from_secs(2));
459 }
460 // Remove the handle so it drops (and on Windows that will kill if still alive)
461 {
462 let mut procs = self.processes.lock().unwrap();
463 if let Some(handle_arc) = procs.remove(&pid) {
464 let mut handle = handle_arc.lock().unwrap();
465 let final_diagnostics = {
466 let diag_lock = handle.diagnostics.lock().unwrap();
467 diag_lock.clone()
468 };
469 handle.result.diagnostics = final_diagnostics.clone();
470
471 // Ensure `es` is properly defined or assigned
472 if let Some(exit_status) = handle.child.try_wait().ok().flatten() {
473 handle.result.exit_status = Some(exit_status);
474 }
475
476 handle.result.end_time = Some(SystemTime::now());
477 if let (Some(start), Some(end)) =
478 (handle.result.start_time, handle.result.end_time)
479 {
480 handle.result.elapsed_time =
481 Some(end.duration_since(start).unwrap_or_default());
482 }
483 self.record_result(handle.result.clone());
484 } else {
485 eprintln!("No process found with PID: {}", pid);
486 }
487 }
488
489 if did_exit {
490 eprintln!("Process {} removed from map after exit.", pid);
491 } else {
492 eprintln!(
493 "Dropped handle for PID {} after {} attempts (process may still be running).",
494 pid, attempts
495 );
496 }
497
498 Ok(did_exit)
499 } else {
500 eprintln!("Process handle with PID {} not found.", pid);
501 Ok(false)
502 }
503 }
504
505 // /// Attempts to kill the process corresponding to the provided PID.
506 // /// Returns Ok(true) if the process was found and successfully killed
507 // /// (or had already exited), Ok(false) if the process was not found
508 // /// or did not exit within the maximum attempts (in which case we drop
509 // /// the handle), or an error if something went wrong.
510 // pub fn kill_by_pid(&self, pid: u32) -> anyhow::Result<bool> {
511 // // Grab a clone of the Arc<Mutex<ProcessHandle>> if it exists
512 // let handle_opt = {
513 // let processes = self.processes.lock().unwrap();
514 // processes.get(&pid).cloned()
515 // };
516
517 // if let Some(handle) = handle_opt {
518 // eprintln!("Attempting to kill PID: {}", pid);
519
520 // let max_attempts = 3;
521 // let mut attempts = 0;
522 // let mut process_exited = false;
523
524 // loop {
525 // // 1) Check status / send signal
526 // if let Ok(mut h) = handle.lock() {
527 // match h.child.try_wait() {
528 // Ok(Some(status)) => {
529 // // Already exited
530 // eprintln!(
531 // "Process {} already exited with status: {:?}",
532 // pid, status
533 // );
534 // process_exited = true;
535 // break;
536 // }
537 // Ok(None) => {
538 // // Still running → send signal
539 // if attempts == 0 {
540 // #[cfg(not(target_os = "windows"))]
541 // {
542 // eprintln!("Sending initial Ctrl+C to PID: {}", pid);
543 // crate::e_runall::send_ctrl_c(&mut h.child)?;
544 // }
545 // #[cfg(target_os = "windows")]
546 // {
547 // eprintln!("Sending initial kill to PID: {}", pid);
548 // }
549 // } else {
550 // eprintln!("Attempt {}: Forcing kill for PID: {}", attempts, pid);
551 // }
552
553 // if let Err(e) = h.kill() {
554 // eprintln!("Failed to send kill to PID {}: {}", pid, e);
555 // }
556 // h.requested_exit = true;
557 // }
558 // Err(e) => {
559 // eprintln!("Error checking status for PID {}: {}", pid, e);
560 // break;
561 // }
562 // }
563 // } else {
564 // eprintln!("Could not lock handle for PID {}", pid);
565 // break;
566 // }
567
568 // attempts += 1;
569 // if attempts >= max_attempts {
570 // eprintln!("Maximum kill attempts reached for PID {}. Dropping handle.", pid);
571 // break;
572 // }
573
574 // // 2) Wait a bit before re-checking
575 // thread::sleep(Duration::from_millis(2_000));
576
577 // // 3) Re-check exit status
578 // if let Ok(mut h) = handle.lock() {
579 // match h.child.try_wait() {
580 // Ok(Some(status)) => {
581 // eprintln!("Process {} exited with status: {:?}", pid, status);
582 // process_exited = true;
583 // break;
584 // }
585 // Ok(None) => {
586 // eprintln!(
587 // "Process {} still running after attempt {}.",
588 // pid, attempts
589 // );
590 // }
591 // Err(e) => {
592 // eprintln!("Error rechecking process {}: {}", pid, e);
593 // break;
594 // }
595 // }
596 // }
597 // }
598
599 // // Remove the handle (dropping it) whether or not the process exited
600 // {
601 // let mut processes = self.processes.lock().unwrap();
602 // processes.remove(&pid);
603 // }
604
605 // if process_exited {
606 // eprintln!("Process {} removed from map after exit.", pid);
607 // } else {
608 // eprintln!(
609 // "Dropped handle for PID {} after {} attempts (process may still be running).",
610 // pid, attempts
611 // );
612 // }
613
614 // Ok(process_exited)
615 // } else {
616 // eprintln!("Process handle with PID {} not found.", pid);
617 // Ok(false)
618 // }
619 // }
620
621 /// Attempts to kill one process.
622 /// Returns Ok(true) if a process was found and killed, Ok(false) if none found,
623 /// or an error if something went wrong.
624 pub fn kill_one(&self) -> anyhow::Result<bool> {
625 // First, lock the map briefly to pick one process handle.
626 let maybe_entry = {
627 let processes = self.processes.lock().unwrap();
628 // Clone the Arc so that we don’t take ownership from the map.
629 processes
630 .iter()
631 .next()
632 .map(|(&pid, handle)| (pid, handle.clone()))
633 };
634
635 if let Some((pid, handle)) = maybe_entry {
636 eprintln!("Attempting to kill PID: {}", pid);
637
638 // We'll attempt to kill the process up to `max_attempts` times.
639 let max_attempts = 3;
640 let mut attempts = 0;
641 let mut process_exited = false;
642
643 loop {
644 // Lock the process handle for this iteration.
645 if let Ok(mut h) = handle.lock() {
646 // Check if the process has already exited.
647 match h.child.try_wait() {
648 Ok(Some(status)) => {
649 eprintln!("Process {} already exited with status: {:?}", pid, status);
650 process_exited = true;
651 sleep(Duration::from_millis(3_000));
652 break;
653 }
654 Ok(None) => {
655 // Process is still running. On the first attempt, or forcefully on later attempts.
656 if attempts == 0 {
657 eprintln!("Sending initial kill signal to PID: {}", pid);
658 } else {
659 eprintln!("Attempt {}: Forcing kill for PID: {}", attempts, pid);
660 }
661 sleep(Duration::from_millis(3_000));
662 // Try to kill the process. Handle errors by printing a debug message.
663 if let Err(e) = h.kill() {
664 eprintln!("Failed to send kill signal to PID {}: {}", pid, e);
665 sleep(Duration::from_millis(3_000));
666 }
667 }
668 Err(e) => {
669 eprintln!("Error checking exit status for PID {}: {}", pid, e);
670 sleep(Duration::from_millis(3_000));
671 break;
672 }
673 }
674 } else {
675 eprintln!("Could not lock process handle for PID {}", pid);
676 sleep(Duration::from_millis(3_000));
677 break;
678 }
679
680 attempts += 1;
681 // Allow some time for the process to exit.
682 sleep(Duration::from_millis(3_000));
683
684 // Check again after the sleep.
685 if let Ok(mut h) = handle.lock() {
686 match h.child.try_wait() {
687 Ok(Some(status)) => {
688 eprintln!("Process {} exited with status: {:?}", pid, status);
689 sleep(Duration::from_millis(3_000));
690 process_exited = true;
691 break;
692 }
693 Ok(None) => {
694 eprintln!("Process {} still running after attempt {}.", pid, attempts);
695 sleep(Duration::from_millis(3_000));
696 }
697 Err(e) => {
698 eprintln!("Error rechecking process {}: {}", pid, e);
699 sleep(Duration::from_millis(3_000));
700 break;
701 }
702 }
703 }
704
705 if attempts >= max_attempts {
706 eprintln!("Maximum kill attempts reached for PID {}.", pid);
707 sleep(Duration::from_millis(3_000));
708 break;
709 }
710 }
711
712 // 4) In all cases, remove the handle so it drops
713 {
714 let mut ps = self.processes.lock().unwrap();
715 ps.remove(&pid);
716 }
717 if process_exited {
718 eprintln!("Process {} removed from map after exit.", pid);
719 } else {
720 eprintln!(
721 "Dropped handle for PID {} after {} attempts (process may still be running).",
722 pid, attempts
723 );
724 }
725 sleep(Duration::from_millis(3_000));
726 Ok(process_exited)
727 } else {
728 println!("No processes to kill.");
729 sleep(Duration::from_millis(3_000));
730 Ok(false)
731 }
732 }
733 // pub fn kill_one(&self) {
734 // let mut processes = self.processes.lock().unwrap();
735 // if let Some((&pid, handle)) = processes.iter().next() {
736 // eprintln!("Killing PID: {}", pid);
737 // if let Ok(mut h) = handle.lock() {
738 // let _ = h.kill();
739 // }
740 // processes.remove(&pid);
741 // } else {
742 // println!("No processes to kill.");
743 // }
744 // }
745
746 pub fn kill_all(&self) {
747 let mut processes = self.processes.lock().unwrap();
748 for (pid, handle) in processes.drain() {
749 eprintln!("Killing PID: {}", pid);
750 if let Ok(mut h) = handle.lock() {
751 let _ = h.kill();
752 }
753 }
754 }
755
756 // Returns the terminal error for a given PID.
757 pub fn get_terminal_error(&self, pid: u32) -> Option<TerminalError> {
758 // Lock the process map
759 let processes = self.processes.lock().unwrap();
760
761 // Check if the process exists
762 if let Some(handle) = processes.get(&pid) {
763 // Lock the handle to access the terminal error flag
764 let handle = handle.lock().unwrap();
765 // Return the terminal error flag value
766 return Some(handle.terminal_error_flag.lock().unwrap().clone());
767 }
768
769 // If no process is found for the given PID, return None
770 None
771 }
772
773 // pub fn install_ctrlc_handler(self: Arc<Self>) {
774 // let manager = Arc::clone(&self);
775 // ctrlc::set_handler(move || {
776 // eprintln!("CTRL-C detected. Killing all processes.");
777 // manager.kill_all();
778 // std::process::exit(1);
779 // })
780 // .expect("Failed to install ctrl-c handler");
781 // }
782
783 /// Wait for the process to finish, show interactive status, then return a result
784 pub fn wait(
785 &self,
786 pid: u32,
787 _duration: Option<Duration>,
788 ) -> anyhow::Result<CargoProcessResult> {
789 // Hide the cursor and ensure it’s restored on exit
790 #[allow(dead_code)]
791 struct CursorHide;
792 impl Drop for CursorHide {
793 fn drop(&mut self) {
794 #[cfg(feature = "tui")]
795 {
796 let _ = crossterm::execute!(std::io::stdout(), crossterm::cursor::Show);
797 }
798 }
799 }
800 #[cfg(feature = "tui")]
801 let _cursor_hide = {
802 let mut out = std::io::stdout();
803 crossterm::execute!(out, crossterm::cursor::Hide)?;
804 CursorHide
805 };
806
807 // 1. Remove the handle from the map
808 let handle_arc = {
809 let mut map = self.processes.lock().unwrap();
810 map.remove(&pid).ok_or_else(|| {
811 let result = CargoProcessResult {
812 target_name: String::new(), // Placeholder, should be set properly in actual use
813 cmd: String::new(), // Placeholder, should be set properly in actual use
814 args: Vec::new(), // Placeholder, should be set properly in actual use
815 pid,
816 exit_status: None,
817 diagnostics: Vec::new(),
818 start_time: None,
819 end_time: Some(SystemTime::now()),
820 elapsed_time: None,
821 terminal_error: None, // Placeholder, should be set properly in actual use
822 build_finished_time: None, // Placeholder, should be set properly in actual use
823 build_elapsed: None, // Placeholder, should be set properly in actual use
824 runtime_elapsed: None, // Placeholder, should be set properly in actual use
825 stats: crate::e_cargocommand_ext::CargoStats::default(), // Provide a default instance of CargoStats
826 build_output_size: 0, // Default value set to 0
827 runtime_output_size: 0, // Placeholder, should be set properly in actual use
828 is_filter: false, // Placeholder, should be set properly in actual use
829 is_could_not_compile: false, // Placeholder, should be set properly in actual use
830 };
831 self.record_result(result.clone());
832 anyhow::anyhow!("Process handle with PID {} not found", pid)
833 })?
834 };
835
836 // 2. Unwrap Arc<Mutex<...>> to get the handle
837 let mut handle = Arc::try_unwrap(handle_arc)
838 .map_err(|_| anyhow::anyhow!("Process handle for PID {} still shared", pid))?
839 .into_inner()
840 .unwrap();
841
842 // 3. Interactive polling loop
843 let mut system = if handle.is_filter {
844 Some(System::new_all())
845 } else {
846 None
847 };
848 const POLL: Duration = Duration::from_secs(1);
849 let mut loop_cnter = 0;
850 loop {
851 loop_cnter += 1;
852 if handle.is_filter && loop_cnter % 2 == 0 {
853 if let Some(ref mut sys) = system {
854 sys.refresh_all();
855 }
856 }
857
858 if handle.is_filter {
859 if let Some(ref sys) = system {
860 if let Some(process) = sys.process((pid as usize).into()) {
861 let status = handle.format_status(Some(process));
862 if !status.is_empty() {
863 print!("\r{}", status);
864 }
865 }
866 }
867 std::io::stdout().flush().unwrap();
868 }
869
870 if let Some(es) = handle.child.try_wait()? {
871 let final_diagnostics = {
872 let diag_lock = handle.diagnostics.lock().unwrap();
873 diag_lock.clone()
874 };
875 handle.result.diagnostics = final_diagnostics.clone();
876 handle.result.exit_status = Some(es);
877 handle.result.end_time = Some(SystemTime::now());
878 if let (Some(start), Some(end)) = (handle.result.start_time, handle.result.end_time)
879 {
880 handle.result.elapsed_time =
881 Some(end.duration_since(start).unwrap_or_default());
882 }
883 println!(
884 "\nProcess with PID {} finished {:?} {}",
885 pid,
886 es,
887 handle.result.diagnostics.len()
888 );
889 break;
890 }
891 std::thread::sleep(POLL);
892 }
893
894 if handle.is_filter {
895 // 4. Extract diagnostics out of Arc<Mutex<_>>
896 let diagnostics = Arc::try_unwrap(handle.diagnostics)
897 .map(|m| m.into_inner().unwrap())
898 .unwrap_or_else(|arc| arc.lock().unwrap().clone());
899
900 // 5. Move them into the final result
901 handle.result.diagnostics = diagnostics;
902 }
903 self.record_result(handle.result.clone());
904 Ok(handle.result)
905 }
906
907 pub fn record_result(&self, result: CargoProcessResult) {
908 let mut results = self.results.lock().unwrap();
909 results.push(result);
910 }
911
912 pub fn generate_report(&self, create_gist: bool) {
913 let results = self.results.lock().unwrap();
914 let report = crate::e_reports::generate_markdown_report(&results);
915 if let Err(e) = crate::e_reports::save_report_to_file(&report, "run_report.md") {
916 eprintln!("Failed to save report: {}", e);
917 }
918 if create_gist {
919 crate::e_reports::create_gist(&report, "run_report.md").unwrap_or_else(|e| {
920 eprintln!("Failed to create Gist: {}", e);
921 });
922 }
923 }
924 // pub fn wait(&self, pid: u32, _duration: Option<Duration>) -> anyhow::Result<CargoProcessResult> {
925 // // Hide the cursor and ensure it is restored on exit.
926 // {
927 // let mut stdout = std::io::stdout();
928 // crossterm::execute!(stdout, crossterm::cursor::Hide)?;
929 // }
930
931 // let mut processes = self.processes.lock().unwrap();
932 // if let Some(handle) = processes.get_mut(&pid) {
933 // let mut handle = handle.lock().unwrap();
934 // let mut system = System::new_all();
935
936 // // Initialize start_time if not already set.
937 // let start_time = handle.result.start_time.unwrap_or_else(|| {
938 // let now = SystemTime::now();
939 // handle.result.start_time = Some(now);
940 // now
941 // });
942
943 // // Main loop.
944 // const POLL_DURATION: Duration = Duration::from_secs(1);
945 // loop {
946 // system.refresh_all();
947 // let maybe_system: Option<&System> = if true { Some(&system) } else { None };
948 // // Get formatted status string.
949 // let output = handle.format_status(maybe_system);
950 // print!("\r{}", output);
951 // std::io::stdout().flush().unwrap();
952
953 // if let Some(status) = handle.child.try_wait()? {
954 // handle.result.exit_status = Some(status);
955 // handle.result.end_time = Some(SystemTime::now());
956 // println!("\nProcess with PID {} finished", pid);
957 // return Ok(handle.result.clone());
958 // }
959 // std::thread::sleep(POLL_DURATION);
960 // }
961 // } else {
962 // Err(anyhow::anyhow!("Process handle with PID {} not found", pid))
963 // }
964 // }
965
966 // pub fn wait(&self, pid: u32, _duration: Option<Duration>) -> anyhow::Result<CargoProcessResult> {
967 // // Turn off (hide) the cursor.
968 // {
969 // let mut stdout = std::io::stdout();
970 // crossterm::execute!(stdout, crossterm::cursor::Hide)?;
971 // }
972 // // Ensure the cursor is shown when we exit.
973 // let _cursor_guard = CursorGuard;
974
975 // let mut processes = self.processes.lock().unwrap();
976 // if let Some(handle) = processes.get_mut(&pid) {
977 // let mut handle = handle.lock().unwrap();
978 // let mut system = System::new_all();
979
980 // // Define the poll duration constant (adjust as needed).
981 // const POLL_DURATION: Duration = Duration::from_secs(1);
982
983 // // Initialize start_time if not already set.
984 // let start_time = handle.result.start_time.unwrap_or_else(|| {
985 // let now = SystemTime::now();
986 // handle.result.start_time = Some(now);
987 // now
988 // });
989 // // Format the start time with seconds precision.
990 // let start_dt: chrono::DateTime<Local> = start_time.into();
991 // let start_str = start_dt.format("%H:%M:%S").to_string();
992 // // Use ANSI color for the start time.
993 // let colored_start = nu_ansi_term::Color::Green.paint(&start_str).to_string();
994 // // Plain version for spacing calculations.
995 // let plain_start = start_str;
996
997 // loop {
998 // system.refresh_all();
999 // let now = SystemTime::now();
1000 // let runtime_duration = now.duration_since(start_time).unwrap();
1001 // let runtime_str = crate::e_fmt::format_duration(runtime_duration);
1002
1003 // // Use usize conversion with into()
1004 // if let Some(process) = system.process((pid as usize).into()) {
1005 // let cpu_usage = process.cpu_usage();
1006 // let mem_kb = process.memory();
1007 // let mem_human = if mem_kb >= 1024 {
1008 // format!("{:.2} MB", mem_kb as f64 / 1024.0)
1009 // } else {
1010 // format!("{} KB", mem_kb)
1011 // };
1012
1013 // let left_display = format!("{} | CPU: {:.2}% | Mem: {}", colored_start, cpu_usage, mem_human);
1014 // let left_plain = format!("{} | CPU: {:.2}% | Mem: {}", plain_start, cpu_usage, mem_human);
1015
1016 // // Get terminal width with crossterm.
1017 // let (cols, _) = crossterm::terminal::size().unwrap_or((80, 20));
1018 // let total_width = cols as usize;
1019 // // Right side: the elapsed duration, underlined.
1020 // let right_display = nu_ansi_term::Style::new()
1021 // .reset_before_style()
1022 // .underline()
1023 // .paint(&runtime_str)
1024 // .to_string();
1025 // let left_len = left_plain.len();
1026 // let right_len = runtime_str.len();
1027 // let padding = if total_width > left_len + right_len {
1028 // total_width - left_len - right_len
1029 // } else {
1030 // 1
1031 // };
1032
1033 // let output = format!("\r{}{}{}", left_display, " ".repeat(padding), right_display);
1034 // print!("{}", output);
1035 // std::io::stdout().flush().unwrap();
1036 // } else {
1037 // print!("\rProcess with PID {} not found in sysinfo", pid);
1038 // std::io::stdout().flush().unwrap();
1039 // }
1040
1041 // if let Some(status) = handle.child.try_wait()? {
1042 // handle.result.exit_status = Some(status);
1043 // handle.result.end_time = Some(SystemTime::now());
1044 // println!("\nProcess with PID {} finished", pid);
1045 // return Ok(handle.result.clone());
1046 // }
1047
1048 // std::thread::sleep(POLL_DURATION);
1049 // }
1050 // } else {
1051 // Err(anyhow::anyhow!("Process handle with PID {} not found", pid))
1052 // }
1053 // }
1054
1055 // pub fn wait(&self, pid: u32, max_polls: Option<usize>) -> anyhow::Result<CargoProcessResult> {
1056 // let mut processes = self.processes.lock().unwrap();
1057 // if let Some(handle) = processes.get_mut(&pid) {
1058 // let mut handle = handle.lock().unwrap();
1059 // let mut system = System::new_all();
1060
1061 // // Initialize start_time if not already set.
1062 // let start_time = handle.result.start_time.unwrap_or_else(|| {
1063 // let now = SystemTime::now();
1064 // handle.result.start_time = Some(now);
1065 // now
1066 // });
1067 // // Format the start time with more precision.
1068 // let start_dt: chrono::DateTime<Local> = start_time.into();
1069 // let start_str = start_dt.format("%H:%M:%S").to_string();
1070
1071 // let mut polls = 0;
1072 // loop {
1073 // system.refresh_all();
1074
1075 // if let Some(process) = system.process((pid as usize).into()) {
1076 // let now = SystemTime::now();
1077 // let runtime = now.duration_since(start_time).unwrap();
1078 // let runtime_str = Self::format_duration(runtime);
1079
1080 // // Get memory usage and convert to a human-readable string.
1081 // let mem_kb = process.memory();
1082 // let mem_human = if mem_kb >= 1024 {
1083 // format!("{:.2} MB", mem_kb as f64 / 1024.0)
1084 // } else {
1085 // format!("{} KB", mem_kb)
1086 // };
1087
1088 // // Build the output string.
1089 // let output = format!(
1090 // "{} | Runtime: {} | Mem: {} | CPU: {:.2}%%",
1091 // start_str,
1092 // runtime_str,
1093 // mem_human,
1094 // process.cpu_usage()
1095 // );
1096 // // Print on one line and pad to clear leftover characters.
1097 // print!("\r{:<80}", output);
1098 // std::io::stdout().flush().unwrap();
1099 // } else {
1100 // print!("\rProcess with PID {} not found in sysinfo", pid);
1101 // std::io::stdout().flush().unwrap();
1102 // }
1103
1104 // // Check if the process has finished.
1105 // if let Some(status) = handle.child.try_wait()? {
1106 // handle.result.exit_status = Some(status);
1107 // handle.result.end_time = Some(SystemTime::now());
1108 // println!("\nProcess with PID {} finished", pid);
1109 // return Ok(handle.result.clone());
1110 // }
1111
1112 // polls += 1;
1113 // if let Some(max) = max_polls {
1114 // if polls >= max {
1115 // println!("\nReached maximum polling iterations ({})", max);
1116 // break;
1117 // }
1118 // }
1119 // std::thread::sleep(Duration::from_secs(1));
1120 // }
1121 // Err(anyhow::anyhow!("Process did not finish after maximum polls"))
1122 // } else {
1123 // Err(anyhow::anyhow!("Process handle with PID {} not found", pid))
1124 // }
1125 // }
1126
1127 // pub fn wait(&self, pid: u32) -> anyhow::Result<CargoProcessResult> {
1128 // let mut processes = self.processes.lock().unwrap();
1129 // if let Some(handle) = processes.get_mut(&pid) {
1130 // let mut handle = handle.lock().unwrap();
1131
1132 // loop {
1133 // println!("Waiting for process with PID: {}", pid);
1134
1135 // let status = handle.child.try_wait()?;
1136
1137 // if let Some(status) = status {
1138 // handle.result.exit_status = Some(status);
1139 // handle.result.end_time = Some(SystemTime::now());
1140 // println!("Process with PID {} finished", pid);
1141 // return Ok(handle.result.clone());
1142 // }
1143
1144 // std::thread::sleep(std::time::Duration::from_secs(1));
1145 // }
1146 // } else {
1147 // Err(anyhow::anyhow!("Process handle with PID {} not found", pid))
1148 // }
1149 // }
1150
1151 pub fn format_process_status(
1152 pid: u32,
1153 start_time: Option<SystemTime>,
1154 system: Arc<Mutex<System>>,
1155 target: &CargoTarget,
1156 target_dimensions: (usize, usize),
1157 ) -> String {
1158 // let start_dt: chrono::DateTime<Local> =
1159 // start_time.unwrap_or_else(|| SystemTime::UNIX_EPOCH).into();
1160 let start_str = start_time
1161 .map(|st| {
1162 chrono::DateTime::<Local>::from(st)
1163 .format("%H:%M:%S")
1164 .to_string()
1165 })
1166 .unwrap_or_else(|| "-".to_string());
1167 let colored_start = nu_ansi_term::Color::LightCyan.paint(&start_str).to_string();
1168 let plain_start = start_str;
1169 if start_time.is_none() {
1170 return String::new();
1171 }
1172 // Refresh the system stats and look up the process.
1173 if let Some(process) = system.lock().unwrap().process((pid as usize).into()) {
1174 let cpu_usage = process.cpu_usage();
1175 let mem_kb = process.memory();
1176 let mem_human = if mem_kb >= 1024 {
1177 format!("{:.2} MB", mem_kb as f64 / 1024.0)
1178 } else {
1179 format!("{} KB", mem_kb)
1180 };
1181
1182 // Calculate runtime.
1183 let now = SystemTime::now();
1184 let runtime_duration = match start_time {
1185 Some(start) => now
1186 .duration_since(start)
1187 .unwrap_or_else(|_| Duration::from_secs(0)),
1188 None => Duration::from_secs(0),
1189 };
1190 let runtime_str = crate::e_fmt::format_duration(runtime_duration);
1191 // compute the max number of digits in either dimension:
1192 let max_digits = target_dimensions
1193 .0
1194 .max(target_dimensions.1)
1195 .to_string()
1196 .len();
1197 let left_display = format!(
1198 "{:0width$}of{:0width$} | {} | {} | PID: {} | CPU: {:.2}% | Mem: {}",
1199 target_dimensions.0,
1200 target_dimensions.1,
1201 nu_ansi_term::Color::Green
1202 .paint(target.display_name.clone())
1203 .to_string(),
1204 colored_start,
1205 pid,
1206 cpu_usage,
1207 mem_human,
1208 width = max_digits,
1209 );
1210 let left_plain = format!(
1211 "{:0width$}of{:0width$} | {} | {} | PID: {} | CPU: {:.2}% | Mem: {}",
1212 target_dimensions.0,
1213 target_dimensions.1,
1214 target.display_name,
1215 plain_start,
1216 pid,
1217 cpu_usage,
1218 mem_human,
1219 width = max_digits,
1220 );
1221
1222 // Get terminal width.
1223 #[cfg(feature = "tui")]
1224 let (cols, _) = crossterm::terminal::size().unwrap_or((80, 20));
1225 #[cfg(not(feature = "tui"))]
1226 let (cols, _) = (80, 20);
1227 let total_width = cols as usize;
1228
1229 // Format the runtime with underlining.
1230 let right_display = nu_ansi_term::Style::new()
1231 .reset_before_style()
1232 .underline()
1233 .paint(&runtime_str)
1234 .to_string();
1235 let left_len = left_plain.len();
1236 let right_len = runtime_str.len();
1237 let padding = if total_width > left_len + right_len {
1238 total_width - left_len - right_len
1239 } else {
1240 1
1241 };
1242
1243 format!("{}{}{}", left_display, " ".repeat(padding), right_display)
1244 } else {
1245 // String::from("xxx")
1246 format!("\rProcess with PID {} not found in sysinfo", pid)
1247 }
1248 }
1249
1250 /// Print the exact diagnostic output as captured.
1251 pub fn print_exact_output(&self) {
1252 let processes = self.processes.lock().unwrap();
1253 for handle in processes.iter() {
1254 println!("--- Full Diagnostics for PID {} ---", handle.0);
1255 let handle_lock = handle.1.lock().unwrap();
1256 let diags = handle_lock.diagnostics.lock().unwrap();
1257 for diag in diags.iter() {
1258 // Print the entire diagnostic.
1259 println!("{:?}: {}", diag.level, diag.message);
1260 }
1261 }
1262 }
1263
1264 /// Print a one‑line summary per warning, numbered with leading zeros.
1265 pub fn print_prefixed_summary(&self) {
1266 // 1. Grab a snapshot of the handles (Arc clones) under the manager lock.
1267 let guard = self.processes.lock().unwrap();
1268 let handles: Vec<_> = guard.iter().map(|h| h.clone()).collect();
1269
1270 // 2. Now we can iterate without holding the manager lock.
1271 for handle in handles {
1272 // Lock only the diagnostics for this handle.
1273 let handle_lock = handle.1.lock().unwrap();
1274 let diags = handle_lock.diagnostics.lock().unwrap();
1275
1276 // Collect warnings.
1277 let warnings: Vec<_> = diags.iter().filter(|d| d.level.eq("warning")).collect();
1278
1279 // Determine width for zero-padding for warnings.
1280 let warning_width = warnings.len().to_string().len().max(1);
1281 println!(
1282 "\n\n--- Warnings for PID {} --- {} {}",
1283 handle.0,
1284 warning_width,
1285 warnings.len()
1286 );
1287
1288 for (i, diag) in warnings.iter().enumerate() {
1289 // Format the index with leading zeros for warnings.
1290 let index = format!("{:0width$}", i + 1, width = warning_width);
1291 // Print the warning with the index.
1292 println!("{}: {}", index, diag.message.trim());
1293 }
1294
1295 // Collect errors.
1296 let errors: Vec<_> = diags.iter().filter(|d| d.level.eq("error")).collect();
1297
1298 // Determine width for zero-padding for errors.
1299 let error_width = errors.len().to_string().len().max(1);
1300 println!(
1301 "\n\n--- Errors for PID {} --- {} {}",
1302 handle.0,
1303 error_width,
1304 errors.len()
1305 );
1306
1307 for (i, diag) in errors.iter().enumerate() {
1308 // Format the index with leading zeros for errors.
1309 let index = format!("{:0width$}", i + 1, width = error_width);
1310 // Print the error with the index.
1311 println!("{}: {}", index, diag.message.trim());
1312 }
1313 }
1314 }
1315
1316 /// file:line:col – source_line, colored by level.
1317 pub fn print_compact(&self) {
1318 let processes = self.processes.lock().unwrap();
1319 for handle in processes.iter() {
1320 println!("--- Compact for PID {} ---", handle.0);
1321 let handle_lock = handle.1.lock().unwrap();
1322 let diags = handle_lock.diagnostics.lock().unwrap();
1323 for diag in diags.iter() {
1324 println!("{}: {} {}", diag.level, diag.lineref, diag.message.trim());
1325 }
1326 }
1327 }
1328 /// Print a shortened version: warnings first then errors.
1329 pub fn print_shortened_output(&self) {
1330 let processes = self.processes.lock().unwrap();
1331 for handle in processes.iter() {
1332 println!("\n\n\n--- Summary for PID {} ---", handle.0);
1333 let handle_lock = handle.1.lock().unwrap();
1334 let diags = handle_lock.diagnostics.lock().unwrap();
1335
1336 // Filter diagnostics for warnings and errors.
1337 let warnings: Vec<_> = diags.iter().filter(|d| d.level.eq("warning")).collect();
1338 let errors: Vec<_> = diags.iter().filter(|d| d.level.eq("error")).collect();
1339
1340 // Print warnings.
1341 if !warnings.is_empty() {
1342 println!("Warnings:");
1343 for diag in warnings {
1344 println!("print_shortened_output:{}", diag.message.trim());
1345 }
1346 } else {
1347 println!("No warnings.");
1348 }
1349
1350 // Print errors.
1351 if !errors.is_empty() {
1352 println!("Errors:");
1353 for diag in errors {
1354 println!("print_shortened_output: {}", diag.message.trim());
1355 }
1356 } else {
1357 println!("No errors.");
1358 }
1359 }
1360 }
1361}
1362
1363// #[cfg(feature = "uses_async")]
1364// impl ProcessManager {
1365// pub async fn wait_for_processes(&self) {
1366// loop {
1367// {
1368// if self.processes.lock().unwrap().is_empty() {
1369// break;
1370// }
1371// }
1372// self.notifier.notified().await;
1373// }
1374// }
1375// }
1376
1377pub struct CursorGuard;
1378
1379impl Drop for CursorGuard {
1380 fn drop(&mut self) {
1381 #[cfg(feature = "tui")]
1382 {
1383 let mut stdout = std::io::stdout();
1384 let _ = crossterm::execute!(stdout, crossterm::cursor::Show);
1385 }
1386 }
1387}