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::Arc;
8use std::time::Duration;
9use std::time::Instant;
10use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, System};
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    // let _ = crate::e_runner::register_ctrlc_handler();
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 manager = ProcessManager::new(&cli);
84
85    let user_requested_quit = false;
86    //for target in targets {
87    for (idx, target) in targets.iter().enumerate() {
88        println!("\nRunning target: {}", target.name);
89
90        let current_bin = env!("CARGO_PKG_NAME");
91        // Skip running our own binary.
92        if target.kind == TargetKind::Binary && target.name == current_bin {
93            continue;
94        }
95
96        // Build the command using CargoCommandBuilder.
97        let manifest_path = PathBuf::from(target.manifest_path.clone());
98        let builder = CargoCommandBuilder::new(&manifest_path, &cli.subcommand, cli.filter)
99            .with_target(&target)
100            .with_required_features(&target.manifest_path, &target)
101            .with_cli(cli)
102            .with_extra_args(&cli.extra);
103
104        // For debugging, print out the full command.
105        log::trace!(
106            "{} {}",
107            builder.alternate_cmd.as_deref().unwrap_or("cargo"),
108            builder.args.join(" ")
109        );
110        // PROMPT let key = crate::e_prompts::prompt(&format!("Full command: {}", cmd_debug), 2)?;
111        // if let Some('q') = key {
112        //     user_requested_quit = true;
113        //     println!("User requested quit.");
114        //     break;
115        // }
116
117        // Build the std::process::Command.
118        // let mut command = builder.build_command();
119        // #[cfg(target_os = "windows")]
120        // {
121        //     command.creation_flags(CREATE_NEW_PROCESS_GROUP);
122        // }
123
124        // Before spawning, check for workspace manifest errors and patch if necessary.
125        let maybe_backup = crate::e_manifest::maybe_patch_manifest_for_run(&target.manifest_path)
126            .context("Failed to patch manifest for run")?;
127
128        //    let pid=    Arc::new(builder).run()?;
129
130        //        let pid = Arc::new(builder).run(|pid, handle| {
131        //     manager.register(handle);
132        let mut system = System::new_all();
133        std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL);
134        // Refresh CPU usage to get actual value.
135        system.refresh_processes_specifics(
136            ProcessesToUpdate::All,
137            true,
138            ProcessRefreshKind::nothing().with_cpu(),
139        );
140        // })?;
141        let pid = Arc::new(builder).run({
142            let manager_ref = Arc::clone(&manager);
143            move |_pid, handle| {
144                manager_ref.register(handle);
145            }
146        })?;
147
148        // Spawn the child process.
149        // let mut child = command
150        //     .spawn()
151        //     .with_context(|| format!("Failed to spawn cargo run for target {}", target.name))?;
152        // {
153        //     let mut global = GLOBAL_CHILD.lock().unwrap();
154        //     *global = Some(child);
155        // }
156
157        // Let the target run for the specified duration.
158        // let run_duration = Duration::from_secs(cli.wait);
159        // thread::sleep(run_duration);
160        // PROMPT let key = crate::e_prompts::prompt("waiting", run_duration.as_secs())?;
161        // if let Some('q') = key {
162        //     user_requested_quit = true;
163        //     println!("User requested quit.");
164        //     break;
165        // }
166
167        let _output = {
168            // let mut global = crate::e_runner::GLOBAL_CHILD.lock().unwrap();
169            // // Take ownership of the child.
170            // let mut child = global
171            //     .take()
172            //     .ok_or_else(|| anyhow::anyhow!("No child process found"))?;
173
174            // Set timeout based on the run_all mode.
175            let timeout = match cli.run_all {
176                RunAll::Timeout(secs) => Duration::from_secs(secs),
177                RunAll::Forever => Duration::from_secs(u64::MAX), // effectively no timeout
178                RunAll::NotSpecified => Duration::from_secs(cli.wait),
179            };
180
181            let mut start = None; //Instant::now();
182                                  // let runtime_start = manager
183                                  //     .get(pid)
184                                  //     .unwrap()
185                                  //     .lock()
186                                  //     .unwrap()
187                                  //     .stats
188                                  //     .lock()
189                                  //     .unwrap()
190                                  //     .build_finished_time;
191                                  //println!("Runtime start time: {:?}", runtime_start);
192            loop {
193                //println!("Checking process status for PID: {}", pid);
194                match manager.try_wait(pid) {
195                    Ok(Some(status)) => {
196                        // Process finished naturally.
197                        println!("Process finished naturally.{:?}", status);
198                        break;
199                    }
200                    _ => {
201                        // Process is still running.
202                        //println!("Process is still running.");
203                    }
204                }
205                if manager.has_signalled() > 0 {
206                    println!(
207                        "Detected Ctrl+C. Exiting run_all loop.{}",
208                        manager.has_signalled()
209                    );
210                    return Ok(false);
211                }
212                // Here, use your non-blocking prompt function if available.
213                // For illustration, assume prompt_nonblocking returns Ok(Some(key)) if a key was pressed.
214                // PROMPT if let Ok(Some(key)) = prompt("waiting press q to quit", 0) {
215                //     // Wait on the child process.
216                //     if key == 'q' {
217                //         println!("User requested stop {}. pid {}", target.name, pid);
218                //         manager.kill_by_pid(pid).ok();
219                //         // let mut global = GLOBAL_CHILDREN.lock().unwrap();
220                //         // if let Some(cargo_process_handle) = global.remove(&pid) {
221                //         //     let mut cargo_process_handle = cargo_process_handle.lock().unwrap();
222                //         //     send_ctrl_c(&mut cargo_process_handle.child)?;
223                //         //     let _ = cargo_process_handle.kill(); // Attempt to kill the process
224                //         //     // Ignore errors if the process has already terminated.
225                //         //     // cargo_process_handle.wait_with_output().ok();
226                //         // }
227                //         break;
228                //     }
229                // }
230
231                // Check if the child process has already finished.
232                // if let Some(_status) = child.try_wait()? {
233                //     // Process finished naturally.
234                //     break child.wait_with_output().context(format!(
235                //         "Failed to get process output for target {}",
236                //         target.name
237                //     ))?;
238                // }
239                // let process_handle = manager.get(pid).unwrap();
240                // let handle = process_handle.lock().unwrap();
241                // let stats = handle.stats.lock().unwrap().clone();
242                // // let runtime_start = manager.get(pid).unwrap().lock().unwrap().stats.lock().unwrap().build_finished_time;
243                // let runtime_start = stats.build_finished_time;
244                let (_stats, runtime_start, end_time, status_display) = {
245                    // Acquire the process handle from the manager.
246                    let process_handle = manager.get(pid).unwrap();
247                    // Lock the process handle to gain mutable or safe read access.
248                    let handle = process_handle.lock().unwrap();
249
250                    // Lock the stats and clone them.
251                    let stats = handle.stats.lock().unwrap().clone();
252                    // Extract the build_finished_time from the cloned stats.
253                    let runtime_start = stats.build_finished_time;
254                    let end_time = handle.result.end_time;
255                    drop(handle);
256                    let status_display = ProcessManager::format_process_status(
257                        pid,
258                        &process_handle,
259                        &system,
260                        &target,
261                        (idx + 1, targets.len()),
262                    );
263                    // Return both the stats and runtime_start.
264                    (stats, runtime_start, end_time, status_display)
265                };
266                // Refresh CPU usage to get actual value.
267                system.refresh_processes_specifics(
268                    ProcessesToUpdate::All,
269                    true,
270                    ProcessRefreshKind::nothing().with_cpu(),
271                );
272
273                ProcessManager::update_status_line(&status_display, true).ok();
274                // println!("start time: {:?} endtime {:?}", runtime_start, end_time);
275                if runtime_start.is_some() {
276                    if start.is_none() {
277                        start = Some(Instant::now());
278                    }
279                    // Check if the timeout has elapsed.
280                    if start.expect("start should have set").elapsed() >= timeout {
281                        println!(
282                            "\nTimeout reached for target {}. Killing child process {}.",
283                            target.name, pid
284                        );
285                        manager.kill_by_pid(pid).ok();
286                        // let mut global = GLOBAL_CHILDREN.lock().unwrap();
287                        // if let Some(cargo_process_handle) = global.remove(&pid) {
288                        //     let mut cargo_process_handle = cargo_process_handle.lock().unwrap();
289                        //     send_ctrl_c(&mut cargo_process_handle.child)?;
290                        //     let _ = cargo_process_handle.kill(); // Attempt to kill the process
291                        //     // Ignore errors if the process has already terminated.
292                        //     // cargo_process_handle.wait_with_output().ok();
293                        // }
294                        break;
295                        // send_ctrl_c(&mut child)?;
296                        // child.kill().ok();
297                        // break child.wait_with_output().context(format!(
298                        //     "Failed to wait on killed process for target {}",
299                        //     target.name
300                        // ))?;
301                    }
302
303                    // Sleep briefly before polling again.
304                    std::thread::sleep(Duration::from_millis(500));
305                } else if end_time.is_some() {
306                    println!("Process finished naturally.");
307                    break;
308                    // } else {
309                    //     // Process is still running.
310                    //     println!("Process is still running.");
311                }
312
313                std::thread::sleep(Duration::from_millis(100));
314            }
315        };
316
317        // let output = {
318        //     let mut global = GLOBAL_CHILD.lock().unwrap();
319        //     if let Some(mut child) = global.take() {
320        //         child.wait_with_output().with_context(|| {
321        //             format!("Failed to wait on cargo run for target {}", target.name)
322        //         })?
323        //     } else {
324        //         return Err(anyhow::anyhow!("Child process missing"));
325        //     }
326        // };
327
328        // println!("{:?}",output);
329        // if !output.stderr.is_empty() {
330        //     eprintln!(
331        //         "Target '{}' produced errors:\n{}",
332        //         target.name,
333        //         String::from_utf8_lossy(&output.stderr)
334        //     );
335        // }
336
337        // Restore the manifest if it was patched.
338        if let Some(original) = maybe_backup {
339            fs::write(&target.manifest_path, original)
340                .context("Failed to restore patched manifest")?;
341        }
342
343        // Check if the user requested to quit.
344        if user_requested_quit {
345            break;
346        }
347
348        // If using a timeout/run_all mechanism, sleep or prompt as needed.
349        // For simplicity, we wait for a fixed duration here.
350        //let run_duration = Duration::from_secs(cli.wait);
351        // PROMPT let _ = crate::e_prompts::prompt("waiting", run_duration.as_secs())?;
352    }
353
354    Ok(user_requested_quit)
355}
356
357// pub fn run_all_examples(cli: &Cli, filtered_targets: &[CargoTarget]) -> Result<()> {
358//     // If --quiet was provided, adjust RUSTFLAGS.
359//     set_rustflags_if_quiet(cli.quiet);
360
361//     // Factor out the prebuild logic.
362//     if cli.pre_build {
363//         crate::e_prebuild::prebuild_examples(filtered_targets)
364//             .context("Prebuild of targets failed")?;
365//     }
366//     let mut targets = filtered_targets.to_vec();
367//     targets.sort_by(|a, b| a.display_name.cmp(&b.display_name));
368//     // For each filtered target, run it with child process management.
369//     for target in targets {
370//         // Clear the screen before running each target.
371
372//         // use crossterm::{execute, terminal::{Clear, ClearType}};
373//         // use std::io::{stdout, Write};
374//         //         execute!(stdout(), Clear(ClearType::All), crossterm::cursor::MoveTo(0, 0))?;
375//         // std::io::Write::flush(&mut std::io::stdout()).unwrap();
376//         println!("Running target: {}", target.name);
377
378//         // Retrieve the current package name (or binary name) at compile time.
379//         let current_bin = env!("CARGO_PKG_NAME");
380//         // Avoid running our own binary if the target's name is the same.
381//         if target.kind == TargetKind::Binary && target.name == current_bin {
382//             continue;
383//         }
384
385//         // Determine the run flag and whether we need to pass the manifest path.
386//         let (run_flag, needs_manifest) = match target.kind {
387//             TargetKind::Example => ("--example", false),
388//             TargetKind::ExtendedExample => ("--example", true),
389//             TargetKind::Binary => ("--bin", false),
390//             TargetKind::ExtendedBinary => ("--bin", true),
391//             TargetKind::ManifestTauri => ("", true),
392//             TargetKind::ManifestTauriExample => ("", true),
393//             TargetKind::Test => ("--test", true),
394//             TargetKind::Manifest => ("", true),
395//             TargetKind::ManifestDioxus => ("", true),
396//             TargetKind::ManifestDioxusExample => ("", true),
397//             TargetKind::Bench => ("", true),
398//         };
399//         let mut cmd_parts = vec!["cargo".to_string()];
400//         cmd_parts.push("run".to_string());
401//         if cli.release {
402//             cmd_parts.push("--release".to_string());
403//         }
404//         // Pass --quiet if requested.
405//         if cli.quiet {
406//             cmd_parts.push("--quiet".to_string());
407//         }
408//         cmd_parts.push(run_flag.to_string());
409//         cmd_parts.push(target.name.clone());
410//         if needs_manifest {
411//             cmd_parts.push("--manifest-path".to_string());
412//             cmd_parts.push(
413//                 target
414//                     .manifest_path
415//                     .clone()
416//                     .to_str()
417//                     .unwrap_or_default()
418//                     .to_owned(),
419//             );
420//         }
421//         cmd_parts.extend(cli.extra.clone());
422
423//         // // Build a vector of command parts for logging.
424//         // let mut cmd_parts = vec!["cargo".to_string(), "run".to_string(), run_flag.to_string(), target.name.clone()];
425//         // if needs_manifest {
426//         //     cmd_parts.push("--manifest-path".to_string());
427//         //     cmd_parts.push(target.manifest_path.clone());
428//         // }
429//         // // Append any extra CLI arguments.
430//         // cmd_parts.extend(cli.extra.clone());
431
432//         // Print out the full command that will be run.
433//         let key = prompt(&format!("Full command: {}", cmd_parts.join(" ")), 2)?;
434//         if let Some('q') = key {
435//             println!("User requested quit.");
436//             break;
437//         }
438
439//         // Clear the screen before running each target.
440//         //println!("\x1B[2J\x1B[H");
441
442//         // Build the command for execution.
443//         let mut command = Command::new("cargo");
444//         command.arg("run");
445//         if cli.release {
446//             command.arg("--release");
447//         }
448//         if cli.quiet {
449//             command.arg("--quiet");
450//         }
451//         command.arg(run_flag).arg(&target.name);
452//         if needs_manifest {
453//             command.args(&[
454//                 "--manifest-path",
455//                 &target.manifest_path.to_str().unwrap_or_default().to_owned(),
456//             ]);
457//         }
458
459//         // --- Inject required-features support using our helper ---
460//         if let Some(features) = crate::e_manifest::get_required_features_from_manifest(
461//             std::path::Path::new(&target.manifest_path),
462//             &target.kind,
463//             &target.name,
464//         ) {
465//             command.args(&["--features", &features]);
466//         }
467//         // --- End required-features support ---
468
469//         // Append any extra CLI arguments.
470//         command.args(&cli.extra);
471
472//         // Spawn the child process.
473//         let child = command
474//             .spawn()
475//             .with_context(|| format!("Failed to spawn cargo run for target {}", target.name))?;
476//         {
477//             let mut global = crate::e_runner::GLOBAL_CHILD.lock().unwrap();
478//             *global = Some(child);
479//         }
480//         // Let the target run for the specified duration.
481//         let run_duration = Duration::from_secs(cli.wait);
482//         thread::sleep(run_duration);
483
484//         // Kill the process (ignoring errors if it already terminated).
485
486//         // Decide on the run duration per target and use it accordingly:
487//         // Determine behavior based on the run_all flag:
488//         let output = {
489//             let mut global = crate::e_runner::GLOBAL_CHILD.lock().unwrap();
490//             if let Some(mut child) = global.take() {
491//                 match cli.run_all {
492//                     RunAll::Timeout(timeout_secs) => {
493//                         let message = format!(
494//                             "Press any key to continue (timeout in {} seconds)...",
495//                             timeout_secs
496//                         );
497//                         let key = prompt(&message, timeout_secs)?;
498//                         if let Some('q') = key {
499//                             println!("User requested quit.");
500//                             // Terminate the process and break out of the loop.
501//                             child.kill().ok();
502//                             break;
503//                         }
504//                         child.kill().ok();
505//                         child.wait_with_output().with_context(|| {
506//                             format!("Failed to wait on cargo run for target {}", target.name)
507//                         })?
508//                     }
509//                     RunAll::Forever => {
510//                         let key = prompt(&"", 0)?;
511//                         if let Some('q') = key {
512//                             println!("User requested quit.");
513//                             // Terminate the process and break out of the loop.
514//                             child.kill().ok();
515//                             break;
516//                         } // Run until natural termination.
517//                         child.wait_with_output().with_context(|| {
518//                             format!("Failed to wait on cargo run for target {}", target.name)
519//                         })?
520//                     }
521//                     RunAll::NotSpecified => {
522//                         let key = prompt(&"", cli.wait)?;
523//                         if let Some('q') = key {
524//                             println!("User requested quit.");
525//                             // Terminate the process and break out of the loop.
526//                             child.kill().ok();
527//                             break;
528//                         }
529//                         child.kill().ok();
530//                         child.wait_with_output().with_context(|| {
531//                             format!("Failed to wait on cargo run for target {}", target.name)
532//                         })?
533//                     }
534//                 }
535//             } else {
536//                 return Err(anyhow::anyhow!("No child process found"));
537//             }
538//         };
539
540//         if !output.stderr.is_empty() {
541//             eprintln!(
542//                 "Target '{}' produced errors:\n{}",
543//                 target.name,
544//                 String::from_utf8_lossy(&output.stderr)
545//             );
546//         }
547//     }
548//     Ok(())
549// }
550
551use std::{env, fs};
552
553/// If quiet mode is enabled, ensure that RUSTFLAGS contains "-Awarnings".
554/// If RUSTFLAGS is already set, and it does not contain "-Awarnings", then append it.
555pub fn set_rustflags_if_quiet(quiet: bool) {
556    if quiet {
557        let current_flags = env::var("RUSTFLAGS").unwrap_or_else(|_| "".to_string());
558        if !current_flags.contains("-Awarnings") {
559            let new_flags = if current_flags.trim().is_empty() {
560                "-Awarnings".to_string()
561            } else {
562                format!("{} -Awarnings", current_flags)
563            };
564            env::set_var("RUSTFLAGS", new_flags);
565        }
566    }
567}