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 std::collections::HashMap;
12use std::fs::File;
13use std::io::{self, BufRead};
14use std::path::Path;
15use std::process::Command;
16use std::sync::atomic::{AtomicUsize, Ordering};
17use std::thread;
18use which::which; // Adjust the import based on your project structure
19
20// lazy_static! {
21// pub static ref GLOBAL_CHILDREN: Arc<Mutex<Vec<Arc<CargoProcessHandle>>>> = Arc::new(Mutex::new(Vec::new()));
22// static CTRL_C_COUNT: Lazy<Mutex<u32>> = Lazy::new(|| Mutex::new(0));
23// }
24
25// pub static GLOBAL_CHILDREN: Lazy<Arc<Mutex<Vec<Arc<Mutex<CargoProcessHandle>>>>>> = Lazy::new(|| Arc::new(Mutex::new(Vec::new())));
26pub static GLOBAL_CHILDREN: Lazy<Arc<Mutex<HashMap<u32, Arc<Mutex<CargoProcessHandle>>>>>> =
27 Lazy::new(|| Arc::new(Mutex::new(HashMap::new())));
28
29static CTRL_C_COUNT: AtomicUsize = AtomicUsize::new(0);
30
31// Global shared container for the currently running child process.
32// pub static GLOBAL_CHILD: Lazy<Arc<Mutex<Option<Child>>>> = Lazy::new(|| Arc::new(Mutex::new(None)));
33// static CTRL_C_COUNT: Lazy<Mutex<u32>> = Lazy::new(|| Mutex::new(0));
34
35// pub static GLOBAL_CHILDREN: Lazy<Arc<Mutex<VecDeque<CargoProcessHandle>>>> = Lazy::new(|| Arc::new(Mutex::new(VecDeque::new())));
36/// Resets the Ctrl+C counter.
37/// This can be called to reset the count when starting a new program or at any other point.
38pub fn reset_ctrl_c_count() {
39 CTRL_C_COUNT.store(0, std::sync::atomic::Ordering::SeqCst);
40}
41
42// pub fn kill_last_process() -> Result<()> {
43// let mut global = GLOBAL_CHILDREN.lock().unwrap();
44
45// if let Some(mut child_handle) = global.pop_back() {
46// // Kill the most recent process
47// eprintln!("Killing the most recent child process...");
48// let _ = child_handle.kill();
49// Ok(())
50// } else {
51// eprintln!("No child processes to kill.");
52// Err(anyhow::anyhow!("No child processes to kill").into())
53// }
54// }
55
56pub fn take_process_results(pid: u32) -> Option<CargoProcessHandle> {
57 let mut global = GLOBAL_CHILDREN.lock().ok()?;
58 // Take ownership
59 // let handle = global.remove(&pid)?;
60 // let mut handle = handle.lock().ok()?;
61 let handle = global.remove(&pid)?;
62 // global.remove(&pid)
63 // This will succeed only if no other Arc exists
64 Arc::try_unwrap(handle)
65 .ok()? // fails if other Arc exists
66 .into_inner()
67 .ok() // fails if poisoned
68}
69
70pub fn get_process_results_in_place(
71 pid: u32,
72) -> Option<crate::e_cargocommand_ext::CargoProcessResult> {
73 let global = GLOBAL_CHILDREN.lock().ok()?; // MutexGuard<HashMap>
74 let handle = global.get(&pid)?.clone(); // Arc<Mutex<CargoProcessHandle>>
75 let handle = handle.lock().ok()?; // MutexGuard<CargoProcessHandle>
76 Some(handle.result.clone()) // ✅ return the result field
77}
78
79// /// Registers a global Ctrl+C handler that interacts with the `GLOBAL_CHILDREN` process container.
80// pub fn register_ctrlc_handler() -> Result<(), Box<dyn Error>> {
81// println!("Registering Ctrl+C handler...");
82// ctrlc::set_handler(move || {
83// let count = CTRL_C_COUNT.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1;
84// {
85// eprintln!("Ctrl+C pressed");
86
87// // lock only ONE mutex safely
88// if let Ok(mut global) = GLOBAL_CHILDREN.try_lock() {
89// // let mut global = GLOBAL_CHILDREN.lock().unwrap();
90// eprintln!("Ctrl+C got lock on global container");
91
92// // If there are processes in the global container, terminate the most recent one
93// if let Some((pid, child_handle)) = global.iter_mut().next() {
94// eprintln!("Ctrl+C pressed, terminating the child process with PID: {}", pid);
95
96// // Lock the child process and kill it
97// let mut child_handle = child_handle.lock().unwrap();
98// if child_handle.requested_exit {
99// eprintln!("Child process is already requested kill...");
100// } else {
101// eprintln!("Child process is not running, no need to kill.");
102// child_handle.requested_exit=true;
103// println!("Killing child process with PID: {}", pid);
104// let _ = child_handle.kill(); // Attempt to kill the process
105// println!("Killed child process with PID: {}", pid);
106
107// reset_ctrl_c_count();
108// return; // Exit after successfully terminating the process
109// }
110
111// // Now remove the process from the global container
112// // let pid_to_remove = *pid;
113
114// // // Reacquire the lock after killing and remove the process from global
115// // drop(global); // Drop the first borrow
116
117// // // Re-lock global and safely remove the entry using the pid
118// // let mut global = GLOBAL_CHILDREN.lock().unwrap();
119// // global.remove(&pid_to_remove); // Remove the process entry by PID
120// // println!("Removed process with PID: {}", pid_to_remove);
121// }
122
123// } else {
124// eprintln!("Couldn't acquire GLOBAL_CHILDREN lock safely");
125// }
126
127/// Registers a global Ctrl+C handler that uses the process manager.
128pub fn register_ctrlc_handler(process_manager: Arc<ProcessManager>) -> Result<(), Box<dyn Error>> {
129 println!("Registering Ctrl+C handler...");
130 ctrlc::set_handler(move || {
131 let count = CTRL_C_COUNT.fetch_add(1, Ordering::SeqCst) + 1;
132 eprintln!("Ctrl+C pressed");
133
134 // Use the process manager's API to handle killing
135 match process_manager.kill_one() {
136 Ok(true) => {
137 eprintln!("Process was successfully terminated.");
138 reset_ctrl_c_count();
139 return; // Exit handler early after a successful kill.
140 }
141 Ok(false) => {
142 eprintln!("No process was killed this time.");
143 }
144 Err(e) => {
145 eprintln!("Error killing process: {:?}", e);
146 }
147 }
148
149 // Handle Ctrl+C count logic for exiting the program.
150 if count == 3 {
151 eprintln!("Ctrl+C pressed 3 times with no child process running. Exiting.");
152 std::process::exit(0);
153 } else if count == 2 {
154 eprintln!("Ctrl+C pressed 2 times, press one more to exit.");
155 } else {
156 eprintln!("Ctrl+C pressed {} times, no child process running.", count);
157 }
158 })?;
159 Ok(())
160}
161
162// }
163
164// // Now handle the Ctrl+C count and display messages
165// // If Ctrl+C is pressed 3 times without any child process, exit the program.
166// if count == 3 {
167// eprintln!("Ctrl+C pressed 3 times with no child process running. Exiting.");
168// std::process::exit(0);
169// } else if count == 2 {
170// // Notify that one more Ctrl+C will exit the program.
171// eprintln!("Ctrl+C pressed 2 times, press one more to exit.");
172// } else {
173// eprintln!("Ctrl+C pressed {} times, no child process running.", count);
174// }
175// })?;
176// Ok(())
177// }
178
179// /// Registers a global Ctrl+C handler once.
180// /// The handler checks GLOBAL_CHILD and kills the child process if present.
181// pub fn register_ctrlc_handler() -> Result<(), Box<dyn Error>> {
182// ctrlc::set_handler(move || {
183// let mut count_lock = CTRL_C_COUNT.lock().unwrap();
184// *count_lock += 1;
185
186// let count = *count_lock;
187
188// // If there is no child process and Ctrl+C is pressed 3 times, exit the program
189// if count == 3 {
190// eprintln!("Ctrl+C pressed 3 times with no child process running. Exiting.");
191// exit(0);
192// } else {
193// let mut child_lock = GLOBAL_CHILD.lock().unwrap();
194// if let Some(child) = child_lock.as_mut() {
195// eprintln!(
196// "Ctrl+C pressed {} times, terminating running child process...",
197// count
198// );
199// let _ = child.kill();
200// } else {
201// eprintln!("Ctrl+C pressed {} times, no child process running.", count);
202// }
203// }
204// })?;
205// Ok(())
206// }
207
208/// Asynchronously launches the GenAI summarization example for the given target.
209/// It builds the command using the target's manifest path as the "origin" argument.
210pub async fn open_ai_summarize_for_target(target: &CargoTarget) {
211 // Extract the origin path from the target (e.g. the manifest path).
212 let origin_path = match &target.origin {
213 Some(TargetOrigin::SingleFile(path)) | Some(TargetOrigin::DefaultBinary(path)) => path,
214 _ => return,
215 };
216
217 let exe_path = match which("cargoe_ai_summarize") {
218 Ok(path) => path,
219 Err(err) => {
220 eprintln!("Error: 'cargoe_ai_summarize' not found in PATH: {}", err);
221 return;
222 }
223 };
224 // Build the command based on the platform.
225 // let mut cmd = if cfg!(target_os = "windows") {
226 // let command_str = format!(
227 // "e_ai_summarize --streaming --stdin {}",
228 // origin_path.as_os_str().to_string_lossy()
229 // );
230 // println!("Running command: {}", command_str);
231 // let mut command = Command::new("cmd");
232 // command.args(["/C", &command_str]);
233 // command
234 // } else {
235 let mut cmd = Command::new(exe_path);
236 cmd.arg("--streaming");
237 cmd.arg("--stdin");
238 // cmd.arg(".");
239 cmd.arg(origin_path);
240 // command
241 // };
242
243 cmd.stdin(Stdio::inherit())
244 .stdout(Stdio::inherit())
245 .stderr(Stdio::inherit());
246
247 // Spawn the command and wait for it to finish.
248 let child = cmd.spawn();
249 let status = child
250 .expect("Failed to spawn command")
251 .wait()
252 .expect("Failed to wait for command");
253
254 if !status.success() {
255 eprintln!("Command exited with status: {}", status);
256 }
257
258 // // Build the command to run the example.
259 // let output = if cfg!(target_os = "windows") {
260 // let command_str = format!("e_ai_summarize --stdin {}", origin_path.as_os_str().to_string_lossy());
261 // println!("Running command: {}", command_str);
262 // Command::new("cmd")
263 // .args([
264 // "/C",
265 // command_str.as_str(),
266 // ])
267 // .output()
268 // } else {
269 // Command::new("e_ai_summarize")
270 // .args([origin_path])
271 // .output()
272 // };
273
274 // // Handle the output from the command.
275 // match output {
276 // Ok(output) if output.status.success() => {
277 // // The summarization example ran successfully.
278 // println!("----
279 // {}", String::from_utf8_lossy(&output.stdout));
280 // }
281 // Ok(output) => {
282 // let msg = format!(
283 // "Error running summarization example:\nstdout: {}\nstderr: {}",
284 // String::from_utf8_lossy(&output.stdout),
285 // String::from_utf8_lossy(&output.stderr)
286 // );
287 // error!("{}", msg);
288 // }
289 // Err(e) => {
290 // let msg = format!("Failed to execute summarization command: {}", e);
291 // error!("{}", msg);
292 // }
293 // }
294}
295
296/// In "equivalent" mode, behave exactly like "cargo run --example <name>"
297#[cfg(feature = "equivalent")]
298pub fn run_equivalent_example(
299 cli: &crate::Cli,
300) -> Result<std::process::ExitStatus, Box<dyn Error>> {
301 // In "equivalent" mode, behave exactly like "cargo run --example <name>"
302 let mut cmd = Command::new("cargo");
303 cmd.args([
304 "run",
305 "--example",
306 cli.explicit_example.as_deref().unwrap_or(""),
307 ]);
308 if !cli.extra.is_empty() {
309 cmd.arg("--").args(cli.extra.clone());
310 }
311 // Inherit the standard input (as well as stdout/stderr) so that input is passed through.
312 use std::process::Stdio;
313 cmd.stdin(Stdio::inherit())
314 .stdout(Stdio::inherit())
315 .stderr(Stdio::inherit());
316
317 let status = cmd.status()?;
318 std::process::exit(status.code().unwrap_or(1));
319}
320
321/// Runs the given example (or binary) target.
322pub fn run_example(
323 manager: Arc<ProcessManager>,
324 cli: &crate::Cli,
325 target: &crate::e_target::CargoTarget,
326) -> anyhow::Result<Option<std::process::ExitStatus>> {
327 crate::e_runall::set_rustflags_if_quiet(cli.quiet);
328 // Retrieve the current package name at compile time.
329 let current_bin = env!("CARGO_PKG_NAME");
330
331 // Avoid running our own binary.
332 if target.kind == crate::e_target::TargetKind::Binary && target.name == current_bin {
333 println!(
334 "Skipping automatic run: {} is the same as the running binary",
335 target.name
336 );
337 return Ok(None);
338 }
339
340 // If this is a plugin-provided target, execute it via the plugin's in-process run
341 #[cfg(feature = "uses_plugins")]
342 if target.kind == crate::e_target::TargetKind::Plugin {
343 if let Some(crate::e_target::TargetOrigin::Plugin { plugin_path, .. }) = &target.origin {
344 // Current working directory
345 let cwd = std::env::current_dir()?;
346 // Load the plugin directly based on its file extension
347 let ext = plugin_path
348 .extension()
349 .and_then(|s| s.to_str())
350 .unwrap_or("");
351 let plugin: Box<dyn crate::plugins::plugin_api::Plugin> = match ext {
352 "lua" => {
353 #[cfg(feature = "uses_lua")]
354 {
355 Box::new(crate::plugins::lua_plugin::LuaPlugin::load(
356 plugin_path,
357 cli,
358 manager.clone(),
359 )?)
360 }
361 #[cfg(not(feature = "uses_lua"))]
362 {
363 return Err(anyhow::anyhow!("Lua plugin support is not enabled"));
364 }
365 }
366 "rhai" => {
367 #[cfg(feature = "uses_rhai")]
368 {
369 Box::new(crate::plugins::rhai_plugin::RhaiPlugin::load(
370 plugin_path,
371 cli,
372 manager.clone(),
373 )?)
374 }
375 #[cfg(not(feature = "uses_rhai"))]
376 {
377 return Err(anyhow::anyhow!("Rhai plugin support is not enabled"));
378 }
379 }
380 "wasm" => {
381 #[cfg(feature = "uses_wasm")]
382 {
383 if let Some(wp) =
384 crate::plugins::wasm_plugin::WasmPlugin::load(plugin_path)?
385 {
386 Box::new(wp)
387 } else {
388 // Fallback to generic export plugin
389 Box::new(
390 crate::plugins::wasm_export_plugin::WasmExportPlugin::load(
391 plugin_path,
392 )?
393 .expect("Failed to load export plugin"),
394 )
395 }
396 }
397 #[cfg(not(feature = "uses_wasm"))]
398 {
399 return Err(anyhow::anyhow!("WASM plugin support is not enabled"));
400 }
401 }
402 "dll" => {
403 #[cfg(feature = "uses_wasm")]
404 {
405 Box::new(
406 crate::plugins::wasm_export_plugin::WasmExportPlugin::load(
407 plugin_path,
408 )?
409 .expect("Failed to load export plugin"),
410 )
411 }
412 #[cfg(not(feature = "uses_wasm"))]
413 {
414 return Err(anyhow::anyhow!("WASM export plugin support is not enabled"));
415 }
416 }
417 other => {
418 return Err(anyhow::anyhow!("Unknown plugin extension: {}", other));
419 }
420 };
421 // Run the plugin and capture output
422 let plugin_target = PluginTarget::from(target.clone());
423 let output = plugin.run(&cwd, &plugin_target)?;
424 // Print exit code and subsequent output lines
425 if !output.is_empty() {
426 if let Ok(code) = output[0].parse::<i32>() {
427 eprintln!("Plugin exited with code: {}", code);
428 }
429 for line in &output[1..] {
430 println!("{}", line);
431 }
432 }
433 return Ok(None);
434 }
435 }
436 // Not a plugin, continue with standard cargo invocation
437 let manifest_path = PathBuf::from(target.manifest_path.clone());
438 // Build the command using the CargoCommandBuilder.
439 let mut builder = crate::e_command_builder::CargoCommandBuilder::new(
440 &manifest_path,
441 &cli.subcommand,
442 cli.filter,
443 )
444 .with_target(target)
445 .with_required_features(&target.manifest_path, target)
446 .with_cli(cli);
447
448 if !cli.extra.is_empty() {
449 builder = builder.with_extra_args(&cli.extra);
450 }
451
452 // Build the command.
453 let mut cmd = builder.clone().build_command();
454
455 // Before spawning, determine the directory to run from.
456 // If a custom execution directory was set (e.g. for Tauri targets), that is used.
457 // Otherwise, if the target is extended, run from its parent directory.
458 if let Some(ref exec_dir) = builder.execution_dir {
459 cmd.current_dir(exec_dir);
460 } else if target.extended {
461 if let Some(dir) = target.manifest_path.parent() {
462 cmd.current_dir(dir);
463 }
464 }
465
466 // Print the full command for debugging.
467 let full_command = format!(
468 "{} {}",
469 cmd.get_program().to_string_lossy(),
470 cmd.get_args()
471 .map(|arg| arg.to_string_lossy())
472 .collect::<Vec<_>>()
473 .join(" ")
474 );
475 println!("Running: {}", full_command);
476
477 // Check if the manifest triggers the workspace error.
478 let maybe_backup = crate::e_manifest::maybe_patch_manifest_for_run(&target.manifest_path)?;
479
480 let pid = Arc::new(builder).run(|_pid, handle| {
481 manager.register(handle);
482 })?;
483 let result = manager.wait(pid, None)?;
484 // println!("HERE IS THE RESULT!{} {:?}",pid,manager.get(pid));
485 // println!("\n\nHERE IS THE RESULT!{} {:?}",pid,result);
486 if result.is_filter {
487 result.print_exact();
488 result.print_short();
489 result.print_compact();
490
491 // manager.print_shortened_output();
492 manager.print_prefixed_summary();
493 // manager.print_compact();
494 }
495
496 // let handle= Arc::new(builder).run_wait()?;
497 // Spawn the process.
498 // let child = cmd.spawn()?;
499 // {
500 // let mut global = GLOBAL_CHILD.lock().unwrap();
501 // *global = Some(child);
502 // }
503 // let status = {
504 // let mut global = GLOBAL_CHILD.lock().unwrap();
505 // if let Some(mut child) = global.take() {
506 // child.wait()?
507 // } else {
508 // return Err(anyhow::anyhow!("Child process missing"));
509 // }
510 // };
511
512 // Restore the manifest if we patched it.
513 if let Some(original) = maybe_backup {
514 fs::write(&target.manifest_path, original)?;
515 }
516
517 Ok(result.exit_status)
518}
519// /// Runs an example or binary target, applying a temporary manifest patch if a workspace error is detected.
520// /// This function uses the same idea as in the collection helpers: if the workspace error is found,
521// /// we patch the manifest, run the command, and then restore the manifest.
522// pub fn run_example(
523// target: &crate::e_target::CargoTarget,
524// extra_args: &[String],
525// ) -> Result<std::process::ExitStatus, Box<dyn Error>> {
526// // Retrieve the current package name (or binary name) at compile time.
527
528// use crate::e_target::TargetKind;
529
530// let current_bin = env!("CARGO_PKG_NAME");
531
532// // Avoid running our own binary if the target's name is the same.
533// if target.kind == TargetKind::Binary && target.name == current_bin {
534// return Err(format!(
535// "Skipping automatic run: {} is the same as the running binary",
536// target.name
537// )
538// .into());
539// }
540
541// let mut cmd = Command::new("cargo");
542// // Determine which manifest file is used.
543// let manifest_path: PathBuf;
544
545// match target.kind {
546// TargetKind::Bench => {
547// manifest_path = PathBuf::from(target.manifest_path.clone());
548// cmd.args([
549// "bench",
550// "--bench",
551// &target.name,
552// "--manifest-path",
553// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
554// ]);
555// }
556// TargetKind::Test => {
557// manifest_path = PathBuf::from(target.manifest_path.clone());
558// cmd.args([
559// "test",
560// "--test",
561// &target.name,
562// "--manifest-path",
563// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
564// ]);
565// }
566// TargetKind::Manifest => {
567// manifest_path = PathBuf::from(target.manifest_path.clone());
568// cmd.args([
569// "run",
570// "--release",
571// "--manifest-path",
572// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
573// "-p",
574// &target.name,
575// ]);
576// }
577// TargetKind::Example => {
578// if target.extended {
579// println!(
580// "Running extended example in folder: examples/{}",
581// target.name
582// );
583// // For extended examples, assume the manifest is inside the example folder.
584// manifest_path = PathBuf::from(format!("examples/{}/Cargo.toml", target.name));
585// cmd.arg("run")
586// .current_dir(format!("examples/{}", target.name));
587// } else {
588// manifest_path = PathBuf::from(crate::locate_manifest(false)?);
589// cmd.args([
590// "run",
591// "--release",
592// "--example",
593// &target.name,
594// "--manifest-path",
595// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
596// ]);
597// }
598// }
599// TargetKind::Binary => {
600// println!("Running binary: {}", target.name);
601// manifest_path = PathBuf::from(crate::locate_manifest(false)?);
602// cmd.args([
603// "run",
604// "--release",
605// "--bin",
606// &target.name,
607// "--manifest-path",
608// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
609// ]);
610// }
611// TargetKind::ExtendedBinary => {
612// println!("Running extended binary: {}", target.name);
613// manifest_path = PathBuf::from(crate::locate_manifest(false)?);
614// cmd.args([
615// "run",
616// "--release",
617// "--manifest-path",
618// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
619// "--bin",
620// &target.name,
621// ]);
622// }
623// TargetKind::ExtendedExample => {
624// println!("Running extended example: {}", target.name);
625// manifest_path = PathBuf::from(crate::locate_manifest(false)?);
626// cmd.args([
627// "run",
628// "--release",
629// "--manifest-path",
630// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
631// "--example",
632// &target.name,
633// ]);
634// }
635// TargetKind::ManifestTauri => {
636// println!("Running tauri: {}", target.name);
637// // For a Tauri example, run `cargo tauri dev`
638// manifest_path = PathBuf::from(target.manifest_path.clone());
639// let manifest_dir = PathBuf::from(manifest_path.parent().expect("expected a parent"));
640// // Start a new command for tauri dev
641// cmd.arg("tauri").arg("dev").current_dir(manifest_dir); // run from the folder where Cargo.toml is located
642// }
643// TargetKind::ManifestDioxus => {
644// println!("Running dioxus: {}", target.name);
645// cmd = Command::new("dx");
646// // For a Tauri example, run `cargo tauri dev`
647// manifest_path = PathBuf::from(target.manifest_path.clone());
648// let manifest_dir = PathBuf::from(manifest_path.parent().expect("expected a parent"));
649// // Start a new command for tauri dev
650// cmd.arg("serve").current_dir(manifest_dir); // run from the folder where Cargo.toml is located
651// }
652// TargetKind::ManifestDioxusExample => {
653// println!("Running dioxus: {}", target.name);
654// cmd = Command::new("dx");
655// // For a Tauri example, run `cargo tauri dev`
656// manifest_path = PathBuf::from(target.manifest_path.clone());
657// let manifest_dir = PathBuf::from(manifest_path.parent().expect("expected a parent"));
658// // Start a new command for tauri dev
659// cmd.arg("serve")
660// .arg("--example")
661// .arg(&target.name)
662// .current_dir(manifest_dir); // run from the folder where Cargo.toml is located
663// }
664// }
665
666// // --- Add required-features support ---
667// // This call will search the provided manifest, and if it's a workspace,
668// // it will search workspace members for the target.
669// if let Some(features) = crate::e_manifest::get_required_features_from_manifest(
670// manifest_path.as_path(),
671// &target.kind,
672// &target.name,
673// ) {
674// cmd.args(&["--features", &features]);
675// }
676// // --- End required-features support ---
677
678// if !extra_args.is_empty() {
679// cmd.arg("--").args(extra_args);
680// }
681
682// let full_command = format!(
683// "{} {}",
684// cmd.get_program().to_string_lossy(),
685// cmd.get_args()
686// .map(|arg| arg.to_string_lossy())
687// .collect::<Vec<_>>()
688// .join(" ")
689// );
690// println!("Running: {}", full_command);
691
692// // Before spawning, check if the manifest triggers the workspace error.
693// // If so, patch it temporarily.
694// let maybe_backup = crate::e_manifest::maybe_patch_manifest_for_run(&manifest_path)?;
695
696// // Spawn the process.
697// let child = cmd.spawn()?;
698// {
699// let mut global = GLOBAL_CHILD.lock().unwrap();
700// *global = Some(child);
701// }
702// let status = {
703// let mut global = GLOBAL_CHILD.lock().unwrap();
704// if let Some(mut child) = global.take() {
705// child.wait()?
706// } else {
707// return Err("Child process missing".into());
708// }
709// };
710
711// // Restore the manifest if we patched it.
712// if let Some(original) = maybe_backup {
713// fs::write(&manifest_path, original)?;
714// }
715
716// // println!("Process exited with status: {:?}", status.code());
717// Ok(status)
718// }
719/// Helper function to spawn a cargo process.
720/// On Windows, this sets the CREATE_NEW_PROCESS_GROUP flag.
721pub fn spawn_cargo_process(args: &[&str]) -> Result<Child, Box<dyn Error>> {
722 // #[cfg(windows)]
723 // {
724 // use std::os::windows::process::CommandExt;
725 // const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
726 // let child = Command::new("cargo")
727 // .args(args)
728 // .creation_flags(CREATE_NEW_PROCESS_GROUP)
729 // .spawn()?;
730 // Ok(child)
731 // }
732 // #[cfg(not(windows))]
733 // {
734 let child = Command::new("cargo").args(args).spawn()?;
735 Ok(child)
736 // }
737}
738
739/// Returns true if the file's a "scriptisto"
740pub fn is_active_scriptisto<P: AsRef<Path>>(path: P) -> io::Result<bool> {
741 let file = File::open(path)?;
742 let mut reader = std::io::BufReader::new(file);
743 let mut first_line = String::new();
744 reader.read_line(&mut first_line)?;
745 if !first_line.contains("scriptisto") || !first_line.starts_with("#") {
746 return Ok(false);
747 }
748 Ok(true)
749}
750
751/// Returns true if the file's a "rust-script"
752pub fn is_active_rust_script<P: AsRef<Path>>(path: P) -> io::Result<bool> {
753 let file = File::open(path)?;
754 let mut reader = std::io::BufReader::new(file);
755 let mut first_line = String::new();
756 reader.read_line(&mut first_line)?;
757 if !first_line.contains("rust-script") || !first_line.starts_with("#") {
758 return Ok(false);
759 }
760 Ok(true)
761}
762
763/// Checks if `scriptisto` is installed and suggests installation if it's not.
764pub fn check_scriptisto_installed() -> Result<std::path::PathBuf, Box<dyn Error>> {
765 let r = which("scriptisto");
766 match r {
767 Ok(_) => {
768 // installed
769 }
770 Err(e) => {
771 // scriptisto is not found in the PATH
772 eprintln!("scriptisto is not installed.");
773 println!("Suggestion: To install scriptisto, run the following command:");
774 println!("cargo install scriptisto");
775 return Err(e.into());
776 }
777 }
778 Ok(r?)
779}
780
781pub fn run_scriptisto<P: AsRef<Path>>(script_path: P, args: &[&str]) -> Option<Child> {
782 let scriptisto = check_scriptisto_installed().ok()?;
783
784 let script: &std::path::Path = script_path.as_ref();
785 let child = Command::new(scriptisto)
786 .arg(script)
787 .args(args)
788 .spawn()
789 .ok()?;
790 Some(child)
791}
792
793/// Checks if `rust-script` is installed and suggests installation if it's not.
794pub fn check_rust_script_installed() -> Result<std::path::PathBuf, Box<dyn Error>> {
795 let r = which("rust-script");
796 match r {
797 Ok(_) => {
798 // rust-script is installed
799 }
800 Err(e) => {
801 // rust-script is not found in the PATH
802 eprintln!("rust-script is not installed.");
803 println!("Suggestion: To install rust-script, run the following command:");
804 println!("cargo install rust-script");
805 return Err(e.into());
806 }
807 }
808 Ok(r?)
809}
810
811pub fn run_rust_script<P: AsRef<Path>>(script_path: P, args: &[&str]) -> Option<Child> {
812 let rust_script = check_rust_script_installed().ok()?;
813
814 let script: &std::path::Path = script_path.as_ref();
815 let child = Command::new(rust_script)
816 .arg(script)
817 .args(args)
818 .spawn()
819 .ok()?;
820 Some(child)
821}
822
823pub fn run_rust_script_with_ctrlc_handling(explicit: String, extra_args: Vec<String>) {
824 let explicit_path = Path::new(&explicit); // Construct Path outside the lock
825
826 if explicit_path.exists() {
827 let extra_str_slice: Vec<String> = extra_args.iter().cloned().collect();
828 if let Ok(true) = is_active_rust_script(explicit_path) {
829 // Run the child process in a separate thread to allow Ctrl+C handling
830 let handle = thread::spawn(move || {
831 let extra_str_slice_cloned = extra_str_slice.clone();
832 let mut child = run_rust_script(
833 &explicit,
834 &extra_str_slice_cloned
835 .iter()
836 .map(String::as_str)
837 .collect::<Vec<_>>(),
838 )
839 .unwrap_or_else(|| {
840 eprintln!("Failed to run rust-script: {:?}", &explicit);
841 std::process::exit(1); // Exit with an error code
842 });
843
844 child.wait()
845 });
846
847 match handle.join() {
848 Ok(_) => {
849 println!("Child process finished successfully.");
850 }
851 Err(_) => {
852 eprintln!("Child process took too long to finish. Exiting...");
853 std::process::exit(1); // Exit if the process takes too long
854 }
855 }
856 }
857 }
858}
859
860pub fn run_scriptisto_with_ctrlc_handling(explicit: String, extra_args: Vec<String>) {
861 let relative: String = make_relative(Path::new(&explicit)).unwrap_or_else(|e| {
862 eprintln!("Error computing relative path: {}", e);
863 std::process::exit(1);
864 });
865
866 let explicit_path = Path::new(&relative);
867 if explicit_path.exists() {
868 // let extra_args = EXTRA_ARGS.lock().unwrap(); // Locking the Mutex to access the data
869 let extra_str_slice: Vec<String> = extra_args.to_vec();
870
871 if let Ok(true) = is_active_scriptisto(explicit_path) {
872 // Run the child process in a separate thread to allow Ctrl+C handling
873 let handle = thread::spawn(move || {
874 let extra_str_slice_cloned: Vec<String> = extra_str_slice.clone();
875 let mut child = run_scriptisto(
876 &relative,
877 &extra_str_slice_cloned
878 .iter()
879 .map(String::as_str)
880 .collect::<Vec<_>>(),
881 )
882 .unwrap_or_else(|| {
883 eprintln!("Failed to run rust-script: {:?}", &explicit);
884 std::process::exit(1); // Exit with an error code
885 });
886
887 // // Lock global to store the child process
888 // {
889 // let mut global = GLOBAL_CHILD.lock().unwrap();
890 // *global = Some(child);
891 // }
892
893 // // Wait for the child process to complete
894 // let status = {
895 // let mut global = GLOBAL_CHILD.lock().unwrap();
896 // if let Some(mut child) = global.take() {
897 child.wait()
898 // } else {
899 // // Handle missing child process
900 // eprintln!("Child process missing");
901 // std::process::exit(1); // Exit with an error code
902 // }
903 // };
904
905 // // Handle the child process exit status
906 // match status {
907 // Ok(status) => {
908 // eprintln!("Child process exited with status code: {:?}", status.code());
909 // std::process::exit(status.code().unwrap_or(1)); // Exit with the child's status code
910 // }
911 // Err(err) => {
912 // eprintln!("Error waiting for child process: {}", err);
913 // std::process::exit(1); // Exit with an error code
914 // }
915 // }
916 });
917
918 // Wait for the thread to complete, but with a timeout
919 // let timeout = Duration::from_secs(10);
920 match handle.join() {
921 Ok(_) => {
922 println!("Child process finished successfully.");
923 }
924 Err(_) => {
925 eprintln!("Child process took too long to finish. Exiting...");
926 std::process::exit(1); // Exit if the process takes too long
927 }
928 }
929 }
930 }
931}
932/// Given any path, produce a relative path string starting with `./` (or `.\` on Windows).
933fn make_relative(path: &Path) -> std::io::Result<String> {
934 let cwd = env::current_dir()?;
935 // Try to strip the cwd prefix; if it isn’t under cwd, just use the original path.
936 let rel: PathBuf = match path.strip_prefix(&cwd) {
937 Ok(stripped) => stripped.to_path_buf(),
938 Err(_) => path.to_path_buf(),
939 };
940
941 let mut rel = if rel.components().count() == 0 {
942 // special case: the same directory
943 PathBuf::from(".")
944 } else {
945 rel
946 };
947
948 // Prepend "./" (or ".\") if it doesn’t already start with "." or ".."
949 let first = rel.components().next().unwrap();
950 match first {
951 std::path::Component::CurDir | std::path::Component::ParentDir => {}
952 _ => {
953 rel = PathBuf::from(".").join(rel);
954 }
955 }
956
957 // Convert back to a string with the correct separator
958 let s = rel
959 .to_str()
960 .expect("Relative path should be valid UTF-8")
961 .to_string();
962
963 Ok(s)
964}
965
966// trait JoinTimeout {
967// fn join_timeout(self, timeout: Duration) -> Result<(), ()>;
968// }
969
970// impl<T> JoinTimeout for thread::JoinHandle<T> {
971// fn join_timeout(self, timeout: Duration) -> Result<(), ()> {
972// println!("Waiting for thread to finish...{}", timeout.as_secs());
973// let _ = thread::sleep(timeout);
974// match self.join() {
975// Ok(_) => Ok(()),
976// Err(_) => Err(()),
977// }
978// }
979// }