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