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, Mutex};
9use std::time::Duration;
10use std::time::Instant;
11use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, System};
12
13#[cfg(unix)]
14use nix::sys::signal::{kill, Signal};
15#[cfg(unix)]
16use nix::unistd::Pid;
17
18// #[cfg(target_os = "windows")]
19// use std::os::windows::process::CommandExt;
20
21// #[cfg(target_os = "windows")]
22// const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
23
24// #[cfg(target_os = "windows")]
25// fn send_ctrl_c(child: &mut Child) -> Result<()> {
26//     println!("Sending CTRL-C to child process...");
27//     use windows::Win32::System::Console::{GenerateConsoleCtrlEvent, CTRL_C_EVENT};
28
29//     // Send CTRL+C to the child process group.
30//     // The child must have been spawned with CREATE_NEW_PROCESS_GROUP.
31//     let result = unsafe { GenerateConsoleCtrlEvent(CTRL_C_EVENT, child.id()) };
32//     if result.is_err() {
33//         return Err(anyhow::anyhow!("Failed to send CTRL_C_EVENT on Windows"));
34//     }
35
36//     // Allow some time for the child to handle the signal gracefully.
37//     std::thread::sleep(std::time::Duration::from_millis(1000));
38
39//     Ok(())
40// }
41
42#[cfg(not(target_os = "windows"))]
43pub fn send_ctrl_c(child: &mut std::process::Child) -> Result<()> {
44    // On Unix, send SIGINT to the child.
45    kill(Pid::from_raw(child.id() as i32), Signal::SIGINT).context("Failed to send SIGINT")?;
46    // Wait briefly to allow graceful shutdown.
47    std::thread::sleep(Duration::from_millis(2000));
48    Ok(())
49}
50
51/// Runs all filtered targets with prebuild, child process management, and timeout‐based termination.
52///
53/// If the CLI flag `pre_build` is enabled, this function first prebuilds all targets by invoking
54/// `cargo build` with the appropriate flags (using `--example` or `--bin` and, for extended targets,
55/// the `--manifest-path` flag). Then it spawns a child process for each target using `cargo run`,
56/// waits for the duration specified by `cli.wait`, kills the child process, and then checks its output.
57///
58/// # Parameters
59///
60/// - `cli`: A reference to the CLI configuration (containing flags like `pre_build`, `wait`, and extra arguments).
61/// - `filtered_targets`: A slice of `Example` instances representing the targets to run.
62///
63/// # Errors
64///
65/// Returns an error if the prebuild step fails or if any child process fails to spawn or complete.
66pub fn run_all_examples(
67    manager: Arc<ProcessManager>,
68    cli: &crate::Cli,
69    filtered_targets: &[CargoTarget],
70) -> Result<bool> {
71    // Adjust RUSTFLAGS if --quiet was provided.
72    set_rustflags_if_quiet(cli.quiet);
73
74    // Prebuild targets if requested.
75    if cli.pre_build {
76        crate::e_prebuild::prebuild_examples(filtered_targets)
77            .context("Prebuild of targets failed")?;
78    }
79
80    let mut targets = filtered_targets.to_vec();
81    targets.sort_by(|a, b| a.display_name.cmp(&b.display_name));
82
83    let user_requested_quit = Arc::new(AtomicBool::new(false));
84
85    let chunk_size = cli.run_at_a_time;
86    let mut idx = 0;
87    while idx < targets.len() {
88        let chunk = &targets[idx..std::cmp::min(idx + chunk_size, targets.len())];
89        let mut handles = vec![];
90
91        for (chunk_idx, target) in chunk.iter().enumerate() {
92            let manager = Arc::clone(&manager);
93            let cli = cli.clone();
94            let target = target.clone();
95            let targets_len = targets.len();
96            let idx = idx + chunk_idx;
97            let user_requested_quit_thread = Arc::clone(&user_requested_quit);
98
99            // Spawn a thread for each target in the chunk
100            let handle = std::thread::spawn(move || {
101                // --- Begin: original per-target logic ---
102                let current_bin = env!("CARGO_PKG_NAME");
103                // Skip running our own binary.
104                if target.kind == TargetKind::Binary && target.name == current_bin {
105                    return Ok(()) as Result<()>;
106                }
107
108                let manifest_path = PathBuf::from(target.manifest_path.clone());
109                let builder = CargoCommandBuilder::new(
110                    &target.name,
111                    &manifest_path,
112                    &cli.subcommand,
113                    cli.filter,
114                )
115                .with_target(&target)
116                .with_cli(&cli)
117                .with_extra_args(&cli.extra);
118
119                builder.print_command();
120
121                let maybe_backup =
122                    crate::e_manifest::maybe_patch_manifest_for_run(&target.manifest_path)
123                        .context("Failed to patch manifest for run")?;
124
125                let system = Arc::new(Mutex::new(System::new_all()));
126                std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL);
127                let mut system_guard = system.lock().unwrap();
128                system_guard.refresh_processes_specifics(
129                    ProcessesToUpdate::All,
130                    true,
131                    ProcessRefreshKind::nothing().with_cpu(),
132                );
133                drop(system_guard);
134
135                let pid = Arc::new(builder).run({
136                    let manager_ref = Arc::clone(&manager);
137                    let t = target.clone();
138                    let len = targets_len;
139                    let system_clone = system.clone();
140                    move |pid, handle| {
141                        let stats = handle.stats.lock().unwrap().clone();
142                        let runtime_start = if stats.is_comiler_target {
143                            stats.build_finished_time
144                        } else {
145                            stats.start_time
146                        };
147                        if !cli.no_status_lines {
148                            let status_display = ProcessManager::format_process_status(
149                                pid,
150                                runtime_start,
151                                system_clone,
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 process_handle = manager.get(pid).unwrap();
199                        let handle = process_handle.lock().unwrap();
200                        let stats = handle.stats.lock().unwrap().clone();
201                        let runtime_start = if stats.is_comiler_target {
202                            stats.build_finished_time
203                        } else {
204                            stats.start_time
205                        };
206                        let end_time = handle.result.end_time;
207                        drop(handle);
208                        let status_display = if !cli.no_status_lines {
209                            ProcessManager::format_process_status(
210                                pid,
211                                runtime_start,
212                                system.clone(),
213                                &target,
214                                (idx + 1, targets_len),
215                            )
216                        } else {
217                            String::new()
218                        };
219                        (stats, runtime_start, end_time, status_display)
220                    };
221
222                    if cli.filter && !cli.no_status_lines {
223                        let mut system_guard = system.lock().unwrap();
224                        system_guard.refresh_processes_specifics(
225                            ProcessesToUpdate::All,
226                            true,
227                            ProcessRefreshKind::nothing().with_cpu(),
228                        );
229                        drop(system_guard);
230                        ProcessManager::update_status_line(&status_display, true).ok();
231                    }
232                    if runtime_start.is_some() {
233                        if start.is_none() {
234                            start = Some(Instant::now());
235                        }
236                        if start.expect("start should have set").elapsed() >= timeout {
237                            println!(
238                                "\nTimeout reached for target {}. Killing child process {}.",
239                                target.name, pid
240                            );
241                            manager.kill_by_pid(pid).ok();
242                            manager.remove(pid);
243                            break;
244                        }
245                        std::thread::sleep(Duration::from_millis(500));
246                    } else if end_time.is_some() {
247                        println!("Process finished naturally.");
248                        manager.remove(pid);
249                        break;
250                    }
251                    std::thread::sleep(Duration::from_millis(100));
252                }
253
254                if let Some(original) = maybe_backup {
255                    fs::write(&target.manifest_path, original)
256                        .context("Failed to restore patched manifest")?;
257                }
258                manager.generate_report(cli.gist);
259
260                Ok(())
261                // --- End: original per-target logic ---
262            });
263            if user_requested_quit.load(Ordering::SeqCst) {
264                break;
265            }
266            handles.push(handle);
267        }
268        // Check if the user requested to quit.
269        if user_requested_quit.load(Ordering::SeqCst) {
270            break;
271        }
272        // Wait for all threads in this chunk to finish
273        for handle in handles {
274            let _ = handle.join();
275        }
276
277        idx += chunk_size;
278    }
279
280    Ok(Arc::clone(&user_requested_quit).load(Ordering::SeqCst))
281}
282
283// pub fn run_all_examples(cli: &Cli, filtered_targets: &[CargoTarget]) -> Result<()> {
284//     // If --quiet was provided, adjust RUSTFLAGS.
285//     set_rustflags_if_quiet(cli.quiet);
286
287//     // Factor out the prebuild logic.
288//     if cli.pre_build {
289//         crate::e_prebuild::prebuild_examples(filtered_targets)
290//             .context("Prebuild of targets failed")?;
291//     }
292//     let mut targets = filtered_targets.to_vec();
293//     targets.sort_by(|a, b| a.display_name.cmp(&b.display_name));
294//     // For each filtered target, run it with child process management.
295//     for target in targets {
296//         // Clear the screen before running each target.
297
298//         // use crossterm::{execute, terminal::{Clear, ClearType}};
299//         // use std::io::{stdout, Write};
300//         //         execute!(stdout(), Clear(ClearType::All), crossterm::cursor::MoveTo(0, 0))?;
301//         // std::io::Write::flush(&mut std::io::stdout()).unwrap();
302//         println!("Running target: {}", target.name);
303
304//         // Retrieve the current package name (or binary name) at compile time.
305//         let current_bin = env!("CARGO_PKG_NAME");
306//         // Avoid running our own binary if the target's name is the same.
307//         if target.kind == TargetKind::Binary && target.name == current_bin {
308//             continue;
309//         }
310
311//         // Determine the run flag and whether we need to pass the manifest path.
312//         let (run_flag, needs_manifest) = match target.kind {
313//             TargetKind::Example => ("--example", false),
314//             TargetKind::ExtendedExample => ("--example", true),
315//             TargetKind::Binary => ("--bin", false),
316//             TargetKind::ExtendedBinary => ("--bin", true),
317//             TargetKind::ManifestTauri => ("", true),
318//             TargetKind::ManifestTauriExample => ("", true),
319//             TargetKind::Test => ("--test", true),
320//             TargetKind::Manifest => ("", true),
321//             TargetKind::ManifestDioxus => ("", true),
322//             TargetKind::ManifestDioxusExample => ("", true),
323//             TargetKind::Bench => ("", true),
324//         };
325//         let mut cmd_parts = vec!["cargo".to_string()];
326//         cmd_parts.push("run".to_string());
327//         if cli.release {
328//             cmd_parts.push("--release".to_string());
329//         }
330//         // Pass --quiet if requested.
331//         if cli.quiet {
332//             cmd_parts.push("--quiet".to_string());
333//         }
334//         cmd_parts.push(run_flag.to_string());
335//         cmd_parts.push(target.name.clone());
336//         if needs_manifest {
337//             cmd_parts.push("--manifest-path".to_string());
338//             cmd_parts.push(
339//                 target
340//                     .manifest_path
341//                     .clone()
342//                     .to_str()
343//                     .unwrap_or_default()
344//                     .to_owned(),
345//             );
346//         }
347//         cmd_parts.extend(cli.extra.clone());
348
349//         // // Build a vector of command parts for logging.
350//         // let mut cmd_parts = vec!["cargo".to_string(), "run".to_string(), run_flag.to_string(), target.name.clone()];
351//         // if needs_manifest {
352//         //     cmd_parts.push("--manifest-path".to_string());
353//         //     cmd_parts.push(target.manifest_path.clone());
354//         // }
355//         // // Append any extra CLI arguments.
356//         // cmd_parts.extend(cli.extra.clone());
357
358//         // Print out the full command that will be run.
359//         let key = prompt(&format!("Full command: {}", cmd_parts.join(" ")), 2)?;
360//         if let Some('q') = key {
361//             println!("User requested quit.");
362//             break;
363//         }
364
365//         // Clear the screen before running each target.
366//         //println!("\x1B[2J\x1B[H");
367
368//         // Build the command for execution.
369//         let mut command = Command::new("cargo");
370//         command.arg("run");
371//         if cli.release {
372//             command.arg("--release");
373//         }
374//         if cli.quiet {
375//             command.arg("--quiet");
376//         }
377//         command.arg(run_flag).arg(&target.name);
378//         if needs_manifest {
379//             command.args(&[
380//                 "--manifest-path",
381//                 &target.manifest_path.to_str().unwrap_or_default().to_owned(),
382//             ]);
383//         }
384
385//         // --- Inject required-features support using our helper ---
386//         if let Some(features) = crate::e_manifest::get_required_features_from_manifest(
387//             std::path::Path::new(&target.manifest_path),
388//             &target.kind,
389//             &target.name,
390//         ) {
391//             command.args(&["--features", &features]);
392//         }
393//         // --- End required-features support ---
394
395//         // Append any extra CLI arguments.
396//         command.args(&cli.extra);
397
398//         // Spawn the child process.
399//         let child = command
400//             .spawn()
401//             .with_context(|| format!("Failed to spawn cargo run for target {}", target.name))?;
402//         {
403//             let mut global = crate::e_runner::GLOBAL_CHILD.lock().unwrap();
404//             *global = Some(child);
405//         }
406//         // Let the target run for the specified duration.
407//         let run_duration = Duration::from_secs(cli.wait);
408//         thread::sleep(run_duration);
409
410//         // Kill the process (ignoring errors if it already terminated).
411
412//         // Decide on the run duration per target and use it accordingly:
413//         // Determine behavior based on the run_all flag:
414//         let output = {
415//             let mut global = crate::e_runner::GLOBAL_CHILD.lock().unwrap();
416//             if let Some(mut child) = global.take() {
417//                 match cli.run_all {
418//                     RunAll::Timeout(timeout_secs) => {
419//                         let message = format!(
420//                             "Press any key to continue (timeout in {} seconds)...",
421//                             timeout_secs
422//                         );
423//                         let key = prompt(&message, timeout_secs)?;
424//                         if let Some('q') = key {
425//                             println!("User requested quit.");
426//                             // Terminate the process and break out of the loop.
427//                             child.kill().ok();
428//                             break;
429//                         }
430//                         child.kill().ok();
431//                         child.wait_with_output().with_context(|| {
432//                             format!("Failed to wait on cargo run for target {}", target.name)
433//                         })?
434//                     }
435//                     RunAll::Forever => {
436//                         let key = prompt(&"", 0)?;
437//                         if let Some('q') = key {
438//                             println!("User requested quit.");
439//                             // Terminate the process and break out of the loop.
440//                             child.kill().ok();
441//                             break;
442//                         } // Run until natural termination.
443//                         child.wait_with_output().with_context(|| {
444//                             format!("Failed to wait on cargo run for target {}", target.name)
445//                         })?
446//                     }
447//                     RunAll::NotSpecified => {
448//                         let key = prompt(&"", cli.wait)?;
449//                         if let Some('q') = key {
450//                             println!("User requested quit.");
451//                             // Terminate the process and break out of the loop.
452//                             child.kill().ok();
453//                             break;
454//                         }
455//                         child.kill().ok();
456//                         child.wait_with_output().with_context(|| {
457//                             format!("Failed to wait on cargo run for target {}", target.name)
458//                         })?
459//                     }
460//                 }
461//             } else {
462//                 return Err(anyhow::anyhow!("No child process found"));
463//             }
464//         };
465
466//         if !output.stderr.is_empty() {
467//             eprintln!(
468//                 "Target '{}' produced errors:\n{}",
469//                 target.name,
470//                 String::from_utf8_lossy(&output.stderr)
471//             );
472//         }
473//     }
474//     Ok(())
475// }
476
477use std::{env, fs};
478
479/// If quiet mode is enabled, ensure that RUSTFLAGS contains "-Awarnings".
480/// If RUSTFLAGS is already set, and it does not contain "-Awarnings", then append it.
481pub fn set_rustflags_if_quiet(quiet: bool) {
482    if quiet {
483        let current_flags = env::var("RUSTFLAGS").unwrap_or_else(|_| "".to_string());
484        if !current_flags.contains("-Awarnings") {
485            let new_flags = if current_flags.trim().is_empty() {
486                "-Awarnings".to_string()
487            } else {
488                format!("{} -Awarnings", current_flags)
489            };
490            env::set_var("RUSTFLAGS", new_flags);
491        }
492    }
493}