feat detect-targets: Improve support of non-std glibc/musl (#1343)

* feat `detect-targets`: Improve support of non-std glibc/musl

Fixed #1329

 - Refactor: Create `linux::detect_alternative_targets` to reuse code
   from other targets
 - Run `/lib/ld-linux-{cpu_arch}.so.1 --version` for checking glibc
   support instead of running `ldd --version` since it could be non-std
   glibc installation and does not provide
   `/lib/ld-linux-{cpu_arch}.so.1`
 - Check for non-std glibc and add fallback target
   `{cpu_arch}-{distro_name}-linux-gnu{abi}`
 - Add `{cpu_arch}-{distro_name}-linux-musl{abi}` fallback for musl
   libc, specially for Alpine since it has a
   `/lib/ld-musl-{cpu_arch}.so.1`
 - For unknown libc flavor, check for the target provided before
   fallback to musl

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>

* feat `detect-targets`: Support glibc on musl target

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>

* feat `detect-targets`: Unify `Libc::{Gnu, Musl}` checks

since we can't really tell if we are on gnu or musl

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>

---------

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
Jiahao XU 2023-09-03 09:19:34 +10:00 committed by GitHub
parent 3e67e3624a
commit 0fa315758b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 124 additions and 69 deletions

View file

@ -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<String> {
#[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`.

View file

@ -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<String> {
let target = guess_host_triple().unwrap_or(TARGET);
use tokio::{process::Command, task};
pub(super) async fn detect_alternative_targets(target: &str) -> impl Iterator<Item = String> {
let (prefix, postfix) = target
.rsplit_once('-')
.expect("unwrap: target always has a -");
@ -25,51 +24,125 @@ pub(super) async fn detect_targets_linux() -> Vec<String> {
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<Libc> {
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<Self> {
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<String> {
task::spawn_blocking(get_distro_name_blocking)
.await
.ok()
.flatten()
}
fn get_distro_name_blocking() -> Option<String> {
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()),
}
}