mirror of
https://github.com/cargo-bins/cargo-binstall.git
synced 2025-05-04 11:10:02 +00:00
Refactor: Extract new crate detect-targets
and improve code quality (#307)
* Refactor: Extract new crate `detect-targets` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Extract new mod `detect` for `detect-targets` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Extract `desired_targets` in crate `detect-targets` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Extract `detect::linux` in crate `detect-targets` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Extract `detect::macos` in crate `detect-targets` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Extract `detect::windows` in crate `detect-targets` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add new dep cfg-if v1.0.0 for `detect-targets` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Simplify mod declaration in `detect` using `cfg_if!` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Simplify `detect_targets` using `cfg_if!` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add crate doc for `detect-targets` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Enable feature "macros" of tokio in `detect-targets` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Enable feature "io-util" of dep tokio Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Rm unused feature "io-util" & "macros" of dep tokio Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Set stdin & stderr to null in `get_target_from_rustc` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Improve doc of `get_desired_targets` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Improve `detect_targets_linux`: Run `ldd` with stdin set to null Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix potential panic in `windows::detect_alternative_targets` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * FIx fmt of `detect_targets_linux` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Do not re-export dep `detect-targets` in `crates/lib` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix typo in crate doc for `detect-targets` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Enable feature "macros" of tokio in dev mode Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add example to crate doc of `detect-targets` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Improve API `get_desired_targets`: Take `Option<&str>` instead of `&Option<String>` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
parent
1102284684
commit
62f9450d2d
20 changed files with 574 additions and 263 deletions
crates/detect-targets/src
59
crates/detect-targets/src/desired_targets.rs
Normal file
59
crates/detect-targets/src/desired_targets.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use crate::detect_targets;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum DesiredTargetsInner {
|
||||
AutoDetect(Arc<OnceCell<Vec<String>>>),
|
||||
Initialized(Vec<String>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DesiredTargets(DesiredTargetsInner);
|
||||
|
||||
impl DesiredTargets {
|
||||
fn initialized(targets: Vec<String>) -> Self {
|
||||
Self(DesiredTargetsInner::Initialized(targets))
|
||||
}
|
||||
|
||||
fn auto_detect() -> Self {
|
||||
let arc = Arc::new(OnceCell::new());
|
||||
|
||||
let once_cell = arc.clone();
|
||||
tokio::spawn(async move {
|
||||
once_cell.get_or_init(detect_targets).await;
|
||||
});
|
||||
|
||||
Self(DesiredTargetsInner::AutoDetect(arc))
|
||||
}
|
||||
|
||||
pub async fn get(&self) -> &[String] {
|
||||
use DesiredTargetsInner::*;
|
||||
|
||||
match &self.0 {
|
||||
Initialized(targets) => targets,
|
||||
|
||||
// This will mostly just wait for the spawned task,
|
||||
// on rare occausion though, it will poll the future
|
||||
// returned by `detect_targets`.
|
||||
AutoDetect(once_cell) => once_cell.get_or_init(detect_targets).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// If opts_targets is `Some`, then it will be parsed in the format of
|
||||
/// `$target1,$target2,...`.
|
||||
/// Otherwise, call `detect_targets` using `tokio::spawn` to detect targets.
|
||||
///
|
||||
/// Since `detect_targets` internally spawns a process and wait for it,
|
||||
/// it's pretty costy, it is recommended to run this fn ASAP and
|
||||
/// reuse the result.
|
||||
pub fn get_desired_targets(opts_targets: Option<&str>) -> DesiredTargets {
|
||||
if let Some(targets) = opts_targets {
|
||||
DesiredTargets::initialized(targets.split(',').map(|t| t.to_string()).collect())
|
||||
} else {
|
||||
DesiredTargets::auto_detect()
|
||||
}
|
||||
}
|
88
crates/detect-targets/src/detect.rs
Normal file
88
crates/detect-targets/src/detect.rs
Normal file
|
@ -0,0 +1,88 @@
|
|||
use std::{
|
||||
io::{BufRead, Cursor},
|
||||
process::{Output, Stdio},
|
||||
};
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use tokio::process::Command;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(target_os = "linux")] {
|
||||
mod linux;
|
||||
} else if #[cfg(target_os = "macos")] {
|
||||
mod macos;
|
||||
} else if #[cfg(target_os = "windows")] {
|
||||
mod windows;
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<String> {
|
||||
if let Some(target) = get_target_from_rustc().await {
|
||||
let mut v = vec![target];
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(target_os = "linux")] {
|
||||
if v[0].contains("gnu") {
|
||||
v.push(v[0].replace("gnu", "musl"));
|
||||
}
|
||||
} else if #[cfg(target_os = "macos")] {
|
||||
if &*v[0] == macos::AARCH64 {
|
||||
v.push(macos::X86.into());
|
||||
}
|
||||
} else if #[cfg(target_os = "windows")] {
|
||||
v.extend(windows::detect_alternative_targets(&v[0]));
|
||||
}
|
||||
}
|
||||
|
||||
v
|
||||
} else {
|
||||
cfg_if! {
|
||||
if #[cfg(target_os = "linux")] {
|
||||
linux::detect_targets_linux().await
|
||||
} else if #[cfg(target_os = "macos")] {
|
||||
macos::detect_targets_macos()
|
||||
} else if #[cfg(target_os = "windows")] {
|
||||
windows::detect_targets_windows()
|
||||
} else {
|
||||
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<String> {
|
||||
let Output { status, stdout, .. } = Command::new("rustc")
|
||||
.arg("-vV")
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.ok()?
|
||||
.wait_with_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()))
|
||||
}
|
86
crates/detect-targets/src/detect/linux.rs
Normal file
86
crates/detect-targets/src/detect/linux.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
use crate::TARGET;
|
||||
|
||||
use std::process::{Output, Stdio};
|
||||
|
||||
use tokio::process::Command;
|
||||
|
||||
pub(super) async fn detect_targets_linux() -> Vec<String> {
|
||||
let (abi, libc) = parse_abi_and_libc();
|
||||
|
||||
if let Libc::Glibc = libc {
|
||||
// Glibc can only be dynamically linked.
|
||||
// If we can run this binary, then it means that the target
|
||||
// supports both glibc and musl.
|
||||
return create_targets_str(&["gnu", "musl"], abi);
|
||||
}
|
||||
|
||||
if let Ok(Output {
|
||||
status: _,
|
||||
stdout,
|
||||
stderr,
|
||||
}) = Command::new("ldd")
|
||||
.arg("--version")
|
||||
.stdin(Stdio::null())
|
||||
.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 create_targets_str(&["gnu", "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
|
||||
}
|
||||
}
|
||||
|
||||
enum Libc {
|
||||
Glibc,
|
||||
Musl,
|
||||
}
|
||||
|
||||
fn parse_abi_and_libc() -> (&'static str, Libc) {
|
||||
let last = TARGET.rsplit_once('-').unwrap().1;
|
||||
|
||||
if let Some(libc_version) = last.strip_prefix("musl") {
|
||||
(libc_version, Libc::Musl)
|
||||
} else if let Some(libc_version) = last.strip_prefix("gnu") {
|
||||
(libc_version, Libc::Glibc)
|
||||
} else {
|
||||
panic!("Unrecognized libc")
|
||||
}
|
||||
}
|
||||
|
||||
fn create_target_str(libc_version: &str, abi: &str) -> String {
|
||||
let prefix = TARGET
|
||||
.rsplit_once('-')
|
||||
.expect("unwrap: TARGET always has a -")
|
||||
.0;
|
||||
|
||||
format!("{prefix}-{libc_version}{abi}")
|
||||
}
|
||||
|
||||
fn create_targets_str(libc_versions: &[&str], abi: &str) -> Vec<String> {
|
||||
libc_versions
|
||||
.iter()
|
||||
.map(|libc_version| create_target_str(libc_version, abi))
|
||||
.collect()
|
||||
}
|
15
crates/detect-targets/src/detect/macos.rs
Normal file
15
crates/detect-targets/src/detect/macos.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
use crate::TARGET;
|
||||
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<String> {
|
||||
let mut targets = vec![guess_host_triple().unwrap_or(TARGET).to_string()];
|
||||
|
||||
if targets[0] == AARCH64 {
|
||||
targets.push(X86.into());
|
||||
}
|
||||
|
||||
targets
|
||||
}
|
17
crates/detect-targets/src/detect/windows.rs
Normal file
17
crates/detect-targets/src/detect/windows.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
use crate::TARGET;
|
||||
use guess_host_triple::guess_host_triple;
|
||||
|
||||
pub(super) fn detect_alternative_targets(target: &str) -> Option<String> {
|
||||
let (prefix, abi) = target.rsplit_once('-')?;
|
||||
|
||||
// detect abi in ["gnu", "gnullvm", ...]
|
||||
(abi != "msvc").then(|| format!("{prefix}-msvc"))
|
||||
}
|
||||
|
||||
pub(super) fn detect_targets_windows() -> Vec<String> {
|
||||
let mut targets = vec![guess_host_triple().unwrap_or(TARGET).to_string()];
|
||||
|
||||
targets.extend(detect_alternative_targets(&targets[0]));
|
||||
|
||||
targets
|
||||
}
|
65
crates/detect-targets/src/lib.rs
Normal file
65
crates/detect-targets/src/lib.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
//! Detect the target at the runtime.
|
||||
//!
|
||||
//! Example use cases:
|
||||
//! - The binary is built with musl libc to run on anywhere, but
|
||||
//! the runtime supports glibc.
|
||||
//! - The binary is built for x86_64-apple-darwin, but run on
|
||||
//! aarch64-apple-darwin.
|
||||
//!
|
||||
//! This crate provides two API:
|
||||
//! - [`detect_targets`] provides the API to get the target
|
||||
//! at runtime, but the code is run on the current thread.
|
||||
//! - [`get_desired_targets`] provides the API to either
|
||||
//! parse `$target1,$target2,...` override provided by the users,
|
||||
//! or run [`detect_targets`] in the background using [`tokio::spawn`].
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! `detect_targets`:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use detect_targets::detect_targets;
|
||||
//! # #[tokio::main(flavor = "current_thread")]
|
||||
//! # async fn main() {
|
||||
//!
|
||||
//! let targets = detect_targets().await;
|
||||
//! eprintln!("Your platform supports targets: {targets:#?}");
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! `get_desired_targets` with user override:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use detect_targets::get_desired_targets;
|
||||
//! # #[tokio::main(flavor = "current_thread")]
|
||||
//! # async fn main() {
|
||||
//!
|
||||
//! assert_eq!(
|
||||
//! get_desired_targets(Some("x86_64-apple-darwin,aarch64-apple-darwin")).get().await,
|
||||
//! &["x86_64-apple-darwin", "aarch64-apple-darwin"],
|
||||
//! );
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! `get_desired_targets` without user override:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use detect_targets::get_desired_targets;
|
||||
//! # #[tokio::main(flavor = "current_thread")]
|
||||
//! # async fn main() {
|
||||
//!
|
||||
//! eprintln!(
|
||||
//! "Your platform supports targets: {:#?}",
|
||||
//! get_desired_targets(None).get().await
|
||||
//! );
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
mod detect;
|
||||
pub use detect::detect_targets;
|
||||
|
||||
mod desired_targets;
|
||||
pub use desired_targets::{get_desired_targets, DesiredTargets};
|
||||
|
||||
/// Compiled target triple, used as default for binary fetching
|
||||
pub const TARGET: &str = env!("TARGET");
|
Loading…
Add table
Add a link
Reference in a new issue