cargo_run_copy/
lib.rs

1use std::{
2    env,
3    fs::File,
4    io::{BufReader, Read},
5    path::{Path, PathBuf},
6    process::{Command, Stdio, exit},
7};
8
9use anyhow::bail;
10use cargo_metadata::Message;
11
12pub fn run(connect_console: bool) -> anyhow::Result<()> {
13    let mut build_args = Vec::new();
14    let mut run_args = Vec::new();
15    let mut is_run_arg = false;
16    for arg in env::args().skip(1) {
17        if !is_run_arg && arg == "--" {
18            is_run_arg = true;
19            continue;
20        }
21        if is_run_arg {
22            run_args.push(arg);
23        } else {
24            build_args.push(arg);
25        }
26    }
27    let exe = build(&build_args, connect_console)?;
28    let target = to_target_dir(&exe)?;
29
30    let hash = to_hash(&exe)?;
31    let Some(file_name) = exe.file_name() else {
32        bail!("Couldn't get file name");
33    };
34    let exe_copied = target.join("run-copy").join(hash).join(file_name);
35    if !exe_copied.exists() {
36        if let Some(parent) = exe_copied.parent() {
37            std::fs::create_dir_all(parent)?;
38            std::fs::copy(&exe, &exe_copied)?;
39        }
40    }
41    eprintln!("     Running {}", exe_copied.display());
42    let mut child = apply_options(&mut Command::new(exe_copied), connect_console)
43        .args(run_args)
44        .spawn()?;
45    let output = child.wait()?;
46    if let Some(code) = output.code() {
47        exit(code);
48    } else {
49        bail!("Process terminated: {output}");
50    }
51}
52
53fn build(build_args: &[String], connect_console: bool) -> anyhow::Result<PathBuf> {
54    let mut command = apply_options(&mut Command::new("cargo"), connect_console)
55        .args(["build", "--message-format=json-render-diagnostics"])
56        .args(build_args)
57        .stdout(Stdio::piped())
58        .spawn()?;
59
60    let reader = BufReader::new(command.stdout.take().unwrap());
61    let mut build_executable = None;
62
63    for message in Message::parse_stream(reader) {
64        match message? {
65            Message::CompilerMessage(_) => {}
66            Message::CompilerArtifact(artifact) => {
67                if let Some(executable) = artifact.executable {
68                    build_executable = Some(executable);
69                }
70            }
71            Message::BuildScriptExecuted(_) => {}
72            Message::BuildFinished(_) => {}
73            _ => (),
74        }
75    }
76    let output = command.wait()?;
77    if !output.success() {
78        if let Some(code) = output.code() {
79            exit(code);
80        } else {
81            bail!("Cargo build failed");
82        }
83    }
84    if let Some(executable) = build_executable {
85        Ok(executable.into())
86    } else {
87        bail!("Cargo build failed to produce an executable");
88    }
89}
90
91fn apply_options(command: &mut Command, connect_console: bool) -> &mut Command {
92    if connect_console {
93        command
94    } else {
95        no_window(command)
96    }
97}
98
99#[cfg(not(windows))]
100fn no_window(command: &mut Command) -> &mut Command {
101    command
102}
103
104#[cfg(windows)]
105fn no_window(command: &mut Command) -> &mut Command {
106    use std::os::windows::process::CommandExt;
107    const CREATE_NO_WINDOW: u32 = 0x08000000;
108    command.creation_flags(CREATE_NO_WINDOW)
109}
110
111fn to_target_dir(mut path: &Path) -> anyhow::Result<PathBuf> {
112    loop {
113        if path.is_dir() {
114            if let Some(file_name) = path.file_name() {
115                if file_name == "target" {
116                    return Ok(path.to_path_buf());
117                }
118            }
119        }
120        if let Some(parent) = path.parent() {
121            path = parent;
122        } else {
123            bail!("Couldn't find target directory");
124        }
125    }
126}
127
128fn to_hash(path: &Path) -> anyhow::Result<String> {
129    let file = File::open(path)?;
130    let mut reader = BufReader::new(file);
131    let mut hasher = blake3::Hasher::new();
132    let mut buffer = [0; 8192];
133
134    loop {
135        let count = reader.read(&mut buffer)?;
136        if count == 0 {
137            break;
138        }
139        hasher.update(&buffer[..count]);
140    }
141
142    Ok(hasher.finalize().to_hex().to_string())
143}