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