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