diff --git a/Cargo.lock b/Cargo.lock index 20176e307..8c985cad1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -148,6 +148,7 @@ dependencies = [ "dirs", "env_logger", "flate2", + "guess_host_triple", "log", "miette", "reqwest", @@ -355,6 +356,27 @@ dependencies = [ "termcolor", ] +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "fastrand" version = "1.7.0" @@ -509,6 +531,18 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "guess_host_triple" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35a8ce923c7490629d84e12fa2f75e1733f1ec692a47c264f9b7fd632855afc" +dependencies = [ + "errno", + "libc", + "log", + "winapi", +] + [[package]] name = "h2" version = "0.3.13" diff --git a/Cargo.toml b/Cargo.toml index 7a2fe0d04..1a4cfe4df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,9 @@ zip = { version = "0.6.2", default-features = false, features = [ "deflate", "bz # Enable feature zstdmt to enable multithreading in libzstd. zstd = { version = "0.10.0", features = [ "bindgen", "zstdmt" ], default-features = false } +[target.'cfg(target_os = "macos")'.dependencies] +guess_host_triple = "0.1.3" + [dev-dependencies] env_logger = "0.9.0" diff --git a/src/lib.rs b/src/lib.rs index 2a8236259..d83d78875 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,8 +15,8 @@ pub use helpers::*; pub mod bins; pub mod fetchers; -/// Compiled target triple, used as default for binary fetching -pub const TARGET: &str = env!("TARGET"); +mod target; +pub use target::*; /// Default package path template (may be overridden in package Cargo.toml) pub const DEFAULT_PKG_URL: &str = diff --git a/src/target.rs b/src/target.rs new file mode 100644 index 000000000..7f5e4f7b4 --- /dev/null +++ b/src/target.rs @@ -0,0 +1,153 @@ +use std::io::{BufRead, Cursor}; +use std::process::Output; +use tokio::process::Command; + +/// Compiled target triple, used as default for binary fetching +pub const TARGET: &str = env!("TARGET"); + +/// Detect the targets supported at runtime, +/// which might be different from `TARGET` which is detected +/// at compile-time. +/// +/// Return targets supported in the order of preference. +/// If target_os is linux and it support gnu, then it is preferred +/// to musl. +/// +/// If target_os is mac and it is aarch64, then aarch64 is preferred +/// to x86_64. +/// +/// Check [this issue](https://github.com/ryankurte/cargo-binstall/issues/155) +/// for more information. +pub async fn detect_targets() -> Vec> { + if let Some(target) = get_target_from_rustc().await { + let mut v = vec![target]; + + #[cfg(target_os = "linux")] + if v[0].contains("gnu") { + v.push(v[0].replace("gnu", "musl").into_boxed_str()); + } + + #[cfg(target_os = "macos")] + if &*v[0] == macos::AARCH64 { + v.push(macos::X86.into()); + } + + v + } else { + #[cfg(target_os = "linux")] + { + linux::detect_targets_linux().await + } + #[cfg(target_os = "macos")] + { + macos::detect_targets_macos() + } + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + { + vec![TARGET.into()] + } + } +} + +/// Figure out what the host target is using `rustc`. +/// If `rustc` is absent, then it would return `None`. +async fn get_target_from_rustc() -> Option> { + let Output { status, stdout, .. } = Command::new("rustc").arg("-vV").output().await.ok()?; + if !status.success() { + return None; + } + + Cursor::new(stdout) + .lines() + .filter_map(|line| line.ok()) + .find_map(|line| { + line.strip_prefix("host: ") + .map(|host| host.to_owned().into_boxed_str()) + }) +} + +#[cfg(target_os = "linux")] +mod linux { + use super::{Command, Output, TARGET}; + + pub(super) async fn detect_targets_linux() -> Vec> { + let abi = parse_abi(); + + if let Ok(Output { + status: _, + stdout, + stderr, + }) = Command::new("ldd").arg("--version").output().await + { + let libc_version = + if let Some(libc_version) = parse_libc_version_from_ldd_output(&stdout) { + libc_version + } else if let Some(libc_version) = parse_libc_version_from_ldd_output(&stderr) { + libc_version + } else { + return vec![create_target_str("musl", abi)]; + }; + + if libc_version == "gnu" { + return vec![ + create_target_str("gnu", abi), + create_target_str("musl", abi), + ]; + } + } + + // Fallback to using musl + vec![create_target_str("musl", abi)] + } + + fn parse_libc_version_from_ldd_output(output: &[u8]) -> Option<&'static str> { + let s = String::from_utf8_lossy(output); + if s.contains("musl libc") { + Some("musl") + } else if s.contains("GLIBC") { + Some("gnu") + } else { + None + } + } + + fn parse_abi() -> &'static str { + let last = TARGET.rsplit_once('-').unwrap().1; + + if let Some(libc_version) = last.strip_prefix("musl") { + libc_version + } else if let Some(libc_version) = last.strip_prefix("gnu") { + libc_version + } else { + panic!("Unrecognized libc") + } + } + + fn create_target_str(libc_version: &str, abi: &str) -> Box { + let prefix = TARGET.rsplit_once('-').unwrap().0; + + let mut target = String::with_capacity(prefix.len() + 1 + libc_version.len() + abi.len()); + target.push_str(prefix); + target.push('-'); + target.push_str(libc_version); + target.push_str(abi); + + target.into_boxed_str() + } +} + +#[cfg(target_os = "macos")] +mod macos { + use guess_host_triple::guess_host_triple; + + pub(super) const AARCH64: &str = "aarch64-apple-darwin"; + pub(super) const X86: &str = "x86_64-apple-darwin"; + + pub(super) fn detect_targets_macos() -> Vec> { + if guess_host_triple() == Some(AARCH64) { + vec![AARCH64.into(), X86.into()] + } else { + vec![X86.into()] + } + } +}