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::Duration;
7use std::time::Instant;
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<bool> {
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    let mut user_requested_quit = false;
82    for target in targets {
83        println!("\nRunning target: {}", target.name);
84
85        let current_bin = env!("CARGO_PKG_NAME");
86        // Skip running our own binary.
87        if target.kind == TargetKind::Binary && target.name == current_bin {
88            continue;
89        }
90
91        // Build the command using CargoCommandBuilder.
92        let builder = CargoCommandBuilder::new()
93            .with_target(&target)
94            .with_required_features(&target.manifest_path, &target)
95            .with_cli(cli)
96            .with_extra_args(&cli.extra);
97
98        // For debugging, print out the full command.
99        let cmd_debug = format!(
100            "{} {}",
101            builder.alternate_cmd.as_deref().unwrap_or("cargo"),
102            builder.args.join(" ")
103        );
104        let key = crate::e_prompts::prompt(&format!("Full command: {}", cmd_debug), 2)?;
105        if let Some('q') = key {
106            user_requested_quit = true;
107            println!("User requested quit.");
108            break;
109        }
110
111        // Build the std::process::Command.
112        let mut command = builder.build_command();
113        #[cfg(target_os = "windows")]
114        {
115            command.creation_flags(CREATE_NEW_PROCESS_GROUP);
116        }
117
118        // Before spawning, check for workspace manifest errors and patch if necessary.
119        let maybe_backup = crate::e_manifest::maybe_patch_manifest_for_run(&target.manifest_path)
120            .context("Failed to patch manifest for run")?;
121
122        // Spawn the child process.
123        let child = command
124            .spawn()
125            .with_context(|| format!("Failed to spawn cargo run for target {}", target.name))?;
126        {
127            let mut global = GLOBAL_CHILD.lock().unwrap();
128            *global = Some(child);
129        }
130
131        // Let the target run for the specified duration.
132        let run_duration = Duration::from_secs(cli.wait);
133        // thread::sleep(run_duration);
134        let key = crate::e_prompts::prompt("waiting", run_duration.as_secs())?;
135        if let Some('q') = key {
136            user_requested_quit = true;
137            println!("User requested quit.");
138            break;
139        }
140
141        let output = {
142            let mut global = crate::e_runner::GLOBAL_CHILD.lock().unwrap();
143            // Take ownership of the child.
144            let mut child = global
145                .take()
146                .ok_or_else(|| anyhow::anyhow!("No child process found"))?;
147
148            // Set timeout based on the run_all mode.
149            let timeout = match cli.run_all {
150                RunAll::Timeout(secs) => Duration::from_secs(secs),
151                RunAll::Forever => Duration::from_secs(u64::MAX), // effectively no timeout
152                RunAll::NotSpecified => Duration::from_secs(cli.wait),
153            };
154
155            let start = Instant::now();
156
157            loop {
158                // Here, use your non-blocking prompt function if available.
159                // For illustration, assume prompt_nonblocking returns Ok(Some(key)) if a key was pressed.
160                if let Ok(Some(key)) = prompt("", timeout.as_secs() / 2) {
161                    // Wait on the child process.
162                    if key == 'q' {
163                        println!("User requested stop {}.", target.name);
164                        send_ctrl_c(&mut child)?;
165                        child.kill().ok();
166                        break child.wait_with_output().context(format!(
167                            "Failed to wait on killed process for target {}",
168                            target.name
169                        ))?;
170                    }
171                }
172
173                // Check if the child process has already finished.
174                if let Some(_status) = child.try_wait()? {
175                    // Process finished naturally.
176                    break child.wait_with_output().context(format!(
177                        "Failed to get process output for target {}",
178                        target.name
179                    ))?;
180                }
181
182                // Check if the timeout has elapsed.
183                if start.elapsed() >= timeout {
184                    println!(
185                        "\nTimeout reached for target {}. Killing child process.",
186                        target.name
187                    );
188                    send_ctrl_c(&mut child)?;
189                    child.kill().ok();
190                    break child.wait_with_output().context(format!(
191                        "Failed to wait on killed process for target {}",
192                        target.name
193                    ))?;
194                }
195
196                // Sleep briefly before polling again.
197                std::thread::sleep(Duration::from_millis(100));
198            }
199        };
200
201        // let output = {
202        //     let mut global = GLOBAL_CHILD.lock().unwrap();
203        //     if let Some(mut child) = global.take() {
204        //         child.wait_with_output().with_context(|| {
205        //             format!("Failed to wait on cargo run for target {}", target.name)
206        //         })?
207        //     } else {
208        //         return Err(anyhow::anyhow!("Child process missing"));
209        //     }
210        // };
211
212        if !output.stderr.is_empty() {
213            eprintln!(
214                "Target '{}' produced errors:\n{}",
215                target.name,
216                String::from_utf8_lossy(&output.stderr)
217            );
218        }
219
220        // Restore the manifest if it was patched.
221        if let Some(original) = maybe_backup {
222            fs::write(&target.manifest_path, original)
223                .context("Failed to restore patched manifest")?;
224        }
225
226        // Check if the user requested to quit.
227        if user_requested_quit {
228            break;
229        }
230
231        // If using a timeout/run_all mechanism, sleep or prompt as needed.
232        // For simplicity, we wait for a fixed duration here.
233        let run_duration = Duration::from_secs(cli.wait);
234        let _ = crate::e_prompts::prompt("waiting", run_duration.as_secs())?;
235    }
236
237    Ok(user_requested_quit)
238}
239
240// pub fn run_all_examples(cli: &Cli, filtered_targets: &[CargoTarget]) -> Result<()> {
241//     // If --quiet was provided, adjust RUSTFLAGS.
242//     set_rustflags_if_quiet(cli.quiet);
243
244//     // Factor out the prebuild logic.
245//     if cli.pre_build {
246//         crate::e_prebuild::prebuild_examples(filtered_targets)
247//             .context("Prebuild of targets failed")?;
248//     }
249//     let mut targets = filtered_targets.to_vec();
250//     targets.sort_by(|a, b| a.display_name.cmp(&b.display_name));
251//     // For each filtered target, run it with child process management.
252//     for target in targets {
253//         // Clear the screen before running each target.
254
255//         // use crossterm::{execute, terminal::{Clear, ClearType}};
256//         // use std::io::{stdout, Write};
257//         //         execute!(stdout(), Clear(ClearType::All), crossterm::cursor::MoveTo(0, 0))?;
258//         // std::io::Write::flush(&mut std::io::stdout()).unwrap();
259//         println!("Running target: {}", target.name);
260
261//         // Retrieve the current package name (or binary name) at compile time.
262//         let current_bin = env!("CARGO_PKG_NAME");
263//         // Avoid running our own binary if the target's name is the same.
264//         if target.kind == TargetKind::Binary && target.name == current_bin {
265//             continue;
266//         }
267
268//         // Determine the run flag and whether we need to pass the manifest path.
269//         let (run_flag, needs_manifest) = match target.kind {
270//             TargetKind::Example => ("--example", false),
271//             TargetKind::ExtendedExample => ("--example", true),
272//             TargetKind::Binary => ("--bin", false),
273//             TargetKind::ExtendedBinary => ("--bin", true),
274//             TargetKind::ManifestTauri => ("", true),
275//             TargetKind::ManifestTauriExample => ("", true),
276//             TargetKind::Test => ("--test", true),
277//             TargetKind::Manifest => ("", true),
278//             TargetKind::ManifestDioxus => ("", true),
279//             TargetKind::ManifestDioxusExample => ("", true),
280//             TargetKind::Bench => ("", true),
281//         };
282//         let mut cmd_parts = vec!["cargo".to_string()];
283//         cmd_parts.push("run".to_string());
284//         if cli.release {
285//             cmd_parts.push("--release".to_string());
286//         }
287//         // Pass --quiet if requested.
288//         if cli.quiet {
289//             cmd_parts.push("--quiet".to_string());
290//         }
291//         cmd_parts.push(run_flag.to_string());
292//         cmd_parts.push(target.name.clone());
293//         if needs_manifest {
294//             cmd_parts.push("--manifest-path".to_string());
295//             cmd_parts.push(
296//                 target
297//                     .manifest_path
298//                     .clone()
299//                     .to_str()
300//                     .unwrap_or_default()
301//                     .to_owned(),
302//             );
303//         }
304//         cmd_parts.extend(cli.extra.clone());
305
306//         // // Build a vector of command parts for logging.
307//         // let mut cmd_parts = vec!["cargo".to_string(), "run".to_string(), run_flag.to_string(), target.name.clone()];
308//         // if needs_manifest {
309//         //     cmd_parts.push("--manifest-path".to_string());
310//         //     cmd_parts.push(target.manifest_path.clone());
311//         // }
312//         // // Append any extra CLI arguments.
313//         // cmd_parts.extend(cli.extra.clone());
314
315//         // Print out the full command that will be run.
316//         let key = prompt(&format!("Full command: {}", cmd_parts.join(" ")), 2)?;
317//         if let Some('q') = key {
318//             println!("User requested quit.");
319//             break;
320//         }
321
322//         // Clear the screen before running each target.
323//         //println!("\x1B[2J\x1B[H");
324
325//         // Build the command for execution.
326//         let mut command = Command::new("cargo");
327//         command.arg("run");
328//         if cli.release {
329//             command.arg("--release");
330//         }
331//         if cli.quiet {
332//             command.arg("--quiet");
333//         }
334//         command.arg(run_flag).arg(&target.name);
335//         if needs_manifest {
336//             command.args(&[
337//                 "--manifest-path",
338//                 &target.manifest_path.to_str().unwrap_or_default().to_owned(),
339//             ]);
340//         }
341
342//         // --- Inject required-features support using our helper ---
343//         if let Some(features) = crate::e_manifest::get_required_features_from_manifest(
344//             std::path::Path::new(&target.manifest_path),
345//             &target.kind,
346//             &target.name,
347//         ) {
348//             command.args(&["--features", &features]);
349//         }
350//         // --- End required-features support ---
351
352//         // Append any extra CLI arguments.
353//         command.args(&cli.extra);
354
355//         // Spawn the child process.
356//         let child = command
357//             .spawn()
358//             .with_context(|| format!("Failed to spawn cargo run for target {}", target.name))?;
359//         {
360//             let mut global = crate::e_runner::GLOBAL_CHILD.lock().unwrap();
361//             *global = Some(child);
362//         }
363//         // Let the target run for the specified duration.
364//         let run_duration = Duration::from_secs(cli.wait);
365//         thread::sleep(run_duration);
366
367//         // Kill the process (ignoring errors if it already terminated).
368
369//         // Decide on the run duration per target and use it accordingly:
370//         // Determine behavior based on the run_all flag:
371//         let output = {
372//             let mut global = crate::e_runner::GLOBAL_CHILD.lock().unwrap();
373//             if let Some(mut child) = global.take() {
374//                 match cli.run_all {
375//                     RunAll::Timeout(timeout_secs) => {
376//                         let message = format!(
377//                             "Press any key to continue (timeout in {} seconds)...",
378//                             timeout_secs
379//                         );
380//                         let key = prompt(&message, timeout_secs)?;
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//                         }
387//                         child.kill().ok();
388//                         child.wait_with_output().with_context(|| {
389//                             format!("Failed to wait on cargo run for target {}", target.name)
390//                         })?
391//                     }
392//                     RunAll::Forever => {
393//                         let key = prompt(&"", 0)?;
394//                         if let Some('q') = key {
395//                             println!("User requested quit.");
396//                             // Terminate the process and break out of the loop.
397//                             child.kill().ok();
398//                             break;
399//                         } // Run until natural termination.
400//                         child.wait_with_output().with_context(|| {
401//                             format!("Failed to wait on cargo run for target {}", target.name)
402//                         })?
403//                     }
404//                     RunAll::NotSpecified => {
405//                         let key = prompt(&"", cli.wait)?;
406//                         if let Some('q') = key {
407//                             println!("User requested quit.");
408//                             // Terminate the process and break out of the loop.
409//                             child.kill().ok();
410//                             break;
411//                         }
412//                         child.kill().ok();
413//                         child.wait_with_output().with_context(|| {
414//                             format!("Failed to wait on cargo run for target {}", target.name)
415//                         })?
416//                     }
417//                 }
418//             } else {
419//                 return Err(anyhow::anyhow!("No child process found"));
420//             }
421//         };
422
423//         if !output.stderr.is_empty() {
424//             eprintln!(
425//                 "Target '{}' produced errors:\n{}",
426//                 target.name,
427//                 String::from_utf8_lossy(&output.stderr)
428//             );
429//         }
430//     }
431//     Ok(())
432// }
433
434use std::{env, fs};
435
436/// If quiet mode is enabled, ensure that RUSTFLAGS contains "-Awarnings".
437/// If RUSTFLAGS is already set, and it does not contain "-Awarnings", then append it.
438pub fn set_rustflags_if_quiet(quiet: bool) {
439    if quiet {
440        let current_flags = env::var("RUSTFLAGS").unwrap_or_else(|_| "".to_string());
441        if !current_flags.contains("-Awarnings") {
442            let new_flags = if current_flags.trim().is_empty() {
443                "-Awarnings".to_string()
444            } else {
445                format!("{} -Awarnings", current_flags)
446            };
447            env::set_var("RUSTFLAGS", new_flags);
448        }
449    }
450}