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}