cargo_e/
e_runner.rs

1use crate::{e_target::TargetOrigin, prelude::*};
2// #[cfg(not(feature = "equivalent"))]
3// use ctrlc;
4use crate::e_target::CargoTarget;
5use once_cell::sync::Lazy;
6use std::fs::File;
7use std::io::{self, BufRead};
8use std::path::Path;
9use std::process::Command;
10use which::which; // Adjust the import based on your project structure
11
12// Global shared container for the currently running child process.
13pub static GLOBAL_CHILD: Lazy<Arc<Mutex<Option<Child>>>> = Lazy::new(|| Arc::new(Mutex::new(None)));
14static CTRL_C_COUNT: Lazy<Mutex<u32>> = Lazy::new(|| Mutex::new(0));
15
16/// Registers a global Ctrl+C handler once.
17/// The handler checks GLOBAL_CHILD and kills the child process if present.
18pub fn register_ctrlc_handler() -> Result<(), Box<dyn Error>> {
19    ctrlc::set_handler(move || {
20        let mut count_lock = CTRL_C_COUNT.lock().unwrap();
21        *count_lock += 1;
22
23        let count = *count_lock;
24
25        // If there is no child process and Ctrl+C is pressed 3 times, exit the program
26        if count == 3 {
27            eprintln!("Ctrl+C pressed 3 times with no child process running. Exiting.");
28            exit(0);
29        } else {
30            let mut child_lock = GLOBAL_CHILD.lock().unwrap();
31            if let Some(child) = child_lock.as_mut() {
32                eprintln!(
33                    "Ctrl+C pressed {} times, terminating running child process...",
34                    count
35                );
36                let _ = child.kill();
37            } else {
38                eprintln!("Ctrl+C pressed {} times, no child process running.", count);
39            }
40        }
41    })?;
42    Ok(())
43}
44
45/// Asynchronously launches the GenAI summarization example for the given target.
46/// It builds the command using the target's manifest path as the "origin" argument.
47pub async fn open_ai_summarize_for_target(target: &CargoTarget) {
48    // Extract the origin path from the target (e.g. the manifest path).
49    let _origin_path = match &target.origin {
50        Some(TargetOrigin::SingleFile(path)) | Some(TargetOrigin::DefaultBinary(path)) => path,
51        _ => return (),
52    };
53
54    let exe_path = match which("cargoe_ai_summarize") {
55        Ok(path) => path,
56        Err(err) => {
57            eprintln!("Error: 'cargoe_ai_summarize' not found in PATH: {}", err);
58            return;
59        }
60    };
61    // Build the command based on the platform.
62    // let mut cmd = if cfg!(target_os = "windows") {
63    //     let command_str = format!(
64    //         "e_ai_summarize --streaming --stdin {}",
65    //         origin_path.as_os_str().to_string_lossy()
66    //     );
67    //     println!("Running command: {}", command_str);
68    //     let mut command = Command::new("cmd");
69    //     command.args(["/C", &command_str]);
70    //     command
71    // } else {
72    let mut cmd = Command::new(exe_path);
73    cmd.arg("--streaming");
74    cmd.arg("--stdin");
75    cmd.arg(".");
76    //    cmd.arg(origin_path);
77    // command
78    // };
79
80    cmd.stdin(Stdio::inherit())
81        .stdout(Stdio::inherit())
82        .stderr(Stdio::inherit());
83
84    // Spawn the command and wait for it to finish.
85    let child = cmd.spawn();
86    let status = child
87        .expect("Failed to spawn command")
88        .wait()
89        .expect("Failed to wait for command");
90
91    if !status.success() {
92        eprintln!("Command exited with status: {}", status);
93    }
94
95    // // Build the command to run the example.
96    // let output = if cfg!(target_os = "windows") {
97    //     let command_str = format!("e_ai_summarize --stdin {}", origin_path.as_os_str().to_string_lossy());
98    //     println!("Running command: {}", command_str);
99    //     Command::new("cmd")
100    //         .args([
101    //             "/C",
102    //             command_str.as_str(),
103    //         ])
104    //         .output()
105    // } else {
106    //     Command::new("e_ai_summarize")
107    //         .args([origin_path])
108    //         .output()
109    // };
110
111    // // Handle the output from the command.
112    // match output {
113    //     Ok(output) if output.status.success() => {
114    //         // The summarization example ran successfully.
115    //         println!("----
116    //         {}", String::from_utf8_lossy(&output.stdout));
117    //     }
118    //     Ok(output) => {
119    //         let msg = format!(
120    //             "Error running summarization example:\nstdout: {}\nstderr: {}",
121    //             String::from_utf8_lossy(&output.stdout),
122    //             String::from_utf8_lossy(&output.stderr)
123    //         );
124    //         error!("{}", msg);
125    //     }
126    //     Err(e) => {
127    //         let msg = format!("Failed to execute summarization command: {}", e);
128    //         error!("{}", msg);
129    //     }
130    // }
131}
132
133/// In "equivalent" mode, behave exactly like "cargo run --example <name>"
134#[cfg(feature = "equivalent")]
135pub fn run_equivalent_example(
136    cli: &crate::Cli,
137) -> Result<std::process::ExitStatus, Box<dyn Error>> {
138    // In "equivalent" mode, behave exactly like "cargo run --example <name>"
139    let mut cmd = Command::new("cargo");
140    cmd.args([
141        "run",
142        "--example",
143        cli.explicit_example.as_deref().unwrap_or(""),
144    ]);
145    if !cli.extra.is_empty() {
146        cmd.arg("--").args(cli.extra.clone());
147    }
148    // Inherit the standard input (as well as stdout/stderr) so that input is passed through.
149    use std::process::Stdio;
150    cmd.stdin(Stdio::inherit())
151        .stdout(Stdio::inherit())
152        .stderr(Stdio::inherit());
153
154    let status = cmd.status()?;
155    std::process::exit(status.code().unwrap_or(1));
156}
157
158/// Runs the given example (or binary) target.
159pub fn run_example(
160    cli: &crate::Cli,
161    target: &crate::e_target::CargoTarget,
162) -> anyhow::Result<std::process::ExitStatus> {
163    // Retrieve the current package name at compile time.
164    let current_bin = env!("CARGO_PKG_NAME");
165
166    // Avoid running our own binary.
167    if target.kind == crate::e_target::TargetKind::Binary && target.name == current_bin {
168        return Err(anyhow::anyhow!(
169            "Skipping automatic run: {} is the same as the running binary",
170            target.name
171        ));
172    }
173
174    // Build the command using the CargoCommandBuilder.
175    let mut builder = crate::e_command_builder::CargoCommandBuilder::new()
176        .with_target(target)
177        .with_required_features(&target.manifest_path, target)
178        .with_cli(cli);
179
180    if !cli.extra.is_empty() {
181        builder = builder.with_extra_args(&cli.extra);
182    }
183
184    // Build the command.
185    let mut cmd = builder.clone().build_command();
186
187    // Before spawning, determine the directory to run from.
188    // If a custom execution directory was set (e.g. for Tauri targets), that is used.
189    // Otherwise, if the target is extended, run from its parent directory.
190    if let Some(exec_dir) = builder.execution_dir {
191        cmd.current_dir(exec_dir);
192    } else if target.extended {
193        if let Some(dir) = target.manifest_path.parent() {
194            cmd.current_dir(dir);
195        }
196    }
197
198    // Print the full command for debugging.
199    let full_command = format!(
200        "{} {}",
201        cmd.get_program().to_string_lossy(),
202        cmd.get_args()
203            .map(|arg| arg.to_string_lossy())
204            .collect::<Vec<_>>()
205            .join(" ")
206    );
207    println!("Running: {}", full_command);
208
209    // Check if the manifest triggers the workspace error.
210    let maybe_backup = crate::e_manifest::maybe_patch_manifest_for_run(&target.manifest_path)?;
211
212    // Spawn the process.
213    let child = cmd.spawn()?;
214    {
215        let mut global = GLOBAL_CHILD.lock().unwrap();
216        *global = Some(child);
217    }
218    let status = {
219        let mut global = GLOBAL_CHILD.lock().unwrap();
220        if let Some(mut child) = global.take() {
221            child.wait()?
222        } else {
223            return Err(anyhow::anyhow!("Child process missing"));
224        }
225    };
226
227    // Restore the manifest if we patched it.
228    if let Some(original) = maybe_backup {
229        fs::write(&target.manifest_path, original)?;
230    }
231
232    Ok(status)
233}
234// /// Runs an example or binary target, applying a temporary manifest patch if a workspace error is detected.
235// /// This function uses the same idea as in the collection helpers: if the workspace error is found,
236// /// we patch the manifest, run the command, and then restore the manifest.
237// pub fn run_example(
238//     target: &crate::e_target::CargoTarget,
239//     extra_args: &[String],
240// ) -> Result<std::process::ExitStatus, Box<dyn Error>> {
241//     // Retrieve the current package name (or binary name) at compile time.
242
243//     use crate::e_target::TargetKind;
244
245//     let current_bin = env!("CARGO_PKG_NAME");
246
247//     // Avoid running our own binary if the target's name is the same.
248//     if target.kind == TargetKind::Binary && target.name == current_bin {
249//         return Err(format!(
250//             "Skipping automatic run: {} is the same as the running binary",
251//             target.name
252//         )
253//         .into());
254//     }
255
256//     let mut cmd = Command::new("cargo");
257//     // Determine which manifest file is used.
258//     let manifest_path: PathBuf;
259
260//     match target.kind {
261//         TargetKind::Bench => {
262//             manifest_path = PathBuf::from(target.manifest_path.clone());
263//             cmd.args([
264//                 "bench",
265//                 "--bench",
266//                 &target.name,
267//                 "--manifest-path",
268//                 &target.manifest_path.to_str().unwrap_or_default().to_owned(),
269//             ]);
270//         }
271//         TargetKind::Test => {
272//             manifest_path = PathBuf::from(target.manifest_path.clone());
273//             cmd.args([
274//                 "test",
275//                 "--test",
276//                 &target.name,
277//                 "--manifest-path",
278//                 &target.manifest_path.to_str().unwrap_or_default().to_owned(),
279//             ]);
280//         }
281//         TargetKind::Manifest => {
282//             manifest_path = PathBuf::from(target.manifest_path.clone());
283//             cmd.args([
284//                 "run",
285//                 "--release",
286//                 "--manifest-path",
287//                 &target.manifest_path.to_str().unwrap_or_default().to_owned(),
288//                 "-p",
289//                 &target.name,
290//             ]);
291//         }
292//         TargetKind::Example => {
293//             if target.extended {
294//                 println!(
295//                     "Running extended example in folder: examples/{}",
296//                     target.name
297//                 );
298//                 // For extended examples, assume the manifest is inside the example folder.
299//                 manifest_path = PathBuf::from(format!("examples/{}/Cargo.toml", target.name));
300//                 cmd.arg("run")
301//                     .current_dir(format!("examples/{}", target.name));
302//             } else {
303//                 manifest_path = PathBuf::from(crate::locate_manifest(false)?);
304//                 cmd.args([
305//                     "run",
306//                     "--release",
307//                     "--example",
308//                     &target.name,
309//                     "--manifest-path",
310//                     &target.manifest_path.to_str().unwrap_or_default().to_owned(),
311//                 ]);
312//             }
313//         }
314//         TargetKind::Binary => {
315//             println!("Running binary: {}", target.name);
316//             manifest_path = PathBuf::from(crate::locate_manifest(false)?);
317//             cmd.args([
318//                 "run",
319//                 "--release",
320//                 "--bin",
321//                 &target.name,
322//                 "--manifest-path",
323//                 &target.manifest_path.to_str().unwrap_or_default().to_owned(),
324//             ]);
325//         }
326//         TargetKind::ExtendedBinary => {
327//             println!("Running extended binary: {}", target.name);
328//             manifest_path = PathBuf::from(crate::locate_manifest(false)?);
329//             cmd.args([
330//                 "run",
331//                 "--release",
332//                 "--manifest-path",
333//                 &target.manifest_path.to_str().unwrap_or_default().to_owned(),
334//                 "--bin",
335//                 &target.name,
336//             ]);
337//         }
338//         TargetKind::ExtendedExample => {
339//             println!("Running extended example: {}", target.name);
340//             manifest_path = PathBuf::from(crate::locate_manifest(false)?);
341//             cmd.args([
342//                 "run",
343//                 "--release",
344//                 "--manifest-path",
345//                 &target.manifest_path.to_str().unwrap_or_default().to_owned(),
346//                 "--example",
347//                 &target.name,
348//             ]);
349//         }
350//         TargetKind::ManifestTauri => {
351//             println!("Running tauri: {}", target.name);
352//             // For a Tauri example, run `cargo tauri dev`
353//             manifest_path = PathBuf::from(target.manifest_path.clone());
354//             let manifest_dir = PathBuf::from(manifest_path.parent().expect("expected a parent"));
355//             // Start a new command for tauri dev
356//             cmd.arg("tauri").arg("dev").current_dir(manifest_dir); // run from the folder where Cargo.toml is located
357//         }
358//         TargetKind::ManifestDioxus => {
359//             println!("Running dioxus: {}", target.name);
360//             cmd = Command::new("dx");
361//             // For a Tauri example, run `cargo tauri dev`
362//             manifest_path = PathBuf::from(target.manifest_path.clone());
363//             let manifest_dir = PathBuf::from(manifest_path.parent().expect("expected a parent"));
364//             // Start a new command for tauri dev
365//             cmd.arg("serve").current_dir(manifest_dir); // run from the folder where Cargo.toml is located
366//         }
367//         TargetKind::ManifestDioxusExample => {
368//             println!("Running dioxus: {}", target.name);
369//             cmd = Command::new("dx");
370//             // For a Tauri example, run `cargo tauri dev`
371//             manifest_path = PathBuf::from(target.manifest_path.clone());
372//             let manifest_dir = PathBuf::from(manifest_path.parent().expect("expected a parent"));
373//             // Start a new command for tauri dev
374//             cmd.arg("serve")
375//                 .arg("--example")
376//                 .arg(&target.name)
377//                 .current_dir(manifest_dir); // run from the folder where Cargo.toml is located
378//         }
379//     }
380
381//     // --- Add required-features support ---
382//     // This call will search the provided manifest, and if it's a workspace,
383//     // it will search workspace members for the target.
384//     if let Some(features) = crate::e_manifest::get_required_features_from_manifest(
385//         manifest_path.as_path(),
386//         &target.kind,
387//         &target.name,
388//     ) {
389//         cmd.args(&["--features", &features]);
390//     }
391//     // --- End required-features support ---
392
393//     if !extra_args.is_empty() {
394//         cmd.arg("--").args(extra_args);
395//     }
396
397//     let full_command = format!(
398//         "{} {}",
399//         cmd.get_program().to_string_lossy(),
400//         cmd.get_args()
401//             .map(|arg| arg.to_string_lossy())
402//             .collect::<Vec<_>>()
403//             .join(" ")
404//     );
405//     println!("Running: {}", full_command);
406
407//     // Before spawning, check if the manifest triggers the workspace error.
408//     // If so, patch it temporarily.
409//     let maybe_backup = crate::e_manifest::maybe_patch_manifest_for_run(&manifest_path)?;
410
411//     // Spawn the process.
412//     let child = cmd.spawn()?;
413//     {
414//         let mut global = GLOBAL_CHILD.lock().unwrap();
415//         *global = Some(child);
416//     }
417//     let status = {
418//         let mut global = GLOBAL_CHILD.lock().unwrap();
419//         if let Some(mut child) = global.take() {
420//             child.wait()?
421//         } else {
422//             return Err("Child process missing".into());
423//         }
424//     };
425
426//     // Restore the manifest if we patched it.
427//     if let Some(original) = maybe_backup {
428//         fs::write(&manifest_path, original)?;
429//     }
430
431//     //    println!("Process exited with status: {:?}", status.code());
432//     Ok(status)
433// }
434/// Helper function to spawn a cargo process.
435/// On Windows, this sets the CREATE_NEW_PROCESS_GROUP flag.
436pub fn spawn_cargo_process(args: &[&str]) -> Result<Child, Box<dyn Error>> {
437    #[cfg(windows)]
438    {
439        use std::os::windows::process::CommandExt;
440        const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
441        let child = Command::new("cargo")
442            .args(args)
443            .creation_flags(CREATE_NEW_PROCESS_GROUP)
444            .spawn()?;
445        Ok(child)
446    }
447    #[cfg(not(windows))]
448    {
449        let child = Command::new("cargo").args(args).spawn()?;
450        Ok(child)
451    }
452}
453
454/// Returns true if the file's a "rust-script"
455pub fn is_active_rust_script<P: AsRef<Path>>(path: P) -> io::Result<bool> {
456    let file = File::open(path)?;
457    let mut reader = std::io::BufReader::new(file);
458    let mut first_line = String::new();
459    reader.read_line(&mut first_line)?;
460    if !first_line.contains("rust-script") || !first_line.starts_with("#") {
461        return Ok(false);
462    }
463    Ok(true)
464}
465
466/// Checks if `rust-script` is installed and suggests installation if it's not.
467pub fn check_rust_script_installed() -> Result<std::path::PathBuf, Box<dyn Error>> {
468    let r = which("rust-script");
469    match r {
470        Ok(_) => {
471            // rust-script is installed
472        }
473        Err(e) => {
474            // rust-script is not found in the PATH
475            eprintln!("rust-script is not installed.");
476            println!("Suggestion: To install rust-script, run the following command:");
477            println!("cargo install rust-script");
478            return Err(e.into());
479        }
480    }
481    Ok(r?)
482}
483
484pub fn run_rust_script<P: AsRef<Path>>(script_path: P, args: &[&str]) -> Option<Child> {
485    let rust_script = check_rust_script_installed().ok()?;
486
487    let script: &std::path::Path = script_path.as_ref();
488    let child = Command::new(rust_script)
489        .arg(script)
490        .args(args)
491        .spawn()
492        .ok()?;
493    Some(child)
494}