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}