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}