cargo_e/
e_runall.rs

1use crate::e_cli::RunAll;
2use crate::e_command_builder::CargoCommandBuilder;
3use crate::e_processmanager::ProcessManager;
4use crate::e_target::{CargoTarget, TargetKind};
5use anyhow::{Context, Result};
6use std::path::PathBuf;
7use std::sync::atomic::{AtomicBool, Ordering};
8use std::sync::Arc;
9use std::time::Duration;
10use std::time::Instant;
11
12#[cfg(unix)]
13use nix::sys::signal::{kill, Signal};
14#[cfg(unix)]
15use nix::unistd::Pid;
16
17// #[cfg(target_os = "windows")]
18// use std::os::windows::process::CommandExt;
19
20// #[cfg(target_os = "windows")]
21// const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
22
23// #[cfg(target_os = "windows")]
24// fn send_ctrl_c(child: &mut Child) -> Result<()> {
25//     println!("Sending CTRL-C to child process...");
26//     use windows::Win32::System::Console::{GenerateConsoleCtrlEvent, CTRL_C_EVENT};
27
28//     // Send CTRL+C to the child process group.
29//     // The child must have been spawned with CREATE_NEW_PROCESS_GROUP.
30//     let result = unsafe { GenerateConsoleCtrlEvent(CTRL_C_EVENT, child.id()) };
31//     if result.is_err() {
32//         return Err(anyhow::anyhow!("Failed to send CTRL_C_EVENT on Windows"));
33//     }
34
35//     // Allow some time for the child to handle the signal gracefully.
36//     std::thread::sleep(std::time::Duration::from_millis(1000));
37
38//     Ok(())
39// }
40
41#[cfg(not(target_os = "windows"))]
42pub fn send_ctrl_c(child: &mut std::process::Child) -> Result<()> {
43    // On Unix, send SIGINT to the child.
44    kill(Pid::from_raw(child.id() as i32), Signal::SIGINT).context("Failed to send SIGINT")?;
45    // Wait briefly to allow graceful shutdown.
46    std::thread::sleep(Duration::from_millis(2000));
47    Ok(())
48}
49
50/// Runs all filtered targets with prebuild, child process management, and timeout‐based termination.
51///
52/// If the CLI flag `pre_build` is enabled, this function first prebuilds all targets by invoking
53/// `cargo build` with the appropriate flags (using `--example` or `--bin` and, for extended targets,
54/// the `--manifest-path` flag). Then it spawns a child process for each target using `cargo run`,
55/// waits for the duration specified by `cli.wait`, kills the child process, and then checks its output.
56///
57/// # Parameters
58///
59/// - `cli`: A reference to the CLI configuration (containing flags like `pre_build`, `wait`, and extra arguments).
60/// - `filtered_targets`: A slice of `Example` instances representing the targets to run.
61///
62/// # Errors
63///
64/// Returns an error if the prebuild step fails or if any child process fails to spawn or complete.
65pub fn run_all_examples(
66    manager: Arc<ProcessManager>,
67    cli: &crate::Cli,
68    filtered_targets: &[CargoTarget],
69) -> Result<bool> {
70    // Adjust RUSTFLAGS if --quiet was provided.
71    set_rustflags_if_quiet(cli.quiet);
72
73    // Prebuild targets if requested.
74    if cli.pre_build {
75        crate::e_prebuild::prebuild_examples(filtered_targets)
76            .context("Prebuild of targets failed")?;
77    }
78
79    let mut targets = filtered_targets.to_vec();
80    targets.sort_by(|a, b| a.display_name.cmp(&b.display_name));
81
82    let user_requested_quit = Arc::new(AtomicBool::new(false));
83
84    let chunk_size = cli.run_at_a_time;
85    let mut idx = 0;
86    while idx < targets.len() {
87        let chunk = &targets[idx..std::cmp::min(idx + chunk_size, targets.len())];
88        let mut handles = vec![];
89
90        for (chunk_idx, target) in chunk.iter().enumerate() {
91            let manager = Arc::clone(&manager);
92            let cli = cli.clone();
93            let target = target.clone();
94            let targets_len = targets.len();
95            let idx = idx + chunk_idx;
96            let user_requested_quit_thread = Arc::clone(&user_requested_quit);
97
98            // Spawn a thread for each target in the chunk
99            let handle = std::thread::spawn(move || {
100                // --- Begin: original per-target logic ---
101                let current_bin = env!("CARGO_PKG_NAME");
102                // Skip running our own binary.
103                if target.kind == TargetKind::Binary && target.name == current_bin {
104                    return Ok(()) as Result<()>;
105                }
106
107                let manifest_path = PathBuf::from(target.manifest_path.clone());
108                let builder = CargoCommandBuilder::new(
109                    &target.name,
110                    &manifest_path,
111                    &cli.subcommand,
112                    cli.filter,
113                    cli.cached,
114                    cli.default_binary_is_runner,
115                    cli.quiet || cli.json_all_targets,
116                    cli.detached,
117                    cli.cwd_wsr,
118                )
119                .with_target(&target)
120                .with_cli(&cli)
121                .with_extra_args(&cli.extra);
122
123                builder.print_command();
124
125                let maybe_backup =
126                    crate::e_manifest::maybe_patch_manifest_for_run(&target.manifest_path)
127                        .context("Failed to patch manifest for run")?;
128
129                // let system = Arc::new(Mutex::new(System::new_all()));
130                // std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL);
131                // let mut system_guard = system.lock().unwrap();
132                // system_guard.refresh_processes_specifics(
133                //     ProcessesToUpdate::All,
134                //     true,
135                //     ProcessRefreshKind::nothing().with_cpu(),
136                // );
137                // drop(system_guard);
138
139                let start = Arc::new(std::sync::Mutex::new(None));
140                let start_for_callback = Arc::clone(&start);
141                let pid = Arc::new(builder).run({
142                    let manager_ref = Arc::clone(&manager);
143                    let t = target.clone();
144                    let len = targets_len;
145                    let start = Arc::clone(&start_for_callback);
146                    // let system_clone = system.clone();
147                    move |pid, handle| {
148                        let stats = {
149                            let handle_guard = handle.lock().unwrap();
150                            let stats = handle_guard.stats.lock().unwrap().clone();
151                            stats
152                        };
153                        let runtime_start = if stats.is_comiler_target {
154                            stats.build_finished_time
155                        } else {
156                            stats.start_time
157                        };
158                        let mut start_guard = start.lock().unwrap();
159                        if start_guard.is_none() {
160                            *start_guard = Some(Instant::now());
161                        }
162                        if !cli.no_status_lines {
163                            let status_display = ProcessManager::format_process_status(
164                                pid,
165                                runtime_start,
166                                &t,
167                                (idx + 1, len),
168                            );
169                            ProcessManager::update_status_line(&status_display, true).ok();
170                        }
171                        manager_ref.register(pid, handle);
172                    }
173                })?;
174
175                let timeout = match cli.run_all {
176                    RunAll::Timeout(secs) => Duration::from_secs(secs),
177                    RunAll::Forever => Duration::from_secs(u64::MAX),
178                    RunAll::NotSpecified => Duration::from_secs(cli.wait),
179                };
180
181                // Use an Arc<Mutex<Option<Instant>>> so it can be set in the run callback and accessed in the main loop.
182                let start_for_callback = Arc::clone(&start);
183                // let target_name_for_timeout = target.name.clone();
184                // let timeout_thread = std::thread::spawn({
185                //     let manager = Arc::clone(&manager);
186                //     let pid = pid.clone();
187                //     move || {
188                //         // Wait until `start` is Some, i.e., the process has started running
189                //         while start.is_none() {
190                //             println!(
191                //                 "Waiting for process {} to start before applying timeout...",
192                //                 pid
193                //             );
194                //             std::thread::sleep(Duration::from_millis(50));
195                //         }
196                //         let start_time = start.expect("start should have been set");
197                //         while start_time.elapsed() < timeout {
198                //             std::thread::sleep(Duration::from_millis(500));
199                //             if manager.try_wait(pid).is_ok() {
200                //                 return; // Process finished naturally
201                //             }
202                //             println!(
203                //                 "Process {} is still running, waiting for timeout...",
204                //                 pid
205                //             );
206                //         }
207                //         // Timeout reached, kill the process
208                //         println!(
209                //             "\nTimeout reached for target {}. Killing child process {}.",
210                //             target_name_for_timeout, pid
211                //         );
212                //         manager.kill_by_pid(pid).ok();
213                //         manager.remove(pid);
214                //     }
215                // });
216
217                // Main thread continues to monitor the process
218                loop {
219                    if manager.is_alive(pid) {
220                        std::thread::sleep(Duration::from_millis(500));
221                        match manager.try_wait(pid) {
222                            Ok(Some(status)) => {
223                                println!("Process {} finished naturally. {:?}", pid, status);
224                                let hold = cli.detached_hold.unwrap_or(0);
225                                if cli.detached_hold.is_some() && hold > 0 {
226                                    println!("holding for the duration (detached_hold enabled). Sleeping for {} seconds...", hold);
227                                    std::thread::sleep(std::time::Duration::from_secs(hold as u64));
228                                }
229                                // manager.e_window_kill(pid);
230                                // manager.remove(pid);
231                                break;
232                            }
233                            _ => {
234                                // Process is still running.
235                                // We can check for timeout here as well.
236                                if let Ok(start_guard) = start_for_callback.lock() {
237                                    if let Some(start_time) = *start_guard {
238                                        if start_time.elapsed() >= timeout {
239                                            println!(
240                                            "\nTimeout reached for target {}. Killing child process {}.",
241                                            target.name,pid);
242                                            let hold = cli.detached_hold.unwrap_or(0);
243                                            if cli.detached_hold.is_some() && hold > 0 {
244                                                println!("holding for the duration (detached_hold enabled). Sleeping for {} seconds...", hold);
245                                                std::thread::sleep(std::time::Duration::from_secs(
246                                                    hold as u64,
247                                                ));
248                                            }
249                                            manager.kill_by_pid(pid).ok();
250                                            // manager.remove(pid);
251                                            // user_requested_kill_thread.store(true, Ordering::SeqCst);
252                                            // pids_to_kill_thread.lock().push(pid);
253                                            break;
254                                        }
255                                    }
256                                }
257                                std::thread::sleep(Duration::from_millis(100));
258                            }
259                        }
260                        if manager.has_signalled() > 0 {
261                            println!("Detected Ctrl+C. {}", manager.has_signalled());
262                            manager.remove(pid); // Clean up the process handle
263
264                            if manager.has_signalled() > 1 {
265                                if let Some(dur) = manager.time_between_signals() {
266                                    if dur < Duration::from_millis(350) {
267                                        println!("User requested quit two times quickly (<350ms).");
268                                        user_requested_quit_thread.store(true, Ordering::SeqCst);
269                                        break;
270                                    }
271                                }
272                            }
273                            println!("Dectected Ctrl+C, coninuing to next target.");
274                            manager.reset_signalled();
275                            break;
276                        }
277
278                        // let (_stats, runtime_start, end_time, status_display) = {
279                        //     let (stats, runtime_start, end_time) =
280                        //         if let Some(process_handle) = manager.get(pid) {
281                        //             if let Ok(handle) = process_handle.lock() {
282                        //                 let stats =
283                        //                     handle.stats.lock().map(|s| s.clone()).unwrap_or_default();
284                        //                 let runtime_start = if stats.is_comiler_target {
285                        //                     stats.build_finished_time
286                        //                 } else {
287                        //                     stats.start_time
288                        //                 };
289                        //                 let end_time = handle.result.end_time;
290                        //                 (stats, runtime_start, end_time)
291                        //             } else {
292                        //                 // If we can't lock, fallback to defaults
293                        //                 (Default::default(), None, None)
294                        //             }
295                        //         } else {
296                        //             // If process handle not found, fallback to defaults
297                        //             (Default::default(), None, None)
298                        //         };
299                        //     let status_display = if !cli.no_status_lines {
300                        //         ProcessManager::format_process_status(
301                        //             pid,
302                        //             runtime_start,
303                        //             &target,
304                        //             (idx + 1, targets_len),
305                        //         )
306                        //     } else {
307                        //         String::new()
308                        //     };
309                        //     (stats, runtime_start, end_time, status_display)
310                        // };
311
312                        // if cli.filter && !cli.no_status_lines {
313                        //     // let mut system_guard = system.lock().unwrap();
314                        //     // system_guard.refresh_processes_specifics(
315                        //     //     ProcessesToUpdate::All,
316                        //     //     true,
317                        //     //     ProcessRefreshKind::nothing().with_cpu(),
318                        //     // );
319                        //     // drop(system_guard);
320                        //     ProcessManager::update_status_line(&status_display, true).ok();
321                        // }
322                        // if runtime_start.is_some() {
323                        //     // let mut start_guard = start_for_callback.lock().unwrap();
324                        //     // if start_guard.is_none() {
325                        //     //     *start_guard = Some(Instant::now());
326                        //     // }
327                        //     if let Some(start_time) = *start_for_callback.lock().unwrap() {
328                        //         if start_time.elapsed() >= timeout {
329                        //             println!(
330                        //                 "\nTimeout reached for target {} after {:.2?}. Killing child process {}.",
331                        //                 target.name,
332                        //                 start_time.elapsed(),
333                        //                 pid
334                        //             );
335                        //             manager.e_window_kill(pid);
336                        //             manager.remove(pid);
337                        //             manager.kill_by_pid(pid).ok();
338                        //             break;
339                        //         }
340                        //     }
341                        //     // std::thread::sleep(Duration::from_millis(500));
342                        // } else if end_time.is_some() {
343                        //     println!("Process finished naturally.");
344                        //     manager.e_window_kill(pid);
345                        //     manager.remove(pid);
346                        //     break;
347                        // }
348                        std::thread::sleep(Duration::from_millis(100));
349                    }
350                }
351
352                // Wait for the timeout thread to finish
353                // let _ = timeout_thread.join();
354
355                if let Some(original) = maybe_backup {
356                    fs::write(&target.manifest_path, original)
357                        .context("Failed to restore patched manifest")?;
358                }
359                manager.generate_report(cli.gist);
360
361                Ok(())
362                // --- End: original per-target logic ---
363            });
364            if user_requested_quit.load(Ordering::SeqCst) {
365                break;
366            }
367            handles.push(handle);
368        }
369        // Check if the user requested to quit.
370        if user_requested_quit.load(Ordering::SeqCst) {
371            break;
372        }
373        // Wait for all threads in this chunk to finish
374        for handle in handles {
375            let _ = handle.join();
376        }
377        manager.e_window_kill_all();
378        idx += chunk_size;
379    }
380
381    Ok(Arc::clone(&user_requested_quit).load(Ordering::SeqCst))
382}
383
384// pub fn run_all_examples(cli: &Cli, filtered_targets: &[CargoTarget]) -> Result<()> {
385//     // If --quiet was provided, adjust RUSTFLAGS.
386//     set_rustflags_if_quiet(cli.quiet);
387
388//     // Factor out the prebuild logic.
389//     if cli.pre_build {
390//         crate::e_prebuild::prebuild_examples(filtered_targets)
391//             .context("Prebuild of targets failed")?;
392//     }
393//     let mut targets = filtered_targets.to_vec();
394//     targets.sort_by(|a, b| a.display_name.cmp(&b.display_name));
395//     // For each filtered target, run it with child process management.
396//     for target in targets {
397//         // Clear the screen before running each target.
398
399//         // use crossterm::{execute, terminal::{Clear, ClearType}};
400//         // use std::io::{stdout, Write};
401//         //         execute!(stdout(), Clear(ClearType::All), crossterm::cursor::MoveTo(0, 0))?;
402//         // std::io::Write::flush(&mut std::io::stdout()).unwrap();
403//         println!("Running target: {}", target.name);
404
405//         // Retrieve the current package name (or binary name) at compile time.
406//         let current_bin = env!("CARGO_PKG_NAME");
407//         // Avoid running our own binary if the target's name is the same.
408//         if target.kind == TargetKind::Binary && target.name == current_bin {
409//             continue;
410//         }
411
412//         // Determine the run flag and whether we need to pass the manifest path.
413//         let (run_flag, needs_manifest) = match target.kind {
414//             TargetKind::Example => ("--example", false),
415//             TargetKind::ExtendedExample => ("--example", true),
416//             TargetKind::Binary => ("--bin", false),
417//             TargetKind::ExtendedBinary => ("--bin", true),
418//             TargetKind::ManifestTauri => ("", true),
419//             TargetKind::ManifestTauriExample => ("", true),
420//             TargetKind::Test => ("--test", true),
421//             TargetKind::Manifest => ("", true),
422//             TargetKind::ManifestDioxus => ("", true),
423//             TargetKind::ManifestDioxusExample => ("", true),
424//             TargetKind::Bench => ("", true),
425//         };
426//         let mut cmd_parts = vec!["cargo".to_string()];
427//         cmd_parts.push("run".to_string());
428//         if cli.release {
429//             cmd_parts.push("--release".to_string());
430//         }
431//         // Pass --quiet if requested.
432//         if cli.quiet {
433//             cmd_parts.push("--quiet".to_string());
434//         }
435//         cmd_parts.push(run_flag.to_string());
436//         cmd_parts.push(target.name.clone());
437//         if needs_manifest {
438//             cmd_parts.push("--manifest-path".to_string());
439//             cmd_parts.push(
440//                 target
441//                     .manifest_path
442//                     .clone()
443//                     .to_str()
444//                     .unwrap_or_default()
445//                     .to_owned(),
446//             );
447//         }
448//         cmd_parts.extend(cli.extra.clone());
449
450//         // // Build a vector of command parts for logging.
451//         // let mut cmd_parts = vec!["cargo".to_string(), "run".to_string(), run_flag.to_string(), target.name.clone()];
452//         // if needs_manifest {
453//         //     cmd_parts.push("--manifest-path".to_string());
454//         //     cmd_parts.push(target.manifest_path.clone());
455//         // }
456//         // // Append any extra CLI arguments.
457//         // cmd_parts.extend(cli.extra.clone());
458
459//         // Print out the full command that will be run.
460//         let key = prompt(&format!("Full command: {}", cmd_parts.join(" ")), 2)?;
461//         if let Some('q') = key {
462//             println!("User requested quit.");
463//             break;
464//         }
465
466//         // Clear the screen before running each target.
467//         //println!("\x1B[2J\x1B[H");
468
469//         // Build the command for execution.
470//         let mut command = Command::new("cargo");
471//         command.arg("run");
472//         if cli.release {
473//             command.arg("--release");
474//         }
475//         if cli.quiet {
476//             command.arg("--quiet");
477//         }
478//         command.arg(run_flag).arg(&target.name);
479//         if needs_manifest {
480//             command.args(&[
481//                 "--manifest-path",
482//                 &target.manifest_path.to_str().unwrap_or_default().to_owned(),
483//             ]);
484//         }
485
486//         // --- Inject required-features support using our helper ---
487//         if let Some(features) = crate::e_manifest::get_required_features_from_manifest(
488//             std::path::Path::new(&target.manifest_path),
489//             &target.kind,
490//             &target.name,
491//         ) {
492//             command.args(&["--features", &features]);
493//         }
494//         // --- End required-features support ---
495
496//         // Append any extra CLI arguments.
497//         command.args(&cli.extra);
498
499//         // Spawn the child process.
500//         let child = command
501//             .spawn()
502//             .with_context(|| format!("Failed to spawn cargo run for target {}", target.name))?;
503//         {
504//             let mut global = crate::e_runner::GLOBAL_CHILD.lock().unwrap();
505//             *global = Some(child);
506//         }
507//         // Let the target run for the specified duration.
508//         let run_duration = Duration::from_secs(cli.wait);
509//         thread::sleep(run_duration);
510
511//         // Kill the process (ignoring errors if it already terminated).
512
513//         // Decide on the run duration per target and use it accordingly:
514//         // Determine behavior based on the run_all flag:
515//         let output = {
516//             let mut global = crate::e_runner::GLOBAL_CHILD.lock().unwrap();
517//             if let Some(mut child) = global.take() {
518//                 match cli.run_all {
519//                     RunAll::Timeout(timeout_secs) => {
520//                         let message = format!(
521//                             "Press any key to continue (timeout in {} seconds)...",
522//                             timeout_secs
523//                         );
524//                         let key = prompt(&message, timeout_secs)?;
525//                         if let Some('q') = key {
526//                             println!("User requested quit.");
527//                             // Terminate the process and break out of the loop.
528//                             child.kill().ok();
529//                             break;
530//                         }
531//                         child.kill().ok();
532//                         child.wait_with_output().with_context(|| {
533//                             format!("Failed to wait on cargo run for target {}", target.name)
534//                         })?
535//                     }
536//                     RunAll::Forever => {
537//                         let key = prompt(&"", 0)?;
538//                         if let Some('q') = key {
539//                             println!("User requested quit.");
540//                             // Terminate the process and break out of the loop.
541//                             child.kill().ok();
542//                             break;
543//                         } // Run until natural termination.
544//                         child.wait_with_output().with_context(|| {
545//                             format!("Failed to wait on cargo run for target {}", target.name)
546//                         })?
547//                     }
548//                     RunAll::NotSpecified => {
549//                         let key = prompt(&"", cli.wait)?;
550//                         if let Some('q') = key {
551//                             println!("User requested quit.");
552//                             // Terminate the process and break out of the loop.
553//                             child.kill().ok();
554//                             break;
555//                         }
556//                         child.kill().ok();
557//                         child.wait_with_output().with_context(|| {
558//                             format!("Failed to wait on cargo run for target {}", target.name)
559//                         })?
560//                     }
561//                 }
562//             } else {
563//                 return Err(anyhow::anyhow!("No child process found"));
564//             }
565//         };
566
567//         if !output.stderr.is_empty() {
568//             eprintln!(
569//                 "Target '{}' produced errors:\n{}",
570//                 target.name,
571//                 String::from_utf8_lossy(&output.stderr)
572//             );
573//         }
574//     }
575//     Ok(())
576// }
577
578use std::{env, fs};
579
580/// If quiet mode is enabled, ensure that RUSTFLAGS contains "-Awarnings".
581/// If RUSTFLAGS is already set, and it does not contain "-Awarnings", then append it.
582pub fn set_rustflags_if_quiet(quiet: bool) {
583    if quiet {
584        let current_flags = env::var("RUSTFLAGS").unwrap_or_else(|_| "".to_string());
585        if !current_flags.contains("-Awarnings") {
586            let new_flags = if current_flags.trim().is_empty() {
587                "-Awarnings".to_string()
588            } else {
589                format!("{} -Awarnings", current_flags)
590            };
591            env::set_var("RUSTFLAGS", new_flags);
592        }
593    }
594}