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:
Jiahao XU 2022-08-21 22:21:33 +10:00 committed by GitHub
parent 1102284684
commit 62f9450d2d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 574 additions and 263 deletions

View file

@ -17,6 +17,7 @@ cargo_toml = "0.11.5"
clap = { version = "3.2.17", features = ["derive"] }
compact_str = { version = "0.5.2", features = ["serde"] }
crates_io_api = { version = "0.8.0", default-features = false }
detect-targets = { version = "0.1.0", path = "../detect-targets" }
flate2 = { version = "1.0.24", default-features = false }
flock = { version = "0.1.0", path = "../flock" }
futures-util = { version = "0.3.23", default-features = false }
@ -57,9 +58,6 @@ zip = { version = "0.6.2", default-features = false, features = ["deflate", "bzi
# otherwise there will be a link conflict.
zstd = { version = "0.10.0", default-features = false }
[target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies]
guess_host_triple = "0.1.3"
[dev-dependencies]
env_logger = "0.9.0"

View file

@ -1,7 +0,0 @@
fn main() {
// Fetch build target and define this for the compiler
println!(
"cargo:rustc-env=TARGET={}",
std::env::var("TARGET").unwrap()
);
}

View file

@ -6,4 +6,5 @@ pub mod fs;
pub mod helpers;
pub mod manifests;
pub mod ops;
pub mod targets;
pub use detect_targets::{get_desired_targets, DesiredTargets};

View file

@ -170,9 +170,10 @@ impl<'a> IntoIterator for &'a Records {
#[cfg(test)]
mod test {
use super::*;
use crate::{manifests::crate_info::CrateSource, targets::TARGET};
use crate::manifests::crate_info::CrateSource;
use compact_str::CompactString;
use detect_targets::TARGET;
use semver::Version;
use tempfile::NamedTempFile;

View file

@ -130,8 +130,9 @@ pub enum CratesTomlParseError {
#[cfg(test)]
mod tests {
use super::*;
use crate::{manifests::crate_info::CrateSource, targets::TARGET};
use crate::manifests::crate_info::CrateSource;
use detect_targets::TARGET;
use semver::Version;
use tempfile::TempDir;

View file

@ -4,7 +4,7 @@ use std::path::PathBuf;
use semver::VersionReq;
use crate::{manifests::cargo_toml_binstall::PkgOverride, targets::DesiredTargets};
use crate::{manifests::cargo_toml_binstall::PkgOverride, DesiredTargets};
pub mod install;
pub mod resolve;

View file

@ -1,253 +0,0 @@
use std::{
io::{BufRead, Cursor},
process::Output,
sync::Arc,
};
use tokio::{process::Command, sync::OnceCell};
/// Compiled target triple, used as default for binary fetching
pub const TARGET: &str = env!("TARGET");
#[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 used.
/// 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.
///
/// Calling it through `tokio::spawn` would enable other tasks, such as
/// fetching the crate tarballs, to be executed concurrently.
pub fn get_desired_targets(opts_targets: &Option<String>) -> DesiredTargets {
if let Some(targets) = opts_targets.as_ref() {
DesiredTargets::initialized(targets.split(',').map(|t| t.to_string()).collect())
} else {
DesiredTargets::auto_detect()
}
}
/// 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(target_os = "linux")]
if v[0].contains("gnu") {
v.push(v[0].replace("gnu", "musl"));
}
#[cfg(target_os = "macos")]
if &*v[0] == macos::AARCH64 {
v.push(macos::X86.into());
}
#[cfg(target_os = "windows")]
v.extend(windows::detect_alternative_targets(&v[0]));
v
} else {
#[cfg(target_os = "linux")]
{
linux::detect_targets_linux().await
}
#[cfg(target_os = "macos")]
{
macos::detect_targets_macos()
}
#[cfg(target_os = "windows")]
{
windows::detect_targets_windows()
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
{
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").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()))
}
#[cfg(target_os = "linux")]
mod linux {
use super::{Command, Output, TARGET};
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").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()
}
}
#[cfg(target_os = "macos")]
mod macos {
use super::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
}
}
#[cfg(target_os = "windows")]
mod windows {
use super::TARGET;
use guess_host_triple::guess_host_triple;
pub(super) fn detect_alternative_targets(target: &str) -> Option<String> {
let (prefix, abi) = target.rsplit_once('-').expect("Invalid target triple");
// 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
}
}