Fix detect-targets on Linux and add CI testing (#1344)

* Testing: Add `detect-targets/src/main.rs`

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

* Fix `detect-targets` linux: `guess_host_triple` could return wrong libc info

so it has to check it manually instead of simply providing alternatives
like other OSes.

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

* Fix `get_ld_flavor` for Alpine's gcompat glibc

Its output is different from regular glibc, so we need to hardcode that
particular cases.

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

* Fix detection of alpine specific musl target

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

* Add ci testing for Alpine

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

* Add CI test for detect-targets on ubuntu

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

* Refactor `get_ld_flavor`

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

* Fix shellcheck

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

* Add more CI test for ubuntu

and fixed typo in it

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

* Rm distro specific target as it breaks `cargo-install` fallback

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

* Make sure all binaries are built in CI

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

* Add `package.metadata.binstall` for `detect-targets`

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

* Fix justfile

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

* Fix `detect-targets-{alpine, ubuntu}-test`

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

* Fix `detect-targets-ubuntu-test`

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

* Fix `debug-targets-ubuntu-test`

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

* `set -exuo pipefail` in `detect-targets-ubuntu-test`

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

* Simplify `detect-targets-*-test`: Use `Swatinem/rust-cache@v2` directly

instead of using `just-setup`

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

* Rm dup steps in `detect-targets-ubuntu-test`

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

* Add `ls` to detect-targets-alpine-test for debugging

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

* FIx `detect-targets-alpine-test`

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

* Fix `get_ld_flavor`

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

* Fix `linux::detect_targets` on ubuntu & glibc system

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

* FIx `linux::detect_targets` glibc checking

Check dynlib suffix

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-07 00:17:43 +10:00 committed by GitHub
parent d657fbe518
commit 76a692224d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 159 additions and 85 deletions

View file

@ -20,3 +20,6 @@ windows-dll = { version = "0.4.1", features = ["windows"], default-features = fa
[dev-dependencies]
tokio = { version = "1.28.2", features = ["macros"], default-features = false }
[package.metadata.binstall]
pkg-url = "{ repo }/releases/download/v{ version }/cargo-binstall-{ target }.full.{ archive-format }"

View file

@ -38,19 +38,21 @@ pub async fn detect_targets() -> Vec<String> {
.to_string()
});
let mut targets = vec![target];
cfg_if! {
if #[cfg(target_os = "macos")] {
let mut targets = vec![target];
targets.extend(macos::detect_alternative_targets(&targets[0]).await);
targets
} else if #[cfg(target_os = "windows")] {
let mut targets = vec![target];
targets.extend(windows::detect_alternative_targets(&targets[0]));
targets
} else if #[cfg(target_os = "linux")] {
targets.extend(linux::detect_alternative_targets(&targets[0]).await);
// Linux is a bit special, since the result from `guess_host_triple`
// might be wrong about whether glibc or musl is used.
linux::detect_targets(target).await
}
}
targets
}
/// Figure out what the host target is using `rustc`.

View file

@ -1,12 +1,11 @@
use std::{
fs,
path::Path,
process::{Output, Stdio},
str,
};
use tokio::{process::Command, task};
pub(super) async fn detect_alternative_targets(target: &str) -> impl Iterator<Item = String> {
pub(super) async fn detect_targets(target: String) -> Vec<String> {
let (prefix, postfix) = target
.rsplit_once('-')
.expect("unwrap: target always has a -");
@ -37,71 +36,81 @@ pub(super) async fn detect_alternative_targets(target: &str) -> impl Iterator<It
.expect("unwrap: target always has a - for cpu_arch")
.0;
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 cpu_arch_suffix = cpu_arch.replace('_', "-");
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 handles: Vec<_> = [
format!("/lib/ld-linux-{cpu_arch_suffix}.so.2"),
format!("/lib/{cpu_arch}-linux-gnu/ld-linux-{cpu_arch_suffix}.so.2"),
format!("/usr/lib/{cpu_arch}-linux-gnu/ld-linux-{cpu_arch_suffix}.so.2"),
]
.into_iter()
.map(|p| AutoAbortHandle(tokio::spawn(is_gnu_ld(p))))
.collect();
let has_glibc = async move {
for mut handle in handles {
if let Ok(true) = (&mut handle.0).await {
return true;
}
}
});
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
};
false
}
.await;
[
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}")),
has_glibc.then(|| format!("{cpu_arch}-unknown-linux-gnu{abi}")),
Some(musl_fallback_target()),
]
}
Libc::Android | Libc::Unknown => [
Some(target.to_string()),
Some(musl_fallback_target()),
None,
None,
],
Libc::Android | Libc::Unknown => [Some(target.clone()), Some(musl_fallback_target())],
}
.into_iter()
.flatten()
.collect()
}
async fn is_gnu_ld(cmd: &str) -> bool {
get_ld_flavor(cmd).await == Some(Libc::Gnu)
async fn is_gnu_ld(cmd: String) -> bool {
get_ld_flavor(&cmd).await == Some(Libc::Gnu)
}
async fn get_ld_flavor(cmd: &str) -> Option<Libc> {
Command::new(cmd)
let Output {
status,
stdout,
stderr,
} = Command::new(cmd)
.arg("--version")
.stdin(Stdio::null())
.output()
.await
.ok()
.and_then(|Output { stdout, stderr, .. }| {
Libc::parse(&stdout).or_else(|| Libc::parse(&stderr))
})
.ok()?;
const ALPINE_GCOMPAT: &str = r#"This is the gcompat ELF interpreter stub.
You are not meant to run this directly.
"#;
if status.success() {
// Executing glibc ldd or /lib/ld-linux-{cpu_arch}.so.1 will always
// succeeds.
String::from_utf8_lossy(&stdout)
.contains("GLIBC")
.then_some(Libc::Gnu)
} else if status.code() == Some(1) {
// On Alpine, executing both the gcompat glibc and the ldd and
// /lib/ld-musl-{cpu_arch}.so.1 will fail with exit status 1.
if str::from_utf8(&stdout).as_deref() == Ok(ALPINE_GCOMPAT) {
// Alpine's gcompat package will output ALPINE_GCOMPAT to stdout
Some(Libc::Gnu)
} else if String::from_utf8_lossy(&stderr).contains("musl libc") {
// Alpine/s ldd and musl dynlib will output to stderr
Some(Libc::Musl)
} else {
None
}
} else {
None
}
}
#[derive(Eq, PartialEq)]
@ -112,37 +121,10 @@ enum Libc {
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()),
struct AutoAbortHandle<T>(task::JoinHandle<T>);
impl<T> Drop for AutoAbortHandle<T> {
fn drop(&mut self) {
self.0.abort();
}
}

View file

@ -0,0 +1,17 @@
use std::io;
use detect_targets::detect_targets;
use tokio::runtime;
fn main() -> io::Result<()> {
let targets = runtime::Builder::new_current_thread()
.enable_all()
.build()?
.block_on(detect_targets());
for target in targets {
println!("{target}");
}
Ok(())
}