|
4 | 4 | //! to be used by tools.
|
5 | 5 |
|
6 | 6 | use std::ffi::OsStr;
|
| 7 | +use std::path::{Path, PathBuf}; |
7 | 8 | use std::process::Command;
|
| 9 | +use std::{env, io}; |
8 | 10 |
|
9 | 11 | use build_helper::ci::CiEnv;
|
10 | 12 | use build_helper::git::{GitConfig, get_closest_upstream_commit};
|
@@ -180,6 +182,69 @@ pub fn files_modified(ci_info: &CiInfo, pred: impl Fn(&str) -> bool) -> bool {
|
180 | 182 | !v.is_empty()
|
181 | 183 | }
|
182 | 184 |
|
| 185 | +/// If the given executable is installed with the given version, use that, |
| 186 | +/// otherwise install via cargo. |
| 187 | +pub fn ensure_version_or_cargo_install( |
| 188 | + build_dir: &Path, |
| 189 | + cargo: &Path, |
| 190 | + pkg_name: &str, |
| 191 | + bin_name: &str, |
| 192 | + version: &str, |
| 193 | +) -> io::Result<PathBuf> { |
| 194 | + // ignore the process exit code here and instead just let the version number check fail. |
| 195 | + // we also importantly don't return if the program wasn't installed, |
| 196 | + // instead we want to continue to the fallback. |
| 197 | + 'ck: { |
| 198 | + // FIXME: rewrite as if-let chain once this crate is 2024 edition. |
| 199 | + let Ok(output) = Command::new(bin_name).arg("--version").output() else { |
| 200 | + break 'ck; |
| 201 | + }; |
| 202 | + let Ok(s) = str::from_utf8(&output.stdout) else { |
| 203 | + break 'ck; |
| 204 | + }; |
| 205 | + let Some(v) = s.trim().split_whitespace().last() else { |
| 206 | + break 'ck; |
| 207 | + }; |
| 208 | + if v == version { |
| 209 | + return Ok(PathBuf::from(bin_name)); |
| 210 | + } |
| 211 | + } |
| 212 | + |
| 213 | + let tool_root_dir = build_dir.join("misc-tools"); |
| 214 | + let tool_bin_dir = tool_root_dir.join("bin"); |
| 215 | + eprintln!("building external tool {bin_name} from package {pkg_name}@{version}"); |
| 216 | + // use --force to ensure that if the required version is bumped, we update it. |
| 217 | + // use --target-dir to ensure we have a build cache so repeated invocations aren't slow. |
| 218 | + // modify PATH so that cargo doesn't print a warning telling the user to modify the path. |
| 219 | + let cargo_exit_code = Command::new(cargo) |
| 220 | + .args(["install", "--locked", "--force", "--quiet"]) |
| 221 | + .arg("--root") |
| 222 | + .arg(&tool_root_dir) |
| 223 | + .arg("--target-dir") |
| 224 | + .arg(tool_root_dir.join("target")) |
| 225 | + .arg(format!("{pkg_name}@{version}")) |
| 226 | + .env( |
| 227 | + "PATH", |
| 228 | + env::join_paths( |
| 229 | + env::split_paths(&env::var("PATH").unwrap()) |
| 230 | + .chain(std::iter::once(tool_bin_dir.clone())), |
| 231 | + ) |
| 232 | + .expect("build dir contains invalid char"), |
| 233 | + ) |
| 234 | + .env("RUSTFLAGS", "-Copt-level=0") |
| 235 | + .spawn()? |
| 236 | + .wait()?; |
| 237 | + if !cargo_exit_code.success() { |
| 238 | + return Err(io::Error::other("cargo install failed")); |
| 239 | + } |
| 240 | + let bin_path = tool_bin_dir.join(bin_name); |
| 241 | + assert!( |
| 242 | + matches!(bin_path.try_exists(), Ok(true)), |
| 243 | + "cargo install did not produce the expected binary" |
| 244 | + ); |
| 245 | + Ok(bin_path) |
| 246 | +} |
| 247 | + |
183 | 248 | pub mod alphabetical;
|
184 | 249 | pub mod bins;
|
185 | 250 | pub mod debug_artifacts;
|
|
0 commit comments