// Copyright 2016 rustc-version-rs developers // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. #![warn(missing_docs)] //! Simple library for getting the version information of a `rustc` //! compiler. //! //! This can be used by build scripts or other tools dealing with Rust sources //! to make decisions based on the version of the compiler. //! //! It calls `$RUSTC --version -v` and parses the output, falling //! back to `rustc` if `$RUSTC` is not set. //! //! # Example //! //! ```rust //! // This could be a cargo build script //! //! use rustc_version::{version, version_meta, Channel, Version}; //! //! // Assert we haven't travelled back in time //! assert!(version().unwrap().major >= 1); //! //! // Set cfg flags depending on release channel //! match version_meta().unwrap().channel { //! Channel::Stable => { //! println!("cargo:rustc-cfg=RUSTC_IS_STABLE"); //! } //! Channel::Beta => { //! println!("cargo:rustc-cfg=RUSTC_IS_BETA"); //! } //! Channel::Nightly => { //! println!("cargo:rustc-cfg=RUSTC_IS_NIGHTLY"); //! } //! Channel::Dev => { //! println!("cargo:rustc-cfg=RUSTC_IS_DEV"); //! } //! } //! //! // Check for a minimum version //! if version().unwrap() >= Version::parse("1.4.0").unwrap() { //! println!("cargo:rustc-cfg=compiler_has_important_bugfix"); //! } //! ``` #[cfg(test)] #[macro_use] extern crate doc_comment; #[cfg(test)] doctest!("../README.md"); use std::collections::HashMap; use std::process::Command; use std::{env, error, fmt, io, num, str}; use std::{ffi::OsString, str::FromStr}; // Convenience re-export to allow version comparison without needing to add // semver crate. pub use semver::Version; use Error::*; /// Release channel of the compiler. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub enum Channel { /// Development release channel Dev, /// Nightly release channel Nightly, /// Beta release channel Beta, /// Stable release channel Stable, } /// LLVM version /// /// LLVM's version numbering scheme is not semver compatible until version 4.0 /// /// rustc [just prints the major and minor versions], so other parts of the version are not included. /// /// [just prints the major and minor versions]: https://github.com/rust-lang/rust/blob/b5c9e2448c9ace53ad5c11585803894651b18b0a/compiler/rustc_codegen_llvm/src/llvm_util.rs#L173-L178 #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct LlvmVersion { // fields must be ordered major, minor for comparison to be correct /// Major version pub major: u64, /// Minor version pub minor: u64, // TODO: expose micro version here } impl fmt::Display for LlvmVersion { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}.{}", self.major, self.minor) } } impl FromStr for LlvmVersion { type Err = LlvmVersionParseError; fn from_str(s: &str) -> Result { let mut parts = s .split('.') .map(|part| -> Result { if part == "0" { Ok(0) } else if part.starts_with('0') { Err(LlvmVersionParseError::ComponentMustNotHaveLeadingZeros) } else if part.starts_with('-') || part.starts_with('+') { Err(LlvmVersionParseError::ComponentMustNotHaveSign) } else { Ok(part.parse()?) } }); let major = parts.next().unwrap()?; let mut minor = 0; if let Some(part) = parts.next() { minor = part?; } else if major < 4 { // LLVM versions earlier than 4.0 have significant minor versions, so require the minor version in this case. return Err(LlvmVersionParseError::MinorVersionRequiredBefore4); } if let Some(Err(e)) = parts.next() { return Err(e); } if parts.next().is_some() { return Err(LlvmVersionParseError::TooManyComponents); } Ok(Self { major, minor }) } } /// Rustc version plus metadata like git short hash and build date. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct VersionMeta { /// Version of the compiler pub semver: Version, /// Git short hash of the build of the compiler pub commit_hash: Option, /// Commit date of the compiler pub commit_date: Option, /// Build date of the compiler; this was removed between Rust 1.0.0 and 1.1.0. pub build_date: Option, /// Release channel of the compiler pub channel: Channel, /// Host target triple of the compiler pub host: String, /// Short version string of the compiler pub short_version_string: String, /// Version of LLVM used by the compiler pub llvm_version: Option, } impl VersionMeta { /// Returns the version metadata for `cmd`, which should be a `rustc` command. pub fn for_command(mut cmd: Command) -> Result { let out = cmd .arg("-vV") .output() .map_err(Error::CouldNotExecuteCommand)?; if !out.status.success() { return Err(Error::CommandError { stdout: String::from_utf8_lossy(&out.stdout).into(), stderr: String::from_utf8_lossy(&out.stderr).into(), }); } version_meta_for(str::from_utf8(&out.stdout)?) } } /// Returns the `rustc` SemVer version. pub fn version() -> Result { Ok(version_meta()?.semver) } /// Returns the `rustc` SemVer version and additional metadata /// like the git short hash and build date. pub fn version_meta() -> Result { let cmd = env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc")); VersionMeta::for_command(Command::new(cmd)) } /// Parses a "rustc -vV" output string and returns /// the SemVer version and additional metadata /// like the git short hash and build date. pub fn version_meta_for(verbose_version_string: &str) -> Result { let mut map = HashMap::new(); for (i, line) in verbose_version_string.lines().enumerate() { if i == 0 { map.insert("short", line); continue; } let mut parts = line.splitn(2, ": "); let key = match parts.next() { Some(key) => key, None => continue, }; if let Some(value) = parts.next() { map.insert(key, value); } } let short_version_string = expect_key("short", &map)?; let host = expect_key("host", &map)?; let release = expect_key("release", &map)?; let semver: Version = release.parse()?; let channel = match semver.pre.split('.').next().unwrap() { "" => Channel::Stable, "dev" => Channel::Dev, "beta" => Channel::Beta, "nightly" => Channel::Nightly, x => return Err(Error::UnknownPreReleaseTag(x.to_owned())), }; let commit_hash = expect_key_or_unknown("commit-hash", &map)?; let commit_date = expect_key_or_unknown("commit-date", &map)?; let build_date = map .get("build-date") .filter(|&v| *v != "unknown") .map(|&v| String::from(v)); let llvm_version = match map.get("LLVM version") { Some(&v) => Some(v.parse()?), None => None, }; Ok(VersionMeta { semver, commit_hash, commit_date, build_date, channel, host, short_version_string, llvm_version, }) } fn expect_key_or_unknown(key: &str, map: &HashMap<&str, &str>) -> Result, Error> { match map.get(key) { Some(&v) if v == "unknown" => Ok(None), Some(&v) => Ok(Some(String::from(v))), None => Err(Error::UnexpectedVersionFormat), } } fn expect_key(key: &str, map: &HashMap<&str, &str>) -> Result { map.get(key) .map(|&v| String::from(v)) .ok_or(Error::UnexpectedVersionFormat) } /// LLVM Version Parse Error #[derive(Debug)] pub enum LlvmVersionParseError { /// An error occurred in parsing a version component as an integer ParseIntError(num::ParseIntError), /// A version component must not have leading zeros ComponentMustNotHaveLeadingZeros, /// A version component has a sign ComponentMustNotHaveSign, /// Minor version component must be zero on LLVM versions later than 4.0 MinorVersionMustBeZeroAfter4, /// Minor version component is required on LLVM versions earlier than 4.0 MinorVersionRequiredBefore4, /// Too many components TooManyComponents, } impl From for LlvmVersionParseError { fn from(e: num::ParseIntError) -> Self { LlvmVersionParseError::ParseIntError(e) } } impl fmt::Display for LlvmVersionParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { LlvmVersionParseError::ParseIntError(e) => { write!(f, "error parsing LLVM version component: {}", e) } LlvmVersionParseError::ComponentMustNotHaveLeadingZeros => { write!(f, "a version component must not have leading zeros") } LlvmVersionParseError::ComponentMustNotHaveSign => { write!(f, "a version component must not have a sign") } LlvmVersionParseError::MinorVersionMustBeZeroAfter4 => write!( f, "LLVM's minor version component must be 0 for versions greater than 4.0" ), LlvmVersionParseError::MinorVersionRequiredBefore4 => write!( f, "LLVM's minor version component is required for versions less than 4.0" ), LlvmVersionParseError::TooManyComponents => write!(f, "too many version components"), } } } impl error::Error for LlvmVersionParseError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { LlvmVersionParseError::ParseIntError(e) => Some(e), LlvmVersionParseError::ComponentMustNotHaveLeadingZeros | LlvmVersionParseError::ComponentMustNotHaveSign | LlvmVersionParseError::MinorVersionMustBeZeroAfter4 | LlvmVersionParseError::MinorVersionRequiredBefore4 | LlvmVersionParseError::TooManyComponents => None, } } } /// The error type for this crate. #[derive(Debug)] pub enum Error { /// An error occurred while trying to find the `rustc` to run. CouldNotExecuteCommand(io::Error), /// Error output from the command that was run. CommandError { /// stdout output from the command stdout: String, /// stderr output from the command stderr: String, }, /// The output of `rustc -vV` was not valid utf-8. Utf8Error(str::Utf8Error), /// The output of `rustc -vV` was not in the expected format. UnexpectedVersionFormat, /// An error occurred in parsing the semver. SemVerError(semver::Error), /// The pre-release tag is unknown. UnknownPreReleaseTag(String), /// An error occurred in parsing a `LlvmVersion`. LlvmVersionError(LlvmVersionParseError), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { CouldNotExecuteCommand(ref e) => write!(f, "could not execute command: {}", e), CommandError { ref stdout, ref stderr, } => write!( f, "error from command -- stderr:\n\n{}\n\nstderr:\n\n{}", stderr, stdout, ), Utf8Error(_) => write!(f, "invalid UTF-8 output from `rustc -vV`"), UnexpectedVersionFormat => write!(f, "unexpected `rustc -vV` format"), SemVerError(ref e) => write!(f, "error parsing version: {}", e), UnknownPreReleaseTag(ref i) => write!(f, "unknown pre-release tag: {}", i), LlvmVersionError(ref e) => write!(f, "error parsing LLVM's version: {}", e), } } } impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match *self { CouldNotExecuteCommand(ref e) => Some(e), CommandError { .. } => None, Utf8Error(ref e) => Some(e), UnexpectedVersionFormat => None, SemVerError(ref e) => Some(e), UnknownPreReleaseTag(_) => None, LlvmVersionError(ref e) => Some(e), } } } macro_rules! impl_from { ($($err_ty:ty => $variant:ident),* $(,)*) => { $( impl From<$err_ty> for Error { fn from(e: $err_ty) -> Error { Error::$variant(e) } } )* } } impl_from! { str::Utf8Error => Utf8Error, semver::Error => SemVerError, LlvmVersionParseError => LlvmVersionError, } /// The result type for this crate. pub type Result = std::result::Result;