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