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