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 | |