| 1 | // Copyright 2016 rustc-version-rs developers |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| 4 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| 5 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
| 6 | // option. This file may not be copied, modified, or distributed |
| 7 | // except according to those terms. |
| 8 | |
| 9 | #![warn (missing_docs)] |
| 10 | |
| 11 | //! Simple library for getting the version information of a `rustc` |
| 12 | //! compiler. |
| 13 | //! |
| 14 | //! This can be used by build scripts or other tools dealing with Rust sources |
| 15 | //! to make decisions based on the version of the compiler. |
| 16 | //! |
| 17 | //! It calls `$RUSTC --version -v` and parses the output, falling |
| 18 | //! back to `rustc` if `$RUSTC` is not set. |
| 19 | //! |
| 20 | //! # Example |
| 21 | //! |
| 22 | //! ```rust |
| 23 | //! // This could be a cargo build script |
| 24 | //! |
| 25 | //! extern crate rustc_version; |
| 26 | //! use rustc_version::{version, version_meta, Channel, Version}; |
| 27 | //! |
| 28 | //! fn main() { |
| 29 | //! // Assert we haven't travelled back in time |
| 30 | //! assert!(version().unwrap().major >= 1); |
| 31 | //! |
| 32 | //! // Set cfg flags depending on release channel |
| 33 | //! match version_meta().unwrap().channel { |
| 34 | //! Channel::Stable => { |
| 35 | //! println!("cargo:rustc-cfg=RUSTC_IS_STABLE" ); |
| 36 | //! } |
| 37 | //! Channel::Beta => { |
| 38 | //! println!("cargo:rustc-cfg=RUSTC_IS_BETA" ); |
| 39 | //! } |
| 40 | //! Channel::Nightly => { |
| 41 | //! println!("cargo:rustc-cfg=RUSTC_IS_NIGHTLY" ); |
| 42 | //! } |
| 43 | //! Channel::Dev => { |
| 44 | //! println!("cargo:rustc-cfg=RUSTC_IS_DEV" ); |
| 45 | //! } |
| 46 | //! } |
| 47 | //! |
| 48 | //! // Check for a minimum version |
| 49 | //! if version().unwrap() >= Version::parse("1.4.0" ).unwrap() { |
| 50 | //! println!("cargo:rustc-cfg=compiler_has_important_bugfix" ); |
| 51 | //! } |
| 52 | //! } |
| 53 | //! ``` |
| 54 | |
| 55 | extern crate semver; |
| 56 | use semver::Identifier; |
| 57 | use std::process::Command; |
| 58 | use std::{env, str}; |
| 59 | use std::ffi::OsString; |
| 60 | |
| 61 | // Convenience re-export to allow version comparison without needing to add |
| 62 | // semver crate. |
| 63 | pub use semver::Version; |
| 64 | |
| 65 | mod errors; |
| 66 | pub use errors::{Error, Result}; |
| 67 | |
| 68 | /// Release channel of the compiler. |
| 69 | #[derive (Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] |
| 70 | pub enum Channel { |
| 71 | /// Development release channel |
| 72 | Dev, |
| 73 | /// Nightly release channel |
| 74 | Nightly, |
| 75 | /// Beta release channel |
| 76 | Beta, |
| 77 | /// Stable release channel |
| 78 | Stable, |
| 79 | } |
| 80 | |
| 81 | /// Rustc version plus metada like git short hash and build date. |
| 82 | #[derive (Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] |
| 83 | pub struct VersionMeta { |
| 84 | /// Version of the compiler |
| 85 | pub semver: Version, |
| 86 | |
| 87 | /// Git short hash of the build of the compiler |
| 88 | pub commit_hash: Option<String>, |
| 89 | |
| 90 | /// Commit date of the compiler |
| 91 | pub commit_date: Option<String>, |
| 92 | |
| 93 | /// Build date of the compiler; this was removed between Rust 1.0.0 and 1.1.0. |
| 94 | pub build_date: Option<String>, |
| 95 | |
| 96 | /// Release channel of the compiler |
| 97 | pub channel: Channel, |
| 98 | |
| 99 | /// Host target triple of the compiler |
| 100 | pub host: String, |
| 101 | |
| 102 | /// Short version string of the compiler |
| 103 | pub short_version_string: String, |
| 104 | } |
| 105 | |
| 106 | impl VersionMeta { |
| 107 | /// Returns the version metadata for `cmd`, which should be a `rustc` command. |
| 108 | pub fn for_command(cmd: Command) -> Result<VersionMeta> { |
| 109 | let mut cmd: Command = cmd; |
| 110 | |
| 111 | let out: Output = cmd.arg("-vV" ).output().map_err(op:Error::CouldNotExecuteCommand)?; |
| 112 | let out: &str = str::from_utf8(&out.stdout)?; |
| 113 | |
| 114 | version_meta_for(verbose_version_string:out) |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | /// Returns the `rustc` SemVer version. |
| 119 | pub fn version() -> Result<Version> { |
| 120 | Ok(version_meta()?.semver) |
| 121 | } |
| 122 | |
| 123 | /// Returns the `rustc` SemVer version and additional metadata |
| 124 | /// like the git short hash and build date. |
| 125 | pub fn version_meta() -> Result<VersionMeta> { |
| 126 | let cmd: OsString = env::var_os(key:"RUSTC" ).unwrap_or_else(|| OsString::from("rustc" )); |
| 127 | |
| 128 | VersionMeta::for_command(cmd:Command::new(program:cmd)) |
| 129 | } |
| 130 | |
| 131 | /// Parses a "rustc -vV" output string and returns |
| 132 | /// the SemVer version and additional metadata |
| 133 | /// like the git short hash and build date. |
| 134 | pub fn version_meta_for(verbose_version_string: &str) -> Result<VersionMeta> { |
| 135 | let out: Vec<_> = verbose_version_string.lines().collect(); |
| 136 | |
| 137 | if !(out.len() >= 6 && out.len() <= 8) { |
| 138 | return Err(Error::UnexpectedVersionFormat); |
| 139 | } |
| 140 | |
| 141 | let short_version_string = out[0]; |
| 142 | |
| 143 | fn expect_prefix<'a>(line: &'a str, prefix: &str) -> Result<&'a str> { |
| 144 | if line.starts_with(prefix) { |
| 145 | Ok(&line[prefix.len()..]) |
| 146 | } else { |
| 147 | Err(Error::UnexpectedVersionFormat) |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | let commit_hash = match expect_prefix(out[2], "commit-hash: " )? { |
| 152 | "unknown" => None, |
| 153 | hash => Some(hash.to_owned()), |
| 154 | }; |
| 155 | |
| 156 | let commit_date = match expect_prefix(out[3], "commit-date: " )? { |
| 157 | "unknown" => None, |
| 158 | hash => Some(hash.to_owned()), |
| 159 | }; |
| 160 | |
| 161 | // Handle that the build date may or may not be present. |
| 162 | let mut idx = 4; |
| 163 | let mut build_date = None; |
| 164 | if out[idx].starts_with("build-date" ) { |
| 165 | build_date = match expect_prefix(out[idx], "build-date: " )? { |
| 166 | "unknown" => None, |
| 167 | s => Some(s.to_owned()), |
| 168 | }; |
| 169 | idx += 1; |
| 170 | } |
| 171 | |
| 172 | let host = expect_prefix(out[idx], "host: " )?; |
| 173 | idx += 1; |
| 174 | let release = expect_prefix(out[idx], "release: " )?; |
| 175 | |
| 176 | let semver: Version = release.parse()?; |
| 177 | |
| 178 | let channel = if semver.pre.is_empty() { |
| 179 | Channel::Stable |
| 180 | } else { |
| 181 | match semver.pre[0] { |
| 182 | Identifier::AlphaNumeric(ref s) if s == "dev" => Channel::Dev, |
| 183 | Identifier::AlphaNumeric(ref s) if s == "beta" => Channel::Beta, |
| 184 | Identifier::AlphaNumeric(ref s) if s == "nightly" => Channel::Nightly, |
| 185 | ref x => return Err(Error::UnknownPreReleaseTag(x.clone())), |
| 186 | } |
| 187 | }; |
| 188 | |
| 189 | Ok(VersionMeta { |
| 190 | semver: semver, |
| 191 | commit_hash: commit_hash, |
| 192 | commit_date: commit_date, |
| 193 | build_date: build_date, |
| 194 | channel: channel, |
| 195 | host: host.into(), |
| 196 | short_version_string: short_version_string.into(), |
| 197 | }) |
| 198 | } |
| 199 | |
| 200 | #[test ] |
| 201 | fn smoketest() { |
| 202 | let v = version().unwrap(); |
| 203 | assert!(v.major >= 1); |
| 204 | |
| 205 | let v = version_meta().unwrap(); |
| 206 | assert!(v.semver.major >= 1); |
| 207 | |
| 208 | assert!(version().unwrap() >= Version::parse("1.0.0" ).unwrap()); |
| 209 | } |
| 210 | |
| 211 | #[test ] |
| 212 | fn parse_unexpected() { |
| 213 | let res = version_meta_for( |
| 214 | "rustc 1.0.0 (a59de37e9 2015-05-13) (built 2015-05-14) |
| 215 | binary: rustc |
| 216 | commit-hash: a59de37e99060162a2674e3ff45409ac73595c0e |
| 217 | commit-date: 2015-05-13 |
| 218 | rust-birthday: 2015-05-14 |
| 219 | host: x86_64-unknown-linux-gnu |
| 220 | release: 1.0.0" ); |
| 221 | |
| 222 | assert!(match res { |
| 223 | Err(Error::UnexpectedVersionFormat) => true, |
| 224 | _ => false, |
| 225 | }); |
| 226 | |
| 227 | } |
| 228 | |
| 229 | #[test ] |
| 230 | fn parse_1_0_0() { |
| 231 | let version = version_meta_for( |
| 232 | "rustc 1.0.0 (a59de37e9 2015-05-13) (built 2015-05-14) |
| 233 | binary: rustc |
| 234 | commit-hash: a59de37e99060162a2674e3ff45409ac73595c0e |
| 235 | commit-date: 2015-05-13 |
| 236 | build-date: 2015-05-14 |
| 237 | host: x86_64-unknown-linux-gnu |
| 238 | release: 1.0.0" ).unwrap(); |
| 239 | |
| 240 | assert_eq!(version.semver, Version::parse("1.0.0" ).unwrap()); |
| 241 | assert_eq!(version.commit_hash, Some("a59de37e99060162a2674e3ff45409ac73595c0e" .into())); |
| 242 | assert_eq!(version.commit_date, Some("2015-05-13" .into())); |
| 243 | assert_eq!(version.build_date, Some("2015-05-14" .into())); |
| 244 | assert_eq!(version.channel, Channel::Stable); |
| 245 | assert_eq!(version.host, "x86_64-unknown-linux-gnu" ); |
| 246 | assert_eq!(version.short_version_string, "rustc 1.0.0 (a59de37e9 2015-05-13) (built 2015-05-14)" ); |
| 247 | } |
| 248 | |
| 249 | |
| 250 | #[test ] |
| 251 | fn parse_unknown() { |
| 252 | let version = version_meta_for( |
| 253 | "rustc 1.3.0 |
| 254 | binary: rustc |
| 255 | commit-hash: unknown |
| 256 | commit-date: unknown |
| 257 | host: x86_64-unknown-linux-gnu |
| 258 | release: 1.3.0" ).unwrap(); |
| 259 | |
| 260 | assert_eq!(version.semver, Version::parse("1.3.0" ).unwrap()); |
| 261 | assert_eq!(version.commit_hash, None); |
| 262 | assert_eq!(version.commit_date, None); |
| 263 | assert_eq!(version.channel, Channel::Stable); |
| 264 | assert_eq!(version.host, "x86_64-unknown-linux-gnu" ); |
| 265 | assert_eq!(version.short_version_string, "rustc 1.3.0" ); |
| 266 | } |
| 267 | |
| 268 | #[test ] |
| 269 | fn parse_nightly() { |
| 270 | let version = version_meta_for( |
| 271 | "rustc 1.5.0-nightly (65d5c0833 2015-09-29) |
| 272 | binary: rustc |
| 273 | commit-hash: 65d5c083377645a115c4ac23a620d3581b9562b6 |
| 274 | commit-date: 2015-09-29 |
| 275 | host: x86_64-unknown-linux-gnu |
| 276 | release: 1.5.0-nightly" ).unwrap(); |
| 277 | |
| 278 | assert_eq!(version.semver, Version::parse("1.5.0-nightly" ).unwrap()); |
| 279 | assert_eq!(version.commit_hash, Some("65d5c083377645a115c4ac23a620d3581b9562b6" .into())); |
| 280 | assert_eq!(version.commit_date, Some("2015-09-29" .into())); |
| 281 | assert_eq!(version.channel, Channel::Nightly); |
| 282 | assert_eq!(version.host, "x86_64-unknown-linux-gnu" ); |
| 283 | assert_eq!(version.short_version_string, "rustc 1.5.0-nightly (65d5c0833 2015-09-29)" ); |
| 284 | } |
| 285 | |
| 286 | #[test ] |
| 287 | fn parse_stable() { |
| 288 | let version = version_meta_for( |
| 289 | "rustc 1.3.0 (9a92aaf19 2015-09-15) |
| 290 | binary: rustc |
| 291 | commit-hash: 9a92aaf19a64603b02b4130fe52958cc12488900 |
| 292 | commit-date: 2015-09-15 |
| 293 | host: x86_64-unknown-linux-gnu |
| 294 | release: 1.3.0" ).unwrap(); |
| 295 | |
| 296 | assert_eq!(version.semver, Version::parse("1.3.0" ).unwrap()); |
| 297 | assert_eq!(version.commit_hash, Some("9a92aaf19a64603b02b4130fe52958cc12488900" .into())); |
| 298 | assert_eq!(version.commit_date, Some("2015-09-15" .into())); |
| 299 | assert_eq!(version.channel, Channel::Stable); |
| 300 | assert_eq!(version.host, "x86_64-unknown-linux-gnu" ); |
| 301 | assert_eq!(version.short_version_string, "rustc 1.3.0 (9a92aaf19 2015-09-15)" ); |
| 302 | } |
| 303 | |
| 304 | #[test ] |
| 305 | fn parse_1_16_0_nightly() { |
| 306 | let version = version_meta_for( |
| 307 | "rustc 1.16.0-nightly (5d994d8b7 2017-01-05) |
| 308 | binary: rustc |
| 309 | commit-hash: 5d994d8b7e482e87467d4a521911477bd8284ce3 |
| 310 | commit-date: 2017-01-05 |
| 311 | host: x86_64-unknown-linux-gnu |
| 312 | release: 1.16.0-nightly |
| 313 | LLVM version: 3.9" ).unwrap(); |
| 314 | |
| 315 | assert_eq!(version.semver, Version::parse("1.16.0-nightly" ).unwrap()); |
| 316 | assert_eq!(version.commit_hash, Some("5d994d8b7e482e87467d4a521911477bd8284ce3" .into())); |
| 317 | assert_eq!(version.commit_date, Some("2017-01-05" .into())); |
| 318 | assert_eq!(version.channel, Channel::Nightly); |
| 319 | assert_eq!(version.host, "x86_64-unknown-linux-gnu" ); |
| 320 | assert_eq!(version.short_version_string, "rustc 1.16.0-nightly (5d994d8b7 2017-01-05)" ); |
| 321 | } |
| 322 | |
| 323 | /* |
| 324 | #[test] |
| 325 | fn version_matches_replacement() { |
| 326 | let f = |s1: &str, s2: &str| { |
| 327 | let a = Version::parse(s1).unwrap(); |
| 328 | let b = Version::parse(s2).unwrap(); |
| 329 | println!("{} <= {} : {}", s1, s2, a <= b); |
| 330 | }; |
| 331 | |
| 332 | println!(); |
| 333 | |
| 334 | f("1.5.0", "1.5.0"); |
| 335 | f("1.5.0-nightly", "1.5.0"); |
| 336 | f("1.5.0", "1.5.0-nightly"); |
| 337 | f("1.5.0-nightly", "1.5.0-nightly"); |
| 338 | |
| 339 | f("1.5.0", "1.6.0"); |
| 340 | f("1.5.0-nightly", "1.6.0"); |
| 341 | f("1.5.0", "1.6.0-nightly"); |
| 342 | f("1.5.0-nightly", "1.6.0-nightly"); |
| 343 | |
| 344 | panic!(); |
| 345 | |
| 346 | } |
| 347 | */ |
| 348 | |