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