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::atomic::{AtomicBool, Ordering};
8use std::sync::Arc;
9use std::time::Duration;
10use std::time::Instant;
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 // Adjust RUSTFLAGS if --quiet was provided.
71 set_rustflags_if_quiet(cli.quiet);
72
73 // Prebuild targets if requested.
74 if cli.pre_build {
75 crate::e_prebuild::prebuild_examples(filtered_targets)
76 .context("Prebuild of targets failed")?;
77 }
78
79 let mut targets = filtered_targets.to_vec();
80 targets.sort_by(|a, b| a.display_name.cmp(&b.display_name));
81
82 let user_requested_quit = Arc::new(AtomicBool::new(false));
83
84 let chunk_size = cli.run_at_a_time;
85 let mut idx = 0;
86 while idx < targets.len() {
87 let chunk = &targets[idx..std::cmp::min(idx + chunk_size, targets.len())];
88 let mut handles = vec![];
89
90 for (chunk_idx, target) in chunk.iter().enumerate() {
91 let manager = Arc::clone(&manager);
92 let cli = cli.clone();
93 let target = target.clone();
94 let targets_len = targets.len();
95 let idx = idx + chunk_idx;
96 let user_requested_quit_thread = Arc::clone(&user_requested_quit);
97
98 // Spawn a thread for each target in the chunk
99 let handle = std::thread::spawn(move || {
100 // --- Begin: original per-target logic ---
101 let current_bin = env!("CARGO_PKG_NAME");
102 // Skip running our own binary.
103 if target.kind == TargetKind::Binary && target.name == current_bin {
104 return Ok(()) as Result<()>;
105 }
106
107 let manifest_path = PathBuf::from(target.manifest_path.clone());
108 let builder = CargoCommandBuilder::new(
109 &target.name,
110 &manifest_path,
111 &cli.subcommand,
112 cli.filter,
113 )
114 .with_target(&target)
115 .with_cli(&cli)
116 .with_extra_args(&cli.extra);
117
118 builder.print_command();
119
120 let maybe_backup =
121 crate::e_manifest::maybe_patch_manifest_for_run(&target.manifest_path)
122 .context("Failed to patch manifest for run")?;
123
124 // let system = Arc::new(Mutex::new(System::new_all()));
125 // std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL);
126 // let mut system_guard = system.lock().unwrap();
127 // system_guard.refresh_processes_specifics(
128 // ProcessesToUpdate::All,
129 // true,
130 // ProcessRefreshKind::nothing().with_cpu(),
131 // );
132 // drop(system_guard);
133
134 let pid = Arc::new(builder).run({
135 let manager_ref = Arc::clone(&manager);
136 let t = target.clone();
137 let len = targets_len;
138 // let system_clone = system.clone();
139 move |pid, handle| {
140 let stats = handle.stats.lock().unwrap().clone();
141 let runtime_start = if stats.is_comiler_target {
142 stats.build_finished_time
143 } else {
144 stats.start_time
145 };
146 if !cli.no_status_lines {
147 let status_display = ProcessManager::format_process_status(
148 pid,
149 runtime_start,
150 &t,
151 (idx + 1, len),
152 );
153 ProcessManager::update_status_line(&status_display, true).ok();
154 }
155 manager_ref.register(handle);
156 }
157 })?;
158
159 let timeout = match cli.run_all {
160 RunAll::Timeout(secs) => Duration::from_secs(secs),
161 RunAll::Forever => Duration::from_secs(u64::MAX),
162 RunAll::NotSpecified => Duration::from_secs(cli.wait),
163 };
164
165 let mut start = None;
166 loop {
167 match manager.try_wait(pid) {
168 Ok(Some(_status)) => {
169 manager.remove(pid);
170 break;
171 }
172 _ => {
173 // Process is still running.
174 //println!("Process is still running.");
175 }
176 }
177 if manager.has_signalled() > 0 {
178 println!("Detected Ctrl+C. {}", manager.has_signalled());
179 manager.remove(pid); // Clean up the process handle
180
181 if manager.has_signalled() > 1 {
182 if let Some(dur) = manager.time_between_signals() {
183 if dur < Duration::from_millis(350) {
184 println!("User requested quit two times quickly (<350ms).");
185 user_requested_quit_thread.store(true, Ordering::SeqCst);
186 break;
187 }
188 }
189 }
190 println!("Dectected Ctrl+C, coninuing to next target.");
191 manager.reset_signalled();
192 break;
193 }
194
195 let (_stats, runtime_start, end_time, status_display) = {
196 let process_handle = manager.get(pid).unwrap();
197 let handle = process_handle.lock().unwrap();
198 let stats = handle.stats.lock().unwrap().clone();
199 let runtime_start = if stats.is_comiler_target {
200 stats.build_finished_time
201 } else {
202 stats.start_time
203 };
204 let end_time = handle.result.end_time;
205 drop(handle);
206 let status_display = if !cli.no_status_lines {
207 ProcessManager::format_process_status(
208 pid,
209 runtime_start,
210 &target,
211 (idx + 1, targets_len),
212 )
213 } else {
214 String::new()
215 };
216 (stats, runtime_start, end_time, status_display)
217 };
218
219 if cli.filter && !cli.no_status_lines {
220 // let mut system_guard = system.lock().unwrap();
221 // system_guard.refresh_processes_specifics(
222 // ProcessesToUpdate::All,
223 // true,
224 // ProcessRefreshKind::nothing().with_cpu(),
225 // );
226 // drop(system_guard);
227 ProcessManager::update_status_line(&status_display, true).ok();
228 }
229 if runtime_start.is_some() {
230 if start.is_none() {
231 start = Some(Instant::now());
232 }
233 if start.expect("start should have set").elapsed() >= timeout {
234 println!(
235 "\nTimeout reached for target {}. Killing child process {}.",
236 target.name, pid
237 );
238 manager.kill_by_pid(pid).ok();
239 manager.remove(pid);
240 break;
241 }
242 std::thread::sleep(Duration::from_millis(500));
243 } else if end_time.is_some() {
244 println!("Process finished naturally.");
245 manager.remove(pid);
246 break;
247 }
248 std::thread::sleep(Duration::from_millis(100));
249 }
250
251 if let Some(original) = maybe_backup {
252 fs::write(&target.manifest_path, original)
253 .context("Failed to restore patched manifest")?;
254 }
255 manager.generate_report(cli.gist);
256
257 Ok(())
258 // --- End: original per-target logic ---
259 });
260 if user_requested_quit.load(Ordering::SeqCst) {
261 break;
262 }
263 handles.push(handle);
264 }
265 // Check if the user requested to quit.
266 if user_requested_quit.load(Ordering::SeqCst) {
267 break;
268 }
269 // Wait for all threads in this chunk to finish
270 for handle in handles {
271 let _ = handle.join();
272 }
273
274 idx += chunk_size;
275 }
276
277 Ok(Arc::clone(&user_requested_quit).load(Ordering::SeqCst))
278}
279
280// pub fn run_all_examples(cli: &Cli, filtered_targets: &[CargoTarget]) -> Result<()> {
281// // If --quiet was provided, adjust RUSTFLAGS.
282// set_rustflags_if_quiet(cli.quiet);
283
284// // Factor out the prebuild logic.
285// if cli.pre_build {
286// crate::e_prebuild::prebuild_examples(filtered_targets)
287// .context("Prebuild of targets failed")?;
288// }
289// let mut targets = filtered_targets.to_vec();
290// targets.sort_by(|a, b| a.display_name.cmp(&b.display_name));
291// // For each filtered target, run it with child process management.
292// for target in targets {
293// // Clear the screen before running each target.
294
295// // use crossterm::{execute, terminal::{Clear, ClearType}};
296// // use std::io::{stdout, Write};
297// // execute!(stdout(), Clear(ClearType::All), crossterm::cursor::MoveTo(0, 0))?;
298// // std::io::Write::flush(&mut std::io::stdout()).unwrap();
299// println!("Running target: {}", target.name);
300
301// // Retrieve the current package name (or binary name) at compile time.
302// let current_bin = env!("CARGO_PKG_NAME");
303// // Avoid running our own binary if the target's name is the same.
304// if target.kind == TargetKind::Binary && target.name == current_bin {
305// continue;
306// }
307
308// // Determine the run flag and whether we need to pass the manifest path.
309// let (run_flag, needs_manifest) = match target.kind {
310// TargetKind::Example => ("--example", false),
311// TargetKind::ExtendedExample => ("--example", true),
312// TargetKind::Binary => ("--bin", false),
313// TargetKind::ExtendedBinary => ("--bin", true),
314// TargetKind::ManifestTauri => ("", true),
315// TargetKind::ManifestTauriExample => ("", true),
316// TargetKind::Test => ("--test", true),
317// TargetKind::Manifest => ("", true),
318// TargetKind::ManifestDioxus => ("", true),
319// TargetKind::ManifestDioxusExample => ("", true),
320// TargetKind::Bench => ("", true),
321// };
322// let mut cmd_parts = vec!["cargo".to_string()];
323// cmd_parts.push("run".to_string());
324// if cli.release {
325// cmd_parts.push("--release".to_string());
326// }
327// // Pass --quiet if requested.
328// if cli.quiet {
329// cmd_parts.push("--quiet".to_string());
330// }
331// cmd_parts.push(run_flag.to_string());
332// cmd_parts.push(target.name.clone());
333// if needs_manifest {
334// cmd_parts.push("--manifest-path".to_string());
335// cmd_parts.push(
336// target
337// .manifest_path
338// .clone()
339// .to_str()
340// .unwrap_or_default()
341// .to_owned(),
342// );
343// }
344// cmd_parts.extend(cli.extra.clone());
345
346// // // Build a vector of command parts for logging.
347// // let mut cmd_parts = vec!["cargo".to_string(), "run".to_string(), run_flag.to_string(), target.name.clone()];
348// // if needs_manifest {
349// // cmd_parts.push("--manifest-path".to_string());
350// // cmd_parts.push(target.manifest_path.clone());
351// // }
352// // // Append any extra CLI arguments.
353// // cmd_parts.extend(cli.extra.clone());
354
355// // Print out the full command that will be run.
356// let key = prompt(&format!("Full command: {}", cmd_parts.join(" ")), 2)?;
357// if let Some('q') = key {
358// println!("User requested quit.");
359// break;
360// }
361
362// // Clear the screen before running each target.
363// //println!("\x1B[2J\x1B[H");
364
365// // Build the command for execution.
366// let mut command = Command::new("cargo");
367// command.arg("run");
368// if cli.release {
369// command.arg("--release");
370// }
371// if cli.quiet {
372// command.arg("--quiet");
373// }
374// command.arg(run_flag).arg(&target.name);
375// if needs_manifest {
376// command.args(&[
377// "--manifest-path",
378// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
379// ]);
380// }
381
382// // --- Inject required-features support using our helper ---
383// if let Some(features) = crate::e_manifest::get_required_features_from_manifest(
384// std::path::Path::new(&target.manifest_path),
385// &target.kind,
386// &target.name,
387// ) {
388// command.args(&["--features", &features]);
389// }
390// // --- End required-features support ---
391
392// // Append any extra CLI arguments.
393// command.args(&cli.extra);
394
395// // Spawn the child process.
396// let child = command
397// .spawn()
398// .with_context(|| format!("Failed to spawn cargo run for target {}", target.name))?;
399// {
400// let mut global = crate::e_runner::GLOBAL_CHILD.lock().unwrap();
401// *global = Some(child);
402// }
403// // Let the target run for the specified duration.
404// let run_duration = Duration::from_secs(cli.wait);
405// thread::sleep(run_duration);
406
407// // Kill the process (ignoring errors if it already terminated).
408
409// // Decide on the run duration per target and use it accordingly:
410// // Determine behavior based on the run_all flag:
411// let output = {
412// let mut global = crate::e_runner::GLOBAL_CHILD.lock().unwrap();
413// if let Some(mut child) = global.take() {
414// match cli.run_all {
415// RunAll::Timeout(timeout_secs) => {
416// let message = format!(
417// "Press any key to continue (timeout in {} seconds)...",
418// timeout_secs
419// );
420// let key = prompt(&message, timeout_secs)?;
421// if let Some('q') = key {
422// println!("User requested quit.");
423// // Terminate the process and break out of the loop.
424// child.kill().ok();
425// break;
426// }
427// child.kill().ok();
428// child.wait_with_output().with_context(|| {
429// format!("Failed to wait on cargo run for target {}", target.name)
430// })?
431// }
432// RunAll::Forever => {
433// let key = prompt(&"", 0)?;
434// if let Some('q') = key {
435// println!("User requested quit.");
436// // Terminate the process and break out of the loop.
437// child.kill().ok();
438// break;
439// } // Run until natural termination.
440// child.wait_with_output().with_context(|| {
441// format!("Failed to wait on cargo run for target {}", target.name)
442// })?
443// }
444// RunAll::NotSpecified => {
445// let key = prompt(&"", cli.wait)?;
446// if let Some('q') = key {
447// println!("User requested quit.");
448// // Terminate the process and break out of the loop.
449// child.kill().ok();
450// break;
451// }
452// child.kill().ok();
453// child.wait_with_output().with_context(|| {
454// format!("Failed to wait on cargo run for target {}", target.name)
455// })?
456// }
457// }
458// } else {
459// return Err(anyhow::anyhow!("No child process found"));
460// }
461// };
462
463// if !output.stderr.is_empty() {
464// eprintln!(
465// "Target '{}' produced errors:\n{}",
466// target.name,
467// String::from_utf8_lossy(&output.stderr)
468// );
469// }
470// }
471// Ok(())
472// }
473
474use std::{env, fs};
475
476/// If quiet mode is enabled, ensure that RUSTFLAGS contains "-Awarnings".
477/// If RUSTFLAGS is already set, and it does not contain "-Awarnings", then append it.
478pub fn set_rustflags_if_quiet(quiet: bool) {
479 if quiet {
480 let current_flags = env::var("RUSTFLAGS").unwrap_or_else(|_| "".to_string());
481 if !current_flags.contains("-Awarnings") {
482 let new_flags = if current_flags.trim().is_empty() {
483 "-Awarnings".to_string()
484 } else {
485 format!("{} -Awarnings", current_flags)
486 };
487 env::set_var("RUSTFLAGS", new_flags);
488 }
489 }
490}