cargo_e/
e_runner.rs

1use crate::prelude::*;
2// #[cfg(not(feature = "equivalent"))]
3// use ctrlc;
4use crate::Example;
5use once_cell::sync::Lazy;
6
7// Global shared container for the currently running child process.
8pub static GLOBAL_CHILD: Lazy<Arc<Mutex<Option<Child>>>> = Lazy::new(|| Arc::new(Mutex::new(None)));
9
10/// Registers a global Ctrl+C handler once.
11/// The handler checks GLOBAL_CHILD and kills the child process if present.
12pub fn register_ctrlc_handler() -> Result<(), Box<dyn Error>> {
13    ctrlc::set_handler(move || {
14        let mut child_lock = GLOBAL_CHILD.lock().unwrap();
15        if let Some(child) = child_lock.as_mut() {
16            eprintln!("Ctrl+C pressed, terminating running child process...");
17            let _ = child.kill();
18        } else {
19            eprintln!("Ctrl+C pressed, no child process running. Exiting nicely.");
20            exit(0);
21        }
22    })?;
23    Ok(())
24}
25
26/// In "equivalent" mode, behave exactly like "cargo run --example <name>"
27#[cfg(feature = "equivalent")]
28pub fn run_example(
29    example: &Example,
30    extra_args: &[String],
31) -> Result<std::process::ExitStatus, Box<dyn Error>> {
32    // In "equivalent" mode, behave exactly like "cargo run --example <name>"
33    let mut cmd = Command::new("cargo");
34    cmd.args(["run", "--example", &example.name]);
35    if !extra_args.is_empty() {
36        cmd.arg("--").args(extra_args);
37    }
38    // Inherit the standard input (as well as stdout/stderr) so that input is passed through.
39    use std::process::Stdio;
40    cmd.stdin(Stdio::inherit())
41        .stdout(Stdio::inherit())
42        .stderr(Stdio::inherit());
43
44    let status = cmd.status()?;
45    std::process::exit(status.code().unwrap_or(1));
46}
47
48/// Runs the given example (or binary) target.
49#[cfg(not(feature = "equivalent"))]
50/// Runs an example or binary target, applying a temporary manifest patch if a workspace error is detected.
51/// This function uses the same idea as in the collection helpers: if the workspace error is found,
52/// we patch the manifest, run the command, and then restore the manifest.
53pub fn run_example(
54    target: &Example,
55    extra_args: &[String],
56) -> Result<std::process::ExitStatus, Box<dyn Error>> {
57    // Retrieve the current package name (or binary name) at compile time.
58    let current_bin = env!("CARGO_PKG_NAME");
59
60    // Avoid running our own binary if the target's name is the same.
61    if target.kind == crate::TargetKind::Binary && target.name == current_bin {
62        return Err(format!(
63            "Skipping automatic run: {} is the same as the running binary",
64            target.name
65        )
66        .into());
67    }
68
69    let mut cmd = Command::new("cargo");
70    // Determine which manifest file is used.
71    let manifest_path: PathBuf;
72
73    match target.kind {
74        crate::TargetKind::Example => {
75            if target.extended {
76                println!(
77                    "Running extended example in folder: examples/{}",
78                    target.name
79                );
80                // For extended examples, assume the manifest is inside the example folder.
81                manifest_path = PathBuf::from(format!("examples/{}/Cargo.toml", target.name));
82                cmd.arg("run")
83                    .current_dir(format!("examples/{}", target.name));
84            } else {
85                manifest_path = PathBuf::from(crate::locate_manifest(false)?);
86                cmd.args(["run", "--release", "--example", &target.name]);
87            }
88        }
89        crate::TargetKind::Binary => {
90            println!("Running binary: {}", target.name);
91            manifest_path = PathBuf::from(crate::locate_manifest(false)?);
92            cmd.args(["run", "--release", "--bin", &target.name]);
93        }
94        crate::TargetKind::ExtendedBinary => {
95            println!("Running extended binary: {}", target.name);
96            manifest_path = PathBuf::from(crate::locate_manifest(false)?);
97            cmd.args([
98                "run",
99                "--release",
100                "--manifest-path",
101                &target.manifest_path,
102                "--bin",
103                &target.name,
104            ]);
105        }
106        crate::TargetKind::ExtendedExample => {
107            println!("Running extended example: {}", target.name);
108            manifest_path = PathBuf::from(crate::locate_manifest(false)?);
109            cmd.args([
110                "run",
111                "--release",
112                "--manifest-path",
113                &target.manifest_path,
114                "--example",
115                &target.name,
116            ]);
117        }
118    }
119
120    // --- Add required-features support ---
121    // This call will search the provided manifest, and if it's a workspace,
122    // it will search workspace members for the target.
123    if let Some(features) = crate::e_manifest::get_required_features_from_manifest(
124        manifest_path.as_path(),
125        &target.kind,
126        &target.name,
127    ) {
128        cmd.args(&["--features", &features]);
129    }
130    // --- End required-features support ---
131
132    if !extra_args.is_empty() {
133        cmd.arg("--").args(extra_args);
134    }
135
136    let full_command = format!(
137        "cargo {}",
138        cmd.get_args()
139            .map(|arg| arg.to_string_lossy())
140            .collect::<Vec<_>>()
141            .join(" ")
142    );
143    println!("Running: {}", full_command);
144
145    // Before spawning, check if the manifest triggers the workspace error.
146    // If so, patch it temporarily.
147    let maybe_backup = crate::e_manifest::maybe_patch_manifest_for_run(&manifest_path)?;
148
149    // Spawn the process.
150    let child = cmd.spawn()?;
151    {
152        let mut global = GLOBAL_CHILD.lock().unwrap();
153        *global = Some(child);
154    }
155    let status = {
156        let mut global = GLOBAL_CHILD.lock().unwrap();
157        if let Some(mut child) = global.take() {
158            child.wait()?
159        } else {
160            return Err("Child process missing".into());
161        }
162    };
163
164    // Restore the manifest if we patched it.
165    if let Some(original) = maybe_backup {
166        fs::write(&manifest_path, original)?;
167    }
168
169    //    println!("Process exited with status: {:?}", status.code());
170    Ok(status)
171}
172/// Helper function to spawn a cargo process.
173/// On Windows, this sets the CREATE_NEW_PROCESS_GROUP flag.
174pub fn spawn_cargo_process(args: &[&str]) -> Result<Child, Box<dyn Error>> {
175    #[cfg(windows)]
176    {
177        use std::os::windows::process::CommandExt;
178        const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
179        let child = Command::new("cargo")
180            .args(args)
181            .creation_flags(CREATE_NEW_PROCESS_GROUP)
182            .spawn()?;
183        Ok(child)
184    }
185    #[cfg(not(windows))]
186    {
187        let child = Command::new("cargo").args(args).spawn()?;
188        Ok(child)
189    }
190}