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 //! use rustc_version::{version, version_meta, Channel, Version};
26 //!
27 //! // Assert we haven't travelled back in time
28 //! assert!(version().unwrap().major >= 1);
29 //!
30 //! // Set cfg flags depending on release channel
31 //! match version_meta().unwrap().channel {
32 //!     Channel::Stable => {
33 //!         println!("cargo:rustc-cfg=RUSTC_IS_STABLE");
34 //!     }
35 //!     Channel::Beta => {
36 //!         println!("cargo:rustc-cfg=RUSTC_IS_BETA");
37 //!     }
38 //!     Channel::Nightly => {
39 //!         println!("cargo:rustc-cfg=RUSTC_IS_NIGHTLY");
40 //!     }
41 //!     Channel::Dev => {
42 //!         println!("cargo:rustc-cfg=RUSTC_IS_DEV");
43 //!     }
44 //! }
45 //!
46 //! // Check for a minimum version
47 //! if version().unwrap() >= Version::parse("1.4.0").unwrap() {
48 //!     println!("cargo:rustc-cfg=compiler_has_important_bugfix");
49 //! }
50 //! ```
51 
52 #[cfg(test)]
53 #[macro_use]
54 extern crate doc_comment;
55 
56 #[cfg(test)]
57 doctest!("../README.md");
58 
59 use std::collections::HashMap;
60 use std::process::Command;
61 use std::{env, error, fmt, io, num, str};
62 use std::{ffi::OsString, str::FromStr};
63 
64 use semver::{self, Identifier};
65 // Convenience re-export to allow version comparison without needing to add
66 // semver crate.
67 pub use semver::Version;
68 
69 use Error::*;
70 
71 /// Release channel of the compiler.
72 #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
73 pub enum Channel {
74     /// Development release channel
75     Dev,
76     /// Nightly release channel
77     Nightly,
78     /// Beta release channel
79     Beta,
80     /// Stable release channel
81     Stable,
82 }
83 
84 /// LLVM version
85 ///
86 /// LLVM's version numbering scheme is not semver compatible until version 4.0
87 ///
88 /// rustc [just prints the major and minor versions], so other parts of the version are not included.
89 ///
90 /// [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
91 #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
92 pub struct LlvmVersion {
93     // fields must be ordered major, minor for comparison to be correct
94     /// Major version
95     pub major: u64,
96     /// Minor version
97     pub minor: u64,
98     // TODO: expose micro version here
99 }
100 
101 impl fmt::Display for LlvmVersion {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result102     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103         write!(f, "{}.{}", self.major, self.minor)
104     }
105 }
106 
107 impl FromStr for LlvmVersion {
108     type Err = LlvmVersionParseError;
109 
from_str(s: &str) -> Result<Self, Self::Err>110     fn from_str(s: &str) -> Result<Self, Self::Err> {
111         let mut parts = s
112             .split('.')
113             .map(|part| -> Result<u64, LlvmVersionParseError> {
114                 if part == "0" {
115                     Ok(0)
116                 } else if part.starts_with('0') {
117                     Err(LlvmVersionParseError::ComponentMustNotHaveLeadingZeros)
118                 } else if part.starts_with('-') || part.starts_with('+') {
119                     Err(LlvmVersionParseError::ComponentMustNotHaveSign)
120                 } else {
121                     Ok(part.parse()?)
122                 }
123             });
124 
125         let major = parts.next().unwrap()?;
126         let mut minor = 0;
127 
128         if let Some(part) = parts.next() {
129             minor = part?;
130         } else if major < 4 {
131             // LLVM versions earlier than 4.0 have significant minor versions, so require the minor version in this case.
132             return Err(LlvmVersionParseError::MinorVersionRequiredBefore4);
133         }
134 
135         if let Some(Err(e)) = parts.next() {
136             return Err(e);
137         }
138 
139         if parts.next().is_some() {
140             return Err(LlvmVersionParseError::TooManyComponents);
141         }
142 
143         Ok(Self { major, minor })
144     }
145 }
146 
147 /// Rustc version plus metadata like git short hash and build date.
148 #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
149 pub struct VersionMeta {
150     /// Version of the compiler
151     pub semver: Version,
152 
153     /// Git short hash of the build of the compiler
154     pub commit_hash: Option<String>,
155 
156     /// Commit date of the compiler
157     pub commit_date: Option<String>,
158 
159     /// Build date of the compiler; this was removed between Rust 1.0.0 and 1.1.0.
160     pub build_date: Option<String>,
161 
162     /// Release channel of the compiler
163     pub channel: Channel,
164 
165     /// Host target triple of the compiler
166     pub host: String,
167 
168     /// Short version string of the compiler
169     pub short_version_string: String,
170 
171     /// Version of LLVM used by the compiler
172     pub llvm_version: Option<LlvmVersion>,
173 }
174 
175 impl VersionMeta {
176     /// Returns the version metadata for `cmd`, which should be a `rustc` command.
for_command(mut cmd: Command) -> Result<VersionMeta>177     pub fn for_command(mut cmd: Command) -> Result<VersionMeta> {
178         let out = cmd
179             .arg("-vV")
180             .output()
181             .map_err(Error::CouldNotExecuteCommand)?;
182 
183         if !out.status.success() {
184             return Err(Error::CommandError {
185                 stdout: String::from_utf8_lossy(&out.stdout).into(),
186                 stderr: String::from_utf8_lossy(&out.stderr).into(),
187             });
188         }
189 
190         version_meta_for(str::from_utf8(&out.stdout)?)
191     }
192 }
193 
194 /// Returns the `rustc` SemVer version.
version() -> Result<Version>195 pub fn version() -> Result<Version> {
196     Ok(version_meta()?.semver)
197 }
198 
199 /// Returns the `rustc` SemVer version and additional metadata
200 /// like the git short hash and build date.
version_meta() -> Result<VersionMeta>201 pub fn version_meta() -> Result<VersionMeta> {
202     let cmd = env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc"));
203 
204     VersionMeta::for_command(Command::new(cmd))
205 }
206 
207 /// Parses a "rustc -vV" output string and returns
208 /// the SemVer version and additional metadata
209 /// like the git short hash and build date.
version_meta_for(verbose_version_string: &str) -> Result<VersionMeta>210 pub fn version_meta_for(verbose_version_string: &str) -> Result<VersionMeta> {
211     let mut map = HashMap::new();
212     for (i, line) in verbose_version_string.lines().enumerate() {
213         if i == 0 {
214             map.insert("short", line);
215             continue;
216         }
217 
218         let mut parts = line.splitn(2, ": ");
219         let key = match parts.next() {
220             Some(key) => key,
221             None => continue,
222         };
223 
224         if let Some(value) = parts.next() {
225             map.insert(key, value);
226         }
227     }
228 
229     let short_version_string = expect_key("short", &map)?;
230     let host = expect_key("host", &map)?;
231     let release = expect_key("release", &map)?;
232     let semver: Version = release.parse()?;
233 
234     let channel = match semver.pre.first() {
235         None => Channel::Stable,
236         Some(Identifier::AlphaNumeric(s)) if s == "dev" => Channel::Dev,
237         Some(Identifier::AlphaNumeric(s)) if s == "beta" => Channel::Beta,
238         Some(Identifier::AlphaNumeric(s)) if s == "nightly" => Channel::Nightly,
239         Some(x) => return Err(Error::UnknownPreReleaseTag(x.clone())),
240     };
241 
242     let commit_hash = expect_key_or_unknown("commit-hash", &map)?;
243     let commit_date = expect_key_or_unknown("commit-date", &map)?;
244     let build_date = map
245         .get("build-date")
246         .filter(|&v| *v != "unknown")
247         .map(|&v| String::from(v));
248     let llvm_version = match map.get("LLVM version") {
249         Some(&v) => Some(v.parse()?),
250         None => None,
251     };
252 
253     Ok(VersionMeta {
254         semver,
255         commit_hash,
256         commit_date,
257         build_date,
258         channel,
259         host,
260         short_version_string,
261         llvm_version,
262     })
263 }
264 
expect_key_or_unknown(key: &str, map: &HashMap<&str, &str>) -> Result<Option<String>, Error>265 fn expect_key_or_unknown(key: &str, map: &HashMap<&str, &str>) -> Result<Option<String>, Error> {
266     match map.get(key) {
267         Some(&v) if v == "unknown" => Ok(None),
268         Some(&v) => Ok(Some(String::from(v))),
269         None => Err(Error::UnexpectedVersionFormat),
270     }
271 }
272 
expect_key(key: &str, map: &HashMap<&str, &str>) -> Result<String, Error>273 fn expect_key(key: &str, map: &HashMap<&str, &str>) -> Result<String, Error> {
274     map.get(key)
275         .map(|&v| String::from(v))
276         .ok_or(Error::UnexpectedVersionFormat)
277 }
278 
279 /// LLVM Version Parse Error
280 #[derive(Debug)]
281 pub enum LlvmVersionParseError {
282     /// An error occurred in parsing a version component as an integer
283     ParseIntError(num::ParseIntError),
284     /// A version component must not have leading zeros
285     ComponentMustNotHaveLeadingZeros,
286     /// A version component has a sign
287     ComponentMustNotHaveSign,
288     /// Minor version component must be zero on LLVM versions later than 4.0
289     MinorVersionMustBeZeroAfter4,
290     /// Minor version component is required on LLVM versions earlier than 4.0
291     MinorVersionRequiredBefore4,
292     /// Too many components
293     TooManyComponents,
294 }
295 
296 impl From<num::ParseIntError> for LlvmVersionParseError {
from(e: num::ParseIntError) -> Self297     fn from(e: num::ParseIntError) -> Self {
298         LlvmVersionParseError::ParseIntError(e)
299     }
300 }
301 
302 impl fmt::Display for LlvmVersionParseError {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result303     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304         match self {
305             LlvmVersionParseError::ParseIntError(e) => {
306                 write!(f, "error parsing LLVM version component: {}", e)
307             }
308             LlvmVersionParseError::ComponentMustNotHaveLeadingZeros => {
309                 write!(f, "a version component must not have leading zeros")
310             }
311             LlvmVersionParseError::ComponentMustNotHaveSign => {
312                 write!(f, "a version component must not have a sign")
313             }
314             LlvmVersionParseError::MinorVersionMustBeZeroAfter4 => write!(
315                 f,
316                 "LLVM's minor version component must be 0 for versions greater than 4.0"
317             ),
318             LlvmVersionParseError::MinorVersionRequiredBefore4 => write!(
319                 f,
320                 "LLVM's minor version component is required for versions less than 4.0"
321             ),
322             LlvmVersionParseError::TooManyComponents => write!(f, "too many version components"),
323         }
324     }
325 }
326 
327 impl error::Error for LlvmVersionParseError {
source(&self) -> Option<&(dyn error::Error + 'static)>328     fn source(&self) -> Option<&(dyn error::Error + 'static)> {
329         match self {
330             LlvmVersionParseError::ParseIntError(e) => Some(e),
331             LlvmVersionParseError::ComponentMustNotHaveLeadingZeros
332             | LlvmVersionParseError::ComponentMustNotHaveSign
333             | LlvmVersionParseError::MinorVersionMustBeZeroAfter4
334             | LlvmVersionParseError::MinorVersionRequiredBefore4
335             | LlvmVersionParseError::TooManyComponents => None,
336         }
337     }
338 }
339 
340 /// The error type for this crate.
341 #[derive(Debug)]
342 pub enum Error {
343     /// An error occurred while trying to find the `rustc` to run.
344     CouldNotExecuteCommand(io::Error),
345     /// Error output from the command that was run.
346     CommandError {
347         /// stdout output from the command
348         stdout: String,
349         /// stderr output from the command
350         stderr: String,
351     },
352     /// The output of `rustc -vV` was not valid utf-8.
353     Utf8Error(str::Utf8Error),
354     /// The output of `rustc -vV` was not in the expected format.
355     UnexpectedVersionFormat,
356     /// An error occurred in parsing a `VersionReq`.
357     ReqParseError(semver::ReqParseError),
358     /// An error occurred in parsing the semver.
359     SemVerError(semver::SemVerError),
360     /// The pre-release tag is unknown.
361     UnknownPreReleaseTag(Identifier),
362     /// An error occurred in parsing a `LlvmVersion`.
363     LlvmVersionError(LlvmVersionParseError),
364 }
365 
366 impl fmt::Display for Error {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result367     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
368         match *self {
369             CouldNotExecuteCommand(ref e) => write!(f, "could not execute command: {}", e),
370             CommandError {
371                 ref stdout,
372                 ref stderr,
373             } => write!(
374                 f,
375                 "error from command -- stderr:\n\n{}\n\nstderr:\n\n{}",
376                 stderr, stdout,
377             ),
378             Utf8Error(_) => write!(f, "invalid UTF-8 output from `rustc -vV`"),
379             UnexpectedVersionFormat => write!(f, "unexpected `rustc -vV` format"),
380             ReqParseError(ref e) => write!(f, "error parsing version requirement: {}", e),
381             SemVerError(ref e) => write!(f, "error parsing version: {}", e),
382             UnknownPreReleaseTag(ref i) => write!(f, "unknown pre-release tag: {}", i),
383             LlvmVersionError(ref e) => write!(f, "error parsing LLVM's version: {}", e),
384         }
385     }
386 }
387 
388 impl error::Error for Error {
source(&self) -> Option<&(dyn error::Error + 'static)>389     fn source(&self) -> Option<&(dyn error::Error + 'static)> {
390         match *self {
391             CouldNotExecuteCommand(ref e) => Some(e),
392             CommandError { .. } => None,
393             Utf8Error(ref e) => Some(e),
394             UnexpectedVersionFormat => None,
395             ReqParseError(ref e) => Some(e),
396             SemVerError(ref e) => Some(e),
397             UnknownPreReleaseTag(_) => None,
398             LlvmVersionError(ref e) => Some(e),
399         }
400     }
401 }
402 
403 macro_rules! impl_from {
404     ($($err_ty:ty => $variant:ident),* $(,)*) => {
405         $(
406             impl From<$err_ty> for Error {
407                 fn from(e: $err_ty) -> Error {
408                     Error::$variant(e)
409                 }
410             }
411         )*
412     }
413 }
414 
415 impl_from! {
416     str::Utf8Error => Utf8Error,
417     semver::SemVerError => SemVerError,
418     semver::ReqParseError => ReqParseError,
419     LlvmVersionParseError => LlvmVersionError,
420 }
421 
422 /// The result type for this crate.
423 pub type Result<T, E = Error> = std::result::Result<T, E>;
424