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                )
116                .with_target(&target)
117                .with_cli(&cli)
118                .with_extra_args(&cli.extra);
119
120                builder.print_command();
121
122                let maybe_backup =
123                    crate::e_manifest::maybe_patch_manifest_for_run(&target.manifest_path)
124                        .context("Failed to patch manifest for run")?;
125
126                // let system = Arc::new(Mutex::new(System::new_all()));
127                // std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL);
128                // let mut system_guard = system.lock().unwrap();
129                // system_guard.refresh_processes_specifics(
130                //     ProcessesToUpdate::All,
131                //     true,
132                //     ProcessRefreshKind::nothing().with_cpu(),
133                // );
134                // drop(system_guard);
135
136                let pid = Arc::new(builder).run({
137                    let manager_ref = Arc::clone(&manager);
138                    let t = target.clone();
139                    let len = targets_len;
140                    // let system_clone = system.clone();
141                    move |pid, handle| {
142                        let stats = handle.stats.lock().unwrap().clone();
143                        let runtime_start = if stats.is_comiler_target {
144                            stats.build_finished_time
145                        } else {
146                            stats.start_time
147                        };
148                        if !cli.no_status_lines {
149                            let status_display = ProcessManager::format_process_status(
150                                pid,
151                                runtime_start,
152                                &t,
153                                (idx + 1, len),
154                            );
155                            ProcessManager::update_status_line(&status_display, true).ok();
156                        }
157                        manager_ref.register(handle);
158                    }
159                })?;
160
161                let timeout = match cli.run_all {
162                    RunAll::Timeout(secs) => Duration::from_secs(secs),
163                    RunAll::Forever => Duration::from_secs(u64::MAX),
164                    RunAll::NotSpecified => Duration::from_secs(cli.wait),
165                };
166
167                let mut start = None;
168                loop {
169                    match manager.try_wait(pid) {
170                        Ok(Some(_status)) => {
171                            manager.remove(pid);
172                            break;
173                        }
174                        _ => {
175                            // Process is still running.
176                            //println!("Process is still running.");
177                        }
178                    }
179                    if manager.has_signalled() > 0 {
180                        println!("Detected Ctrl+C. {}", manager.has_signalled());
181                        manager.remove(pid); // Clean up the process handle
182
183                        if manager.has_signalled() > 1 {
184                            if let Some(dur) = manager.time_between_signals() {
185                                if dur < Duration::from_millis(350) {
186                                    println!("User requested quit two times quickly (<350ms).");
187                                    user_requested_quit_thread.store(true, Ordering::SeqCst);
188                                    break;
189                                }
190                            }
191                        }
192                        println!("Dectected Ctrl+C, coninuing to next target.");
193                        manager.reset_signalled();
194                        break;
195                    }
196
197                    let (_stats, runtime_start, end_time, status_display) = {
198                        let (stats, runtime_start, end_time) =
199                            if let Some(process_handle) = manager.get(pid) {
200                                if let Ok(handle) = process_handle.lock() {
201                                    let stats =
202                                        handle.stats.lock().map(|s| s.clone()).unwrap_or_default();
203                                    let runtime_start = if stats.is_comiler_target {
204                                        stats.build_finished_time
205                                    } else {
206                                        stats.start_time
207                                    };
208                                    let end_time = handle.result.end_time;
209                                    (stats, runtime_start, end_time)
210                                } else {
211                                    // If we can't lock, fallback to defaults
212                                    (Default::default(), None, None)
213                                }
214                            } else {
215                                // If process handle not found, fallback to defaults
216                                (Default::default(), None, None)
217                            };
218                        let status_display = if !cli.no_status_lines {
219                            ProcessManager::format_process_status(
220                                pid,
221                                runtime_start,
222                                &target,
223                                (idx + 1, targets_len),
224                            )
225                        } else {
226                            String::new()
227                        };
228                        (stats, runtime_start, end_time, status_display)
229                    };
230
231                    if cli.filter && !cli.no_status_lines {
232                        // let mut system_guard = system.lock().unwrap();
233                        // system_guard.refresh_processes_specifics(
234                        //     ProcessesToUpdate::All,
235                        //     true,
236                        //     ProcessRefreshKind::nothing().with_cpu(),
237                        // );
238                        // drop(system_guard);
239                        ProcessManager::update_status_line(&status_display, true).ok();
240                    }
241                    if runtime_start.is_some() {
242                        if start.is_none() {
243                            start = Some(Instant::now());
244                        }
245                        if start.expect("start should have set").elapsed() >= timeout {
246                            println!(
247                                "\nTimeout reached for target {}. Killing child process {}.",
248                                target.name, pid
249                            );
250                            manager.kill_by_pid(pid).ok();
251                            manager.remove(pid);
252                            break;
253                        }
254                        std::thread::sleep(Duration::from_millis(500));
255                    } else if end_time.is_some() {
256                        println!("Process finished naturally.");
257                        manager.remove(pid);
258                        break;
259                    }
260                    std::thread::sleep(Duration::from_millis(100));
261                }
262
263                if let Some(original) = maybe_backup {
264                    fs::write(&target.manifest_path, original)
265                        .context("Failed to restore patched manifest")?;
266                }
267                manager.generate_report(cli.gist);
268
269                Ok(())
270                // --- End: original per-target logic ---
271            });
272            if user_requested_quit.load(Ordering::SeqCst) {
273                break;
274            }
275            handles.push(handle);
276        }
277        // Check if the user requested to quit.
278        if user_requested_quit.load(Ordering::SeqCst) {
279            break;
280        }
281        // Wait for all threads in this chunk to finish
282        for handle in handles {
283            let _ = handle.join();
284        }
285
286        idx += chunk_size;
287    }
288
289    Ok(Arc::clone(&user_requested_quit).load(Ordering::SeqCst))
290}
291
292// pub fn run_all_examples(cli: &Cli, filtered_targets: &[CargoTarget]) -> Result<()> {
293//     // If --quiet was provided, adjust RUSTFLAGS.
294//     set_rustflags_if_quiet(cli.quiet);
295
296//     // Factor out the prebuild logic.
297//     if cli.pre_build {
298//         crate::e_prebuild::prebuild_examples(filtered_targets)
299//             .context("Prebuild of targets failed")?;
300//     }
301//     let mut targets = filtered_targets.to_vec();
302//     targets.sort_by(|a, b| a.display_name.cmp(&b.display_name));
303//     // For each filtered target, run it with child process management.
304//     for target in targets {
305//         // Clear the screen before running each target.
306
307//         // use crossterm::{execute, terminal::{Clear, ClearType}};
308//         // use std::io::{stdout, Write};
309//         //         execute!(stdout(), Clear(ClearType::All), crossterm::cursor::MoveTo(0, 0))?;
310//         // std::io::Write::flush(&mut std::io::stdout()).unwrap();
311//         println!("Running target: {}", target.name);
312
313//         // Retrieve the current package name (or binary name) at compile time.
314//         let current_bin = env!("CARGO_PKG_NAME");
315//         // Avoid running our own binary if the target's name is the same.
316//         if target.kind == TargetKind::Binary && target.name == current_bin {
317//             continue;
318//         }
319
320//         // Determine the run flag and whether we need to pass the manifest path.
321//         let (run_flag, needs_manifest) = match target.kind {
322//             TargetKind::Example => ("--example", false),
323//             TargetKind::ExtendedExample => ("--example", true),
324//             TargetKind::Binary => ("--bin", false),
325//             TargetKind::ExtendedBinary => ("--bin", true),
326//             TargetKind::ManifestTauri => ("", true),
327//             TargetKind::ManifestTauriExample => ("", true),
328//             TargetKind::Test => ("--test", true),
329//             TargetKind::Manifest => ("", true),
330//             TargetKind::ManifestDioxus => ("", true),
331//             TargetKind::ManifestDioxusExample => ("", true),
332//             TargetKind::Bench => ("", true),
333//         };
334//         let mut cmd_parts = vec!["cargo".to_string()];
335//         cmd_parts.push("run".to_string());
336//         if cli.release {
337//             cmd_parts.push("--release".to_string());
338//         }
339//         // Pass --quiet if requested.
340//         if cli.quiet {
341//             cmd_parts.push("--quiet".to_string());
342//         }
343//         cmd_parts.push(run_flag.to_string());
344//         cmd_parts.push(target.name.clone());
345//         if needs_manifest {
346//             cmd_parts.push("--manifest-path".to_string());
347//             cmd_parts.push(
348//                 target
349//                     .manifest_path
350//                     .clone()
351//                     .to_str()
352//                     .unwrap_or_default()
353//                     .to_owned(),
354//             );
355//         }
356//         cmd_parts.extend(cli.extra.clone());
357
358//         // // Build a vector of command parts for logging.
359//         // let mut cmd_parts = vec!["cargo".to_string(), "run".to_string(), run_flag.to_string(), target.name.clone()];
360//         // if needs_manifest {
361//         //     cmd_parts.push("--manifest-path".to_string());
362//         //     cmd_parts.push(target.manifest_path.clone());
363//         // }
364//         // // Append any extra CLI arguments.
365//         // cmd_parts.extend(cli.extra.clone());
366
367//         // Print out the full command that will be run.
368//         let key = prompt(&format!("Full command: {}", cmd_parts.join(" ")), 2)?;
369//         if let Some('q') = key {
370//             println!("User requested quit.");
371//             break;
372//         }
373
374//         // Clear the screen before running each target.
375//         //println!("\x1B[2J\x1B[H");
376
377//         // Build the command for execution.
378//         let mut command = Command::new("cargo");
379//         command.arg("run");
380//         if cli.release {
381//             command.arg("--release");
382//         }
383//         if cli.quiet {
384//             command.arg("--quiet");
385//         }
386//         command.arg(run_flag).arg(&target.name);
387//         if needs_manifest {
388//             command.args(&[
389//                 "--manifest-path",
390//                 &target.manifest_path.to_str().unwrap_or_default().to_owned(),
391//             ]);
392//         }
393
394//         // --- Inject required-features support using our helper ---
395//         if let Some(features) = crate::e_manifest::get_required_features_from_manifest(
396//             std::path::Path::new(&target.manifest_path),
397//             &target.kind,
398//             &target.name,
399//         ) {
400//             command.args(&["--features", &features]);
401//         }
402//         // --- End required-features support ---
403
404//         // Append any extra CLI arguments.
405//         command.args(&cli.extra);
406
407//         // Spawn the child process.
408//         let child = command
409//             .spawn()
410//             .with_context(|| format!("Failed to spawn cargo run for target {}", target.name))?;
411//         {
412//             let mut global = crate::e_runner::GLOBAL_CHILD.lock().unwrap();
413//             *global = Some(child);
414//         }
415//         // Let the target run for the specified duration.
416//         let run_duration = Duration::from_secs(cli.wait);
417//         thread::sleep(run_duration);
418
419//         // Kill the process (ignoring errors if it already terminated).
420
421//         // Decide on the run duration per target and use it accordingly:
422//         // Determine behavior based on the run_all flag:
423//         let output = {
424//             let mut global = crate::e_runner::GLOBAL_CHILD.lock().unwrap();
425//             if let Some(mut child) = global.take() {
426//                 match cli.run_all {
427//                     RunAll::Timeout(timeout_secs) => {
428//                         let message = format!(
429//                             "Press any key to continue (timeout in {} seconds)...",
430//                             timeout_secs
431//                         );
432//                         let key = prompt(&message, timeout_secs)?;
433//                         if let Some('q') = key {
434//                             println!("User requested quit.");
435//                             // Terminate the process and break out of the loop.
436//                             child.kill().ok();
437//                             break;
438//                         }
439//                         child.kill().ok();
440//                         child.wait_with_output().with_context(|| {
441//                             format!("Failed to wait on cargo run for target {}", target.name)
442//                         })?
443//                     }
444//                     RunAll::Forever => {
445//                         let key = prompt(&"", 0)?;
446//                         if let Some('q') = key {
447//                             println!("User requested quit.");
448//                             // Terminate the process and break out of the loop.
449//                             child.kill().ok();
450//                             break;
451//                         } // Run until natural termination.
452//                         child.wait_with_output().with_context(|| {
453//                             format!("Failed to wait on cargo run for target {}", target.name)
454//                         })?
455//                     }
456//                     RunAll::NotSpecified => {
457//                         let key = prompt(&"", cli.wait)?;
458//                         if let Some('q') = key {
459//                             println!("User requested quit.");
460//                             // Terminate the process and break out of the loop.
461//                             child.kill().ok();
462//                             break;
463//                         }
464//                         child.kill().ok();
465//                         child.wait_with_output().with_context(|| {
466//                             format!("Failed to wait on cargo run for target {}", target.name)
467//                         })?
468//                     }
469//                 }
470//             } else {
471//                 return Err(anyhow::anyhow!("No child process found"));
472//             }
473//         };
474
475//         if !output.stderr.is_empty() {
476//             eprintln!(
477//                 "Target '{}' produced errors:\n{}",
478//                 target.name,
479//                 String::from_utf8_lossy(&output.stderr)
480//             );
481//         }
482//     }
483//     Ok(())
484// }
485
486use std::{env, fs};
487
488/// If quiet mode is enabled, ensure that RUSTFLAGS contains "-Awarnings".
489/// If RUSTFLAGS is already set, and it does not contain "-Awarnings", then append it.
490pub fn set_rustflags_if_quiet(quiet: bool) {
491    if quiet {
492        let current_flags = env::var("RUSTFLAGS").unwrap_or_else(|_| "".to_string());
493        if !current_flags.contains("-Awarnings") {
494            let new_flags = if current_flags.trim().is_empty() {
495                "-Awarnings".to_string()
496            } else {
497                format!("{} -Awarnings", current_flags)
498            };
499            env::set_var("RUSTFLAGS", new_flags);
500        }
501    }
502}