diff --git a/crates/detect-targets/src/detect.rs b/crates/detect-targets/src/detect.rs index a7863e6c..1850be0a 100644 --- a/crates/detect-targets/src/detect.rs +++ b/crates/detect-targets/src/detect.rs @@ -32,43 +32,25 @@ cfg_if! { /// Check [this issue](https://github.com/ryankurte/cargo-binstall/issues/155) /// for more information. pub async fn detect_targets() -> Vec { - #[cfg(target_os = "linux")] - { - if let Some(target) = get_target_from_rustc().await { - let mut targets = vec![target]; + let target = get_target_from_rustc().await.unwrap_or_else(|| { + guess_host_triple::guess_host_triple() + .unwrap_or(crate::TARGET) + .to_string() + }); - if targets[0].contains("gnu") { - targets.push(targets[0].replace("gnu", "musl")); - } else if targets[0].contains("android") { - targets.push(targets[0].replace("android", "musl")); - } + let mut targets = vec![target]; - targets - } else { - linux::detect_targets_linux().await + cfg_if! { + if #[cfg(target_os = "macos")] { + targets.extend(macos::detect_alternative_targets(&targets[0]).await); + } else if #[cfg(target_os = "windows")] { + targets.extend(windows::detect_alternative_targets(&targets[0])); + } else if #[cfg(target_os = "linux")] { + targets.extend(linux::detect_alternative_targets(&targets[0]).await); } } - #[cfg(not(target_os = "linux"))] - { - let target = get_target_from_rustc().await.unwrap_or_else(|| { - guess_host_triple::guess_host_triple() - .unwrap_or(crate::TARGET) - .to_string() - }); - - let mut targets = vec![target]; - - cfg_if! { - if #[cfg(target_os = "macos")] { - targets.extend(macos::detect_alternative_targets(&targets[0]).await); - } else if #[cfg(target_os = "windows")] { - targets.extend(windows::detect_alternative_targets(&targets[0])); - } - } - - targets - } + targets } /// Figure out what the host target is using `rustc`. diff --git a/crates/detect-targets/src/detect/linux.rs b/crates/detect-targets/src/detect/linux.rs index bedbc775..ea7f4bc0 100644 --- a/crates/detect-targets/src/detect/linux.rs +++ b/crates/detect-targets/src/detect/linux.rs @@ -1,13 +1,12 @@ -use crate::TARGET; +use std::{ + fs, + path::Path, + process::{Output, Stdio}, +}; -use std::process::{Output, Stdio}; - -use guess_host_triple::guess_host_triple; -use tokio::process::Command; - -pub(super) async fn detect_targets_linux() -> Vec { - let target = guess_host_triple().unwrap_or(TARGET); +use tokio::{process::Command, task}; +pub(super) async fn detect_alternative_targets(target: &str) -> impl Iterator { let (prefix, postfix) = target .rsplit_once('-') .expect("unwrap: target always has a -"); @@ -25,51 +24,125 @@ pub(super) async fn detect_targets_linux() -> Vec { let musl_fallback_target = || format!("{prefix}-{}{abi}", "musl"); match libc { - Libc::Gnu => { - // guess_host_triple cannot detect whether the system is using glibc, - // musl libc or other libc. - // - // As such, we need to launch the test ourselves. - if supports_gnu().await { - vec![target.to_string(), musl_fallback_target()] - } else { - vec![musl_fallback_target()] - } - } - Libc::Android => vec![target.to_string(), musl_fallback_target()], + // guess_host_triple cannot detect whether the system is using glibc, + // musl libc or other libc. + // + // On Alpine, you can use `apk add gcompat` to install glibc + // and run glibc programs. + // + // As such, we need to launch the test ourselves. + Libc::Gnu | Libc::Musl => { + let cpu_arch = target + .split_once('-') + .expect("unwrap: target always has a - for cpu_arch") + .0; - _ => vec![target.to_string()], + let has_glibc = task::spawn({ + let glibc_path = format!("/lib/ld-linux-{cpu_arch}.so.1"); + async move { is_gnu_ld(&glibc_path).await } + }); + + let distro_if_has_non_std_glibc = task::spawn(async { + if is_gnu_ld("/usr/bin/ldd").await { + get_distro_name().await + } else { + None + } + }); + + let distro_if_has_musl_dynlib = if get_ld_flavor(&format!( + "/lib/ld-musl-{cpu_arch}.so.1" + )) + .await + == Some(Libc::Musl) + { + get_distro_name().await + } else { + None + }; + + [ + has_glibc + .await + .unwrap_or(false) + .then(|| format!("{cpu_arch}-unknown-linux-gnu{abi}")), + distro_if_has_non_std_glibc + .await + .ok() + .flatten() + .map(|distro_name| format!("{cpu_arch}-{distro_name}-linux-gnu{abi}")), + // Fallback for Linux flavors like Alpine, which has a musl dyn libc + distro_if_has_musl_dynlib + .map(|distro_name| format!("{cpu_arch}-{distro_name}-linux-musl{abi}")), + Some(musl_fallback_target()), + ] + } + Libc::Android | Libc::Unknown => [ + Some(target.to_string()), + Some(musl_fallback_target()), + None, + None, + ], } + .into_iter() + .flatten() } -async fn supports_gnu() -> bool { - Command::new("ldd") +async fn is_gnu_ld(cmd: &str) -> bool { + get_ld_flavor(cmd).await == Some(Libc::Gnu) +} + +async fn get_ld_flavor(cmd: &str) -> Option { + Command::new(cmd) .arg("--version") .stdin(Stdio::null()) .output() .await .ok() .and_then(|Output { stdout, stderr, .. }| { - parse_libc_version_from_ldd_output(&stdout) - .or_else(|| parse_libc_version_from_ldd_output(&stderr)) + Libc::parse(&stdout).or_else(|| Libc::parse(&stderr)) }) - == Some("gnu") -} - -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 - } } +#[derive(Eq, PartialEq)] enum Libc { Gnu, Musl, Android, Unknown, } + +impl Libc { + fn parse(output: &[u8]) -> Option { + let s = String::from_utf8_lossy(output); + if s.contains("musl libc") { + Some(Self::Musl) + } else if s.contains("GLIBC") { + Some(Self::Gnu) + } else { + None + } + } +} + +async fn get_distro_name() -> Option { + task::spawn_blocking(get_distro_name_blocking) + .await + .ok() + .flatten() +} + +fn get_distro_name_blocking() -> Option { + match fs::read_to_string("/etc/os-release") { + Ok(os_release) => os_release + .lines() + .find_map(|line| line.strip_prefix("ID=\"")?.strip_suffix('"')) + .map(ToString::to_string), + Err(_) => (Path::new("/etc/nix/nix.conf").is_file() + && ["/nix/store", "/nix/var/profiles"] + .into_iter() + .map(Path::new) + .all(Path::is_dir)) + .then_some("nixos".to_string()), + } +}