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.
8static 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    }
95
96    if !extra_args.is_empty() {
97        cmd.arg("--").args(extra_args);
98    }
99
100    let full_command = format!(
101        "cargo {}",
102        cmd.get_args()
103            .map(|arg| arg.to_string_lossy())
104            .collect::<Vec<_>>()
105            .join(" ")
106    );
107    println!("Running: {}", full_command);
108
109    // Before spawning, check if the manifest triggers the workspace error.
110    // If so, patch it temporarily.
111    let maybe_backup = crate::e_manifest::maybe_patch_manifest_for_run(&manifest_path)?;
112
113    // Spawn the process.
114    let child = cmd.spawn()?;
115    {
116        let mut global = GLOBAL_CHILD.lock().unwrap();
117        *global = Some(child);
118    }
119    let status = {
120        let mut global = GLOBAL_CHILD.lock().unwrap();
121        if let Some(mut child) = global.take() {
122            child.wait()?
123        } else {
124            return Err("Child process missing".into());
125        }
126    };
127
128    // Restore the manifest if we patched it.
129    if let Some(original) = maybe_backup {
130        fs::write(&manifest_path, original)?;
131    }
132
133    //    println!("Process exited with status: {:?}", status.code());
134    Ok(status)
135}
136/// Helper function to spawn a cargo process.
137/// On Windows, this sets the CREATE_NEW_PROCESS_GROUP flag.
138pub fn spawn_cargo_process(args: &[&str]) -> Result<Child, Box<dyn Error>> {
139    #[cfg(windows)]
140    {
141        use std::os::windows::process::CommandExt;
142        const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
143        let child = Command::new("cargo")
144            .args(args)
145            .creation_flags(CREATE_NEW_PROCESS_GROUP)
146            .spawn()?;
147        Ok(child)
148    }
149    #[cfg(not(windows))]
150    {
151        let child = Command::new("cargo").args(args).spawn()?;
152        Ok(child)
153    }
154}