cargo_e/
e_runall.rs

1use crate::e_command_builder::CargoCommandBuilder;
2use crate::e_runner::GLOBAL_CHILD;
3use crate::e_target::{CargoTarget, TargetKind};
4use crate::{e_cli::RunAll, e_prompts::prompt};
5use anyhow::{Context, Result};
6use std::time::Instant;
7use std::{thread, time::Duration};
8
9#[cfg(unix)]
10use nix::sys::signal::{kill, Signal};
11#[cfg(unix)]
12use nix::unistd::Pid;
13
14use std::process::Child;
15
16#[cfg(target_os = "windows")]
17use std::os::windows::process::CommandExt;
18
19#[cfg(target_os = "windows")]
20const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
21
22#[cfg(target_os = "windows")]
23fn send_ctrl_c(child: &mut Child) -> Result<()> {
24    println!("Sending CTRL-C to child process...");
25    use windows::Win32::System::Console::{GenerateConsoleCtrlEvent, CTRL_C_EVENT};
26
27    // Send CTRL+C to the child process group.
28    // The child must have been spawned with CREATE_NEW_PROCESS_GROUP.
29    let result = unsafe { GenerateConsoleCtrlEvent(CTRL_C_EVENT, child.id()) };
30    if result.is_err() {
31        return Err(anyhow::anyhow!("Failed to send CTRL_C_EVENT on Windows"));
32    }
33
34    // Allow some time for the child to handle the signal gracefully.
35    std::thread::sleep(std::time::Duration::from_millis(1000));
36
37    Ok(())
38}
39
40#[cfg(not(target_os = "windows"))]
41fn send_ctrl_c(child: &mut Child) -> Result<()> {
42    // On Unix, send SIGINT to the child.
43    kill(Pid::from_raw(child.id() as i32), Signal::SIGINT).context("Failed to send SIGINT")?;
44    // Wait briefly to allow graceful shutdown.
45    std::thread::sleep(Duration::from_millis(2000));
46    Ok(())
47}
48
49/// Runs all filtered targets with prebuild, child process management, and timeout‐based termination.
50///
51/// If the CLI flag `pre_build` is enabled, this function first prebuilds all targets by invoking
52/// `cargo build` with the appropriate flags (using `--example` or `--bin` and, for extended targets,
53/// the `--manifest-path` flag). Then it spawns a child process for each target using `cargo run`,
54/// waits for the duration specified by `cli.wait`, kills the child process, and then checks its output.
55///
56/// # Parameters
57///
58/// - `cli`: A reference to the CLI configuration (containing flags like `pre_build`, `wait`, and extra arguments).
59/// - `filtered_targets`: A slice of `Example` instances representing the targets to run.
60///
61/// # Errors
62///
63/// Returns an error if the prebuild step fails or if any child process fails to spawn or complete.
64///
65///
66///
67
68pub fn run_all_examples(cli: &crate::Cli, filtered_targets: &[CargoTarget]) -> Result<()> {
69    // Adjust RUSTFLAGS if --quiet was provided.
70    set_rustflags_if_quiet(cli.quiet);
71
72    // Prebuild targets if requested.
73    if cli.pre_build {
74        crate::e_prebuild::prebuild_examples(filtered_targets)
75            .context("Prebuild of targets failed")?;
76    }
77
78    let mut targets = filtered_targets.to_vec();
79    targets.sort_by(|a, b| a.display_name.cmp(&b.display_name));
80
81    for target in targets {
82        println!("Running target: {}", target.name);
83
84        let current_bin = env!("CARGO_PKG_NAME");
85        // Skip running our own binary.
86        if target.kind == TargetKind::Binary && target.name == current_bin {
87            continue;
88        }
89
90        // Build the command using CargoCommandBuilder.
91        let builder = CargoCommandBuilder::new()
92            .with_target(&target)
93            .with_required_features(&target.manifest_path, &target)
94            .with_cli(cli)
95            .with_extra_args(&cli.extra);
96
97        // For debugging, print out the full command.
98        let cmd_debug = format!(
99            "{} {}",
100            builder.alternate_cmd.as_deref().unwrap_or("cargo"),
101            builder.args.join(" ")
102        );
103        let key = crate::e_prompts::prompt(&format!("Full command: {}", cmd_debug), 2)?;
104        if let Some('q') = key {
105            println!("User requested quit.");
106            break;
107        }
108
109        // Build the std::process::Command.
110        let mut command = builder.build_command();
111        #[cfg(target_os = "windows")]
112        {
113            command.creation_flags(CREATE_NEW_PROCESS_GROUP);
114        }
115
116        // Before spawning, check for workspace manifest errors and patch if necessary.
117        let maybe_backup = crate::e_manifest::maybe_patch_manifest_for_run(&target.manifest_path)
118            .context("Failed to patch manifest for run")?;
119
120        // Spawn the child process.
121        let child = command
122            .spawn()
123            .with_context(|| format!("Failed to spawn cargo run for target {}", target.name))?;
124        {
125            let mut global = GLOBAL_CHILD.lock().unwrap();
126            *global = Some(child);
127        }
128
129        // Let the target run for the specified duration.
130        let run_duration = Duration::from_secs(cli.wait);
131        thread::sleep(run_duration);
132
133        let output = {
134            let mut global = crate::e_runner::GLOBAL_CHILD.lock().unwrap();
135            // Take ownership of the child.
136            let mut child = global
137                .take()
138                .ok_or_else(|| anyhow::anyhow!("No child process found"))?;
139
140            // Set timeout based on the run_all mode.
141            let timeout = match cli.run_all {
142                RunAll::Timeout(secs) => Duration::from_secs(secs),
143                RunAll::Forever => Duration::from_secs(u64::MAX), // effectively no timeout
144                RunAll::NotSpecified => Duration::from_secs(cli.wait),
145            };
146
147            let start = Instant::now();
148
149            loop {
150                // Here, use your non-blocking prompt function if available.
151                // For illustration, assume prompt_nonblocking returns Ok(Some(key)) if a key was pressed.
152                if let Ok(Some(key)) = prompt("", timeout.as_secs() / 2) {
153                    // Wait on the child process.
154                    if key == 'q' {
155                        println!("User requested quit.");
156                        send_ctrl_c(&mut child)?;
157                        child.kill().ok();
158                        break child.wait_with_output().context(format!(
159                            "Failed to wait on killed process for target {}",
160                            target.name
161                        ))?;
162                    }
163                }
164
165                // Check if the child process has already finished.
166                if let Some(_status) = child.try_wait()? {
167                    // Process finished naturally.
168                    break child.wait_with_output().context(format!(
169                        "Failed to get process output for target {}",
170                        target.name
171                    ))?;
172                }
173
174                // Check if the timeout has elapsed.
175                if start.elapsed() >= timeout {
176                    println!(
177                        "\nTimeout reached for target {}. Killing child process.",
178                        target.name
179                    );
180                    send_ctrl_c(&mut child)?;
181                    child.kill().ok();
182                    break child.wait_with_output().context(format!(
183                        "Failed to wait on killed process for target {}",
184                        target.name
185                    ))?;
186                }
187
188                // Sleep briefly before polling again.
189                std::thread::sleep(Duration::from_millis(100));
190            }
191        };
192
193        // let output = {
194        //     let mut global = GLOBAL_CHILD.lock().unwrap();
195        //     if let Some(mut child) = global.take() {
196        //         child.wait_with_output().with_context(|| {
197        //             format!("Failed to wait on cargo run for target {}", target.name)
198        //         })?
199        //     } else {
200        //         return Err(anyhow::anyhow!("Child process missing"));
201        //     }
202        // };
203
204        if !output.stderr.is_empty() {
205            eprintln!(
206                "Target '{}' produced errors:\n{}",
207                target.name,
208                String::from_utf8_lossy(&output.stderr)
209            );
210        }
211
212        // Restore the manifest if it was patched.
213        if let Some(original) = maybe_backup {
214            fs::write(&target.manifest_path, original)
215                .context("Failed to restore patched manifest")?;
216        }
217
218        // If using a timeout/run_all mechanism, sleep or prompt as needed.
219        // For simplicity, we wait for a fixed duration here.
220        let run_duration = Duration::from_secs(cli.wait);
221        thread::sleep(run_duration);
222    }
223
224    Ok(())
225}
226
227// pub fn run_all_examples(cli: &Cli, filtered_targets: &[CargoTarget]) -> Result<()> {
228//     // If --quiet was provided, adjust RUSTFLAGS.
229//     set_rustflags_if_quiet(cli.quiet);
230
231//     // Factor out the prebuild logic.
232//     if cli.pre_build {
233//         crate::e_prebuild::prebuild_examples(filtered_targets)
234//             .context("Prebuild of targets failed")?;
235//     }
236//     let mut targets = filtered_targets.to_vec();
237//     targets.sort_by(|a, b| a.display_name.cmp(&b.display_name));
238//     // For each filtered target, run it with child process management.
239//     for target in targets {
240//         // Clear the screen before running each target.
241
242//         // use crossterm::{execute, terminal::{Clear, ClearType}};
243//         // use std::io::{stdout, Write};
244//         //         execute!(stdout(), Clear(ClearType::All), crossterm::cursor::MoveTo(0, 0))?;
245//         // std::io::Write::flush(&mut std::io::stdout()).unwrap();
246//         println!("Running target: {}", target.name);
247
248//         // Retrieve the current package name (or binary name) at compile time.
249//         let current_bin = env!("CARGO_PKG_NAME");
250//         // Avoid running our own binary if the target's name is the same.
251//         if target.kind == TargetKind::Binary && target.name == current_bin {
252//             continue;
253//         }
254
255//         // Determine the run flag and whether we need to pass the manifest path.
256//         let (run_flag, needs_manifest) = match target.kind {
257//             TargetKind::Example => ("--example", false),
258//             TargetKind::ExtendedExample => ("--example", true),
259//             TargetKind::Binary => ("--bin", false),
260//             TargetKind::ExtendedBinary => ("--bin", true),
261//             TargetKind::ManifestTauri => ("", true),
262//             TargetKind::ManifestTauriExample => ("", true),
263//             TargetKind::Test => ("--test", true),
264//             TargetKind::Manifest => ("", true),
265//             TargetKind::ManifestDioxus => ("", true),
266//             TargetKind::ManifestDioxusExample => ("", true),
267//             TargetKind::Bench => ("", true),
268//         };
269//         let mut cmd_parts = vec!["cargo".to_string()];
270//         cmd_parts.push("run".to_string());
271//         if cli.release {
272//             cmd_parts.push("--release".to_string());
273//         }
274//         // Pass --quiet if requested.
275//         if cli.quiet {
276//             cmd_parts.push("--quiet".to_string());
277//         }
278//         cmd_parts.push(run_flag.to_string());
279//         cmd_parts.push(target.name.clone());
280//         if needs_manifest {
281//             cmd_parts.push("--manifest-path".to_string());
282//             cmd_parts.push(
283//                 target
284//                     .manifest_path
285//                     .clone()
286//                     .to_str()
287//                     .unwrap_or_default()
288//                     .to_owned(),
289//             );
290//         }
291//         cmd_parts.extend(cli.extra.clone());
292
293//         // // Build a vector of command parts for logging.
294//         // let mut cmd_parts = vec!["cargo".to_string(), "run".to_string(), run_flag.to_string(), target.name.clone()];
295//         // if needs_manifest {
296//         //     cmd_parts.push("--manifest-path".to_string());
297//         //     cmd_parts.push(target.manifest_path.clone());
298//         // }
299//         // // Append any extra CLI arguments.
300//         // cmd_parts.extend(cli.extra.clone());
301
302//         // Print out the full command that will be run.
303//         let key = prompt(&format!("Full command: {}", cmd_parts.join(" ")), 2)?;
304//         if let Some('q') = key {
305//             println!("User requested quit.");
306//             break;
307//         }
308
309//         // Clear the screen before running each target.
310//         //println!("\x1B[2J\x1B[H");
311
312//         // Build the command for execution.
313//         let mut command = Command::new("cargo");
314//         command.arg("run");
315//         if cli.release {
316//             command.arg("--release");
317//         }
318//         if cli.quiet {
319//             command.arg("--quiet");
320//         }
321//         command.arg(run_flag).arg(&target.name);
322//         if needs_manifest {
323//             command.args(&[
324//                 "--manifest-path",
325//                 &target.manifest_path.to_str().unwrap_or_default().to_owned(),
326//             ]);
327//         }
328
329//         // --- Inject required-features support using our helper ---
330//         if let Some(features) = crate::e_manifest::get_required_features_from_manifest(
331//             std::path::Path::new(&target.manifest_path),
332//             &target.kind,
333//             &target.name,
334//         ) {
335//             command.args(&["--features", &features]);
336//         }
337//         // --- End required-features support ---
338
339//         // Append any extra CLI arguments.
340//         command.args(&cli.extra);
341
342//         // Spawn the child process.
343//         let child = command
344//             .spawn()
345//             .with_context(|| format!("Failed to spawn cargo run for target {}", target.name))?;
346//         {
347//             let mut global = crate::e_runner::GLOBAL_CHILD.lock().unwrap();
348//             *global = Some(child);
349//         }
350//         // Let the target run for the specified duration.
351//         let run_duration = Duration::from_secs(cli.wait);
352//         thread::sleep(run_duration);
353
354//         // Kill the process (ignoring errors if it already terminated).
355
356//         // Decide on the run duration per target and use it accordingly:
357//         // Determine behavior based on the run_all flag:
358//         let output = {
359//             let mut global = crate::e_runner::GLOBAL_CHILD.lock().unwrap();
360//             if let Some(mut child) = global.take() {
361//                 match cli.run_all {
362//                     RunAll::Timeout(timeout_secs) => {
363//                         let message = format!(
364//                             "Press any key to continue (timeout in {} seconds)...",
365//                             timeout_secs
366//                         );
367//                         let key = prompt(&message, timeout_secs)?;
368//                         if let Some('q') = key {
369//                             println!("User requested quit.");
370//                             // Terminate the process and break out of the loop.
371//                             child.kill().ok();
372//                             break;
373//                         }
374//                         child.kill().ok();
375//                         child.wait_with_output().with_context(|| {
376//                             format!("Failed to wait on cargo run for target {}", target.name)
377//                         })?
378//                     }
379//                     RunAll::Forever => {
380//                         let key = prompt(&"", 0)?;
381//                         if let Some('q') = key {
382//                             println!("User requested quit.");
383//                             // Terminate the process and break out of the loop.
384//                             child.kill().ok();
385//                             break;
386//                         } // Run until natural termination.
387//                         child.wait_with_output().with_context(|| {
388//                             format!("Failed to wait on cargo run for target {}", target.name)
389//                         })?
390//                     }
391//                     RunAll::NotSpecified => {
392//                         let key = prompt(&"", cli.wait)?;
393//                         if let Some('q') = key {
394//                             println!("User requested quit.");
395//                             // Terminate the process and break out of the loop.
396//                             child.kill().ok();
397//                             break;
398//                         }
399//                         child.kill().ok();
400//                         child.wait_with_output().with_context(|| {
401//                             format!("Failed to wait on cargo run for target {}", target.name)
402//                         })?
403//                     }
404//                 }
405//             } else {
406//                 return Err(anyhow::anyhow!("No child process found"));
407//             }
408//         };
409
410//         if !output.stderr.is_empty() {
411//             eprintln!(
412//                 "Target '{}' produced errors:\n{}",
413//                 target.name,
414//                 String::from_utf8_lossy(&output.stderr)
415//             );
416//         }
417//     }
418//     Ok(())
419// }
420
421use std::{env, fs};
422
423/// If quiet mode is enabled, ensure that RUSTFLAGS contains "-Awarnings".
424/// If RUSTFLAGS is already set, and it does not contain "-Awarnings", then append it.
425pub fn set_rustflags_if_quiet(quiet: bool) {
426    if quiet {
427        let current_flags = env::var("RUSTFLAGS").unwrap_or_else(|_| "".to_string());
428        if !current_flags.contains("-Awarnings") {
429            let new_flags = if current_flags.trim().is_empty() {
430                "-Awarnings".to_string()
431            } else {
432                format!("{} -Awarnings", current_flags)
433            };
434            env::set_var("RUSTFLAGS", new_flags);
435        }
436    }
437}