myd 0.1.1

An implementation of the rust module system
Documentation
//! Information about the platform / target

use std::{
    fmt::Display, io, num::ParseIntError, process::Command, str::FromStr, string::FromUtf8Error,
};

/// An error which arises when making a [`SystemInfo`]
#[derive(Debug, thiserror::Error)]
pub enum PlatformError {
    /// The endian is neither "little" nor "big"
    #[error("invalid endian: {0}")]
    InvalidEndian(String),
    /// Failed to evoke rustc
    #[error("io error: {0}")]
    Io(#[from] io::Error),
    /// Failed to convert rustc's output to utf8
    #[error("failed to convert string to utf8: {0}")]
    Utf8(#[from] FromUtf8Error),
    /// Failed to parse an integer in rustc's output
    #[error("failed to parse integer: {0}")]
    ParseInt(#[from] ParseIntError),
    /// The output of `rustup show active-toolchain` gave an invalid response
    #[error("rustup show active-toolchain gave invalid response")]
    RustupInvalidForm,
}

/// Information about the system this is being compiled to
///
/// This is used for the purpose of determining conditional
/// compilation
#[derive(Default, Debug)]
pub struct SystemInfo {
    /// Key-value option set once with the target's CPU architecture. The value is
    /// similar to the first element of the platform's target triple, but not
    /// identical.
    ///
    /// Example values:
    ///
    /// * `"x86"`
    /// * `"x86_64"`
    /// * `"mips"`
    /// * `"powerpc"`
    /// * `"powerpc64"`
    /// * `"arm"`
    /// * `"aarch64"`
    pub target_arch: String,
    /// Key-value option set for each platform feature available for the current
    /// compilation target.
    ///
    /// Example values:
    ///
    /// * `"avx"`
    /// * `"avx2"`
    /// * `"crt-static"`
    /// * `"rdrand"`
    /// * `"sse"`
    /// * `"sse2"`
    /// * `"sse4.1"`
    ///
    /// See the [`target_feature` attribute](https://doc.rust-lang.org/reference/attributes/codegen.html#the-target_feature-attribute)
    /// for more details on the available
    /// features. An additional feature of `crt-static` is available to the
    /// `target_feature` option to indicate that a [static C runtime](https://doc.rust-lang.org/reference/linkage.html#static-and-dynamic-c-runtimes) is available.
    pub target_features: Vec<String>,
    /// Key-value option set once with the target's operating system. This value is
    /// similar to the second and third element of the platform's target triple.
    ///
    /// Example values:
    ///
    /// * `"windows"`
    /// * `"macos"`
    /// * `"ios"`
    /// * `"linux"`
    /// * `"android"`
    /// * `"freebsd"`
    /// * `"dragonfly"`
    /// * `"openbsd"`
    /// * `"netbsd"`
    pub target_os: String,
    /// Key-value option providing a more generic description of a target, such as the family of the
    /// operating systems or architectures that the target generally falls into. Any number of
    /// `target_family` key-value pairs can be set.
    ///
    /// Example values:
    ///
    /// * `"unix"`
    /// * `"windows"`
    /// * `"wasm"`
    pub target_family: Vec<String>,
    /// Key-value option set with further disambiguating information about the target
    /// platform with information about the ABI or `libc` used. For historical reasons,
    /// this value is only defined as not the empty-string when actually needed for
    /// disambiguation. Thus, for example, on many GNU platforms, this value will be
    /// empty. This value is similar to the fourth element of the platform's target
    /// triple. One difference is that embedded ABIs such as `gnueabihf` will simply
    /// define `target_env` as `"gnu"`.
    ///
    /// Example values:
    ///
    /// * `""`
    /// * `"gnu"`
    /// * `"msvc"`
    /// * `"musl"`
    /// * `"sgx"`
    pub target_env: String,
    /// Key-value option set once with either a value of [`Endian::Little`] or [`Endian::Big`]
    ///on the endianness of the target's CPU.
    pub target_endian: Endian,
    /// Key-value option set once with the target's pointer width in bits.
    ///
    /// Example values:
    ///
    /// * `"16"`
    /// * `"32"`
    /// * `"64"`
    pub target_pointer_width: usize,
    /// Key-value option set once with the vendor of the target.
    ///
    /// Example values:
    ///
    /// * `"apple"`
    /// * `"fortanix"`
    /// * `"pc"`
    /// * `"unknown"`
    pub target_vendor: String,
    /// Enabled when compiling the test harness. Done with `rustc` by using the
    /// `--test` flag.
    pub test: bool,
    /// Enabled by default when compiling without optimizations.
    /// This can be used to enable extra debugging code in development but not in
    /// production.  For example, it controls the behavior of the standard library's
    /// [`debug_assert!`](std::debug_assert) macro.
    pub debug_assertions: bool,
    /// Set when the crate being compiled is being compiled with the `proc_macro`
    /// crate type.
    pub proc_macro: bool,
    /// Set when the crate being compiled is being compiled with the `proc_macro`
    /// [crate type].
    pub panic: String,
}

impl SystemInfo {
    /// Creates a new [`SystemInfo`] from the default target (from rustup)
    pub fn new_from_rustup() -> Result<SystemInfo, PlatformError> {
        // Use rustup to get the information in the form:
        //
        // {stable|nightly}-{triple} (default)
        let output = Command::new("rustup")
            .args(["show", "active-toolchain"])
            .output()?;
        let output = String::from_utf8(output.stdout)?;

        // Get the active toolchain (remove the (default))
        let toolchain = output
            .split_whitespace()
            .next()
            .ok_or(PlatformError::RustupInvalidForm)?;

        // Gather the triple and parse it
        match toolchain.split_once('-') {
            Some((_release, triple)) => Self::new_from_triple(triple),
            None => Err(PlatformError::RustupInvalidForm),
        }
    }
    /// Evokes rustc to find information about the triple
    pub fn new_from_triple(triple: &str) -> Result<SystemInfo, PlatformError> {
        // Use rustc to gather information about the triple
        let output = Command::new("rustc")
            .args(["--print=cfg", "--target", triple])
            .output()?;
        let output = String::from_utf8(output.stdout)?;

        // Create a value of self with default values
        let mut this = Self::default();

        // Parse the lines
        for line in output.lines() {
            match line.split_once(' ') {
                Some((var, val)) => match var {
                    "target_arch" => this.target_arch = val.to_owned(),
                    "target_features" => this.target_features.push(val.to_owned()),
                    "target_os" => this.target_os = val.to_owned(),
                    "target_family" => this.target_features.push(val.to_owned()),
                    "target_env" => this.target_env = val.to_owned(),
                    "target_endian" => this.target_endian = val.parse()?,
                    "target_pointer_width" => this.target_pointer_width = val.parse()?,
                    "target_vendor" => this.target_vendor = val.to_owned(),
                    "panic" => this.panic = val.to_owned(),
                    _ => (),
                },
                None => match line {
                    "test" => this.test = true,
                    "debug_assertions" => this.debug_assertions = true,
                    "proc_macro" => this.proc_macro = true,
                    _ => (),
                },
            }
        }

        Ok(this)
    }

    /// Returns [`true`] if this is a unixlike
    pub fn unix(&self) -> bool {
        self.target_family.iter().any(|x| x == "unix")
    }

    /// Returns [`true`] if this is like windows
    pub fn windows(&self) -> bool {
        self.target_family.iter().any(|x| x == "windows")
    }
}

/// The CPU's endianness
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Default)]
pub enum Endian {
    /// Big endian
    #[default]
    Big,
    /// Little endian
    Little,
}

impl FromStr for Endian {
    type Err = PlatformError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "little" => Ok(Endian::Little),
            "big" => Ok(Endian::Big),
            s => Err(PlatformError::InvalidEndian(s.to_owned())),
        }
    }
}

impl Display for Endian {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Endian::Big => write!(f, "big"),
            Endian::Little => write!(f, "little"),
        }
    }
}