1use std::env;
2use std::fs;
3use std::io;
4use std::path;
5use std::process;
6
7use anyhow::anyhow;
8use anyhow::bail;
9use anyhow::Result;
10use cfg_if::cfg_if;
11use version_check as rustc;
12use which::which;
13
14use crate::cargo_config;
15use crate::metadata;
16use crate::shims;
17
18#[rustversion::since(1.74)]
19fn stderr_to_stdio() -> io::Result<process::Stdio> {
20 return Ok(io::stderr().into());
21}
22
23#[rustversion::before(1.74)]
24#[cfg(target_family = "unix")]
25fn stderr_to_stdio() -> io::Result<process::Stdio> {
26 use std::os::fd::AsFd;
27
28 return Ok(io::stderr().as_fd().try_clone_to_owned()?.into());
29}
30
31#[rustversion::before(1.74)]
32#[cfg(target_family = "windows")]
33fn stderr_to_stdio() -> io::Result<process::Stdio> {
34 use std::os::windows::io::AsHandle;
35
36 return Ok(io::stderr().as_handle().try_clone_to_owned()?.into());
37}
38
39pub fn cargo_install(
41 binary_package: metadata::BinaryPackage,
42 cache_path: path::PathBuf,
43) -> Result<()> {
44 let mut cmd_prefix = process::Command::new("cargo");
45
46 cmd_prefix
47 .stdout(stderr_to_stdio()?)
48 .stderr(process::Stdio::inherit())
49 .arg("install")
50 .arg("--root")
51 .arg(&cache_path)
52 .arg("--version")
53 .arg(binary_package.version);
54
55 if let Some(git) = &binary_package.git {
56 cmd_prefix.arg("--git").arg(git);
57 if let Some(branch) = &binary_package.branch {
58 cmd_prefix.arg("--branch").arg(branch);
59 } else if let Some(tag) = &binary_package.tag {
60 cmd_prefix.arg("--tag").arg(tag);
61 } else if let Some(rev) = &binary_package.rev {
62 cmd_prefix.arg("--rev").arg(rev);
63 }
64 } else if let Some(path) = &binary_package.path {
65 cmd_prefix.arg("--path").arg(path);
66 }
67
68 if let Some(bin_target) = &binary_package.bin_target {
69 cmd_prefix.arg("--bin").arg(bin_target);
70 }
71
72 if let Some(locked) = &binary_package.locked {
73 if *locked {
74 cmd_prefix.arg("--locked");
75 }
76 }
77
78 if let Some(features) = &binary_package.features {
79 cmd_prefix.arg("--features");
80 cmd_prefix.arg(features.join(","));
81 }
82
83 if let Some(default_features) = &binary_package.default_features {
84 if !*default_features {
85 cmd_prefix.arg("--no-default-features");
86 }
87 }
88
89 cmd_prefix.arg(binary_package.package).output()?;
90
91 return Ok(());
92}
93
94pub fn binstall(binary_package: metadata::BinaryPackage, cache_path: path::PathBuf) -> Result<()> {
96 let mut cmd_prefix = process::Command::new("cargo");
97
98 cmd_prefix
99 .stdout(stderr_to_stdio()?)
100 .stderr(process::Stdio::inherit())
101 .arg("binstall")
102 .arg("--no-confirm")
103 .arg("--no-symlinks")
104 .arg("--root")
105 .arg(&cache_path)
106 .arg("--install-path")
107 .arg(cache_path.join("bin"));
108
109 if let Some(bin) = &binary_package.bin_target {
110 cmd_prefix.arg("--bin").arg(bin);
111 }
112
113 if let Some(git) = &binary_package.git {
114 cmd_prefix.arg("--git").arg(git);
115 }
116
117 if let Some(locked) = &binary_package.locked {
118 if *locked {
119 cmd_prefix.arg("--locked");
120 }
121 }
122
123 cmd_prefix.arg("--");
124
125 cmd_prefix.arg(format!(
126 "{package}@{version}",
127 package = binary_package.package,
128 version = binary_package.version,
129 ));
130
131 cmd_prefix.output()?;
132
133 return Ok(());
134}
135
136pub fn install(binary_package: metadata::BinaryPackage) -> Result<String> {
138 let mut rust_version = "unknown".to_string();
139 if let Some(res) = rustc::triple() {
140 if res.1.is_nightly() {
141 rust_version = "nightly".to_string();
142 } else {
143 rust_version = res.0.to_string();
144 }
145 }
146
147 let mut bin_name = binary_package.package.to_owned();
148 if let Some(bin_target) = &binary_package.bin_target {
149 bin_name = bin_target.to_string();
150 }
151
152 let cache_path = metadata::get_project_root()?
153 .join(".bin")
154 .join(format!("rust-{rust_version}"))
155 .join(binary_package.package.clone())
156 .join(binary_package.version.clone());
157
158 let mut cache_bin_path = cache_path.join("bin").join(bin_name);
159 cache_bin_path = cache_bin_path.clone();
160
161 cfg_if! {
162 if #[cfg(not(target_family = "unix"))] {
163 cache_bin_path.set_extension("exe");
164 }
165 }
166
167 if !path::Path::new(&cache_bin_path).exists() {
168 fs::create_dir_all(&cache_path)?;
169 if binary_package.features.is_none()
170 && binary_package.default_features.is_none()
171 && binary_package.branch.is_none()
172 && binary_package.tag.is_none()
173 && binary_package.rev.is_none()
174 && binary_package.package != "cargo-binstall"
175 && (cargo_config::binstall_alias_exists()? || which("cargo-binstall").is_ok())
176 {
177 binstall(binary_package, cache_path)?;
178 } else {
179 cargo_install(binary_package, cache_path)?;
180 }
181 }
182
183 return Ok(cache_bin_path.to_str().unwrap().to_string());
184}
185
186pub fn run(bin_path: String, args: Vec<String>) -> Result<()> {
189 let mut final_args = args.clone();
191 let bin_name = path::Path::new(&bin_path)
192 .file_name()
193 .unwrap()
194 .to_str()
195 .unwrap();
196 if bin_name.starts_with("cargo-") {
197 final_args = vec![bin_name
198 .to_string()
199 .replace("cargo-", "")
200 .replace(".exe", "")];
201 final_args.append(&mut args.clone());
202 }
203
204 let mut shell_paths = shims::get_shim_paths()?;
205 shell_paths.push(env::var("PATH").unwrap_or("".to_string()));
206
207 let spawn = process::Command::new(bin_path.clone())
208 .stdout(process::Stdio::inherit())
209 .stderr(process::Stdio::inherit())
210 .stdin(process::Stdio::inherit())
211 .args(&final_args)
212 .env("PATH", shell_paths.join(":"))
213 .spawn();
214
215 if let Ok(mut spawn) = spawn {
216 let status = spawn
217 .wait()?
218 .code()
219 .ok_or_else(|| return anyhow!("Failed to get spawn exit code"))?;
220
221 process::exit(status);
222 }
223
224 bail!(format!("Process failed to start: {bin_path}"));
225}