|  | // Copyright 2016 rustc-version-rs developers | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | 
|  | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | 
|  | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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 | 
|  | //! | 
|  | //! extern crate rustc_version; | 
|  | //! use rustc_version::{version, version_meta, Channel, Version}; | 
|  | //! | 
|  | //! fn main() { | 
|  | //!     // 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"); | 
|  | //!     } | 
|  | //! } | 
|  | //! ``` | 
|  |  | 
|  | extern crate semver; | 
|  | use semver::Identifier; | 
|  | use std::process::Command; | 
|  | use std::{env, str}; | 
|  | use std::ffi::OsString; | 
|  |  | 
|  | // Convenience re-export to allow version comparison without needing to add | 
|  | // semver crate. | 
|  | pub use semver::Version; | 
|  |  | 
|  | mod errors; | 
|  | pub use errors::{Error, Result}; | 
|  |  | 
|  | /// 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, | 
|  | } | 
|  |  | 
|  | /// Rustc version plus metada 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<String>, | 
|  |  | 
|  | /// Commit date of the compiler | 
|  | pub commit_date: Option<String>, | 
|  |  | 
|  | /// Build date of the compiler; this was removed between Rust 1.0.0 and 1.1.0. | 
|  | pub build_date: Option<String>, | 
|  |  | 
|  | /// 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, | 
|  | } | 
|  |  | 
|  | impl VersionMeta { | 
|  | /// Returns the version metadata for `cmd`, which should be a `rustc` command. | 
|  | pub fn for_command(cmd: Command) -> Result<VersionMeta> { | 
|  | let mut cmd = cmd; | 
|  |  | 
|  | let out = cmd.arg("-vV").output().map_err(Error::CouldNotExecuteCommand)?; | 
|  | let out = str::from_utf8(&out.stdout)?; | 
|  |  | 
|  | version_meta_for(out) | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Returns the `rustc` SemVer version. | 
|  | pub fn version() -> Result<Version> { | 
|  | 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<VersionMeta> { | 
|  | 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<VersionMeta> { | 
|  | let out: Vec<_> = verbose_version_string.lines().collect(); | 
|  |  | 
|  | if !(out.len() >= 6 && out.len() <= 8) { | 
|  | return Err(Error::UnexpectedVersionFormat); | 
|  | } | 
|  |  | 
|  | let short_version_string = out[0]; | 
|  |  | 
|  | fn expect_prefix<'a>(line: &'a str, prefix: &str) -> Result<&'a str> { | 
|  | if line.starts_with(prefix) { | 
|  | Ok(&line[prefix.len()..]) | 
|  | } else { | 
|  | Err(Error::UnexpectedVersionFormat) | 
|  | } | 
|  | } | 
|  |  | 
|  | let commit_hash = match expect_prefix(out[2], "commit-hash: ")? { | 
|  | "unknown" => None, | 
|  | hash => Some(hash.to_owned()), | 
|  | }; | 
|  |  | 
|  | let commit_date = match expect_prefix(out[3], "commit-date: ")? { | 
|  | "unknown" => None, | 
|  | hash => Some(hash.to_owned()), | 
|  | }; | 
|  |  | 
|  | // Handle that the build date may or may not be present. | 
|  | let mut idx = 4; | 
|  | let mut build_date = None; | 
|  | if out[idx].starts_with("build-date") { | 
|  | build_date = match expect_prefix(out[idx], "build-date: ")? { | 
|  | "unknown" => None, | 
|  | s => Some(s.to_owned()), | 
|  | }; | 
|  | idx += 1; | 
|  | } | 
|  |  | 
|  | let host = expect_prefix(out[idx], "host: ")?; | 
|  | idx += 1; | 
|  | let release = expect_prefix(out[idx], "release: ")?; | 
|  |  | 
|  | let semver: Version = release.parse()?; | 
|  |  | 
|  | let channel = if semver.pre.is_empty() { | 
|  | Channel::Stable | 
|  | } else { | 
|  | match semver.pre[0] { | 
|  | Identifier::AlphaNumeric(ref s) if s == "dev" => Channel::Dev, | 
|  | Identifier::AlphaNumeric(ref s) if s == "beta" => Channel::Beta, | 
|  | Identifier::AlphaNumeric(ref s) if s == "nightly" => Channel::Nightly, | 
|  | ref x => return Err(Error::UnknownPreReleaseTag(x.clone())), | 
|  | } | 
|  | }; | 
|  |  | 
|  | Ok(VersionMeta { | 
|  | semver: semver, | 
|  | commit_hash: commit_hash, | 
|  | commit_date: commit_date, | 
|  | build_date: build_date, | 
|  | channel: channel, | 
|  | host: host.into(), | 
|  | short_version_string: short_version_string.into(), | 
|  | }) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn smoketest() { | 
|  | let v = version().unwrap(); | 
|  | assert!(v.major >= 1); | 
|  |  | 
|  | let v = version_meta().unwrap(); | 
|  | assert!(v.semver.major >= 1); | 
|  |  | 
|  | assert!(version().unwrap() >= Version::parse("1.0.0").unwrap()); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn parse_unexpected() { | 
|  | let res = version_meta_for( | 
|  | "rustc 1.0.0 (a59de37e9 2015-05-13) (built 2015-05-14) | 
|  | binary: rustc | 
|  | commit-hash: a59de37e99060162a2674e3ff45409ac73595c0e | 
|  | commit-date: 2015-05-13 | 
|  | rust-birthday: 2015-05-14 | 
|  | host: x86_64-unknown-linux-gnu | 
|  | release: 1.0.0"); | 
|  |  | 
|  | assert!(match res { | 
|  | Err(Error::UnexpectedVersionFormat) => true, | 
|  | _ => false, | 
|  | }); | 
|  |  | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn parse_1_0_0() { | 
|  | let version = version_meta_for( | 
|  | "rustc 1.0.0 (a59de37e9 2015-05-13) (built 2015-05-14) | 
|  | binary: rustc | 
|  | commit-hash: a59de37e99060162a2674e3ff45409ac73595c0e | 
|  | commit-date: 2015-05-13 | 
|  | build-date: 2015-05-14 | 
|  | host: x86_64-unknown-linux-gnu | 
|  | release: 1.0.0").unwrap(); | 
|  |  | 
|  | assert_eq!(version.semver, Version::parse("1.0.0").unwrap()); | 
|  | assert_eq!(version.commit_hash, Some("a59de37e99060162a2674e3ff45409ac73595c0e".into())); | 
|  | assert_eq!(version.commit_date, Some("2015-05-13".into())); | 
|  | assert_eq!(version.build_date, Some("2015-05-14".into())); | 
|  | assert_eq!(version.channel, Channel::Stable); | 
|  | assert_eq!(version.host, "x86_64-unknown-linux-gnu"); | 
|  | assert_eq!(version.short_version_string, "rustc 1.0.0 (a59de37e9 2015-05-13) (built 2015-05-14)"); | 
|  | } | 
|  |  | 
|  |  | 
|  | #[test] | 
|  | fn parse_unknown() { | 
|  | let version = version_meta_for( | 
|  | "rustc 1.3.0 | 
|  | binary: rustc | 
|  | commit-hash: unknown | 
|  | commit-date: unknown | 
|  | host: x86_64-unknown-linux-gnu | 
|  | release: 1.3.0").unwrap(); | 
|  |  | 
|  | assert_eq!(version.semver, Version::parse("1.3.0").unwrap()); | 
|  | assert_eq!(version.commit_hash, None); | 
|  | assert_eq!(version.commit_date, None); | 
|  | assert_eq!(version.channel, Channel::Stable); | 
|  | assert_eq!(version.host, "x86_64-unknown-linux-gnu"); | 
|  | assert_eq!(version.short_version_string, "rustc 1.3.0"); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn parse_nightly() { | 
|  | let version = version_meta_for( | 
|  | "rustc 1.5.0-nightly (65d5c0833 2015-09-29) | 
|  | binary: rustc | 
|  | commit-hash: 65d5c083377645a115c4ac23a620d3581b9562b6 | 
|  | commit-date: 2015-09-29 | 
|  | host: x86_64-unknown-linux-gnu | 
|  | release: 1.5.0-nightly").unwrap(); | 
|  |  | 
|  | assert_eq!(version.semver, Version::parse("1.5.0-nightly").unwrap()); | 
|  | assert_eq!(version.commit_hash, Some("65d5c083377645a115c4ac23a620d3581b9562b6".into())); | 
|  | assert_eq!(version.commit_date, Some("2015-09-29".into())); | 
|  | assert_eq!(version.channel, Channel::Nightly); | 
|  | assert_eq!(version.host, "x86_64-unknown-linux-gnu"); | 
|  | assert_eq!(version.short_version_string, "rustc 1.5.0-nightly (65d5c0833 2015-09-29)"); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn parse_stable() { | 
|  | let version = version_meta_for( | 
|  | "rustc 1.3.0 (9a92aaf19 2015-09-15) | 
|  | binary: rustc | 
|  | commit-hash: 9a92aaf19a64603b02b4130fe52958cc12488900 | 
|  | commit-date: 2015-09-15 | 
|  | host: x86_64-unknown-linux-gnu | 
|  | release: 1.3.0").unwrap(); | 
|  |  | 
|  | assert_eq!(version.semver, Version::parse("1.3.0").unwrap()); | 
|  | assert_eq!(version.commit_hash, Some("9a92aaf19a64603b02b4130fe52958cc12488900".into())); | 
|  | assert_eq!(version.commit_date, Some("2015-09-15".into())); | 
|  | assert_eq!(version.channel, Channel::Stable); | 
|  | assert_eq!(version.host, "x86_64-unknown-linux-gnu"); | 
|  | assert_eq!(version.short_version_string, "rustc 1.3.0 (9a92aaf19 2015-09-15)"); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn parse_1_16_0_nightly() { | 
|  | let version = version_meta_for( | 
|  | "rustc 1.16.0-nightly (5d994d8b7 2017-01-05) | 
|  | binary: rustc | 
|  | commit-hash: 5d994d8b7e482e87467d4a521911477bd8284ce3 | 
|  | commit-date: 2017-01-05 | 
|  | host: x86_64-unknown-linux-gnu | 
|  | release: 1.16.0-nightly | 
|  | LLVM version: 3.9").unwrap(); | 
|  |  | 
|  | assert_eq!(version.semver, Version::parse("1.16.0-nightly").unwrap()); | 
|  | assert_eq!(version.commit_hash, Some("5d994d8b7e482e87467d4a521911477bd8284ce3".into())); | 
|  | assert_eq!(version.commit_date, Some("2017-01-05".into())); | 
|  | assert_eq!(version.channel, Channel::Nightly); | 
|  | assert_eq!(version.host, "x86_64-unknown-linux-gnu"); | 
|  | assert_eq!(version.short_version_string, "rustc 1.16.0-nightly (5d994d8b7 2017-01-05)"); | 
|  | } | 
|  |  | 
|  | /* | 
|  | #[test] | 
|  | fn version_matches_replacement() { | 
|  | let f = |s1: &str, s2: &str| { | 
|  | let a = Version::parse(s1).unwrap(); | 
|  | let b = Version::parse(s2).unwrap(); | 
|  | println!("{} <= {} : {}", s1, s2, a <= b); | 
|  | }; | 
|  |  | 
|  | println!(); | 
|  |  | 
|  | f("1.5.0",         "1.5.0"); | 
|  | f("1.5.0-nightly", "1.5.0"); | 
|  | f("1.5.0",         "1.5.0-nightly"); | 
|  | f("1.5.0-nightly", "1.5.0-nightly"); | 
|  |  | 
|  | f("1.5.0",         "1.6.0"); | 
|  | f("1.5.0-nightly", "1.6.0"); | 
|  | f("1.5.0",         "1.6.0-nightly"); | 
|  | f("1.5.0-nightly", "1.6.0-nightly"); | 
|  |  | 
|  | panic!(); | 
|  |  | 
|  | } | 
|  | */ |