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