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// }