cargo_e/
e_runner.rs

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