cargo_e/e_runner.rs
1use crate::e_processmanager::ProcessManager;
2use crate::{e_target::TargetOrigin, prelude::*};
3// #[cfg(not(feature = "equivalent"))]
4// use ctrlc;
5use crate::e_cargocommand_ext::CargoProcessHandle;
6use crate::e_target::CargoTarget;
7#[cfg(feature = "uses_plugins")]
8use crate::plugins::plugin_api::Target as PluginTarget;
9use anyhow::Result;
10use once_cell::sync::Lazy;
11use regex::Regex;
12use std::collections::HashMap;
13use std::fs::File;
14use std::io::{self, BufRead};
15use std::path::Path;
16use std::process::Command;
17use std::sync::atomic::{AtomicUsize, Ordering};
18use std::thread;
19use which::which; // Adjust the import based on your project structure
20
21// lazy_static! {
22// pub static ref GLOBAL_CHILDREN: Arc<Mutex<Vec<Arc<CargoProcessHandle>>>> = Arc::new(Mutex::new(Vec::new()));
23// static CTRL_C_COUNT: Lazy<Mutex<u32>> = Lazy::new(|| Mutex::new(0));
24// }
25
26// pub static GLOBAL_CHILDREN: Lazy<Arc<Mutex<Vec<Arc<Mutex<CargoProcessHandle>>>>>> = Lazy::new(|| Arc::new(Mutex::new(Vec::new())));
27pub static GLOBAL_CHILDREN: Lazy<Arc<Mutex<HashMap<u32, Arc<Mutex<CargoProcessHandle>>>>>> =
28 Lazy::new(|| Arc::new(Mutex::new(HashMap::new())));
29
30static CTRL_C_COUNT: AtomicUsize = AtomicUsize::new(0);
31
32// Global shared container for the currently running child process.
33// pub static GLOBAL_CHILD: Lazy<Arc<Mutex<Option<Child>>>> = Lazy::new(|| Arc::new(Mutex::new(None)));
34// static CTRL_C_COUNT: Lazy<Mutex<u32>> = Lazy::new(|| Mutex::new(0));
35
36// pub static GLOBAL_CHILDREN: Lazy<Arc<Mutex<VecDeque<CargoProcessHandle>>>> = Lazy::new(|| Arc::new(Mutex::new(VecDeque::new())));
37/// Resets the Ctrl+C counter.
38/// This can be called to reset the count when starting a new program or at any other point.
39pub fn reset_ctrl_c_count() {
40 CTRL_C_COUNT.store(0, std::sync::atomic::Ordering::SeqCst);
41}
42
43// pub fn kill_last_process() -> Result<()> {
44// let mut global = GLOBAL_CHILDREN.lock().unwrap();
45
46// if let Some(mut child_handle) = global.pop_back() {
47// // Kill the most recent process
48// eprintln!("Killing the most recent child process...");
49// let _ = child_handle.kill();
50// Ok(())
51// } else {
52// eprintln!("No child processes to kill.");
53// Err(anyhow::anyhow!("No child processes to kill").into())
54// }
55// }
56
57pub fn take_process_results(pid: u32) -> Option<CargoProcessHandle> {
58 let mut global = GLOBAL_CHILDREN.lock().ok()?;
59 // Take ownership
60 // let handle = global.remove(&pid)?;
61 // let mut handle = handle.lock().ok()?;
62 let handle = global.remove(&pid)?;
63 // global.remove(&pid)
64 // This will succeed only if no other Arc exists
65 Arc::try_unwrap(handle)
66 .ok()? // fails if other Arc exists
67 .into_inner()
68 .ok() // fails if poisoned
69}
70
71pub fn get_process_results_in_place(
72 pid: u32,
73) -> Option<crate::e_cargocommand_ext::CargoProcessResult> {
74 let global = GLOBAL_CHILDREN.lock().ok()?; // MutexGuard<HashMap>
75 let handle = global.get(&pid)?.clone(); // Arc<Mutex<CargoProcessHandle>>
76 let handle = handle.lock().ok()?; // MutexGuard<CargoProcessHandle>
77 Some(handle.result.clone()) // ✅ return the result field
78}
79
80// /// Registers a global Ctrl+C handler that interacts with the `GLOBAL_CHILDREN` process container.
81// pub fn register_ctrlc_handler() -> Result<(), Box<dyn Error>> {
82// println!("Registering Ctrl+C handler...");
83// ctrlc::set_handler(move || {
84// let count = CTRL_C_COUNT.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1;
85// {
86// eprintln!("Ctrl+C pressed");
87
88// // lock only ONE mutex safely
89// if let Ok(mut global) = GLOBAL_CHILDREN.try_lock() {
90// // let mut global = GLOBAL_CHILDREN.lock().unwrap();
91// eprintln!("Ctrl+C got lock on global container");
92
93// // If there are processes in the global container, terminate the most recent one
94// if let Some((pid, child_handle)) = global.iter_mut().next() {
95// eprintln!("Ctrl+C pressed, terminating the child process with PID: {}", pid);
96
97// // Lock the child process and kill it
98// let mut child_handle = child_handle.lock().unwrap();
99// if child_handle.requested_exit {
100// eprintln!("Child process is already requested kill...");
101// } else {
102// eprintln!("Child process is not running, no need to kill.");
103// child_handle.requested_exit=true;
104// println!("Killing child process with PID: {}", pid);
105// let _ = child_handle.kill(); // Attempt to kill the process
106// println!("Killed child process with PID: {}", pid);
107
108// reset_ctrl_c_count();
109// return; // Exit after successfully terminating the process
110// }
111
112// // Now remove the process from the global container
113// // let pid_to_remove = *pid;
114
115// // // Reacquire the lock after killing and remove the process from global
116// // drop(global); // Drop the first borrow
117
118// // // Re-lock global and safely remove the entry using the pid
119// // let mut global = GLOBAL_CHILDREN.lock().unwrap();
120// // global.remove(&pid_to_remove); // Remove the process entry by PID
121// // println!("Removed process with PID: {}", pid_to_remove);
122// }
123
124// } else {
125// eprintln!("Couldn't acquire GLOBAL_CHILDREN lock safely");
126// }
127
128/// Registers a global Ctrl+C handler that uses the process manager.
129pub fn register_ctrlc_handler(process_manager: Arc<ProcessManager>) -> Result<(), Box<dyn Error>> {
130 println!("Registering Ctrl+C handler...");
131 ctrlc::set_handler(move || {
132 let count = CTRL_C_COUNT.fetch_add(1, Ordering::SeqCst) + 1;
133 eprintln!("Ctrl+C pressed");
134
135 // Use the process manager's API to handle killing
136 match process_manager.kill_one() {
137 Ok(true) => {
138 eprintln!("Process was successfully terminated.");
139 reset_ctrl_c_count();
140 return; // Exit handler early after a successful kill.
141 }
142 Ok(false) => {
143 eprintln!("No process was killed this time.");
144 }
145 Err(e) => {
146 eprintln!("Error killing process: {:?}", e);
147 }
148 }
149
150 // Handle Ctrl+C count logic for exiting the program.
151 if count == 3 {
152 eprintln!("Ctrl+C pressed 3 times with no child process running. Exiting.");
153 std::process::exit(0);
154 } else if count == 2 {
155 eprintln!("Ctrl+C pressed 2 times, press one more to exit.");
156 } else {
157 eprintln!("Ctrl+C pressed {} times, no child process running.", count);
158 }
159 })?;
160 Ok(())
161}
162
163// }
164
165// // Now handle the Ctrl+C count and display messages
166// // If Ctrl+C is pressed 3 times without any child process, exit the program.
167// if count == 3 {
168// eprintln!("Ctrl+C pressed 3 times with no child process running. Exiting.");
169// std::process::exit(0);
170// } else if count == 2 {
171// // Notify that one more Ctrl+C will exit the program.
172// eprintln!("Ctrl+C pressed 2 times, press one more to exit.");
173// } else {
174// eprintln!("Ctrl+C pressed {} times, no child process running.", count);
175// }
176// })?;
177// Ok(())
178// }
179
180// /// Registers a global Ctrl+C handler once.
181// /// The handler checks GLOBAL_CHILD and kills the child process if present.
182// pub fn register_ctrlc_handler() -> Result<(), Box<dyn Error>> {
183// ctrlc::set_handler(move || {
184// let mut count_lock = CTRL_C_COUNT.lock().unwrap();
185// *count_lock += 1;
186
187// let count = *count_lock;
188
189// // If there is no child process and Ctrl+C is pressed 3 times, exit the program
190// if count == 3 {
191// eprintln!("Ctrl+C pressed 3 times with no child process running. Exiting.");
192// exit(0);
193// } else {
194// let mut child_lock = GLOBAL_CHILD.lock().unwrap();
195// if let Some(child) = child_lock.as_mut() {
196// eprintln!(
197// "Ctrl+C pressed {} times, terminating running child process...",
198// count
199// );
200// let _ = child.kill();
201// } else {
202// eprintln!("Ctrl+C pressed {} times, no child process running.", count);
203// }
204// }
205// })?;
206// Ok(())
207// }
208
209/// Asynchronously launches the GenAI summarization example for the given target.
210/// It builds the command using the target's manifest path as the "origin" argument.
211pub async fn open_ai_summarize_for_target(target: &CargoTarget) {
212 // Extract the origin path from the target (e.g. the manifest path).
213 let origin_path = match &target.origin {
214 Some(TargetOrigin::SingleFile(path)) | Some(TargetOrigin::DefaultBinary(path)) => path,
215 _ => return,
216 };
217
218 let exe_path = match which("cargoe_ai_summarize") {
219 Ok(path) => path,
220 Err(err) => {
221 eprintln!("Error: 'cargoe_ai_summarize' not found in PATH: {}", err);
222 return;
223 }
224 };
225 // Build the command based on the platform.
226 // let mut cmd = if cfg!(target_os = "windows") {
227 // let command_str = format!(
228 // "e_ai_summarize --streaming --stdin {}",
229 // origin_path.as_os_str().to_string_lossy()
230 // );
231 // println!("Running command: {}", command_str);
232 // let mut command = Command::new("cmd");
233 // command.args(["/C", &command_str]);
234 // command
235 // } else {
236 let mut cmd = Command::new(exe_path);
237 cmd.arg("--streaming");
238 cmd.arg("--stdin");
239 // cmd.arg(".");
240 cmd.arg(origin_path);
241 // command
242 // };
243
244 cmd.stdin(Stdio::inherit())
245 .stdout(Stdio::inherit())
246 .stderr(Stdio::inherit());
247
248 // Spawn the command and wait for it to finish.
249 let child = cmd.spawn();
250 let status = child
251 .expect("Failed to spawn command")
252 .wait()
253 .expect("Failed to wait for command");
254
255 if !status.success() {
256 eprintln!("Command exited with status: {}", status);
257 }
258
259 // // Build the command to run the example.
260 // let output = if cfg!(target_os = "windows") {
261 // let command_str = format!("e_ai_summarize --stdin {}", origin_path.as_os_str().to_string_lossy());
262 // println!("Running command: {}", command_str);
263 // Command::new("cmd")
264 // .args([
265 // "/C",
266 // command_str.as_str(),
267 // ])
268 // .output()
269 // } else {
270 // Command::new("e_ai_summarize")
271 // .args([origin_path])
272 // .output()
273 // };
274
275 // // Handle the output from the command.
276 // match output {
277 // Ok(output) if output.status.success() => {
278 // // The summarization example ran successfully.
279 // println!("----
280 // {}", String::from_utf8_lossy(&output.stdout));
281 // }
282 // Ok(output) => {
283 // let msg = format!(
284 // "Error running summarization example:\nstdout: {}\nstderr: {}",
285 // String::from_utf8_lossy(&output.stdout),
286 // String::from_utf8_lossy(&output.stderr)
287 // );
288 // error!("{}", msg);
289 // }
290 // Err(e) => {
291 // let msg = format!("Failed to execute summarization command: {}", e);
292 // error!("{}", msg);
293 // }
294 // }
295}
296
297fn library_hint(lib: &str) -> &str {
298 match lib {
299 "javascriptcoregtk-4.1" => "libjavascriptcoregtk-4.1-dev",
300 "libsoup-3.0" => "libsoup-3.0-dev",
301 "webkit2gtk-4.1" => "libwebkit2gtk-4.1-dev",
302 "openssl" => "libssl-dev",
303 _ => lib, // Fallback, assume same name
304 }
305}
306
307/// In "equivalent" mode, behave exactly like "cargo run --example <name>"
308#[cfg(feature = "equivalent")]
309pub fn run_equivalent_example(
310 cli: &crate::Cli,
311) -> Result<std::process::ExitStatus, Box<dyn Error>> {
312 // In "equivalent" mode, behave exactly like "cargo run --example <name>"
313 let mut cmd = Command::new("cargo");
314 cmd.args([
315 "run",
316 "--example",
317 cli.explicit_example.as_deref().unwrap_or(""),
318 ]);
319 if !cli.extra.is_empty() {
320 cmd.arg("--").args(cli.extra.clone());
321 }
322 // Inherit the standard input (as well as stdout/stderr) so that input is passed through.
323 use std::process::Stdio;
324 cmd.stdin(Stdio::inherit())
325 .stdout(Stdio::inherit())
326 .stderr(Stdio::inherit());
327
328 let status = cmd.status()?;
329 std::process::exit(status.code().unwrap_or(1));
330}
331
332/// Runs the given example (or binary) target.
333pub fn run_example(
334 manager: Arc<ProcessManager>,
335 cli: &crate::Cli,
336 target: &crate::e_target::CargoTarget,
337) -> anyhow::Result<Option<std::process::ExitStatus>> {
338 crate::e_runall::set_rustflags_if_quiet(cli.quiet);
339 // Retrieve the current package name at compile time.
340 let current_bin = env!("CARGO_PKG_NAME");
341
342 // Avoid running our own binary.
343 if target.kind == crate::e_target::TargetKind::Binary && target.name == current_bin {
344 println!(
345 "Skipping automatic run: {} is the same as the running binary",
346 target.name
347 );
348 return Ok(None);
349 }
350
351 // If this is a plugin-provided target, execute it via the plugin's in-process run
352 #[cfg(feature = "uses_plugins")]
353 if target.kind == crate::e_target::TargetKind::Plugin {
354 if let Some(crate::e_target::TargetOrigin::Plugin { plugin_path, .. }) = &target.origin {
355 // Current working directory
356 let cwd = std::env::current_dir()?;
357 // Load the plugin directly based on its file extension
358 let ext = plugin_path
359 .extension()
360 .and_then(|s| s.to_str())
361 .unwrap_or("");
362 let plugin: Box<dyn crate::plugins::plugin_api::Plugin> = match ext {
363 "lua" => {
364 #[cfg(feature = "uses_lua")]
365 {
366 Box::new(crate::plugins::lua_plugin::LuaPlugin::load(
367 plugin_path,
368 cli,
369 manager.clone(),
370 )?)
371 }
372 #[cfg(not(feature = "uses_lua"))]
373 {
374 return Err(anyhow::anyhow!("Lua plugin support is not enabled"));
375 }
376 }
377 "rhai" => {
378 #[cfg(feature = "uses_rhai")]
379 {
380 Box::new(crate::plugins::rhai_plugin::RhaiPlugin::load(
381 plugin_path,
382 cli,
383 manager.clone(),
384 )?)
385 }
386 #[cfg(not(feature = "uses_rhai"))]
387 {
388 return Err(anyhow::anyhow!("Rhai plugin support is not enabled"));
389 }
390 }
391 "wasm" => {
392 #[cfg(feature = "uses_wasm")]
393 {
394 if let Some(wp) =
395 crate::plugins::wasm_plugin::WasmPlugin::load(plugin_path)?
396 {
397 Box::new(wp)
398 } else {
399 // Fallback to generic export plugin
400 Box::new(
401 crate::plugins::wasm_export_plugin::WasmExportPlugin::load(
402 plugin_path,
403 )?
404 .expect("Failed to load export plugin"),
405 )
406 }
407 }
408 #[cfg(not(feature = "uses_wasm"))]
409 {
410 return Err(anyhow::anyhow!("WASM plugin support is not enabled"));
411 }
412 }
413 "dll" => {
414 #[cfg(feature = "uses_wasm")]
415 {
416 Box::new(
417 crate::plugins::wasm_export_plugin::WasmExportPlugin::load(
418 plugin_path,
419 )?
420 .expect("Failed to load export plugin"),
421 )
422 }
423 #[cfg(not(feature = "uses_wasm"))]
424 {
425 return Err(anyhow::anyhow!("WASM export plugin support is not enabled"));
426 }
427 }
428 other => {
429 return Err(anyhow::anyhow!("Unknown plugin extension: {}", other));
430 }
431 };
432 // Run the plugin and capture output
433 let plugin_target = PluginTarget::from(target.clone());
434 let output = plugin.run(&cwd, &plugin_target)?;
435 // Print exit code and subsequent output lines
436 if !output.is_empty() {
437 if let Ok(code) = output[0].parse::<i32>() {
438 eprintln!("Plugin exited with code: {}", code);
439 }
440 for line in &output[1..] {
441 println!("{}", line);
442 }
443 }
444 return Ok(None);
445 }
446 }
447 // Not a plugin, continue with standard cargo invocation
448 let manifest_path = PathBuf::from(target.manifest_path.clone());
449 // Build the command using the CargoCommandBuilder.
450 let mut builder = crate::e_command_builder::CargoCommandBuilder::new(
451 &target.name,
452 &manifest_path,
453 &cli.subcommand,
454 cli.filter,
455 cli.cached,
456 cli.default_binary_is_runner,
457 cli.quiet || cli.json_all_targets,
458 cli.detached,
459 cli.cwd_wsr,
460 )
461 .with_target(target)
462 .with_required_features(&target.manifest_path, target)
463 .with_cli(cli);
464
465 // Build the command.
466 let mut cmd = builder.clone().build_command();
467
468 // Before spawning, determine the directory to run from.
469 // By default, run from the current working directory (cwd).
470 // If --cwd-wsr is specified, run from the workspace root (manifest's parent directory).
471 let mut exec_dir: Option<std::path::PathBuf> = None;
472 if let Some(ref dir) = builder.execution_dir {
473 cmd.current_dir(dir);
474 exec_dir = Some(dir.as_path().to_path_buf());
475 } else if cli.cwd_wsr {
476 // Assume cli has a field `cwd_wsr: bool` set by --cwd-wsr
477 if let Some(dir) = target.manifest_path.parent() {
478 cmd.current_dir(dir);
479 exec_dir = Some(dir.to_path_buf());
480 }
481 } // else: do not set current_dir, so it runs from process cwd
482
483 // Print the full command for debugging.
484 let full_command = format!(
485 "{} {}",
486 cmd.get_program().to_string_lossy(),
487 cmd.get_args()
488 .map(|arg| arg.to_string_lossy())
489 .collect::<Vec<_>>()
490 .join(" ")
491 );
492 println!("Running: {} [from {:?}]", full_command, exec_dir);
493
494 // Check if the manifest triggers the workspace error.
495 let maybe_backup = crate::e_manifest::maybe_patch_manifest_for_run(&target.manifest_path)?;
496 let a_blder = Arc::new(builder.clone());
497 let pid = a_blder.run(|pid, handle| {
498 manager.register(pid, handle);
499 })?;
500 let result = manager.wait(pid, None)?;
501 // println!("HERE IS THE RESULT!{} {:?}",pid,manager.get(pid));
502 // println!("\n\nHERE IS THE RESULT!{} {:?}",pid,result);
503 if result
504 .exit_status
505 .map_or(false, |status| status.code() == Some(101))
506 {
507 println!(
508 "ProcessManager senses pid {} cargo error, running again to capture and analyze",
509 pid,
510 );
511 match builder.clone().capture_output() {
512 Ok(output) => {
513 let system_lib_regex = Regex::new(
514 r"\s*The system library `([^`]+)` required by crate `([^`]+)` was not found\.",
515 )
516 .unwrap();
517
518 if let Some(captures) = system_lib_regex.captures(&output) {
519 let library = &captures[1];
520 let crate_name = &captures[2];
521 println!(
522 "cargo-e detected missing system library '{}' required by crate '{}'.",
523 library, crate_name
524 );
525
526 // Suggest installation based on common package managers
527 println!(
528 "You might need to install '{}' via your system package manager.",
529 library
530 );
531 println!("For example:");
532
533 println!(
534 " • Debian/Ubuntu: sudo apt install {}",
535 library_hint(library)
536 );
537 println!(" • Fedora: sudo dnf install {}", library_hint(library));
538 println!(" • Arch: sudo pacman -S {}", library_hint(library));
539 println!(
540 " • macOS (Homebrew): brew install {}",
541 library_hint(library)
542 );
543 std::process::exit(0);
544 } else if output.contains("error: failed to load manifest for workspace member") {
545 println!("cargo-e error: failed to load manifest for workspace member, please check your workspace configuration.");
546 println!("cargo-e autorecovery: removing manfifest path from argument and changing to parent of Cargo.toml.");
547 let cwd = target
548 .manifest_path
549 .parent()
550 .unwrap_or_else(|| Path::new("."));
551 // Rebuild the command with the new cwd
552 builder.execution_dir = Some(cwd.to_path_buf());
553 // Remove --manifest-path and its associated value from the args array
554 if let Some(pos) = builder.args.iter().position(|arg| arg == "--manifest-path")
555 {
556 // Remove --manifest-path and the next argument (the manifest path value)
557 builder.args.remove(pos); // Remove --manifest-path
558 if pos < builder.args.len() {
559 builder.args.remove(pos); // Remove the manifest path value
560 }
561 }
562 let mut cmd = builder.clone().build_command();
563 cmd.current_dir(cwd);
564
565 // Retry the command execution
566 let mut child = cmd.spawn()?;
567 let status = child.wait()?;
568 return Ok(Some(status)); // Return the exit status after retrying
569 // return run_example(manager, cli, target); // Recursively call run_example
570 }
571 if output.contains("no such command: `tauri`") {
572 println!("cargo tauri is not installed, please install it with cargo install tauri-cli");
573 // Use the yesno function to prompt the user
574 match crate::e_prompts::yesno(
575 "Do you want to install tauri-cli?",
576 Some(true), // Default to yes
577 ) {
578 Ok(Some(true)) => {
579 println!("Installing tauri-cli...");
580 match spawn_cargo_process(&["install", "tauri-cli"]) {
581 Ok(mut child) => {
582 child.wait().ok(); // Wait for the installation to finish
583 } // Installation successful
584 Err(e) => {
585 eprintln!("Error installing tauri-cli: {}", e);
586 }
587 }
588 }
589 Ok(Some(false)) => {}
590 Ok(None) => {
591 println!("Installation cancelled (timeout or invalid input).");
592 }
593 Err(e) => {
594 eprintln!("Error during prompt: {}", e);
595 }
596 }
597 } else if output.contains("error: no such command: `leptos`") {
598 println!("cargo-leptos is not installed, please install it with cargo install cargo-leptos");
599 // Use the yesno function to prompt the user
600 match crate::e_prompts::yesno(
601 "Do you want to install cargo-leptos?",
602 Some(true), // Default to yes
603 ) {
604 Ok(Some(true)) => {
605 println!("Installing cargo-leptos...");
606 match spawn_cargo_process(&["install", "cargo-leptos"]) {
607 Ok(mut child) => {
608 child.wait().ok(); // Wait for the installation to finish
609 } // Installation successful
610 Err(e) => {
611 eprintln!("Error installing cargo-leptos: {}", e);
612 }
613 }
614 }
615 Ok(Some(false)) => {}
616 Ok(None) => {
617 println!("Installation cancelled (timeout or invalid input).");
618 }
619 Err(e) => {
620 eprintln!("Error during prompt: {}", e);
621 }
622 }
623 // needed for cargo-leptos but as part of tool installer
624 // } else if output.contains("Command 'perl' not found. Is perl installed?") {
625 // println!("cargo e sees a perl issue; maybe a prompt in the future or auto-resolution.");
626 // crate::e_autosense::auto_sense_perl();
627 } else if output.contains("Unable to find libclang")
628 || output.contains("couldn't find any valid shared libraries matching: ['clang.dll', 'libclang.dll']")
629{
630 crate::e_autosense::auto_sense_llvm();
631
632 } else if output.contains("no such command: `dx`") {
633 println!("cargo dx is not installed, please install it with cargo install dioxus-cli");
634 } else if output.contains("no such command: `scriptisto`") {
635 println!("cargo scriptisto is not installed, please install it with cargo install scriptisto");
636 } else if output.contains("no such command: `rust-script`") {
637 println!("cargo rust-script is not installed, please install it with cargo install rust-script");
638 } else if output.contains(
639 "No platform feature enabled. Please enable one of the following features:",
640 ) {
641 println!("cargo e sees a dioxus issue; maybe a prompt in the future or auto-resolution.");
642 } else {
643 //println!("cargo error: {}", output);
644 }
645 }
646 Err(e) => {
647 eprintln!("Error running cargo: {}", e);
648 }
649 }
650 }
651 // let is_run_command = matches!(cli.subcommand.as_str(), "run" | "r");
652 // if !is_run_command && result.is_filter || ( result.is_filter && !result.is_could_not_compile ) {
653 result.print_exact();
654 result.print_compact();
655 result.print_short();
656 manager.print_prefixed_summary();
657 let errors: Vec<_> = result
658 .diagnostics
659 .iter()
660 .filter(|d| d.level.eq("error"))
661 .collect();
662 let error_width = errors.len().to_string().len().max(1);
663 let line: Vec<String> = errors
664 .iter()
665 .enumerate()
666 .map(|(i, diag)| {
667 let index = format!("{:0width$}", i + 1, width = error_width);
668 let lineref = if diag.lineref.is_empty() {
669 ""
670 } else {
671 &diag.lineref
672 };
673 // Resolve filename to full path
674 let (filename, goto) = if let Some((file, line, col)) = diag
675 .lineref
676 .split_once(':')
677 .and_then(|(f, rest)| rest.split_once(':').and_then(|(l, c)| Some((f, l, c))))
678 {
679 let full_path = std::fs::canonicalize(file).unwrap_or_else(|_| {
680 let manifest_dir = std::path::Path::new(&manifest_path)
681 .parent()
682 .unwrap_or_else(|| {
683 eprintln!(
684 "Failed to determine parent directory for manifest: {:?}",
685 manifest_path
686 );
687 std::path::Path::new(".")
688 });
689 let fallback_path = manifest_dir.join(file);
690 std::fs::canonicalize(&fallback_path).unwrap_or_else(|_| {
691 let parent_fallback_path = manifest_dir.join("../").join(file);
692 std::fs::canonicalize(&parent_fallback_path).unwrap_or_else(|_| {
693 eprintln!("Failed to resolve full path for: {} using ../", file);
694 file.into()
695 })
696 })
697 });
698 let stripped_file = full_path.to_string_lossy().replace("\\\\?\\", "");
699
700 (stripped_file.to_string(), format!("{}:{}", line, col))
701 } else {
702 ("".to_string(), "".to_string())
703 };
704 let code_path = which("code").unwrap_or_else(|_| "code".to_string().into());
705 format!(
706 "{}: {}\nanchor:{}: {}\\n {}|\"{}\" --goto \"{}:{}\"\n",
707 index,
708 diag.message.trim(),
709 index,
710 diag.message.trim(),
711 lineref,
712 code_path.display(),
713 filename,
714 goto,
715 )
716 })
717 .collect();
718 if !errors.is_empty() {
719 if let Ok(e_window_path) = which("e_window") {
720 // Compose a nice message for e_window's stdin
721 let stats = result.stats;
722 // Compose a table with cargo-e and its version, plus panic info
723 let cargo_e_version = env!("CARGO_PKG_VERSION");
724 let card = format!(
725 "--title \"failed build: {target}\" --width 400 --height 300 --decode-debug\n\
726 target | {target} | string\n\
727 cargo-e | {version} | string\n\
728 \n\
729 failed build: {target}\n{errors} errors.\n\n{additional_errors}",
730 target = stats.target_name,
731 version = cargo_e_version,
732 errors = errors.len(),
733 additional_errors = line
734 .iter()
735 .map(|l| l.as_str())
736 .collect::<Vec<_>>()
737 .join("\n"),
738 );
739 // Set the working directory to the manifest's parent directory
740 let manifest_dir = std::path::Path::new(&manifest_path)
741 .parent()
742 .unwrap_or_else(|| {
743 eprintln!(
744 "Failed to determine parent directory for manifest: {:?}",
745 target.manifest_path
746 );
747 std::path::Path::new(".")
748 });
749
750 let child = std::process::Command::new(e_window_path)
751 .current_dir(manifest_dir) // Set working directory
752 .stdin(std::process::Stdio::piped())
753 .spawn();
754 if let Ok(mut child) = child {
755 if let Some(stdin) = child.stdin.as_mut() {
756 use std::io::Write;
757 let _ = stdin.write_all(card.as_bytes());
758 if let Some(global) = crate::GLOBAL_EWINDOW_PIDS.get() {
759 global.insert(child.id(), child.id());
760 println!("[DEBUG] Added pid {} to GLOBAL_EWINDOW_PIDS", pid);
761 } else {
762 eprintln!("[DEBUG] GLOBAL_EWINDOW_PIDS is not initialized");
763 // If GLOBAL_EWINDOW_PIDS is not initialized, you may want to initialize it or handle the error accordingly.
764 // For now, just print a debug message.
765 }
766 }
767 }
768 }
769 }
770
771 // }
772
773 // let handle= Arc::new(builder).run_wait()?;
774 // Spawn the process.
775 // let child = cmd.spawn()?;
776 // {
777 // let mut global = GLOBAL_CHILD.lock().unwrap();
778 // *global = Some(child);
779 // }
780 // let status = {
781 // let mut global = GLOBAL_CHILD.lock().unwrap();
782 // if let Some(mut child) = global.take() {
783 // child.wait()?
784 // } else {
785 // return Err(anyhow::anyhow!("Child process missing"));
786 // }
787 // };
788
789 // Restore the manifest if we patched it.
790 if let Some(original) = maybe_backup {
791 fs::write(&target.manifest_path, original)?;
792 }
793 #[cfg(feature = "uses_tts")]
794 wait_for_tts_to_finish(15000);
795
796 Ok(result.exit_status)
797}
798
799#[cfg(feature = "uses_tts")]
800pub fn wait_for_tts_to_finish(max_wait_ms: u64) {
801 let tts_mutex = crate::GLOBAL_TTS.get();
802 if tts_mutex.is_none() {
803 return;
804 } else {
805 println!("Waiting for TTS to finish speaking...");
806 }
807 let start = std::time::Instant::now();
808 let mut tts_guard = None;
809 for _ in 0..3 {
810 if let Ok(guard) = tts_mutex.unwrap().lock() {
811 tts_guard = Some(guard);
812 break;
813 } else {
814 std::thread::sleep(std::time::Duration::from_millis(100));
815 }
816 if start.elapsed().as_millis() as u64 >= max_wait_ms {
817 eprintln!("Timeout while trying to lock TTS mutex.");
818 return;
819 }
820 }
821 if let Some(tts) = tts_guard {
822 while tts.is_speaking().unwrap_or(false) {
823 if start.elapsed().as_millis() as u64 >= max_wait_ms {
824 eprintln!("Timeout while waiting for TTS to finish speaking.");
825 break;
826 }
827 std::thread::sleep(std::time::Duration::from_millis(100));
828 }
829 } else {
830 eprintln!("Failed to lock TTS mutex after 3 attempts, skipping wait.");
831 }
832}
833
834// /// Runs an example or binary target, applying a temporary manifest patch if a workspace error is detected.
835// /// This function uses the same idea as in the collection helpers: if the workspace error is found,
836// /// we patch the manifest, run the command, and then restore the manifest.
837// pub fn run_example(
838// target: &crate::e_target::CargoTarget,
839// extra_args: &[String],
840// ) -> Result<std::process::ExitStatus, Box<dyn Error>> {
841// // Retrieve the current package name (or binary name) at compile time.
842
843// use crate::e_target::TargetKind;
844
845// let current_bin = env!("CARGO_PKG_NAME");
846
847// // Avoid running our own binary if the target's name is the same.
848// if target.kind == TargetKind::Binary && target.name == current_bin {
849// return Err(format!(
850// "Skipping automatic run: {} is the same as the running binary",
851// target.name
852// )
853// .into());
854// }
855
856// let mut cmd = Command::new("cargo");
857// // Determine which manifest file is used.
858// let manifest_path: PathBuf;
859
860// match target.kind {
861// TargetKind::Bench => {
862// manifest_path = PathBuf::from(target.manifest_path.clone());
863// cmd.args([
864// "bench",
865// "--bench",
866// &target.name,
867// "--manifest-path",
868// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
869// ]);
870// }
871// TargetKind::Test => {
872// manifest_path = PathBuf::from(target.manifest_path.clone());
873// cmd.args([
874// "test",
875// "--test",
876// &target.name,
877// "--manifest-path",
878// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
879// ]);
880// }
881// TargetKind::Manifest => {
882// manifest_path = PathBuf::from(target.manifest_path.clone());
883// cmd.args([
884// "run",
885// "--release",
886// "--manifest-path",
887// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
888// "-p",
889// &target.name,
890// ]);
891// }
892// TargetKind::Example => {
893// if target.extended {
894// println!(
895// "Running extended example in folder: examples/{}",
896// target.name
897// );
898// // For extended examples, assume the manifest is inside the example folder.
899// manifest_path = PathBuf::from(format!("examples/{}/Cargo.toml", target.name));
900// cmd.arg("run")
901// .current_dir(format!("examples/{}", target.name));
902// } else {
903// manifest_path = PathBuf::from(crate::locate_manifest(false)?);
904// cmd.args([
905// "run",
906// "--release",
907// "--example",
908// &target.name,
909// "--manifest-path",
910// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
911// ]);
912// }
913// }
914// TargetKind::Binary => {
915// println!("Running binary: {}", target.name);
916// manifest_path = PathBuf::from(crate::locate_manifest(false)?);
917// cmd.args([
918// "run",
919// "--release",
920// "--bin",
921// &target.name,
922// "--manifest-path",
923// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
924// ]);
925// }
926// TargetKind::ExtendedBinary => {
927// println!("Running extended binary: {}", target.name);
928// manifest_path = PathBuf::from(crate::locate_manifest(false)?);
929// cmd.args([
930// "run",
931// "--release",
932// "--manifest-path",
933// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
934// "--bin",
935// &target.name,
936// ]);
937// }
938// TargetKind::ExtendedExample => {
939// println!("Running extended example: {}", target.name);
940// manifest_path = PathBuf::from(crate::locate_manifest(false)?);
941// cmd.args([
942// "run",
943// "--release",
944// "--manifest-path",
945// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
946// "--example",
947// &target.name,
948// ]);
949// }
950// TargetKind::ManifestTauri => {
951// println!("Running tauri: {}", target.name);
952// // For a Tauri example, run `cargo tauri dev`
953// manifest_path = PathBuf::from(target.manifest_path.clone());
954// let manifest_dir = PathBuf::from(manifest_path.parent().expect("expected a parent"));
955// // Start a new command for tauri dev
956// cmd.arg("tauri").arg("dev").current_dir(manifest_dir); // run from the folder where Cargo.toml is located
957// }
958// TargetKind::ManifestDioxus => {
959// println!("Running dioxus: {}", target.name);
960// cmd = Command::new("dx");
961// // For a Tauri example, run `cargo tauri dev`
962// manifest_path = PathBuf::from(target.manifest_path.clone());
963// let manifest_dir = PathBuf::from(manifest_path.parent().expect("expected a parent"));
964// // Start a new command for tauri dev
965// cmd.arg("serve").current_dir(manifest_dir); // run from the folder where Cargo.toml is located
966// }
967// TargetKind::ManifestDioxusExample => {
968// println!("Running dioxus: {}", target.name);
969// cmd = Command::new("dx");
970// // For a Tauri example, run `cargo tauri dev`
971// manifest_path = PathBuf::from(target.manifest_path.clone());
972// let manifest_dir = PathBuf::from(manifest_path.parent().expect("expected a parent"));
973// // Start a new command for tauri dev
974// cmd.arg("serve")
975// .arg("--example")
976// .arg(&target.name)
977// .current_dir(manifest_dir); // run from the folder where Cargo.toml is located
978// }
979// }
980
981// // --- Add required-features support ---
982// // This call will search the provided manifest, and if it's a workspace,
983// // it will search workspace members for the target.
984// if let Some(features) = crate::e_manifest::get_required_features_from_manifest(
985// manifest_path.as_path(),
986// &target.kind,
987// &target.name,
988// ) {
989// cmd.args(&["--features", &features]);
990// }
991// // --- End required-features support ---
992
993// if !extra_args.is_empty() {
994// cmd.arg("--").args(extra_args);
995// }
996
997// let full_command = format!(
998// "{} {}",
999// cmd.get_program().to_string_lossy(),
1000// cmd.get_args()
1001// .map(|arg| arg.to_string_lossy())
1002// .collect::<Vec<_>>()
1003// .join(" ")
1004// );
1005// println!("Running: {}", full_command);
1006
1007// // Before spawning, check if the manifest triggers the workspace error.
1008// // If so, patch it temporarily.
1009// let maybe_backup = crate::e_manifest::maybe_patch_manifest_for_run(&manifest_path)?;
1010
1011// // Spawn the process.
1012// let child = cmd.spawn()?;
1013// {
1014// let mut global = GLOBAL_CHILD.lock().unwrap();
1015// *global = Some(child);
1016// }
1017// let status = {
1018// let mut global = GLOBAL_CHILD.lock().unwrap();
1019// if let Some(mut child) = global.take() {
1020// child.wait()?
1021// } else {
1022// return Err("Child process missing".into());
1023// }
1024// };
1025
1026// // Restore the manifest if we patched it.
1027// if let Some(original) = maybe_backup {
1028// fs::write(&manifest_path, original)?;
1029// }
1030
1031// // println!("Process exited with status: {:?}", status.code());
1032// Ok(status)
1033// }
1034/// Helper function to spawn a cargo process.
1035/// On Windows, this sets the CREATE_NEW_PROCESS_GROUP flag.
1036pub fn spawn_cargo_process(args: &[&str]) -> Result<Child, Box<dyn Error>> {
1037 // #[cfg(windows)]
1038 // {
1039 // use std::os::windows::process::CommandExt;
1040 // const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
1041 // let child = Command::new("cargo")
1042 // .args(args)
1043 // .creation_flags(CREATE_NEW_PROCESS_GROUP)
1044 // .spawn()?;
1045 // Ok(child)
1046 // }
1047 // #[cfg(not(windows))]
1048 // {
1049 let child = Command::new("cargo").args(args).spawn()?;
1050 Ok(child)
1051 // }
1052}
1053
1054/// Returns true if the file's a "scriptisto"
1055pub fn is_active_scriptisto<P: AsRef<Path>>(path: P) -> io::Result<bool> {
1056 let file = File::open(path)?;
1057 let mut reader = std::io::BufReader::new(file);
1058 let mut first_line = String::new();
1059 reader.read_line(&mut first_line)?;
1060 if !first_line.contains("scriptisto") || !first_line.starts_with("#") {
1061 return Ok(false);
1062 }
1063 Ok(true)
1064}
1065
1066/// Returns true if the file's a "rust-script"
1067pub fn is_active_rust_script<P: AsRef<Path>>(path: P) -> io::Result<bool> {
1068 let file = File::open(path)?;
1069 let mut reader = std::io::BufReader::new(file);
1070 let mut first_line = String::new();
1071 reader.read_line(&mut first_line)?;
1072 if !first_line.contains("rust-script") || !first_line.starts_with("#") {
1073 return Ok(false);
1074 }
1075 Ok(true)
1076}
1077
1078/// Checks if `scriptisto` is installed and suggests installation if it's not.
1079pub fn check_scriptisto_installed() -> Result<std::path::PathBuf, Box<dyn Error>> {
1080 let r = which("scriptisto");
1081 match r {
1082 Ok(_) => {
1083 // installed
1084 }
1085 Err(e) => {
1086 // scriptisto is not found in the PATH
1087 eprintln!("scriptisto is not installed.");
1088 println!("Suggestion: To install scriptisto, run the following command:");
1089 println!("cargo install scriptisto");
1090 return Err(e.into());
1091 }
1092 }
1093 Ok(r?)
1094}
1095
1096pub fn run_scriptisto<P: AsRef<Path>>(script_path: P, args: &[&str]) -> Option<Child> {
1097 let scriptisto = check_scriptisto_installed().ok()?;
1098
1099 let script: &std::path::Path = script_path.as_ref();
1100 let child = Command::new(scriptisto)
1101 .arg(script)
1102 .args(args)
1103 .spawn()
1104 .ok()?;
1105 Some(child)
1106}
1107
1108/// Checks if `rust-script` is installed and suggests installation if it's not.
1109pub fn check_rust_script_installed() -> Result<std::path::PathBuf, Box<dyn Error>> {
1110 let r = which("rust-script");
1111 match r {
1112 Ok(_) => {
1113 // rust-script is installed
1114 }
1115 Err(e) => {
1116 // rust-script is not found in the PATH
1117 eprintln!("rust-script is not installed.");
1118 println!("Suggestion: To install rust-script, run the following command:");
1119 println!("cargo install rust-script");
1120 return Err(e.into());
1121 }
1122 }
1123 Ok(r?)
1124}
1125
1126pub fn run_rust_script<P: AsRef<Path>>(script_path: P, args: &[&str]) -> Option<Child> {
1127 let rust_script = check_rust_script_installed();
1128 if rust_script.is_err() {
1129 return None;
1130 }
1131 let rust_script = rust_script.unwrap();
1132 let script: &std::path::Path = script_path.as_ref();
1133 let child = Command::new(rust_script)
1134 .arg(script)
1135 .args(args)
1136 .spawn()
1137 .ok()?;
1138 Some(child)
1139}
1140
1141pub fn run_rust_script_with_ctrlc_handling(explicit: String, extra_args: Vec<String>) {
1142 let explicit_path = Path::new(&explicit); // Construct Path outside the lock
1143
1144 if explicit_path.exists() {
1145 let extra_str_slice: Vec<String> = extra_args.iter().cloned().collect();
1146 if let Ok(true) = is_active_rust_script(explicit_path) {
1147 // Run the child process in a separate thread to allow Ctrl+C handling
1148 let handle = thread::spawn(move || {
1149 let extra_str_slice_cloned = extra_str_slice.clone();
1150 let mut child = run_rust_script(
1151 &explicit,
1152 &extra_str_slice_cloned
1153 .iter()
1154 .map(String::as_str)
1155 .collect::<Vec<_>>(),
1156 )
1157 .unwrap_or_else(|| {
1158 eprintln!("Failed to run rust-script: {:?}", &explicit);
1159 std::process::exit(1); // Exit with an error code
1160 });
1161
1162 child.wait()
1163 });
1164
1165 match handle.join() {
1166 Ok(_) => {
1167 println!("Child process finished successfully.");
1168 }
1169 Err(_) => {
1170 eprintln!("Child process took too long to finish. Exiting...");
1171 std::process::exit(1); // Exit if the process takes too long
1172 }
1173 }
1174 }
1175 }
1176}
1177
1178pub fn run_scriptisto_with_ctrlc_handling(explicit: String, extra_args: Vec<String>) {
1179 let relative: String = make_relative(Path::new(&explicit)).unwrap_or_else(|e| {
1180 eprintln!("Error computing relative path: {}", e);
1181 std::process::exit(1);
1182 });
1183
1184 let explicit_path = Path::new(&relative);
1185 if explicit_path.exists() {
1186 // let extra_args = EXTRA_ARGS.lock().unwrap(); // Locking the Mutex to access the data
1187 let extra_str_slice: Vec<String> = extra_args.to_vec();
1188
1189 if let Ok(true) = is_active_scriptisto(explicit_path) {
1190 // Run the child process in a separate thread to allow Ctrl+C handling
1191 let handle = thread::spawn(move || {
1192 let extra_str_slice_cloned: Vec<String> = extra_str_slice.clone();
1193 let mut child = run_scriptisto(
1194 &relative,
1195 &extra_str_slice_cloned
1196 .iter()
1197 .map(String::as_str)
1198 .collect::<Vec<_>>(),
1199 )
1200 .unwrap_or_else(|| {
1201 eprintln!("Failed to run rust-script: {:?}", &explicit);
1202 std::process::exit(1); // Exit with an error code
1203 });
1204 let _ = child.wait();
1205 // // Lock global to store the child process
1206 // {
1207 // let mut global = GLOBAL_CHILD.lock().unwrap();
1208 // *global = Some(child);
1209 // }
1210
1211 // // Wait for the child process to complete
1212 // let status = {
1213 // let mut global = GLOBAL_CHILD.lock().unwrap();
1214 // if let Some(mut child) = global.take() {
1215 // child.wait()
1216 // } else {
1217 // // Handle missing child process
1218 // eprintln!("Child process missing");
1219 // std::process::exit(1); // Exit with an error code
1220 // }
1221 // };
1222
1223 // // Handle the child process exit status
1224 // match status {
1225 // Ok(status) => {
1226 // eprintln!("Child process exited with status code: {:?}", status.code());
1227 // std::process::exit(status.code().unwrap_or(1)); // Exit with the child's status code
1228 // }
1229 // Err(err) => {
1230 // eprintln!("Error waiting for child process: {}", err);
1231 // std::process::exit(1); // Exit with an error code
1232 // }
1233 // }
1234 });
1235
1236 // Wait for the thread to complete, but with a timeout
1237 // let timeout = Duration::from_secs(10);
1238 match handle.join() {
1239 Ok(_) => {
1240 println!("Child process finished successfully.");
1241 }
1242 Err(_) => {
1243 eprintln!("Child process took too long to finish. Exiting...");
1244 std::process::exit(1); // Exit if the process takes too long
1245 }
1246 }
1247 }
1248 }
1249}
1250/// Given any path, produce a relative path string starting with `./` (or `.\` on Windows).
1251fn make_relative(path: &Path) -> std::io::Result<String> {
1252 let cwd = env::current_dir()?;
1253 // Try to strip the cwd prefix; if it isn’t under cwd, just use the original path.
1254 let rel: PathBuf = match path.strip_prefix(&cwd) {
1255 Ok(stripped) => stripped.to_path_buf(),
1256 Err(_) => path.to_path_buf(),
1257 };
1258
1259 let mut rel = if rel.components().count() == 0 {
1260 // special case: the same directory
1261 PathBuf::from(".")
1262 } else {
1263 rel
1264 };
1265
1266 // Prepend "./" (or ".\") if it doesn’t already start with "." or ".."
1267 let first = rel.components().next().unwrap();
1268 match first {
1269 std::path::Component::CurDir | std::path::Component::ParentDir => {}
1270 _ => {
1271 rel = PathBuf::from(".").join(rel);
1272 }
1273 }
1274
1275 // Convert back to a string with the correct separator
1276 let s = rel
1277 .to_str()
1278 .expect("Relative path should be valid UTF-8")
1279 .to_string();
1280
1281 Ok(s)
1282}
1283
1284// trait JoinTimeout {
1285// fn join_timeout(self, timeout: Duration) -> Result<(), ()>;
1286// }
1287
1288// impl<T> JoinTimeout for thread::JoinHandle<T> {
1289// fn join_timeout(self, timeout: Duration) -> Result<(), ()> {
1290// println!("Waiting for thread to finish...{}", timeout.as_secs());
1291// let _ = thread::sleep(timeout);
1292// match self.join() {
1293// Ok(_) => Ok(()),
1294// Err(_) => Err(()),
1295// }
1296// }
1297// }