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