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