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 cli.default_binary_is_runner,
115 cli.quiet || cli.json_all_targets,
116 cli.detached,
117 cli.cwd_wsr,
118 )
119 .with_target(&target)
120 .with_cli(&cli)
121 .with_extra_args(&cli.extra);
122
123 builder.print_command();
124
125 let maybe_backup =
126 crate::e_manifest::maybe_patch_manifest_for_run(&target.manifest_path)
127 .context("Failed to patch manifest for run")?;
128
129 // let system = Arc::new(Mutex::new(System::new_all()));
130 // std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL);
131 // let mut system_guard = system.lock().unwrap();
132 // system_guard.refresh_processes_specifics(
133 // ProcessesToUpdate::All,
134 // true,
135 // ProcessRefreshKind::nothing().with_cpu(),
136 // );
137 // drop(system_guard);
138
139 let start = Arc::new(std::sync::Mutex::new(None));
140 let start_for_callback = Arc::clone(&start);
141 let pid = Arc::new(builder).run({
142 let manager_ref = Arc::clone(&manager);
143 let t = target.clone();
144 let len = targets_len;
145 let start = Arc::clone(&start_for_callback);
146 // let system_clone = system.clone();
147 move |pid, handle| {
148 let stats = {
149 let handle_guard = handle.lock().unwrap();
150 let stats = handle_guard.stats.lock().unwrap().clone();
151 stats
152 };
153 let runtime_start = if stats.is_comiler_target {
154 stats.build_finished_time
155 } else {
156 stats.start_time
157 };
158 let mut start_guard = start.lock().unwrap();
159 if start_guard.is_none() {
160 *start_guard = Some(Instant::now());
161 }
162 if !cli.no_status_lines {
163 let status_display = ProcessManager::format_process_status(
164 pid,
165 runtime_start,
166 &t,
167 (idx + 1, len),
168 );
169 ProcessManager::update_status_line(&status_display, true).ok();
170 }
171 manager_ref.register(pid, handle);
172 }
173 })?;
174
175 let timeout = match cli.run_all {
176 RunAll::Timeout(secs) => Duration::from_secs(secs),
177 RunAll::Forever => Duration::from_secs(u64::MAX),
178 RunAll::NotSpecified => Duration::from_secs(cli.wait),
179 };
180
181 // Use an Arc<Mutex<Option<Instant>>> so it can be set in the run callback and accessed in the main loop.
182 let start_for_callback = Arc::clone(&start);
183 // let target_name_for_timeout = target.name.clone();
184 // let timeout_thread = std::thread::spawn({
185 // let manager = Arc::clone(&manager);
186 // let pid = pid.clone();
187 // move || {
188 // // Wait until `start` is Some, i.e., the process has started running
189 // while start.is_none() {
190 // println!(
191 // "Waiting for process {} to start before applying timeout...",
192 // pid
193 // );
194 // std::thread::sleep(Duration::from_millis(50));
195 // }
196 // let start_time = start.expect("start should have been set");
197 // while start_time.elapsed() < timeout {
198 // std::thread::sleep(Duration::from_millis(500));
199 // if manager.try_wait(pid).is_ok() {
200 // return; // Process finished naturally
201 // }
202 // println!(
203 // "Process {} is still running, waiting for timeout...",
204 // pid
205 // );
206 // }
207 // // Timeout reached, kill the process
208 // println!(
209 // "\nTimeout reached for target {}. Killing child process {}.",
210 // target_name_for_timeout, pid
211 // );
212 // manager.kill_by_pid(pid).ok();
213 // manager.remove(pid);
214 // }
215 // });
216
217 // Main thread continues to monitor the process
218 loop {
219 if manager.is_alive(pid) {
220 std::thread::sleep(Duration::from_millis(500));
221 match manager.try_wait(pid) {
222 Ok(Some(status)) => {
223 println!("Process {} finished naturally. {:?}", pid, status);
224 let hold = cli.detached_hold.unwrap_or(0);
225 if cli.detached_hold.is_some() && hold > 0 {
226 println!("holding for the duration (detached_hold enabled). Sleeping for {} seconds...", hold);
227 std::thread::sleep(std::time::Duration::from_secs(hold as u64));
228 }
229 // manager.e_window_kill(pid);
230 // manager.remove(pid);
231 break;
232 }
233 _ => {
234 // Process is still running.
235 // We can check for timeout here as well.
236 if let Ok(start_guard) = start_for_callback.lock() {
237 if let Some(start_time) = *start_guard {
238 if start_time.elapsed() >= timeout {
239 println!(
240 "\nTimeout reached for target {}. Killing child process {}.",
241 target.name,pid);
242 let hold = cli.detached_hold.unwrap_or(0);
243 if cli.detached_hold.is_some() && hold > 0 {
244 println!("holding for the duration (detached_hold enabled). Sleeping for {} seconds...", hold);
245 std::thread::sleep(std::time::Duration::from_secs(
246 hold as u64,
247 ));
248 }
249 manager.kill_by_pid(pid).ok();
250 // manager.remove(pid);
251 // user_requested_kill_thread.store(true, Ordering::SeqCst);
252 // pids_to_kill_thread.lock().push(pid);
253 break;
254 }
255 }
256 }
257 std::thread::sleep(Duration::from_millis(100));
258 }
259 }
260 if manager.has_signalled() > 0 {
261 println!("Detected Ctrl+C. {}", manager.has_signalled());
262 manager.remove(pid); // Clean up the process handle
263
264 if manager.has_signalled() > 1 {
265 if let Some(dur) = manager.time_between_signals() {
266 if dur < Duration::from_millis(350) {
267 println!("User requested quit two times quickly (<350ms).");
268 user_requested_quit_thread.store(true, Ordering::SeqCst);
269 break;
270 }
271 }
272 }
273 println!("Dectected Ctrl+C, coninuing to next target.");
274 manager.reset_signalled();
275 break;
276 }
277
278 // let (_stats, runtime_start, end_time, status_display) = {
279 // let (stats, runtime_start, end_time) =
280 // if let Some(process_handle) = manager.get(pid) {
281 // if let Ok(handle) = process_handle.lock() {
282 // let stats =
283 // handle.stats.lock().map(|s| s.clone()).unwrap_or_default();
284 // let runtime_start = if stats.is_comiler_target {
285 // stats.build_finished_time
286 // } else {
287 // stats.start_time
288 // };
289 // let end_time = handle.result.end_time;
290 // (stats, runtime_start, end_time)
291 // } else {
292 // // If we can't lock, fallback to defaults
293 // (Default::default(), None, None)
294 // }
295 // } else {
296 // // If process handle not found, fallback to defaults
297 // (Default::default(), None, None)
298 // };
299 // let status_display = if !cli.no_status_lines {
300 // ProcessManager::format_process_status(
301 // pid,
302 // runtime_start,
303 // &target,
304 // (idx + 1, targets_len),
305 // )
306 // } else {
307 // String::new()
308 // };
309 // (stats, runtime_start, end_time, status_display)
310 // };
311
312 // if cli.filter && !cli.no_status_lines {
313 // // let mut system_guard = system.lock().unwrap();
314 // // system_guard.refresh_processes_specifics(
315 // // ProcessesToUpdate::All,
316 // // true,
317 // // ProcessRefreshKind::nothing().with_cpu(),
318 // // );
319 // // drop(system_guard);
320 // ProcessManager::update_status_line(&status_display, true).ok();
321 // }
322 // if runtime_start.is_some() {
323 // // let mut start_guard = start_for_callback.lock().unwrap();
324 // // if start_guard.is_none() {
325 // // *start_guard = Some(Instant::now());
326 // // }
327 // if let Some(start_time) = *start_for_callback.lock().unwrap() {
328 // if start_time.elapsed() >= timeout {
329 // println!(
330 // "\nTimeout reached for target {} after {:.2?}. Killing child process {}.",
331 // target.name,
332 // start_time.elapsed(),
333 // pid
334 // );
335 // manager.e_window_kill(pid);
336 // manager.remove(pid);
337 // manager.kill_by_pid(pid).ok();
338 // break;
339 // }
340 // }
341 // // std::thread::sleep(Duration::from_millis(500));
342 // } else if end_time.is_some() {
343 // println!("Process finished naturally.");
344 // manager.e_window_kill(pid);
345 // manager.remove(pid);
346 // break;
347 // }
348 std::thread::sleep(Duration::from_millis(100));
349 }
350 }
351
352 // Wait for the timeout thread to finish
353 // let _ = timeout_thread.join();
354
355 if let Some(original) = maybe_backup {
356 fs::write(&target.manifest_path, original)
357 .context("Failed to restore patched manifest")?;
358 }
359 manager.generate_report(cli.gist);
360
361 Ok(())
362 // --- End: original per-target logic ---
363 });
364 if user_requested_quit.load(Ordering::SeqCst) {
365 break;
366 }
367 handles.push(handle);
368 }
369 // Check if the user requested to quit.
370 if user_requested_quit.load(Ordering::SeqCst) {
371 break;
372 }
373 // Wait for all threads in this chunk to finish
374 for handle in handles {
375 let _ = handle.join();
376 }
377 manager.e_window_kill_all();
378 idx += chunk_size;
379 }
380
381 Ok(Arc::clone(&user_requested_quit).load(Ordering::SeqCst))
382}
383
384// pub fn run_all_examples(cli: &Cli, filtered_targets: &[CargoTarget]) -> Result<()> {
385// // If --quiet was provided, adjust RUSTFLAGS.
386// set_rustflags_if_quiet(cli.quiet);
387
388// // Factor out the prebuild logic.
389// if cli.pre_build {
390// crate::e_prebuild::prebuild_examples(filtered_targets)
391// .context("Prebuild of targets failed")?;
392// }
393// let mut targets = filtered_targets.to_vec();
394// targets.sort_by(|a, b| a.display_name.cmp(&b.display_name));
395// // For each filtered target, run it with child process management.
396// for target in targets {
397// // Clear the screen before running each target.
398
399// // use crossterm::{execute, terminal::{Clear, ClearType}};
400// // use std::io::{stdout, Write};
401// // execute!(stdout(), Clear(ClearType::All), crossterm::cursor::MoveTo(0, 0))?;
402// // std::io::Write::flush(&mut std::io::stdout()).unwrap();
403// println!("Running target: {}", target.name);
404
405// // Retrieve the current package name (or binary name) at compile time.
406// let current_bin = env!("CARGO_PKG_NAME");
407// // Avoid running our own binary if the target's name is the same.
408// if target.kind == TargetKind::Binary && target.name == current_bin {
409// continue;
410// }
411
412// // Determine the run flag and whether we need to pass the manifest path.
413// let (run_flag, needs_manifest) = match target.kind {
414// TargetKind::Example => ("--example", false),
415// TargetKind::ExtendedExample => ("--example", true),
416// TargetKind::Binary => ("--bin", false),
417// TargetKind::ExtendedBinary => ("--bin", true),
418// TargetKind::ManifestTauri => ("", true),
419// TargetKind::ManifestTauriExample => ("", true),
420// TargetKind::Test => ("--test", true),
421// TargetKind::Manifest => ("", true),
422// TargetKind::ManifestDioxus => ("", true),
423// TargetKind::ManifestDioxusExample => ("", true),
424// TargetKind::Bench => ("", true),
425// };
426// let mut cmd_parts = vec!["cargo".to_string()];
427// cmd_parts.push("run".to_string());
428// if cli.release {
429// cmd_parts.push("--release".to_string());
430// }
431// // Pass --quiet if requested.
432// if cli.quiet {
433// cmd_parts.push("--quiet".to_string());
434// }
435// cmd_parts.push(run_flag.to_string());
436// cmd_parts.push(target.name.clone());
437// if needs_manifest {
438// cmd_parts.push("--manifest-path".to_string());
439// cmd_parts.push(
440// target
441// .manifest_path
442// .clone()
443// .to_str()
444// .unwrap_or_default()
445// .to_owned(),
446// );
447// }
448// cmd_parts.extend(cli.extra.clone());
449
450// // // Build a vector of command parts for logging.
451// // let mut cmd_parts = vec!["cargo".to_string(), "run".to_string(), run_flag.to_string(), target.name.clone()];
452// // if needs_manifest {
453// // cmd_parts.push("--manifest-path".to_string());
454// // cmd_parts.push(target.manifest_path.clone());
455// // }
456// // // Append any extra CLI arguments.
457// // cmd_parts.extend(cli.extra.clone());
458
459// // Print out the full command that will be run.
460// let key = prompt(&format!("Full command: {}", cmd_parts.join(" ")), 2)?;
461// if let Some('q') = key {
462// println!("User requested quit.");
463// break;
464// }
465
466// // Clear the screen before running each target.
467// //println!("\x1B[2J\x1B[H");
468
469// // Build the command for execution.
470// let mut command = Command::new("cargo");
471// command.arg("run");
472// if cli.release {
473// command.arg("--release");
474// }
475// if cli.quiet {
476// command.arg("--quiet");
477// }
478// command.arg(run_flag).arg(&target.name);
479// if needs_manifest {
480// command.args(&[
481// "--manifest-path",
482// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
483// ]);
484// }
485
486// // --- Inject required-features support using our helper ---
487// if let Some(features) = crate::e_manifest::get_required_features_from_manifest(
488// std::path::Path::new(&target.manifest_path),
489// &target.kind,
490// &target.name,
491// ) {
492// command.args(&["--features", &features]);
493// }
494// // --- End required-features support ---
495
496// // Append any extra CLI arguments.
497// command.args(&cli.extra);
498
499// // Spawn the child process.
500// let child = command
501// .spawn()
502// .with_context(|| format!("Failed to spawn cargo run for target {}", target.name))?;
503// {
504// let mut global = crate::e_runner::GLOBAL_CHILD.lock().unwrap();
505// *global = Some(child);
506// }
507// // Let the target run for the specified duration.
508// let run_duration = Duration::from_secs(cli.wait);
509// thread::sleep(run_duration);
510
511// // Kill the process (ignoring errors if it already terminated).
512
513// // Decide on the run duration per target and use it accordingly:
514// // Determine behavior based on the run_all flag:
515// let output = {
516// let mut global = crate::e_runner::GLOBAL_CHILD.lock().unwrap();
517// if let Some(mut child) = global.take() {
518// match cli.run_all {
519// RunAll::Timeout(timeout_secs) => {
520// let message = format!(
521// "Press any key to continue (timeout in {} seconds)...",
522// timeout_secs
523// );
524// let key = prompt(&message, timeout_secs)?;
525// if let Some('q') = key {
526// println!("User requested quit.");
527// // Terminate the process and break out of the loop.
528// child.kill().ok();
529// break;
530// }
531// child.kill().ok();
532// child.wait_with_output().with_context(|| {
533// format!("Failed to wait on cargo run for target {}", target.name)
534// })?
535// }
536// RunAll::Forever => {
537// let key = prompt(&"", 0)?;
538// if let Some('q') = key {
539// println!("User requested quit.");
540// // Terminate the process and break out of the loop.
541// child.kill().ok();
542// break;
543// } // Run until natural termination.
544// child.wait_with_output().with_context(|| {
545// format!("Failed to wait on cargo run for target {}", target.name)
546// })?
547// }
548// RunAll::NotSpecified => {
549// let key = prompt(&"", cli.wait)?;
550// if let Some('q') = key {
551// println!("User requested quit.");
552// // Terminate the process and break out of the loop.
553// child.kill().ok();
554// break;
555// }
556// child.kill().ok();
557// child.wait_with_output().with_context(|| {
558// format!("Failed to wait on cargo run for target {}", target.name)
559// })?
560// }
561// }
562// } else {
563// return Err(anyhow::anyhow!("No child process found"));
564// }
565// };
566
567// if !output.stderr.is_empty() {
568// eprintln!(
569// "Target '{}' produced errors:\n{}",
570// target.name,
571// String::from_utf8_lossy(&output.stderr)
572// );
573// }
574// }
575// Ok(())
576// }
577
578use std::{env, fs};
579
580/// If quiet mode is enabled, ensure that RUSTFLAGS contains "-Awarnings".
581/// If RUSTFLAGS is already set, and it does not contain "-Awarnings", then append it.
582pub fn set_rustflags_if_quiet(quiet: bool) {
583 if quiet {
584 let current_flags = env::var("RUSTFLAGS").unwrap_or_else(|_| "".to_string());
585 if !current_flags.contains("-Awarnings") {
586 let new_flags = if current_flags.trim().is_empty() {
587 "-Awarnings".to_string()
588 } else {
589 format!("{} -Awarnings", current_flags)
590 };
591 env::set_var("RUSTFLAGS", new_flags);
592 }
593 }
594}