rustc_host/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::process::Command;
4use thiserror::Error;
5
6/// Error type for `rustc_host`.
7#[derive(Error, Debug)]
8pub enum Error {
9    /// I/O error when executing `rustc -vV`.
10    #[error("I/O error when executing `rustc -vV`. {0}")]
11    Io(#[from] std::io::Error),
12    /// Output of `rustc -vV` was not valid UTF-8.
13    #[error("Output of `rustc -vV` was not valid UTF-8. {0}")]
14    Utf8(#[from] std::str::Utf8Error),
15    /// Unexpected output structure for `rustc -vV` after successful execution.
16    #[error("Unexpected output structure for `rustc -vV` after successful execution")]
17    UnexpectedOutputStructure,
18}
19
20/// Returns the host triple of the current rustc using CLI.
21///
22/// Notice that such implementation relies on presence of `rustc` on the machine
23/// where this function is called. Two good places for it are in a build script
24/// or in a procedural macro.
25///
26/// # Example
27///
28/// ```rust
29#[doc = include_str!("../examples/host.rs")]
30/// ```
31///
32/// # Implementation details
33///
34/// *At the moment of writing*, it relies on the output of `rustc -vV`, which is expected to be
35/// nearly in the following format:
36///
37/// ```text
38/// rustc 1.66.0 (69f9c33d7 2022-12-12)
39/// binary: rustc
40/// commit-hash: 69f9c33d71c871fc16ac445211281c6e7a340943
41/// commit-date: 2022-12-12
42/// host: x86_64-pc-windows-msvc
43/// release: 1.66.0
44/// LLVM version: 15.0.2
45/// ```
46///
47/// To be precise, it expects a line starting with `host: `.
48pub fn from_cli() -> Result<String, Error> {
49    let output = Command::new("rustc")
50        .arg("-vV")
51        .output()
52        .map_err(Error::from)?;
53
54    let stdout_buf =
55        String::from_utf8(output.stdout).map_err(|e| Error::from(e.utf8_error()))?;
56    // TODO: consider reusing the String from output
57    #[cfg(not(feature = "unsafe"))]
58    match stdout_buf.lines().find_map(|l| l.strip_prefix("host: ")) {
59        Some(host) => Ok(host.to_string()),
60        None => Err(Error::UnexpectedOutputStructure),
61    }
62    #[cfg(feature = "unsafe")]
63    {
64        const HOST_PREFIX: &str = "\nhost: ";
65        let beg_incl = stdout_buf
66            .find(HOST_PREFIX)
67            .map(|i| i + HOST_PREFIX.len())
68            .ok_or(Error::UnexpectedOutputStructure)?;
69
70        let end_excl = unsafe { stdout_buf.get_unchecked(beg_incl..) }
71            .find('\n')
72            .map(|i| i + beg_incl)
73            .ok_or(Error::UnexpectedOutputStructure)?;
74        let mut bytes = stdout_buf.into_bytes();
75        let src = unsafe { bytes.as_ptr().add(beg_incl) };
76        let dst = bytes.as_mut_ptr();
77        let count = end_excl - beg_incl;
78        unsafe { std::ptr::copy(src, dst, count) };
79        bytes.truncate(end_excl - beg_incl);
80        Ok(unsafe { String::from_utf8_unchecked(bytes) })
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    #[cfg(all(
90        target_arch = "x86_64",
91        target_vendor = "pc",
92        target_os = "windows",
93        target_env = "msvc"
94    ))]
95    fn test_from_cli() {
96        let host = from_cli().unwrap();
97        assert_eq!(host, "x86_64-pc-windows-msvc");
98    }
99}