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