cargo_e/
e_runner.rs

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