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