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 = CargoCommandBuilder::new(&manifest_path, &cli.subcommand, cli.filter)
108 .with_target(&target)
109 // .with_required_features(&target.manifest_path, &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 return Ok(false);
238 }
239 println!("Dectected Ctrl+C, coninuing to next target.");
240 break;
241 }
242 // Here, use your non-blocking prompt function if available.
243 // For illustration, assume prompt_nonblocking returns Ok(Some(key)) if a key was pressed.
244 // PROMPT if let Ok(Some(key)) = prompt("waiting press q to quit", 0) {
245 // // Wait on the child process.
246 // if key == 'q' {
247 // println!("User requested stop {}. pid {}", target.name, pid);
248 // manager.kill_by_pid(pid).ok();
249 // // let mut global = GLOBAL_CHILDREN.lock().unwrap();
250 // // if let Some(cargo_process_handle) = global.remove(&pid) {
251 // // let mut cargo_process_handle = cargo_process_handle.lock().unwrap();
252 // // send_ctrl_c(&mut cargo_process_handle.child)?;
253 // // let _ = cargo_process_handle.kill(); // Attempt to kill the process
254 // // // Ignore errors if the process has already terminated.
255 // // // cargo_process_handle.wait_with_output().ok();
256 // // }
257 // break;
258 // }
259 // }
260
261 // Check if the child process has already finished.
262 // if let Some(_status) = child.try_wait()? {
263 // // Process finished naturally.
264 // break child.wait_with_output().context(format!(
265 // "Failed to get process output for target {}",
266 // target.name
267 // ))?;
268 // }
269 // let process_handle = manager.get(pid).unwrap();
270 // let handle = process_handle.lock().unwrap();
271 // let stats = handle.stats.lock().unwrap().clone();
272 // // let runtime_start = manager.get(pid).unwrap().lock().unwrap().stats.lock().unwrap().build_finished_time;
273 // let runtime_start = stats.build_finished_time;
274 let (_stats, runtime_start, end_time, status_display) = {
275 // Acquire the process handle from the manager.
276 let process_handle = manager.get(pid).unwrap();
277 // Lock the process handle to gain mutable or safe read access.
278 let handle = process_handle.lock().unwrap();
279
280 // Lock the stats and clone them.
281 let stats = handle.stats.lock().unwrap().clone();
282 // Extract the build_finished_time from the cloned stats.
283 let runtime_start = if stats.is_comiler_target {
284 stats.build_finished_time
285 } else {
286 stats.start_time
287 };
288 let end_time = handle.result.end_time;
289 drop(handle);
290 let status_display = ProcessManager::format_process_status(
291 pid,
292 runtime_start,
293 system.clone(),
294 &target,
295 (idx + 1, targets.len()),
296 );
297 // Return both the stats and runtime_start.
298 (stats, runtime_start, end_time, status_display)
299 };
300 // Refresh CPU usage to get actual value.
301 // Refresh CPU usage to get actual value.
302 let mut system_guard = system.lock().unwrap();
303 system_guard.refresh_processes_specifics(
304 ProcessesToUpdate::All,
305 true,
306 ProcessRefreshKind::nothing().with_cpu(),
307 );
308 drop(system_guard);
309
310 if cli.filter {
311 ProcessManager::update_status_line(&status_display, true).ok();
312 }
313 // println!("start time: {:?} endtime {:?}", runtime_start, end_time);
314 if runtime_start.is_some() {
315 if start.is_none() {
316 start = Some(Instant::now());
317 }
318 // Check if the timeout has elapsed.
319 if start.expect("start should have set").elapsed() >= timeout {
320 println!(
321 "\nTimeout reached for target {}. Killing child process {}.",
322 target.name, pid
323 );
324 manager.kill_by_pid(pid).ok();
325 manager.remove(pid);
326 // let mut global = GLOBAL_CHILDREN.lock().unwrap();
327 // if let Some(cargo_process_handle) = global.remove(&pid) {
328 // let mut cargo_process_handle = cargo_process_handle.lock().unwrap();
329 // send_ctrl_c(&mut cargo_process_handle.child)?;
330 // let _ = cargo_process_handle.kill(); // Attempt to kill the process
331 // // Ignore errors if the process has already terminated.
332 // // cargo_process_handle.wait_with_output().ok();
333 // }
334 break;
335 // send_ctrl_c(&mut child)?;
336 // child.kill().ok();
337 // break child.wait_with_output().context(format!(
338 // "Failed to wait on killed process for target {}",
339 // target.name
340 // ))?;
341 }
342
343 // Sleep briefly before polling again.
344 std::thread::sleep(Duration::from_millis(500));
345 } else if end_time.is_some() {
346 println!("Process finished naturally.");
347 break;
348 // } else {
349 // // Process is still running.
350 // println!("Process is still running.");
351 }
352
353 std::thread::sleep(Duration::from_millis(100));
354 }
355 };
356
357 // let output = {
358 // let mut global = GLOBAL_CHILD.lock().unwrap();
359 // if let Some(mut child) = global.take() {
360 // child.wait_with_output().with_context(|| {
361 // format!("Failed to wait on cargo run for target {}", target.name)
362 // })?
363 // } else {
364 // return Err(anyhow::anyhow!("Child process missing"));
365 // }
366 // };
367
368 // println!("{:?}",output);
369 // if !output.stderr.is_empty() {
370 // eprintln!(
371 // "Target '{}' produced errors:\n{}",
372 // target.name,
373 // String::from_utf8_lossy(&output.stderr)
374 // );
375 // }
376
377 // Restore the manifest if it was patched.
378 if let Some(original) = maybe_backup {
379 fs::write(&target.manifest_path, original)
380 .context("Failed to restore patched manifest")?;
381 }
382
383 // Check if the user requested to quit.
384 if user_requested_quit {
385 break;
386 }
387
388 // If using a timeout/run_all mechanism, sleep or prompt as needed.
389 // For simplicity, we wait for a fixed duration here.
390 //let run_duration = Duration::from_secs(cli.wait);
391 // PROMPT let _ = crate::e_prompts::prompt("waiting", run_duration.as_secs())?;
392 }
393
394 Ok(user_requested_quit)
395}
396
397// pub fn run_all_examples(cli: &Cli, filtered_targets: &[CargoTarget]) -> Result<()> {
398// // If --quiet was provided, adjust RUSTFLAGS.
399// set_rustflags_if_quiet(cli.quiet);
400
401// // Factor out the prebuild logic.
402// if cli.pre_build {
403// crate::e_prebuild::prebuild_examples(filtered_targets)
404// .context("Prebuild of targets failed")?;
405// }
406// let mut targets = filtered_targets.to_vec();
407// targets.sort_by(|a, b| a.display_name.cmp(&b.display_name));
408// // For each filtered target, run it with child process management.
409// for target in targets {
410// // Clear the screen before running each target.
411
412// // use crossterm::{execute, terminal::{Clear, ClearType}};
413// // use std::io::{stdout, Write};
414// // execute!(stdout(), Clear(ClearType::All), crossterm::cursor::MoveTo(0, 0))?;
415// // std::io::Write::flush(&mut std::io::stdout()).unwrap();
416// println!("Running target: {}", target.name);
417
418// // Retrieve the current package name (or binary name) at compile time.
419// let current_bin = env!("CARGO_PKG_NAME");
420// // Avoid running our own binary if the target's name is the same.
421// if target.kind == TargetKind::Binary && target.name == current_bin {
422// continue;
423// }
424
425// // Determine the run flag and whether we need to pass the manifest path.
426// let (run_flag, needs_manifest) = match target.kind {
427// TargetKind::Example => ("--example", false),
428// TargetKind::ExtendedExample => ("--example", true),
429// TargetKind::Binary => ("--bin", false),
430// TargetKind::ExtendedBinary => ("--bin", true),
431// TargetKind::ManifestTauri => ("", true),
432// TargetKind::ManifestTauriExample => ("", true),
433// TargetKind::Test => ("--test", true),
434// TargetKind::Manifest => ("", true),
435// TargetKind::ManifestDioxus => ("", true),
436// TargetKind::ManifestDioxusExample => ("", true),
437// TargetKind::Bench => ("", true),
438// };
439// let mut cmd_parts = vec!["cargo".to_string()];
440// cmd_parts.push("run".to_string());
441// if cli.release {
442// cmd_parts.push("--release".to_string());
443// }
444// // Pass --quiet if requested.
445// if cli.quiet {
446// cmd_parts.push("--quiet".to_string());
447// }
448// cmd_parts.push(run_flag.to_string());
449// cmd_parts.push(target.name.clone());
450// if needs_manifest {
451// cmd_parts.push("--manifest-path".to_string());
452// cmd_parts.push(
453// target
454// .manifest_path
455// .clone()
456// .to_str()
457// .unwrap_or_default()
458// .to_owned(),
459// );
460// }
461// cmd_parts.extend(cli.extra.clone());
462
463// // // Build a vector of command parts for logging.
464// // let mut cmd_parts = vec!["cargo".to_string(), "run".to_string(), run_flag.to_string(), target.name.clone()];
465// // if needs_manifest {
466// // cmd_parts.push("--manifest-path".to_string());
467// // cmd_parts.push(target.manifest_path.clone());
468// // }
469// // // Append any extra CLI arguments.
470// // cmd_parts.extend(cli.extra.clone());
471
472// // Print out the full command that will be run.
473// let key = prompt(&format!("Full command: {}", cmd_parts.join(" ")), 2)?;
474// if let Some('q') = key {
475// println!("User requested quit.");
476// break;
477// }
478
479// // Clear the screen before running each target.
480// //println!("\x1B[2J\x1B[H");
481
482// // Build the command for execution.
483// let mut command = Command::new("cargo");
484// command.arg("run");
485// if cli.release {
486// command.arg("--release");
487// }
488// if cli.quiet {
489// command.arg("--quiet");
490// }
491// command.arg(run_flag).arg(&target.name);
492// if needs_manifest {
493// command.args(&[
494// "--manifest-path",
495// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
496// ]);
497// }
498
499// // --- Inject required-features support using our helper ---
500// if let Some(features) = crate::e_manifest::get_required_features_from_manifest(
501// std::path::Path::new(&target.manifest_path),
502// &target.kind,
503// &target.name,
504// ) {
505// command.args(&["--features", &features]);
506// }
507// // --- End required-features support ---
508
509// // Append any extra CLI arguments.
510// command.args(&cli.extra);
511
512// // Spawn the child process.
513// let child = command
514// .spawn()
515// .with_context(|| format!("Failed to spawn cargo run for target {}", target.name))?;
516// {
517// let mut global = crate::e_runner::GLOBAL_CHILD.lock().unwrap();
518// *global = Some(child);
519// }
520// // Let the target run for the specified duration.
521// let run_duration = Duration::from_secs(cli.wait);
522// thread::sleep(run_duration);
523
524// // Kill the process (ignoring errors if it already terminated).
525
526// // Decide on the run duration per target and use it accordingly:
527// // Determine behavior based on the run_all flag:
528// let output = {
529// let mut global = crate::e_runner::GLOBAL_CHILD.lock().unwrap();
530// if let Some(mut child) = global.take() {
531// match cli.run_all {
532// RunAll::Timeout(timeout_secs) => {
533// let message = format!(
534// "Press any key to continue (timeout in {} seconds)...",
535// timeout_secs
536// );
537// let key = prompt(&message, timeout_secs)?;
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// }
544// child.kill().ok();
545// child.wait_with_output().with_context(|| {
546// format!("Failed to wait on cargo run for target {}", target.name)
547// })?
548// }
549// RunAll::Forever => {
550// let key = prompt(&"", 0)?;
551// if let Some('q') = key {
552// println!("User requested quit.");
553// // Terminate the process and break out of the loop.
554// child.kill().ok();
555// break;
556// } // Run until natural termination.
557// child.wait_with_output().with_context(|| {
558// format!("Failed to wait on cargo run for target {}", target.name)
559// })?
560// }
561// RunAll::NotSpecified => {
562// let key = prompt(&"", cli.wait)?;
563// if let Some('q') = key {
564// println!("User requested quit.");
565// // Terminate the process and break out of the loop.
566// child.kill().ok();
567// break;
568// }
569// child.kill().ok();
570// child.wait_with_output().with_context(|| {
571// format!("Failed to wait on cargo run for target {}", target.name)
572// })?
573// }
574// }
575// } else {
576// return Err(anyhow::anyhow!("No child process found"));
577// }
578// };
579
580// if !output.stderr.is_empty() {
581// eprintln!(
582// "Target '{}' produced errors:\n{}",
583// target.name,
584// String::from_utf8_lossy(&output.stderr)
585// );
586// }
587// }
588// Ok(())
589// }
590
591use std::{env, fs};
592
593/// If quiet mode is enabled, ensure that RUSTFLAGS contains "-Awarnings".
594/// If RUSTFLAGS is already set, and it does not contain "-Awarnings", then append it.
595pub fn set_rustflags_if_quiet(quiet: bool) {
596 if quiet {
597 let current_flags = env::var("RUSTFLAGS").unwrap_or_else(|_| "".to_string());
598 if !current_flags.contains("-Awarnings") {
599 let new_flags = if current_flags.trim().is_empty() {
600 "-Awarnings".to_string()
601 } else {
602 format!("{} -Awarnings", current_flags)
603 };
604 env::set_var("RUSTFLAGS", new_flags);
605 }
606 }
607}