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