cargo_e/e_runner.rs
1use crate::prelude::*;
2// #[cfg(not(feature = "equivalent"))]
3// use ctrlc;
4use once_cell::sync::Lazy;
5
6// Global shared container for the currently running child process.
7pub static GLOBAL_CHILD: Lazy<Arc<Mutex<Option<Child>>>> = Lazy::new(|| Arc::new(Mutex::new(None)));
8
9/// Registers a global Ctrl+C handler once.
10/// The handler checks GLOBAL_CHILD and kills the child process if present.
11pub fn register_ctrlc_handler() -> Result<(), Box<dyn Error>> {
12 ctrlc::set_handler(move || {
13 let mut child_lock = GLOBAL_CHILD.lock().unwrap();
14 if let Some(child) = child_lock.as_mut() {
15 eprintln!("Ctrl+C pressed, terminating running child process...");
16 let _ = child.kill();
17 } else {
18 eprintln!("Ctrl+C pressed, no child process running. Exiting nicely.");
19 exit(0);
20 }
21 })?;
22 Ok(())
23}
24
25/// In "equivalent" mode, behave exactly like "cargo run --example <name>"
26#[cfg(feature = "equivalent")]
27pub fn run_equivalent_example(
28 cli: &crate::Cli,
29) -> Result<std::process::ExitStatus, Box<dyn Error>> {
30 // In "equivalent" mode, behave exactly like "cargo run --example <name>"
31 let mut cmd = Command::new("cargo");
32 cmd.args([
33 "run",
34 "--example",
35 cli.explicit_example.as_deref().unwrap_or(""),
36 ]);
37 if !cli.extra.is_empty() {
38 cmd.arg("--").args(cli.extra.clone());
39 }
40 // Inherit the standard input (as well as stdout/stderr) so that input is passed through.
41 use std::process::Stdio;
42 cmd.stdin(Stdio::inherit())
43 .stdout(Stdio::inherit())
44 .stderr(Stdio::inherit());
45
46 let status = cmd.status()?;
47 std::process::exit(status.code().unwrap_or(1));
48}
49
50/// Runs the given example (or binary) target.
51pub fn run_example(
52 cli: &crate::Cli,
53 target: &crate::e_target::CargoTarget,
54) -> anyhow::Result<std::process::ExitStatus> {
55 // Retrieve the current package name at compile time.
56 let current_bin = env!("CARGO_PKG_NAME");
57
58 // Avoid running our own binary.
59 if target.kind == crate::e_target::TargetKind::Binary && target.name == current_bin {
60 return Err(anyhow::anyhow!(
61 "Skipping automatic run: {} is the same as the running binary",
62 target.name
63 ));
64 }
65
66 // Build the command using the CargoCommandBuilder.
67 let mut builder = crate::e_command_builder::CargoCommandBuilder::new()
68 .with_target(target)
69 .with_required_features(&target.manifest_path, target)
70 .with_cli(cli);
71
72 if !cli.extra.is_empty() {
73 builder = builder.with_extra_args(&cli.extra);
74 }
75
76 // Build the command.
77 let mut cmd = builder.clone().build_command();
78
79 // Before spawning, determine the directory to run from.
80 // If a custom execution directory was set (e.g. for Tauri targets), that is used.
81 // Otherwise, if the target is extended, run from its parent directory.
82 if let Some(exec_dir) = builder.execution_dir {
83 cmd.current_dir(exec_dir);
84 } else if target.extended {
85 if let Some(dir) = target.manifest_path.parent() {
86 cmd.current_dir(dir);
87 }
88 }
89
90 // Print the full command for debugging.
91 let full_command = format!(
92 "{} {}",
93 cmd.get_program().to_string_lossy(),
94 cmd.get_args()
95 .map(|arg| arg.to_string_lossy())
96 .collect::<Vec<_>>()
97 .join(" ")
98 );
99 println!("Running: {}", full_command);
100
101 // Check if the manifest triggers the workspace error.
102 let maybe_backup = crate::e_manifest::maybe_patch_manifest_for_run(&target.manifest_path)?;
103
104 // Spawn the process.
105 let child = cmd.spawn()?;
106 {
107 let mut global = GLOBAL_CHILD.lock().unwrap();
108 *global = Some(child);
109 }
110 let status = {
111 let mut global = GLOBAL_CHILD.lock().unwrap();
112 if let Some(mut child) = global.take() {
113 child.wait()?
114 } else {
115 return Err(anyhow::anyhow!("Child process missing"));
116 }
117 };
118
119 // Restore the manifest if we patched it.
120 if let Some(original) = maybe_backup {
121 fs::write(&target.manifest_path, original)?;
122 }
123
124 Ok(status)
125}
126// /// Runs an example or binary target, applying a temporary manifest patch if a workspace error is detected.
127// /// This function uses the same idea as in the collection helpers: if the workspace error is found,
128// /// we patch the manifest, run the command, and then restore the manifest.
129// pub fn run_example(
130// target: &crate::e_target::CargoTarget,
131// extra_args: &[String],
132// ) -> Result<std::process::ExitStatus, Box<dyn Error>> {
133// // Retrieve the current package name (or binary name) at compile time.
134
135// use crate::e_target::TargetKind;
136
137// let current_bin = env!("CARGO_PKG_NAME");
138
139// // Avoid running our own binary if the target's name is the same.
140// if target.kind == TargetKind::Binary && target.name == current_bin {
141// return Err(format!(
142// "Skipping automatic run: {} is the same as the running binary",
143// target.name
144// )
145// .into());
146// }
147
148// let mut cmd = Command::new("cargo");
149// // Determine which manifest file is used.
150// let manifest_path: PathBuf;
151
152// match target.kind {
153// TargetKind::Bench => {
154// manifest_path = PathBuf::from(target.manifest_path.clone());
155// cmd.args([
156// "bench",
157// "--bench",
158// &target.name,
159// "--manifest-path",
160// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
161// ]);
162// }
163// TargetKind::Test => {
164// manifest_path = PathBuf::from(target.manifest_path.clone());
165// cmd.args([
166// "test",
167// "--test",
168// &target.name,
169// "--manifest-path",
170// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
171// ]);
172// }
173// TargetKind::Manifest => {
174// manifest_path = PathBuf::from(target.manifest_path.clone());
175// cmd.args([
176// "run",
177// "--release",
178// "--manifest-path",
179// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
180// "-p",
181// &target.name,
182// ]);
183// }
184// TargetKind::Example => {
185// if target.extended {
186// println!(
187// "Running extended example in folder: examples/{}",
188// target.name
189// );
190// // For extended examples, assume the manifest is inside the example folder.
191// manifest_path = PathBuf::from(format!("examples/{}/Cargo.toml", target.name));
192// cmd.arg("run")
193// .current_dir(format!("examples/{}", target.name));
194// } else {
195// manifest_path = PathBuf::from(crate::locate_manifest(false)?);
196// cmd.args([
197// "run",
198// "--release",
199// "--example",
200// &target.name,
201// "--manifest-path",
202// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
203// ]);
204// }
205// }
206// TargetKind::Binary => {
207// println!("Running binary: {}", target.name);
208// manifest_path = PathBuf::from(crate::locate_manifest(false)?);
209// cmd.args([
210// "run",
211// "--release",
212// "--bin",
213// &target.name,
214// "--manifest-path",
215// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
216// ]);
217// }
218// TargetKind::ExtendedBinary => {
219// println!("Running extended binary: {}", target.name);
220// manifest_path = PathBuf::from(crate::locate_manifest(false)?);
221// cmd.args([
222// "run",
223// "--release",
224// "--manifest-path",
225// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
226// "--bin",
227// &target.name,
228// ]);
229// }
230// TargetKind::ExtendedExample => {
231// println!("Running extended example: {}", target.name);
232// manifest_path = PathBuf::from(crate::locate_manifest(false)?);
233// cmd.args([
234// "run",
235// "--release",
236// "--manifest-path",
237// &target.manifest_path.to_str().unwrap_or_default().to_owned(),
238// "--example",
239// &target.name,
240// ]);
241// }
242// TargetKind::ManifestTauri => {
243// println!("Running tauri: {}", target.name);
244// // For a Tauri example, run `cargo tauri dev`
245// manifest_path = PathBuf::from(target.manifest_path.clone());
246// let manifest_dir = PathBuf::from(manifest_path.parent().expect("expected a parent"));
247// // Start a new command for tauri dev
248// cmd.arg("tauri").arg("dev").current_dir(manifest_dir); // run from the folder where Cargo.toml is located
249// }
250// TargetKind::ManifestDioxus => {
251// println!("Running dioxus: {}", target.name);
252// cmd = Command::new("dx");
253// // For a Tauri example, run `cargo tauri dev`
254// manifest_path = PathBuf::from(target.manifest_path.clone());
255// let manifest_dir = PathBuf::from(manifest_path.parent().expect("expected a parent"));
256// // Start a new command for tauri dev
257// cmd.arg("serve").current_dir(manifest_dir); // run from the folder where Cargo.toml is located
258// }
259// TargetKind::ManifestDioxusExample => {
260// println!("Running dioxus: {}", target.name);
261// cmd = Command::new("dx");
262// // For a Tauri example, run `cargo tauri dev`
263// manifest_path = PathBuf::from(target.manifest_path.clone());
264// let manifest_dir = PathBuf::from(manifest_path.parent().expect("expected a parent"));
265// // Start a new command for tauri dev
266// cmd.arg("serve")
267// .arg("--example")
268// .arg(&target.name)
269// .current_dir(manifest_dir); // run from the folder where Cargo.toml is located
270// }
271// }
272
273// // --- Add required-features support ---
274// // This call will search the provided manifest, and if it's a workspace,
275// // it will search workspace members for the target.
276// if let Some(features) = crate::e_manifest::get_required_features_from_manifest(
277// manifest_path.as_path(),
278// &target.kind,
279// &target.name,
280// ) {
281// cmd.args(&["--features", &features]);
282// }
283// // --- End required-features support ---
284
285// if !extra_args.is_empty() {
286// cmd.arg("--").args(extra_args);
287// }
288
289// let full_command = format!(
290// "{} {}",
291// cmd.get_program().to_string_lossy(),
292// cmd.get_args()
293// .map(|arg| arg.to_string_lossy())
294// .collect::<Vec<_>>()
295// .join(" ")
296// );
297// println!("Running: {}", full_command);
298
299// // Before spawning, check if the manifest triggers the workspace error.
300// // If so, patch it temporarily.
301// let maybe_backup = crate::e_manifest::maybe_patch_manifest_for_run(&manifest_path)?;
302
303// // Spawn the process.
304// let child = cmd.spawn()?;
305// {
306// let mut global = GLOBAL_CHILD.lock().unwrap();
307// *global = Some(child);
308// }
309// let status = {
310// let mut global = GLOBAL_CHILD.lock().unwrap();
311// if let Some(mut child) = global.take() {
312// child.wait()?
313// } else {
314// return Err("Child process missing".into());
315// }
316// };
317
318// // Restore the manifest if we patched it.
319// if let Some(original) = maybe_backup {
320// fs::write(&manifest_path, original)?;
321// }
322
323// // println!("Process exited with status: {:?}", status.code());
324// Ok(status)
325// }
326/// Helper function to spawn a cargo process.
327/// On Windows, this sets the CREATE_NEW_PROCESS_GROUP flag.
328pub fn spawn_cargo_process(args: &[&str]) -> Result<Child, Box<dyn Error>> {
329 #[cfg(windows)]
330 {
331 use std::os::windows::process::CommandExt;
332 const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
333 let child = Command::new("cargo")
334 .args(args)
335 .creation_flags(CREATE_NEW_PROCESS_GROUP)
336 .spawn()?;
337 Ok(child)
338 }
339 #[cfg(not(windows))]
340 {
341 let child = Command::new("cargo").args(args).spawn()?;
342 Ok(child)
343 }
344}