detect_targets/
detect.rs

1use std::{
2    borrow::Cow,
3    env,
4    ffi::OsStr,
5    process::{Output, Stdio},
6};
7
8use cfg_if::cfg_if;
9use tokio::process::Command;
10#[cfg(feature = "tracing")]
11use tracing::debug;
12
13cfg_if! {
14    if #[cfg(any(target_os = "linux",  target_os = "android"))] {
15        mod linux;
16    } else if #[cfg(target_os = "macos")] {
17        mod macos;
18    } else if #[cfg(target_os = "windows")] {
19        mod windows;
20    }
21}
22
23/// Detect the targets supported at runtime,
24/// which might be different from `TARGET` which is detected
25/// at compile-time.
26///
27/// Return targets supported in the order of preference.
28/// If target_os is linux and it support gnu, then it is preferred
29/// to musl.
30///
31/// If target_os is mac and it is aarch64, then aarch64 is preferred
32/// to x86_64.
33///
34/// Check [this issue](https://github.com/ryankurte/cargo-binstall/issues/155)
35/// for more information.
36pub async fn detect_targets() -> Vec<String> {
37    let target = get_target_from_rustc().await;
38    #[cfg(feature = "tracing")]
39    debug!("get_target_from_rustc()={target:?}");
40    let target = target.unwrap_or_else(|| {
41        let target = guess_host_triple::guess_host_triple();
42        #[cfg(feature = "tracing")]
43        debug!("guess_host_triple::guess_host_triple()={target:?}");
44        target.unwrap_or(crate::TARGET).to_string()
45    });
46
47    cfg_if! {
48        if #[cfg(target_os = "macos")] {
49            let mut targets = vec![target];
50            targets.extend(macos::detect_alternative_targets(&targets[0]).await);
51            targets
52        } else if #[cfg(target_os = "windows")] {
53            let mut targets = vec![target];
54            targets.extend(windows::detect_alternative_targets(&targets[0]));
55            targets
56        } else if #[cfg(any(target_os = "linux", target_os = "android"))] {
57            // Linux is a bit special, since the result from `guess_host_triple`
58            // might be wrong about whether glibc or musl is used.
59            linux::detect_targets(target).await
60        } else {
61            vec![target]
62        }
63    }
64}
65
66/// Figure out what the host target is using `rustc`.
67/// If `rustc` is absent, then it would return `None`.
68///
69/// If environment variable `CARGO` is present, then
70/// `$CARGO -vV` will be run instead.
71///
72/// Otherwise, it will run `rustc -vV` to detect target.
73async fn get_target_from_rustc() -> Option<String> {
74    let cmd = env::var_os("CARGO")
75        .map(Cow::Owned)
76        .unwrap_or_else(|| Cow::Borrowed(OsStr::new("rustc")));
77
78    let Output { status, stdout, .. } = Command::new(cmd)
79        .arg("-vV")
80        .stdin(Stdio::null())
81        .stdout(Stdio::piped())
82        .stderr(Stdio::null())
83        .spawn()
84        .ok()?
85        .wait_with_output()
86        .await
87        .ok()?;
88
89    if !status.success() {
90        return None;
91    }
92
93    let stdout = String::from_utf8_lossy(&stdout);
94    let target = stdout
95        .lines()
96        .find_map(|line| line.strip_prefix("host: "))?;
97
98    // The target triplets have the form of 'arch-vendor-system'.
99    //
100    // When building for Linux (e.g. the 'system' part is
101    // 'linux-something'), replace the vendor with 'unknown'
102    // so that mapping to rust standard targets happens correctly.
103    //
104    // For example, alpine set `rustc` host triple to
105    // `x86_64-alpine-linux-musl`.
106    //
107    // Here we use splitn with n=4 since we just need to check
108    // the third part to see if it equals to "linux" and verify
109    // that we have at least three parts.
110    let mut parts: Vec<&str> = target.splitn(4, '-').collect();
111    if *parts.get(2)? == "linux" {
112        parts[1] = "unknown";
113    }
114    Some(parts.join("-"))
115}