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}