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"))]
50pub fn run_example(
51    target: &Example,
52    extra_args: &[String],
53) -> Result<std::process::ExitStatus, Box<dyn Error>> {
54    // Retrieve the current package name (or binary name) at compile time.
55    let current_bin = env!("CARGO_PKG_NAME");
56
57    // Avoid running our own binary if the target's name is the same.
58    // this check is for the developer running cargo run --; cargo-e is the only binary and so loops.
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
69    match target.kind {
70        // For examples:
71        crate::TargetKind::Example => {
72            if target.extended {
73                println!(
74                    "Running extended example in folder: examples/{}",
75                    target.name
76                );
77                cmd.arg("run")
78                    .current_dir(format!("examples/{}", target.name));
79            } else {
80                cmd.args(["run", "--release", "--example", &target.name]);
81            }
82        }
83        // For binaries:
84        crate::TargetKind::Binary => {
85            println!("Running binary: {}", target.name);
86            cmd.args(["run", "--release", "--bin", &target.name]);
87        } // Optionally handle other target kinds.
88          // _ => { unreach able unsupported.
89          //     return Err(format!("Unsupported target kind: {:?}", target.kind).into());
90          // }
91    }
92
93    if !extra_args.is_empty() {
94        cmd.arg("--").args(extra_args);
95    }
96
97    let full_command = format!(
98        "cargo {}",
99        cmd.get_args()
100            .map(|arg| arg.to_string_lossy())
101            .collect::<Vec<_>>()
102            .join(" ")
103    );
104    println!("Running: {}", full_command);
105
106    // Spawn the child process.
107    let child = cmd.spawn()?;
108    {
109        // Update the global handle.
110        let mut global = GLOBAL_CHILD.lock().unwrap();
111        *global = Some(child);
112    }
113    // Wait for the child process to complete.
114    let status = {
115        let mut global = GLOBAL_CHILD.lock().unwrap();
116        // Take ownership of the child so we can wait on it.
117        if let Some(mut child) = global.take() {
118            child.wait()?
119        } else {
120            return Err("Child process missing".into());
121        }
122    };
123
124    // let child = cmd.spawn()?;
125    // use std::sync::{Arc, Mutex};
126    // let child_arc = Arc::new(Mutex::new(child));
127    // let child_for_handler = Arc::clone(&child_arc);
128
129    // ctrlc::set_handler(move || {
130    //     eprintln!("Ctrl+C pressed, terminating process...");
131    //     let mut child = child_for_handler.lock().unwrap();
132    //     let _ = child.kill();
133    // })?;
134
135    // let status: std::process::ExitStatus = child_arc.lock().unwrap().wait()?;
136    println!("Process exited with status: {:?}", status.code());
137    Ok(status)
138}
139
140/// Helper function to spawn a cargo process.
141/// On Windows, this sets the CREATE_NEW_PROCESS_GROUP flag.
142pub fn spawn_cargo_process(args: &[&str]) -> Result<Child, Box<dyn Error>> {
143    #[cfg(windows)]
144    {
145        use std::os::windows::process::CommandExt;
146        const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
147        let child = Command::new("cargo")
148            .args(args)
149            .creation_flags(CREATE_NEW_PROCESS_GROUP)
150            .spawn()?;
151        Ok(child)
152    }
153    #[cfg(not(windows))]
154    {
155        let child = Command::new("cargo").args(args).spawn()?;
156        Ok(child)
157    }
158}