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}